github.com/mweagle/Sparta@v1.15.0/resources/describe/cytoscape.js/dist/cytoscape.esm.js (about) 1 /** 2 * Copyright (c) 2016-2020, The Cytoscape Consortium. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 * this software and associated documentation files (the “Software”), to deal in 6 * the Software without restriction, including without limitation the rights to 7 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 * of the Software, and to permit persons to whom the Software is furnished to do 9 * so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 */ 22 23 import util from 'lodash.debounce'; 24 import Heap from 'heap'; 25 26 function _typeof(obj) { 27 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 28 _typeof = function (obj) { 29 return typeof obj; 30 }; 31 } else { 32 _typeof = function (obj) { 33 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 34 }; 35 } 36 37 return _typeof(obj); 38 } 39 40 function _classCallCheck(instance, Constructor) { 41 if (!(instance instanceof Constructor)) { 42 throw new TypeError("Cannot call a class as a function"); 43 } 44 } 45 46 function _defineProperties(target, props) { 47 for (var i = 0; i < props.length; i++) { 48 var descriptor = props[i]; 49 descriptor.enumerable = descriptor.enumerable || false; 50 descriptor.configurable = true; 51 if ("value" in descriptor) descriptor.writable = true; 52 Object.defineProperty(target, descriptor.key, descriptor); 53 } 54 } 55 56 function _createClass(Constructor, protoProps, staticProps) { 57 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 58 if (staticProps) _defineProperties(Constructor, staticProps); 59 return Constructor; 60 } 61 62 function _defineProperty(obj, key, value) { 63 if (key in obj) { 64 Object.defineProperty(obj, key, { 65 value: value, 66 enumerable: true, 67 configurable: true, 68 writable: true 69 }); 70 } else { 71 obj[key] = value; 72 } 73 74 return obj; 75 } 76 77 function _slicedToArray(arr, i) { 78 return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); 79 } 80 81 function _arrayWithHoles(arr) { 82 if (Array.isArray(arr)) return arr; 83 } 84 85 function _iterableToArrayLimit(arr, i) { 86 var _arr = []; 87 var _n = true; 88 var _d = false; 89 var _e = undefined; 90 91 try { 92 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 93 _arr.push(_s.value); 94 95 if (i && _arr.length === i) break; 96 } 97 } catch (err) { 98 _d = true; 99 _e = err; 100 } finally { 101 try { 102 if (!_n && _i["return"] != null) _i["return"](); 103 } finally { 104 if (_d) throw _e; 105 } 106 } 107 108 return _arr; 109 } 110 111 function _nonIterableRest() { 112 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 113 } 114 115 var window$1 = typeof window === 'undefined' ? null : window; // eslint-disable-line no-undef 116 117 var navigator = window$1 ? window$1.navigator : null; 118 var document$1 = window$1 ? window$1.document : null; 119 120 var typeofstr = _typeof(''); 121 122 var typeofobj = _typeof({}); 123 124 var typeoffn = _typeof(function () {}); 125 126 var typeofhtmlele = typeof HTMLElement === "undefined" ? "undefined" : _typeof(HTMLElement); 127 128 var instanceStr = function instanceStr(obj) { 129 return obj && obj.instanceString && fn(obj.instanceString) ? obj.instanceString() : null; 130 }; 131 132 var string = function string(obj) { 133 return obj != null && _typeof(obj) == typeofstr; 134 }; 135 var fn = function fn(obj) { 136 return obj != null && _typeof(obj) === typeoffn; 137 }; 138 var array = function array(obj) { 139 return Array.isArray ? Array.isArray(obj) : obj != null && obj instanceof Array; 140 }; 141 var plainObject = function plainObject(obj) { 142 return obj != null && _typeof(obj) === typeofobj && !array(obj) && obj.constructor === Object; 143 }; 144 var object = function object(obj) { 145 return obj != null && _typeof(obj) === typeofobj; 146 }; 147 var number = function number(obj) { 148 return obj != null && _typeof(obj) === _typeof(1) && !isNaN(obj); 149 }; 150 var integer = function integer(obj) { 151 return number(obj) && Math.floor(obj) === obj; 152 }; 153 var htmlElement = function htmlElement(obj) { 154 if ('undefined' === typeofhtmlele) { 155 return undefined; 156 } else { 157 return null != obj && obj instanceof HTMLElement; 158 } 159 }; 160 var elementOrCollection = function elementOrCollection(obj) { 161 return element(obj) || collection(obj); 162 }; 163 var element = function element(obj) { 164 return instanceStr(obj) === 'collection' && obj._private.single; 165 }; 166 var collection = function collection(obj) { 167 return instanceStr(obj) === 'collection' && !obj._private.single; 168 }; 169 var core = function core(obj) { 170 return instanceStr(obj) === 'core'; 171 }; 172 var stylesheet = function stylesheet(obj) { 173 return instanceStr(obj) === 'stylesheet'; 174 }; 175 var event = function event(obj) { 176 return instanceStr(obj) === 'event'; 177 }; 178 var emptyString = function emptyString(obj) { 179 if (obj === undefined || obj === null) { 180 // null is empty 181 return true; 182 } else if (obj === '' || obj.match(/^\s+$/)) { 183 return true; // empty string is empty 184 } 185 186 return false; // otherwise, we don't know what we've got 187 }; 188 var domElement = function domElement(obj) { 189 if (typeof HTMLElement === 'undefined') { 190 return false; // we're not in a browser so it doesn't matter 191 } else { 192 return obj instanceof HTMLElement; 193 } 194 }; 195 var boundingBox = function boundingBox(obj) { 196 return plainObject(obj) && number(obj.x1) && number(obj.x2) && number(obj.y1) && number(obj.y2); 197 }; 198 var promise = function promise(obj) { 199 return object(obj) && fn(obj.then); 200 }; 201 var ms = function ms() { 202 return navigator && navigator.userAgent.match(/msie|trident|edge/i); 203 }; // probably a better way to detect this... 204 205 var memoize = function memoize(fn, keyFn) { 206 if (!keyFn) { 207 keyFn = function keyFn() { 208 if (arguments.length === 1) { 209 return arguments[0]; 210 } else if (arguments.length === 0) { 211 return 'undefined'; 212 } 213 214 var args = []; 215 216 for (var i = 0; i < arguments.length; i++) { 217 args.push(arguments[i]); 218 } 219 220 return args.join('$'); 221 }; 222 } 223 224 var memoizedFn = function memoizedFn() { 225 var self = this; 226 var args = arguments; 227 var ret; 228 var k = keyFn.apply(self, args); 229 var cache = memoizedFn.cache; 230 231 if (!(ret = cache[k])) { 232 ret = cache[k] = fn.apply(self, args); 233 } 234 235 return ret; 236 }; 237 238 memoizedFn.cache = {}; 239 return memoizedFn; 240 }; 241 242 var camel2dash = memoize(function (str) { 243 return str.replace(/([A-Z])/g, function (v) { 244 return '-' + v.toLowerCase(); 245 }); 246 }); 247 var dash2camel = memoize(function (str) { 248 return str.replace(/(-\w)/g, function (v) { 249 return v[1].toUpperCase(); 250 }); 251 }); 252 var prependCamel = memoize(function (prefix, str) { 253 return prefix + str[0].toUpperCase() + str.substring(1); 254 }, function (prefix, str) { 255 return prefix + '$' + str; 256 }); 257 var capitalize = function capitalize(str) { 258 if (emptyString(str)) { 259 return str; 260 } 261 262 return str.charAt(0).toUpperCase() + str.substring(1); 263 }; 264 265 var number$1 = '(?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))'; 266 var rgba = 'rgb[a]?\\((' + number$1 + '[%]?)\\s*,\\s*(' + number$1 + '[%]?)\\s*,\\s*(' + number$1 + '[%]?)(?:\\s*,\\s*(' + number$1 + '))?\\)'; 267 var rgbaNoBackRefs = 'rgb[a]?\\((?:' + number$1 + '[%]?)\\s*,\\s*(?:' + number$1 + '[%]?)\\s*,\\s*(?:' + number$1 + '[%]?)(?:\\s*,\\s*(?:' + number$1 + '))?\\)'; 268 var hsla = 'hsl[a]?\\((' + number$1 + ')\\s*,\\s*(' + number$1 + '[%])\\s*,\\s*(' + number$1 + '[%])(?:\\s*,\\s*(' + number$1 + '))?\\)'; 269 var hslaNoBackRefs = 'hsl[a]?\\((?:' + number$1 + ')\\s*,\\s*(?:' + number$1 + '[%])\\s*,\\s*(?:' + number$1 + '[%])(?:\\s*,\\s*(?:' + number$1 + '))?\\)'; 270 var hex3 = '\\#[0-9a-fA-F]{3}'; 271 var hex6 = '\\#[0-9a-fA-F]{6}'; 272 273 var ascending = function ascending(a, b) { 274 if (a < b) { 275 return -1; 276 } else if (a > b) { 277 return 1; 278 } else { 279 return 0; 280 } 281 }; 282 var descending = function descending(a, b) { 283 return -1 * ascending(a, b); 284 }; 285 286 var extend = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { 287 var args = arguments; 288 289 for (var i = 1; i < args.length; i++) { 290 var obj = args[i]; 291 292 if (obj == null) { 293 continue; 294 } 295 296 var keys = Object.keys(obj); 297 298 for (var j = 0; j < keys.length; j++) { 299 var k = keys[j]; 300 tgt[k] = obj[k]; 301 } 302 } 303 304 return tgt; 305 }; 306 307 var hex2tuple = function hex2tuple(hex) { 308 if (!(hex.length === 4 || hex.length === 7) || hex[0] !== '#') { 309 return; 310 } 311 312 var shortHex = hex.length === 4; 313 var r, g, b; 314 var base = 16; 315 316 if (shortHex) { 317 r = parseInt(hex[1] + hex[1], base); 318 g = parseInt(hex[2] + hex[2], base); 319 b = parseInt(hex[3] + hex[3], base); 320 } else { 321 r = parseInt(hex[1] + hex[2], base); 322 g = parseInt(hex[3] + hex[4], base); 323 b = parseInt(hex[5] + hex[6], base); 324 } 325 326 return [r, g, b]; 327 }; // get [r, g, b, a] from hsl(0, 0, 0) or hsla(0, 0, 0, 0) 328 329 var hsl2tuple = function hsl2tuple(hsl) { 330 var ret; 331 var h, s, l, a, r, g, b; 332 333 function hue2rgb(p, q, t) { 334 if (t < 0) t += 1; 335 if (t > 1) t -= 1; 336 if (t < 1 / 6) return p + (q - p) * 6 * t; 337 if (t < 1 / 2) return q; 338 if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 339 return p; 340 } 341 342 var m = new RegExp('^' + hsla + '$').exec(hsl); 343 344 if (m) { 345 // get hue 346 h = parseInt(m[1]); 347 348 if (h < 0) { 349 h = (360 - -1 * h % 360) % 360; 350 } else if (h > 360) { 351 h = h % 360; 352 } 353 354 h /= 360; // normalise on [0, 1] 355 356 s = parseFloat(m[2]); 357 358 if (s < 0 || s > 100) { 359 return; 360 } // saturation is [0, 100] 361 362 363 s = s / 100; // normalise on [0, 1] 364 365 l = parseFloat(m[3]); 366 367 if (l < 0 || l > 100) { 368 return; 369 } // lightness is [0, 100] 370 371 372 l = l / 100; // normalise on [0, 1] 373 374 a = m[4]; 375 376 if (a !== undefined) { 377 a = parseFloat(a); 378 379 if (a < 0 || a > 1) { 380 return; 381 } // alpha is [0, 1] 382 383 } // now, convert to rgb 384 // code from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript 385 386 387 if (s === 0) { 388 r = g = b = Math.round(l * 255); // achromatic 389 } else { 390 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 391 var p = 2 * l - q; 392 r = Math.round(255 * hue2rgb(p, q, h + 1 / 3)); 393 g = Math.round(255 * hue2rgb(p, q, h)); 394 b = Math.round(255 * hue2rgb(p, q, h - 1 / 3)); 395 } 396 397 ret = [r, g, b, a]; 398 } 399 400 return ret; 401 }; // get [r, g, b, a] from rgb(0, 0, 0) or rgba(0, 0, 0, 0) 402 403 var rgb2tuple = function rgb2tuple(rgb) { 404 var ret; 405 var m = new RegExp('^' + rgba + '$').exec(rgb); 406 407 if (m) { 408 ret = []; 409 var isPct = []; 410 411 for (var i = 1; i <= 3; i++) { 412 var channel = m[i]; 413 414 if (channel[channel.length - 1] === '%') { 415 isPct[i] = true; 416 } 417 418 channel = parseFloat(channel); 419 420 if (isPct[i]) { 421 channel = channel / 100 * 255; // normalise to [0, 255] 422 } 423 424 if (channel < 0 || channel > 255) { 425 return; 426 } // invalid channel value 427 428 429 ret.push(Math.floor(channel)); 430 } 431 432 var atLeastOneIsPct = isPct[1] || isPct[2] || isPct[3]; 433 var allArePct = isPct[1] && isPct[2] && isPct[3]; 434 435 if (atLeastOneIsPct && !allArePct) { 436 return; 437 } // must all be percent values if one is 438 439 440 var alpha = m[4]; 441 442 if (alpha !== undefined) { 443 alpha = parseFloat(alpha); 444 445 if (alpha < 0 || alpha > 1) { 446 return; 447 } // invalid alpha value 448 449 450 ret.push(alpha); 451 } 452 } 453 454 return ret; 455 }; 456 var colorname2tuple = function colorname2tuple(color) { 457 return colors[color.toLowerCase()]; 458 }; 459 var color2tuple = function color2tuple(color) { 460 return (array(color) ? color : null) || colorname2tuple(color) || hex2tuple(color) || rgb2tuple(color) || hsl2tuple(color); 461 }; 462 var colors = { 463 // special colour names 464 transparent: [0, 0, 0, 0], 465 // NB alpha === 0 466 // regular colours 467 aliceblue: [240, 248, 255], 468 antiquewhite: [250, 235, 215], 469 aqua: [0, 255, 255], 470 aquamarine: [127, 255, 212], 471 azure: [240, 255, 255], 472 beige: [245, 245, 220], 473 bisque: [255, 228, 196], 474 black: [0, 0, 0], 475 blanchedalmond: [255, 235, 205], 476 blue: [0, 0, 255], 477 blueviolet: [138, 43, 226], 478 brown: [165, 42, 42], 479 burlywood: [222, 184, 135], 480 cadetblue: [95, 158, 160], 481 chartreuse: [127, 255, 0], 482 chocolate: [210, 105, 30], 483 coral: [255, 127, 80], 484 cornflowerblue: [100, 149, 237], 485 cornsilk: [255, 248, 220], 486 crimson: [220, 20, 60], 487 cyan: [0, 255, 255], 488 darkblue: [0, 0, 139], 489 darkcyan: [0, 139, 139], 490 darkgoldenrod: [184, 134, 11], 491 darkgray: [169, 169, 169], 492 darkgreen: [0, 100, 0], 493 darkgrey: [169, 169, 169], 494 darkkhaki: [189, 183, 107], 495 darkmagenta: [139, 0, 139], 496 darkolivegreen: [85, 107, 47], 497 darkorange: [255, 140, 0], 498 darkorchid: [153, 50, 204], 499 darkred: [139, 0, 0], 500 darksalmon: [233, 150, 122], 501 darkseagreen: [143, 188, 143], 502 darkslateblue: [72, 61, 139], 503 darkslategray: [47, 79, 79], 504 darkslategrey: [47, 79, 79], 505 darkturquoise: [0, 206, 209], 506 darkviolet: [148, 0, 211], 507 deeppink: [255, 20, 147], 508 deepskyblue: [0, 191, 255], 509 dimgray: [105, 105, 105], 510 dimgrey: [105, 105, 105], 511 dodgerblue: [30, 144, 255], 512 firebrick: [178, 34, 34], 513 floralwhite: [255, 250, 240], 514 forestgreen: [34, 139, 34], 515 fuchsia: [255, 0, 255], 516 gainsboro: [220, 220, 220], 517 ghostwhite: [248, 248, 255], 518 gold: [255, 215, 0], 519 goldenrod: [218, 165, 32], 520 gray: [128, 128, 128], 521 grey: [128, 128, 128], 522 green: [0, 128, 0], 523 greenyellow: [173, 255, 47], 524 honeydew: [240, 255, 240], 525 hotpink: [255, 105, 180], 526 indianred: [205, 92, 92], 527 indigo: [75, 0, 130], 528 ivory: [255, 255, 240], 529 khaki: [240, 230, 140], 530 lavender: [230, 230, 250], 531 lavenderblush: [255, 240, 245], 532 lawngreen: [124, 252, 0], 533 lemonchiffon: [255, 250, 205], 534 lightblue: [173, 216, 230], 535 lightcoral: [240, 128, 128], 536 lightcyan: [224, 255, 255], 537 lightgoldenrodyellow: [250, 250, 210], 538 lightgray: [211, 211, 211], 539 lightgreen: [144, 238, 144], 540 lightgrey: [211, 211, 211], 541 lightpink: [255, 182, 193], 542 lightsalmon: [255, 160, 122], 543 lightseagreen: [32, 178, 170], 544 lightskyblue: [135, 206, 250], 545 lightslategray: [119, 136, 153], 546 lightslategrey: [119, 136, 153], 547 lightsteelblue: [176, 196, 222], 548 lightyellow: [255, 255, 224], 549 lime: [0, 255, 0], 550 limegreen: [50, 205, 50], 551 linen: [250, 240, 230], 552 magenta: [255, 0, 255], 553 maroon: [128, 0, 0], 554 mediumaquamarine: [102, 205, 170], 555 mediumblue: [0, 0, 205], 556 mediumorchid: [186, 85, 211], 557 mediumpurple: [147, 112, 219], 558 mediumseagreen: [60, 179, 113], 559 mediumslateblue: [123, 104, 238], 560 mediumspringgreen: [0, 250, 154], 561 mediumturquoise: [72, 209, 204], 562 mediumvioletred: [199, 21, 133], 563 midnightblue: [25, 25, 112], 564 mintcream: [245, 255, 250], 565 mistyrose: [255, 228, 225], 566 moccasin: [255, 228, 181], 567 navajowhite: [255, 222, 173], 568 navy: [0, 0, 128], 569 oldlace: [253, 245, 230], 570 olive: [128, 128, 0], 571 olivedrab: [107, 142, 35], 572 orange: [255, 165, 0], 573 orangered: [255, 69, 0], 574 orchid: [218, 112, 214], 575 palegoldenrod: [238, 232, 170], 576 palegreen: [152, 251, 152], 577 paleturquoise: [175, 238, 238], 578 palevioletred: [219, 112, 147], 579 papayawhip: [255, 239, 213], 580 peachpuff: [255, 218, 185], 581 peru: [205, 133, 63], 582 pink: [255, 192, 203], 583 plum: [221, 160, 221], 584 powderblue: [176, 224, 230], 585 purple: [128, 0, 128], 586 red: [255, 0, 0], 587 rosybrown: [188, 143, 143], 588 royalblue: [65, 105, 225], 589 saddlebrown: [139, 69, 19], 590 salmon: [250, 128, 114], 591 sandybrown: [244, 164, 96], 592 seagreen: [46, 139, 87], 593 seashell: [255, 245, 238], 594 sienna: [160, 82, 45], 595 silver: [192, 192, 192], 596 skyblue: [135, 206, 235], 597 slateblue: [106, 90, 205], 598 slategray: [112, 128, 144], 599 slategrey: [112, 128, 144], 600 snow: [255, 250, 250], 601 springgreen: [0, 255, 127], 602 steelblue: [70, 130, 180], 603 tan: [210, 180, 140], 604 teal: [0, 128, 128], 605 thistle: [216, 191, 216], 606 tomato: [255, 99, 71], 607 turquoise: [64, 224, 208], 608 violet: [238, 130, 238], 609 wheat: [245, 222, 179], 610 white: [255, 255, 255], 611 whitesmoke: [245, 245, 245], 612 yellow: [255, 255, 0], 613 yellowgreen: [154, 205, 50] 614 }; 615 616 var setMap = function setMap(options) { 617 var obj = options.map; 618 var keys = options.keys; 619 var l = keys.length; 620 621 for (var i = 0; i < l; i++) { 622 var key = keys[i]; 623 624 if (plainObject(key)) { 625 throw Error('Tried to set map with object key'); 626 } 627 628 if (i < keys.length - 1) { 629 // extend the map if necessary 630 if (obj[key] == null) { 631 obj[key] = {}; 632 } 633 634 obj = obj[key]; 635 } else { 636 // set the value 637 obj[key] = options.value; 638 } 639 } 640 }; // gets the value in a map even if it's not built in places 641 642 var getMap = function getMap(options) { 643 var obj = options.map; 644 var keys = options.keys; 645 var l = keys.length; 646 647 for (var i = 0; i < l; i++) { 648 var key = keys[i]; 649 650 if (plainObject(key)) { 651 throw Error('Tried to get map with object key'); 652 } 653 654 obj = obj[key]; 655 656 if (obj == null) { 657 return obj; 658 } 659 } 660 661 return obj; 662 }; // deletes the entry in the map 663 664 var performance = window$1 ? window$1.performance : null; 665 var pnow = performance && performance.now ? function () { 666 return performance.now(); 667 } : function () { 668 return Date.now(); 669 }; 670 671 var raf = function () { 672 if (window$1) { 673 if (window$1.requestAnimationFrame) { 674 return function (fn) { 675 window$1.requestAnimationFrame(fn); 676 }; 677 } else if (window$1.mozRequestAnimationFrame) { 678 return function (fn) { 679 window$1.mozRequestAnimationFrame(fn); 680 }; 681 } else if (window$1.webkitRequestAnimationFrame) { 682 return function (fn) { 683 window$1.webkitRequestAnimationFrame(fn); 684 }; 685 } else if (window$1.msRequestAnimationFrame) { 686 return function (fn) { 687 window$1.msRequestAnimationFrame(fn); 688 }; 689 } 690 } 691 692 return function (fn) { 693 if (fn) { 694 setTimeout(function () { 695 fn(pnow()); 696 }, 1000 / 60); 697 } 698 }; 699 }(); 700 701 var requestAnimationFrame = function requestAnimationFrame(fn) { 702 return raf(fn); 703 }; 704 var performanceNow = pnow; 705 706 var DEFAULT_SEED = 5381; 707 var hashIterableInts = function hashIterableInts(iterator) { 708 var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_SEED; 709 // djb2/string-hash 710 var hash = seed; 711 var entry; 712 713 for (;;) { 714 entry = iterator.next(); 715 716 if (entry.done) { 717 break; 718 } 719 720 hash = (hash << 5) + hash + entry.value | 0; 721 } 722 723 return hash; 724 }; 725 var hashInt = function hashInt(num) { 726 var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_SEED; 727 // djb2/string-hash 728 return (seed << 5) + seed + num | 0; 729 }; 730 var hashIntsArray = function hashIntsArray(ints, seed) { 731 var entry = { 732 value: 0, 733 done: false 734 }; 735 var i = 0; 736 var length = ints.length; 737 var iterator = { 738 next: function next() { 739 if (i < length) { 740 entry.value = ints[i++]; 741 } else { 742 entry.done = true; 743 } 744 745 return entry; 746 } 747 }; 748 return hashIterableInts(iterator, seed); 749 }; 750 var hashString = function hashString(str, seed) { 751 var entry = { 752 value: 0, 753 done: false 754 }; 755 var i = 0; 756 var length = str.length; 757 var iterator = { 758 next: function next() { 759 if (i < length) { 760 entry.value = str.charCodeAt(i++); 761 } else { 762 entry.done = true; 763 } 764 765 return entry; 766 } 767 }; 768 return hashIterableInts(iterator, seed); 769 }; 770 var hashStrings = function hashStrings() { 771 return hashStringsArray(arguments); 772 }; 773 var hashStringsArray = function hashStringsArray(strs) { 774 var hash; 775 776 for (var i = 0; i < strs.length; i++) { 777 var str = strs[i]; 778 779 if (i === 0) { 780 hash = hashString(str); 781 } else { 782 hash = hashString(str, hash); 783 } 784 } 785 786 return hash; 787 }; 788 789 /*global console */ 790 var warningsEnabled = true; 791 var warnSupported = console.warn != null; // eslint-disable-line no-console 792 793 var traceSupported = console.trace != null; // eslint-disable-line no-console 794 795 var MAX_INT = Number.MAX_SAFE_INTEGER || 9007199254740991; 796 var trueify = function trueify() { 797 return true; 798 }; 799 var falsify = function falsify() { 800 return false; 801 }; 802 var zeroify = function zeroify() { 803 return 0; 804 }; 805 var noop = function noop() {}; 806 var error = function error(msg) { 807 throw new Error(msg); 808 }; 809 var warnings = function warnings(enabled) { 810 if (enabled !== undefined) { 811 warningsEnabled = !!enabled; 812 } else { 813 return warningsEnabled; 814 } 815 }; 816 var warn = function warn(msg) { 817 /* eslint-disable no-console */ 818 if (!warnings()) { 819 return; 820 } 821 822 if (warnSupported) { 823 console.warn(msg); 824 } else { 825 console.log(msg); 826 827 if (traceSupported) { 828 console.trace(); 829 } 830 } 831 }; 832 /* eslint-enable */ 833 834 var clone = function clone(obj) { 835 return extend({}, obj); 836 }; // gets a shallow copy of the argument 837 838 var copy = function copy(obj) { 839 if (obj == null) { 840 return obj; 841 } 842 843 if (array(obj)) { 844 return obj.slice(); 845 } else if (plainObject(obj)) { 846 return clone(obj); 847 } else { 848 return obj; 849 } 850 }; 851 var copyArray = function copyArray(arr) { 852 return arr.slice(); 853 }; 854 var uuid = function uuid(a, b 855 /* placeholders */ 856 ) { 857 for ( // loop :) 858 b = a = ''; // b - result , a - numeric letiable 859 a++ < 36; // 860 b += a * 51 & 52 // if "a" is not 9 or 14 or 19 or 24 861 ? // return a random number or 4 862 (a ^ 15 // if "a" is not 15 863 ? // genetate a random number from 0 to 15 864 8 ^ Math.random() * (a ^ 20 ? 16 : 4) // unless "a" is 20, in which case a random number from 8 to 11 865 : 4 // otherwise 4 866 ).toString(16) : '-' // in other cases (if "a" is 9,14,19,24) insert "-" 867 ) { 868 } 869 870 return b; 871 }; 872 var _staticEmptyObject = {}; 873 var staticEmptyObject = function staticEmptyObject() { 874 return _staticEmptyObject; 875 }; 876 var defaults = function defaults(_defaults) { 877 var keys = Object.keys(_defaults); 878 return function (opts) { 879 var filledOpts = {}; 880 881 for (var i = 0; i < keys.length; i++) { 882 var key = keys[i]; 883 var optVal = opts == null ? undefined : opts[key]; 884 filledOpts[key] = optVal === undefined ? _defaults[key] : optVal; 885 } 886 887 return filledOpts; 888 }; 889 }; 890 var removeFromArray = function removeFromArray(arr, ele, manyCopies) { 891 for (var i = arr.length; i >= 0; i--) { 892 if (arr[i] === ele) { 893 arr.splice(i, 1); 894 895 if (!manyCopies) { 896 break; 897 } 898 } 899 } 900 }; 901 var clearArray = function clearArray(arr) { 902 arr.splice(0, arr.length); 903 }; 904 var push = function push(arr, otherArr) { 905 for (var i = 0; i < otherArr.length; i++) { 906 var el = otherArr[i]; 907 arr.push(el); 908 } 909 }; 910 var getPrefixedProperty = function getPrefixedProperty(obj, propName, prefix) { 911 if (prefix) { 912 propName = prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth 913 } 914 915 return obj[propName]; 916 }; 917 var setPrefixedProperty = function setPrefixedProperty(obj, propName, prefix, value) { 918 if (prefix) { 919 propName = prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth 920 } 921 922 obj[propName] = value; 923 }; 924 925 /* global Map */ 926 var ObjectMap = 927 /*#__PURE__*/ 928 function () { 929 function ObjectMap() { 930 _classCallCheck(this, ObjectMap); 931 932 this._obj = {}; 933 } 934 935 _createClass(ObjectMap, [{ 936 key: "set", 937 value: function set(key, val) { 938 this._obj[key] = val; 939 return this; 940 } 941 }, { 942 key: "delete", 943 value: function _delete(key) { 944 this._obj[key] = undefined; 945 return this; 946 } 947 }, { 948 key: "clear", 949 value: function clear() { 950 this._obj = {}; 951 } 952 }, { 953 key: "has", 954 value: function has(key) { 955 return this._obj[key] !== undefined; 956 } 957 }, { 958 key: "get", 959 value: function get(key) { 960 return this._obj[key]; 961 } 962 }]); 963 964 return ObjectMap; 965 }(); 966 967 var Map$1 = typeof Map !== 'undefined' ? Map : ObjectMap; 968 969 /* global Set */ 970 var undef = "undefined" ; 971 972 var ObjectSet = 973 /*#__PURE__*/ 974 function () { 975 function ObjectSet(arrayOrObjectSet) { 976 _classCallCheck(this, ObjectSet); 977 978 this._obj = Object.create(null); 979 this.size = 0; 980 981 if (arrayOrObjectSet != null) { 982 var arr; 983 984 if (arrayOrObjectSet.instanceString != null && arrayOrObjectSet.instanceString() === this.instanceString()) { 985 arr = arrayOrObjectSet.toArray(); 986 } else { 987 arr = arrayOrObjectSet; 988 } 989 990 for (var i = 0; i < arr.length; i++) { 991 this.add(arr[i]); 992 } 993 } 994 } 995 996 _createClass(ObjectSet, [{ 997 key: "instanceString", 998 value: function instanceString() { 999 return 'set'; 1000 } 1001 }, { 1002 key: "add", 1003 value: function add(val) { 1004 var o = this._obj; 1005 1006 if (o[val] !== 1) { 1007 o[val] = 1; 1008 this.size++; 1009 } 1010 } 1011 }, { 1012 key: "delete", 1013 value: function _delete(val) { 1014 var o = this._obj; 1015 1016 if (o[val] === 1) { 1017 o[val] = 0; 1018 this.size--; 1019 } 1020 } 1021 }, { 1022 key: "clear", 1023 value: function clear() { 1024 this._obj = Object.create(null); 1025 } 1026 }, { 1027 key: "has", 1028 value: function has(val) { 1029 return this._obj[val] === 1; 1030 } 1031 }, { 1032 key: "toArray", 1033 value: function toArray() { 1034 var _this = this; 1035 1036 return Object.keys(this._obj).filter(function (key) { 1037 return _this.has(key); 1038 }); 1039 } 1040 }, { 1041 key: "forEach", 1042 value: function forEach(callback, thisArg) { 1043 return this.toArray().forEach(callback, thisArg); 1044 } 1045 }]); 1046 1047 return ObjectSet; 1048 }(); 1049 1050 var Set$1 = (typeof Set === "undefined" ? "undefined" : _typeof(Set)) !== undef ? Set : ObjectSet; 1051 1052 var Element = function Element(cy, params, restore) { 1053 restore = restore === undefined || restore ? true : false; 1054 1055 if (cy === undefined || params === undefined || !core(cy)) { 1056 error('An element must have a core reference and parameters set'); 1057 return; 1058 } 1059 1060 var group = params.group; // try to automatically infer the group if unspecified 1061 1062 if (group == null) { 1063 if (params.data && params.data.source != null && params.data.target != null) { 1064 group = 'edges'; 1065 } else { 1066 group = 'nodes'; 1067 } 1068 } // validate group 1069 1070 1071 if (group !== 'nodes' && group !== 'edges') { 1072 error('An element must be of type `nodes` or `edges`; you specified `' + group + '`'); 1073 return; 1074 } // make the element array-like, just like a collection 1075 1076 1077 this.length = 1; 1078 this[0] = this; // NOTE: when something is added here, add also to ele.json() 1079 1080 var _p = this._private = { 1081 cy: cy, 1082 single: true, 1083 // indicates this is an element 1084 data: params.data || {}, 1085 // data object 1086 position: params.position || { 1087 x: 0, 1088 y: 0 1089 }, 1090 // (x, y) position pair 1091 autoWidth: undefined, 1092 // width and height of nodes calculated by the renderer when set to special 'auto' value 1093 autoHeight: undefined, 1094 autoPadding: undefined, 1095 compoundBoundsClean: false, 1096 // whether the compound dimensions need to be recalculated the next time dimensions are read 1097 listeners: [], 1098 // array of bound listeners 1099 group: group, 1100 // string; 'nodes' or 'edges' 1101 style: {}, 1102 // properties as set by the style 1103 rstyle: {}, 1104 // properties for style sent from the renderer to the core 1105 styleCxts: [], 1106 // applied style contexts from the styler 1107 styleKeys: {}, 1108 // per-group keys of style property values 1109 removed: true, 1110 // whether it's inside the vis; true if removed (set true here since we call restore) 1111 selected: params.selected ? true : false, 1112 // whether it's selected 1113 selectable: params.selectable === undefined ? true : params.selectable ? true : false, 1114 // whether it's selectable 1115 locked: params.locked ? true : false, 1116 // whether the element is locked (cannot be moved) 1117 grabbed: false, 1118 // whether the element is grabbed by the mouse; renderer sets this privately 1119 grabbable: params.grabbable === undefined ? true : params.grabbable ? true : false, 1120 // whether the element can be grabbed 1121 pannable: params.pannable === undefined ? group === 'edges' ? true : false : params.pannable ? true : false, 1122 // whether the element has passthrough panning enabled 1123 active: false, 1124 // whether the element is active from user interaction 1125 classes: new Set$1(), 1126 // map ( className => true ) 1127 animation: { 1128 // object for currently-running animations 1129 current: [], 1130 queue: [] 1131 }, 1132 rscratch: {}, 1133 // object in which the renderer can store information 1134 scratch: params.scratch || {}, 1135 // scratch objects 1136 edges: [], 1137 // array of connected edges 1138 children: [], 1139 // array of children 1140 parent: null, 1141 // parent ref 1142 traversalCache: {}, 1143 // cache of output of traversal functions 1144 backgrounding: false, 1145 // whether background images are loading 1146 bbCache: null, 1147 // cache of the current bounding box 1148 bbCacheShift: { 1149 x: 0, 1150 y: 0 1151 }, 1152 // shift applied to cached bb to be applied on next get 1153 bodyBounds: null, 1154 // bounds cache of element body, w/o overlay 1155 overlayBounds: null, 1156 // bounds cache of element body, including overlay 1157 labelBounds: { 1158 // bounds cache of labels 1159 all: null, 1160 source: null, 1161 target: null, 1162 main: null 1163 }, 1164 arrowBounds: { 1165 // bounds cache of edge arrows 1166 source: null, 1167 target: null, 1168 'mid-source': null, 1169 'mid-target': null 1170 } 1171 }; 1172 1173 if (_p.position.x == null) { 1174 _p.position.x = 0; 1175 } 1176 1177 if (_p.position.y == null) { 1178 _p.position.y = 0; 1179 } // renderedPosition overrides if specified 1180 1181 1182 if (params.renderedPosition) { 1183 var rpos = params.renderedPosition; 1184 var pan = cy.pan(); 1185 var zoom = cy.zoom(); 1186 _p.position = { 1187 x: (rpos.x - pan.x) / zoom, 1188 y: (rpos.y - pan.y) / zoom 1189 }; 1190 } 1191 1192 var classes = []; 1193 1194 if (array(params.classes)) { 1195 classes = params.classes; 1196 } else if (string(params.classes)) { 1197 classes = params.classes.split(/\s+/); 1198 } 1199 1200 for (var i = 0, l = classes.length; i < l; i++) { 1201 var cls = classes[i]; 1202 1203 if (!cls || cls === '') { 1204 continue; 1205 } 1206 1207 _p.classes.add(cls); 1208 } 1209 1210 this.createEmitter(); 1211 var bypass = params.style || params.css; 1212 1213 if (bypass) { 1214 warn('Setting a `style` bypass at element creation is deprecated'); 1215 this.style(bypass); 1216 } 1217 1218 if (restore === undefined || restore) { 1219 this.restore(); 1220 } 1221 }; 1222 1223 var defineSearch = function defineSearch(params) { 1224 params = { 1225 bfs: params.bfs || !params.dfs, 1226 dfs: params.dfs || !params.bfs 1227 }; // from pseudocode on wikipedia 1228 1229 return function searchFn(roots, fn$1, directed) { 1230 var options; 1231 1232 if (plainObject(roots) && !elementOrCollection(roots)) { 1233 options = roots; 1234 roots = options.roots || options.root; 1235 fn$1 = options.visit; 1236 directed = options.directed; 1237 } 1238 1239 directed = arguments.length === 2 && !fn(fn$1) ? fn$1 : directed; 1240 fn$1 = fn(fn$1) ? fn$1 : function () {}; 1241 var cy = this._private.cy; 1242 var v = roots = string(roots) ? this.filter(roots) : roots; 1243 var Q = []; 1244 var connectedNodes = []; 1245 var connectedBy = {}; 1246 var id2depth = {}; 1247 var V = {}; 1248 var j = 0; 1249 var found; 1250 1251 var _this$byGroup = this.byGroup(), 1252 nodes = _this$byGroup.nodes, 1253 edges = _this$byGroup.edges; // enqueue v 1254 1255 1256 for (var i = 0; i < v.length; i++) { 1257 var vi = v[i]; 1258 var viId = vi.id(); 1259 1260 if (vi.isNode()) { 1261 Q.unshift(vi); 1262 1263 if (params.bfs) { 1264 V[viId] = true; 1265 connectedNodes.push(vi); 1266 } 1267 1268 id2depth[viId] = 0; 1269 } 1270 } 1271 1272 var _loop2 = function _loop2() { 1273 var v = params.bfs ? Q.shift() : Q.pop(); 1274 var vId = v.id(); 1275 1276 if (params.dfs) { 1277 if (V[vId]) { 1278 return "continue"; 1279 } 1280 1281 V[vId] = true; 1282 connectedNodes.push(v); 1283 } 1284 1285 var depth = id2depth[vId]; 1286 var prevEdge = connectedBy[vId]; 1287 var src = prevEdge != null ? prevEdge.source() : null; 1288 var tgt = prevEdge != null ? prevEdge.target() : null; 1289 var prevNode = prevEdge == null ? undefined : v.same(src) ? tgt[0] : src[0]; 1290 var ret = void 0; 1291 ret = fn$1(v, prevEdge, prevNode, j++, depth); 1292 1293 if (ret === true) { 1294 found = v; 1295 return "break"; 1296 } 1297 1298 if (ret === false) { 1299 return "break"; 1300 } 1301 1302 var vwEdges = v.connectedEdges().filter(function (e) { 1303 return (!directed || e.source().same(v)) && edges.has(e); 1304 }); 1305 1306 for (var _i2 = 0; _i2 < vwEdges.length; _i2++) { 1307 var e = vwEdges[_i2]; 1308 var w = e.connectedNodes().filter(function (n) { 1309 return !n.same(v) && nodes.has(n); 1310 }); 1311 var wId = w.id(); 1312 1313 if (w.length !== 0 && !V[wId]) { 1314 w = w[0]; 1315 Q.push(w); 1316 1317 if (params.bfs) { 1318 V[wId] = true; 1319 connectedNodes.push(w); 1320 } 1321 1322 connectedBy[wId] = e; 1323 id2depth[wId] = id2depth[vId] + 1; 1324 } 1325 } 1326 }; 1327 1328 _loop: while (Q.length !== 0) { 1329 var _ret = _loop2(); 1330 1331 switch (_ret) { 1332 case "continue": 1333 continue; 1334 1335 case "break": 1336 break _loop; 1337 } 1338 } 1339 1340 var connectedEles = cy.collection(); 1341 1342 for (var _i = 0; _i < connectedNodes.length; _i++) { 1343 var node = connectedNodes[_i]; 1344 var edge = connectedBy[node.id()]; 1345 1346 if (edge != null) { 1347 connectedEles.merge(edge); 1348 } 1349 1350 connectedEles.merge(node); 1351 } 1352 1353 return { 1354 path: cy.collection(connectedEles), 1355 found: cy.collection(found) 1356 }; 1357 }; 1358 }; // search, spanning trees, etc 1359 1360 1361 var elesfn = { 1362 breadthFirstSearch: defineSearch({ 1363 bfs: true 1364 }), 1365 depthFirstSearch: defineSearch({ 1366 dfs: true 1367 }) 1368 }; // nice, short mathemathical alias 1369 1370 elesfn.bfs = elesfn.breadthFirstSearch; 1371 elesfn.dfs = elesfn.depthFirstSearch; 1372 1373 var dijkstraDefaults = defaults({ 1374 root: null, 1375 weight: function weight(edge) { 1376 return 1; 1377 }, 1378 directed: false 1379 }); 1380 var elesfn$1 = { 1381 dijkstra: function dijkstra(options) { 1382 if (!plainObject(options)) { 1383 var args = arguments; 1384 options = { 1385 root: args[0], 1386 weight: args[1], 1387 directed: args[2] 1388 }; 1389 } 1390 1391 var _dijkstraDefaults = dijkstraDefaults(options), 1392 root = _dijkstraDefaults.root, 1393 weight = _dijkstraDefaults.weight, 1394 directed = _dijkstraDefaults.directed; 1395 1396 var eles = this; 1397 var weightFn = weight; 1398 var source = string(root) ? this.filter(root)[0] : root[0]; 1399 var dist = {}; 1400 var prev = {}; 1401 var knownDist = {}; 1402 1403 var _this$byGroup = this.byGroup(), 1404 nodes = _this$byGroup.nodes, 1405 edges = _this$byGroup.edges; 1406 1407 edges.unmergeBy(function (ele) { 1408 return ele.isLoop(); 1409 }); 1410 1411 var getDist = function getDist(node) { 1412 return dist[node.id()]; 1413 }; 1414 1415 var setDist = function setDist(node, d) { 1416 dist[node.id()] = d; 1417 Q.updateItem(node); 1418 }; 1419 1420 var Q = new Heap(function (a, b) { 1421 return getDist(a) - getDist(b); 1422 }); 1423 1424 for (var i = 0; i < nodes.length; i++) { 1425 var node = nodes[i]; 1426 dist[node.id()] = node.same(source) ? 0 : Infinity; 1427 Q.push(node); 1428 } 1429 1430 var distBetween = function distBetween(u, v) { 1431 var uvs = (directed ? u.edgesTo(v) : u.edgesWith(v)).intersect(edges); 1432 var smallestDistance = Infinity; 1433 var smallestEdge; 1434 1435 for (var _i = 0; _i < uvs.length; _i++) { 1436 var edge = uvs[_i]; 1437 1438 var _weight = weightFn(edge); 1439 1440 if (_weight < smallestDistance || !smallestEdge) { 1441 smallestDistance = _weight; 1442 smallestEdge = edge; 1443 } 1444 } 1445 1446 return { 1447 edge: smallestEdge, 1448 dist: smallestDistance 1449 }; 1450 }; 1451 1452 while (Q.size() > 0) { 1453 var u = Q.pop(); 1454 var smalletsDist = getDist(u); 1455 var uid = u.id(); 1456 knownDist[uid] = smalletsDist; 1457 1458 if (smalletsDist === Infinity) { 1459 continue; 1460 } 1461 1462 var neighbors = u.neighborhood().intersect(nodes); 1463 1464 for (var _i2 = 0; _i2 < neighbors.length; _i2++) { 1465 var v = neighbors[_i2]; 1466 var vid = v.id(); 1467 var vDist = distBetween(u, v); 1468 var alt = smalletsDist + vDist.dist; 1469 1470 if (alt < getDist(v)) { 1471 setDist(v, alt); 1472 prev[vid] = { 1473 node: u, 1474 edge: vDist.edge 1475 }; 1476 } 1477 } // for 1478 1479 } // while 1480 1481 1482 return { 1483 distanceTo: function distanceTo(node) { 1484 var target = string(node) ? nodes.filter(node)[0] : node[0]; 1485 return knownDist[target.id()]; 1486 }, 1487 pathTo: function pathTo(node) { 1488 var target = string(node) ? nodes.filter(node)[0] : node[0]; 1489 var S = []; 1490 var u = target; 1491 var uid = u.id(); 1492 1493 if (target.length > 0) { 1494 S.unshift(target); 1495 1496 while (prev[uid]) { 1497 var p = prev[uid]; 1498 S.unshift(p.edge); 1499 S.unshift(p.node); 1500 u = p.node; 1501 uid = u.id(); 1502 } 1503 } 1504 1505 return eles.spawn(S); 1506 } 1507 }; 1508 } 1509 }; 1510 1511 var elesfn$2 = { 1512 // kruskal's algorithm (finds min spanning tree, assuming undirected graph) 1513 // implemented from pseudocode from wikipedia 1514 kruskal: function kruskal(weightFn) { 1515 weightFn = weightFn || function (edge) { 1516 return 1; 1517 }; 1518 1519 var _this$byGroup = this.byGroup(), 1520 nodes = _this$byGroup.nodes, 1521 edges = _this$byGroup.edges; 1522 1523 var numNodes = nodes.length; 1524 var forest = new Array(numNodes); 1525 var A = nodes; // assumes byGroup() creates new collections that can be safely mutated 1526 1527 var findSetIndex = function findSetIndex(ele) { 1528 for (var i = 0; i < forest.length; i++) { 1529 var eles = forest[i]; 1530 1531 if (eles.has(ele)) { 1532 return i; 1533 } 1534 } 1535 }; // start with one forest per node 1536 1537 1538 for (var i = 0; i < numNodes; i++) { 1539 forest[i] = this.spawn(nodes[i]); 1540 } 1541 1542 var S = edges.sort(function (a, b) { 1543 return weightFn(a) - weightFn(b); 1544 }); 1545 1546 for (var _i = 0; _i < S.length; _i++) { 1547 var edge = S[_i]; 1548 var u = edge.source()[0]; 1549 var v = edge.target()[0]; 1550 var setUIndex = findSetIndex(u); 1551 var setVIndex = findSetIndex(v); 1552 var setU = forest[setUIndex]; 1553 var setV = forest[setVIndex]; 1554 1555 if (setUIndex !== setVIndex) { 1556 A.merge(edge); // combine forests for u and v 1557 1558 setU.merge(setV); 1559 forest.splice(setVIndex, 1); 1560 } 1561 } 1562 1563 return A; 1564 } 1565 }; 1566 1567 var aStarDefaults = defaults({ 1568 root: null, 1569 goal: null, 1570 weight: function weight(edge) { 1571 return 1; 1572 }, 1573 heuristic: function heuristic(edge) { 1574 return 0; 1575 }, 1576 directed: false 1577 }); 1578 var elesfn$3 = { 1579 // Implemented from pseudocode from wikipedia 1580 aStar: function aStar(options) { 1581 var cy = this.cy(); 1582 1583 var _aStarDefaults = aStarDefaults(options), 1584 root = _aStarDefaults.root, 1585 goal = _aStarDefaults.goal, 1586 heuristic = _aStarDefaults.heuristic, 1587 directed = _aStarDefaults.directed, 1588 weight = _aStarDefaults.weight; 1589 1590 root = cy.collection(root)[0]; 1591 goal = cy.collection(goal)[0]; 1592 var sid = root.id(); 1593 var tid = goal.id(); 1594 var gScore = {}; 1595 var fScore = {}; 1596 var closedSetIds = {}; 1597 var openSet = new Heap(function (a, b) { 1598 return fScore[a.id()] - fScore[b.id()]; 1599 }); 1600 var openSetIds = new Set$1(); 1601 var cameFrom = {}; 1602 var cameFromEdge = {}; 1603 1604 var addToOpenSet = function addToOpenSet(ele, id) { 1605 openSet.push(ele); 1606 openSetIds.add(id); 1607 }; 1608 1609 var cMin, cMinId; 1610 1611 var popFromOpenSet = function popFromOpenSet() { 1612 cMin = openSet.pop(); 1613 cMinId = cMin.id(); 1614 openSetIds["delete"](cMinId); 1615 }; 1616 1617 var isInOpenSet = function isInOpenSet(id) { 1618 return openSetIds.has(id); 1619 }; 1620 1621 addToOpenSet(root, sid); 1622 gScore[sid] = 0; 1623 fScore[sid] = heuristic(root); // Counter 1624 1625 var steps = 0; // Main loop 1626 1627 while (openSet.size() > 0) { 1628 popFromOpenSet(); 1629 steps++; // If we've found our goal, then we are done 1630 1631 if (cMinId === tid) { 1632 var path = []; 1633 var pathNode = goal; 1634 var pathNodeId = tid; 1635 var pathEdge = cameFromEdge[pathNodeId]; 1636 1637 for (;;) { 1638 path.unshift(pathNode); 1639 1640 if (pathEdge != null) { 1641 path.unshift(pathEdge); 1642 } 1643 1644 pathNode = cameFrom[pathNodeId]; 1645 1646 if (pathNode == null) { 1647 break; 1648 } 1649 1650 pathNodeId = pathNode.id(); 1651 pathEdge = cameFromEdge[pathNodeId]; 1652 } 1653 1654 return { 1655 found: true, 1656 distance: gScore[cMinId], 1657 path: this.spawn(path), 1658 steps: steps 1659 }; 1660 } // Add cMin to processed nodes 1661 1662 1663 closedSetIds[cMinId] = true; // Update scores for neighbors of cMin 1664 // Take into account if graph is directed or not 1665 1666 var vwEdges = cMin._private.edges; 1667 1668 for (var i = 0; i < vwEdges.length; i++) { 1669 var e = vwEdges[i]; // edge must be in set of calling eles 1670 1671 if (!this.hasElementWithId(e.id())) { 1672 continue; 1673 } // cMin must be the source of edge if directed 1674 1675 1676 if (directed && e.data('source') !== cMinId) { 1677 continue; 1678 } 1679 1680 var wSrc = e.source(); 1681 var wTgt = e.target(); 1682 var w = wSrc.id() !== cMinId ? wSrc : wTgt; 1683 var wid = w.id(); // node must be in set of calling eles 1684 1685 if (!this.hasElementWithId(wid)) { 1686 continue; 1687 } // if node is in closedSet, ignore it 1688 1689 1690 if (closedSetIds[wid]) { 1691 continue; 1692 } // New tentative score for node w 1693 1694 1695 var tempScore = gScore[cMinId] + weight(e); // Update gScore for node w if: 1696 // w not present in openSet 1697 // OR 1698 // tentative gScore is less than previous value 1699 // w not in openSet 1700 1701 if (!isInOpenSet(wid)) { 1702 gScore[wid] = tempScore; 1703 fScore[wid] = tempScore + heuristic(w); 1704 addToOpenSet(w, wid); 1705 cameFrom[wid] = cMin; 1706 cameFromEdge[wid] = e; 1707 continue; 1708 } // w already in openSet, but with greater gScore 1709 1710 1711 if (tempScore < gScore[wid]) { 1712 gScore[wid] = tempScore; 1713 fScore[wid] = tempScore + heuristic(w); 1714 cameFrom[wid] = cMin; 1715 } 1716 } // End of neighbors update 1717 1718 } // End of main loop 1719 // If we've reached here, then we've not reached our goal 1720 1721 1722 return { 1723 found: false, 1724 distance: undefined, 1725 path: undefined, 1726 steps: steps 1727 }; 1728 } 1729 }; // elesfn 1730 1731 var floydWarshallDefaults = defaults({ 1732 weight: function weight(edge) { 1733 return 1; 1734 }, 1735 directed: false 1736 }); 1737 var elesfn$4 = { 1738 // Implemented from pseudocode from wikipedia 1739 floydWarshall: function floydWarshall(options) { 1740 var cy = this.cy(); 1741 1742 var _floydWarshallDefault = floydWarshallDefaults(options), 1743 weight = _floydWarshallDefault.weight, 1744 directed = _floydWarshallDefault.directed; 1745 1746 var weightFn = weight; 1747 1748 var _this$byGroup = this.byGroup(), 1749 nodes = _this$byGroup.nodes, 1750 edges = _this$byGroup.edges; 1751 1752 var N = nodes.length; 1753 var Nsq = N * N; 1754 1755 var indexOf = function indexOf(node) { 1756 return nodes.indexOf(node); 1757 }; 1758 1759 var atIndex = function atIndex(i) { 1760 return nodes[i]; 1761 }; // Initialize distance matrix 1762 1763 1764 var dist = new Array(Nsq); 1765 1766 for (var n = 0; n < Nsq; n++) { 1767 var j = n % N; 1768 var i = (n - j) / N; 1769 1770 if (i === j) { 1771 dist[n] = 0; 1772 } else { 1773 dist[n] = Infinity; 1774 } 1775 } // Initialize matrix used for path reconstruction 1776 // Initialize distance matrix 1777 1778 1779 var next = new Array(Nsq); 1780 var edgeNext = new Array(Nsq); // Process edges 1781 1782 for (var _i = 0; _i < edges.length; _i++) { 1783 var edge = edges[_i]; 1784 var src = edge.source()[0]; 1785 var tgt = edge.target()[0]; 1786 1787 if (src === tgt) { 1788 continue; 1789 } // exclude loops 1790 1791 1792 var s = indexOf(src); 1793 var t = indexOf(tgt); 1794 var st = s * N + t; // source to target index 1795 1796 var _weight = weightFn(edge); // Check if already process another edge between same 2 nodes 1797 1798 1799 if (dist[st] > _weight) { 1800 dist[st] = _weight; 1801 next[st] = t; 1802 edgeNext[st] = edge; 1803 } // If undirected graph, process 'reversed' edge 1804 1805 1806 if (!directed) { 1807 var ts = t * N + s; // target to source index 1808 1809 if (!directed && dist[ts] > _weight) { 1810 dist[ts] = _weight; 1811 next[ts] = s; 1812 edgeNext[ts] = edge; 1813 } 1814 } 1815 } // Main loop 1816 1817 1818 for (var k = 0; k < N; k++) { 1819 for (var _i2 = 0; _i2 < N; _i2++) { 1820 var ik = _i2 * N + k; 1821 1822 for (var _j = 0; _j < N; _j++) { 1823 var ij = _i2 * N + _j; 1824 var kj = k * N + _j; 1825 1826 if (dist[ik] + dist[kj] < dist[ij]) { 1827 dist[ij] = dist[ik] + dist[kj]; 1828 next[ij] = next[ik]; 1829 } 1830 } 1831 } 1832 } 1833 1834 var getArgEle = function getArgEle(ele) { 1835 return (string(ele) ? cy.filter(ele) : ele)[0]; 1836 }; 1837 1838 var indexOfArgEle = function indexOfArgEle(ele) { 1839 return indexOf(getArgEle(ele)); 1840 }; 1841 1842 var res = { 1843 distance: function distance(from, to) { 1844 var i = indexOfArgEle(from); 1845 var j = indexOfArgEle(to); 1846 return dist[i * N + j]; 1847 }, 1848 path: function path(from, to) { 1849 var i = indexOfArgEle(from); 1850 var j = indexOfArgEle(to); 1851 var fromNode = atIndex(i); 1852 1853 if (i === j) { 1854 return fromNode.collection(); 1855 } 1856 1857 if (next[i * N + j] == null) { 1858 return cy.collection(); 1859 } 1860 1861 var path = cy.collection(); 1862 var prev = i; 1863 var edge; 1864 path.merge(fromNode); 1865 1866 while (i !== j) { 1867 prev = i; 1868 i = next[i * N + j]; 1869 edge = edgeNext[prev * N + i]; 1870 path.merge(edge); 1871 path.merge(atIndex(i)); 1872 } 1873 1874 return path; 1875 } 1876 }; 1877 return res; 1878 } // floydWarshall 1879 1880 }; // elesfn 1881 1882 var bellmanFordDefaults = defaults({ 1883 weight: function weight(edge) { 1884 return 1; 1885 }, 1886 directed: false, 1887 root: null 1888 }); 1889 var elesfn$5 = { 1890 // Implemented from pseudocode from wikipedia 1891 bellmanFord: function bellmanFord(options) { 1892 var _this = this; 1893 1894 var _bellmanFordDefaults = bellmanFordDefaults(options), 1895 weight = _bellmanFordDefaults.weight, 1896 directed = _bellmanFordDefaults.directed, 1897 root = _bellmanFordDefaults.root; 1898 1899 var weightFn = weight; 1900 var eles = this; 1901 var cy = this.cy(); 1902 1903 var _this$byGroup = this.byGroup(), 1904 edges = _this$byGroup.edges, 1905 nodes = _this$byGroup.nodes; 1906 1907 var numNodes = nodes.length; 1908 var infoMap = new Map$1(); 1909 var hasNegativeWeightCycle = false; 1910 var negativeWeightCycles = []; 1911 root = cy.collection(root)[0]; // in case selector passed 1912 1913 edges.unmergeBy(function (edge) { 1914 return edge.isLoop(); 1915 }); 1916 var numEdges = edges.length; 1917 1918 var getInfo = function getInfo(node) { 1919 var obj = infoMap.get(node.id()); 1920 1921 if (!obj) { 1922 obj = {}; 1923 infoMap.set(node.id(), obj); 1924 } 1925 1926 return obj; 1927 }; 1928 1929 var getNodeFromTo = function getNodeFromTo(to) { 1930 return (string(to) ? cy.$(to) : to)[0]; 1931 }; 1932 1933 var distanceTo = function distanceTo(to) { 1934 return getInfo(getNodeFromTo(to)).dist; 1935 }; 1936 1937 var pathTo = function pathTo(to) { 1938 var thisStart = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : root; 1939 var end = getNodeFromTo(to); 1940 var path = []; 1941 var node = end; 1942 1943 for (;;) { 1944 if (node == null) { 1945 return _this.spawn(); 1946 } 1947 1948 var _getInfo = getInfo(node), 1949 edge = _getInfo.edge, 1950 pred = _getInfo.pred; 1951 1952 path.unshift(node[0]); 1953 1954 if (node.same(thisStart) && path.length > 0) { 1955 break; 1956 } 1957 1958 if (edge != null) { 1959 path.unshift(edge); 1960 } 1961 1962 node = pred; 1963 } 1964 1965 return eles.spawn(path); 1966 }; // Initializations { dist, pred, edge } 1967 1968 1969 for (var i = 0; i < numNodes; i++) { 1970 var node = nodes[i]; 1971 var info = getInfo(node); 1972 1973 if (node.same(root)) { 1974 info.dist = 0; 1975 } else { 1976 info.dist = Infinity; 1977 } 1978 1979 info.pred = null; 1980 info.edge = null; 1981 } // Edges relaxation 1982 1983 1984 var replacedEdge = false; 1985 1986 var checkForEdgeReplacement = function checkForEdgeReplacement(node1, node2, edge, info1, info2, weight) { 1987 var dist = info1.dist + weight; 1988 1989 if (dist < info2.dist && !edge.same(info1.edge)) { 1990 info2.dist = dist; 1991 info2.pred = node1; 1992 info2.edge = edge; 1993 replacedEdge = true; 1994 } 1995 }; 1996 1997 for (var _i = 1; _i < numNodes; _i++) { 1998 replacedEdge = false; 1999 2000 for (var e = 0; e < numEdges; e++) { 2001 var edge = edges[e]; 2002 var src = edge.source(); 2003 var tgt = edge.target(); 2004 2005 var _weight = weightFn(edge); 2006 2007 var srcInfo = getInfo(src); 2008 var tgtInfo = getInfo(tgt); 2009 checkForEdgeReplacement(src, tgt, edge, srcInfo, tgtInfo, _weight); // If undirected graph, we need to take into account the 'reverse' edge 2010 2011 if (!directed) { 2012 checkForEdgeReplacement(tgt, src, edge, tgtInfo, srcInfo, _weight); 2013 } 2014 } 2015 2016 if (!replacedEdge) { 2017 break; 2018 } 2019 } 2020 2021 if (replacedEdge) { 2022 // Check for negative weight cycles 2023 for (var _e = 0; _e < numEdges; _e++) { 2024 var _edge = edges[_e]; 2025 2026 var _src = _edge.source(); 2027 2028 var _tgt = _edge.target(); 2029 2030 var _weight2 = weightFn(_edge); 2031 2032 var srcDist = getInfo(_src).dist; 2033 var tgtDist = getInfo(_tgt).dist; 2034 2035 if (srcDist + _weight2 < tgtDist || !directed && tgtDist + _weight2 < srcDist) { 2036 warn('Graph contains a negative weight cycle for Bellman-Ford'); 2037 hasNegativeWeightCycle = true; 2038 break; 2039 } 2040 } 2041 } 2042 2043 return { 2044 distanceTo: distanceTo, 2045 pathTo: pathTo, 2046 hasNegativeWeightCycle: hasNegativeWeightCycle, 2047 negativeWeightCycles: negativeWeightCycles 2048 }; 2049 } // bellmanFord 2050 2051 }; // elesfn 2052 2053 var sqrt2 = Math.sqrt(2); // Function which colapses 2 (meta) nodes into one 2054 // Updates the remaining edge lists 2055 // Receives as a paramater the edge which causes the collapse 2056 2057 var collapse = function collapse(edgeIndex, nodeMap, remainingEdges) { 2058 if (remainingEdges.length === 0) { 2059 error("Karger-Stein must be run on a connected (sub)graph"); 2060 } 2061 2062 var edgeInfo = remainingEdges[edgeIndex]; 2063 var sourceIn = edgeInfo[1]; 2064 var targetIn = edgeInfo[2]; 2065 var partition1 = nodeMap[sourceIn]; 2066 var partition2 = nodeMap[targetIn]; 2067 var newEdges = remainingEdges; // re-use array 2068 // Delete all edges between partition1 and partition2 2069 2070 for (var i = newEdges.length - 1; i >= 0; i--) { 2071 var edge = newEdges[i]; 2072 var src = edge[1]; 2073 var tgt = edge[2]; 2074 2075 if (nodeMap[src] === partition1 && nodeMap[tgt] === partition2 || nodeMap[src] === partition2 && nodeMap[tgt] === partition1) { 2076 newEdges.splice(i, 1); 2077 } 2078 } // All edges pointing to partition2 should now point to partition1 2079 2080 2081 for (var _i = 0; _i < newEdges.length; _i++) { 2082 var _edge = newEdges[_i]; 2083 2084 if (_edge[1] === partition2) { 2085 // Check source 2086 newEdges[_i] = _edge.slice(); // copy 2087 2088 newEdges[_i][1] = partition1; 2089 } else if (_edge[2] === partition2) { 2090 // Check target 2091 newEdges[_i] = _edge.slice(); // copy 2092 2093 newEdges[_i][2] = partition1; 2094 } 2095 } // Move all nodes from partition2 to partition1 2096 2097 2098 for (var _i2 = 0; _i2 < nodeMap.length; _i2++) { 2099 if (nodeMap[_i2] === partition2) { 2100 nodeMap[_i2] = partition1; 2101 } 2102 } 2103 2104 return newEdges; 2105 }; // Contracts a graph until we reach a certain number of meta nodes 2106 2107 2108 var contractUntil = function contractUntil(metaNodeMap, remainingEdges, size, sizeLimit) { 2109 while (size > sizeLimit) { 2110 // Choose an edge randomly 2111 var edgeIndex = Math.floor(Math.random() * remainingEdges.length); // Collapse graph based on edge 2112 2113 remainingEdges = collapse(edgeIndex, metaNodeMap, remainingEdges); 2114 size--; 2115 } 2116 2117 return remainingEdges; 2118 }; 2119 2120 var elesfn$6 = { 2121 // Computes the minimum cut of an undirected graph 2122 // Returns the correct answer with high probability 2123 kargerStein: function kargerStein() { 2124 var _this = this; 2125 2126 var _this$byGroup = this.byGroup(), 2127 nodes = _this$byGroup.nodes, 2128 edges = _this$byGroup.edges; 2129 2130 edges.unmergeBy(function (edge) { 2131 return edge.isLoop(); 2132 }); 2133 var numNodes = nodes.length; 2134 var numEdges = edges.length; 2135 var numIter = Math.ceil(Math.pow(Math.log(numNodes) / Math.LN2, 2)); 2136 var stopSize = Math.floor(numNodes / sqrt2); 2137 2138 if (numNodes < 2) { 2139 error('At least 2 nodes are required for Karger-Stein algorithm'); 2140 return undefined; 2141 } // Now store edge destination as indexes 2142 // Format for each edge (edge index, source node index, target node index) 2143 2144 2145 var edgeIndexes = []; 2146 2147 for (var i = 0; i < numEdges; i++) { 2148 var e = edges[i]; 2149 edgeIndexes.push([i, nodes.indexOf(e.source()), nodes.indexOf(e.target())]); 2150 } // We will store the best cut found here 2151 2152 2153 var minCutSize = Infinity; 2154 var minCutEdgeIndexes = []; 2155 var minCutNodeMap = new Array(numNodes); // Initial meta node partition 2156 2157 var metaNodeMap = new Array(numNodes); 2158 var metaNodeMap2 = new Array(numNodes); 2159 2160 var copyNodesMap = function copyNodesMap(from, to) { 2161 for (var _i3 = 0; _i3 < numNodes; _i3++) { 2162 to[_i3] = from[_i3]; 2163 } 2164 }; // Main loop 2165 2166 2167 for (var iter = 0; iter <= numIter; iter++) { 2168 // Reset meta node partition 2169 for (var _i4 = 0; _i4 < numNodes; _i4++) { 2170 metaNodeMap[_i4] = _i4; 2171 } // Contract until stop point (stopSize nodes) 2172 2173 2174 var edgesState = contractUntil(metaNodeMap, edgeIndexes.slice(), numNodes, stopSize); 2175 var edgesState2 = edgesState.slice(); // copy 2176 // Create a copy of the colapsed nodes state 2177 2178 copyNodesMap(metaNodeMap, metaNodeMap2); // Run 2 iterations starting in the stop state 2179 2180 var res1 = contractUntil(metaNodeMap, edgesState, stopSize, 2); 2181 var res2 = contractUntil(metaNodeMap2, edgesState2, stopSize, 2); // Is any of the 2 results the best cut so far? 2182 2183 if (res1.length <= res2.length && res1.length < minCutSize) { 2184 minCutSize = res1.length; 2185 minCutEdgeIndexes = res1; 2186 copyNodesMap(metaNodeMap, minCutNodeMap); 2187 } else if (res2.length <= res1.length && res2.length < minCutSize) { 2188 minCutSize = res2.length; 2189 minCutEdgeIndexes = res2; 2190 copyNodesMap(metaNodeMap2, minCutNodeMap); 2191 } 2192 } // end of main loop 2193 // Construct result 2194 2195 2196 var cut = this.spawn(minCutEdgeIndexes.map(function (e) { 2197 return edges[e[0]]; 2198 })); 2199 var partition1 = this.spawn(); 2200 var partition2 = this.spawn(); // traverse metaNodeMap for best cut 2201 2202 var witnessNodePartition = minCutNodeMap[0]; 2203 2204 for (var _i5 = 0; _i5 < minCutNodeMap.length; _i5++) { 2205 var partitionId = minCutNodeMap[_i5]; 2206 var node = nodes[_i5]; 2207 2208 if (partitionId === witnessNodePartition) { 2209 partition1.merge(node); 2210 } else { 2211 partition2.merge(node); 2212 } 2213 } // construct components corresponding to each disjoint subset of nodes 2214 2215 2216 var constructComponent = function constructComponent(subset) { 2217 var component = _this.spawn(); 2218 2219 subset.forEach(function (node) { 2220 component.merge(node); 2221 node.connectedEdges().forEach(function (edge) { 2222 // ensure edge is within calling collection and edge is not in cut 2223 if (_this.contains(edge) && !cut.contains(edge)) { 2224 component.merge(edge); 2225 } 2226 }); 2227 }); 2228 return component; 2229 }; 2230 2231 var components = [constructComponent(partition1), constructComponent(partition2)]; 2232 var ret = { 2233 cut: cut, 2234 components: components, 2235 // n.b. partitions are included to be compatible with the old api spec 2236 // (could be removed in a future major version) 2237 partition1: partition1, 2238 partition2: partition2 2239 }; 2240 return ret; 2241 } 2242 }; // elesfn 2243 2244 var copyPosition = function copyPosition(p) { 2245 return { 2246 x: p.x, 2247 y: p.y 2248 }; 2249 }; 2250 var modelToRenderedPosition = function modelToRenderedPosition(p, zoom, pan) { 2251 return { 2252 x: p.x * zoom + pan.x, 2253 y: p.y * zoom + pan.y 2254 }; 2255 }; 2256 var renderedToModelPosition = function renderedToModelPosition(p, zoom, pan) { 2257 return { 2258 x: (p.x - pan.x) / zoom, 2259 y: (p.y - pan.y) / zoom 2260 }; 2261 }; 2262 var array2point = function array2point(arr) { 2263 return { 2264 x: arr[0], 2265 y: arr[1] 2266 }; 2267 }; 2268 var min = function min(arr) { 2269 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2270 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2271 var min = Infinity; 2272 2273 for (var i = begin; i < end; i++) { 2274 var val = arr[i]; 2275 2276 if (isFinite(val)) { 2277 min = Math.min(val, min); 2278 } 2279 } 2280 2281 return min; 2282 }; 2283 var max = function max(arr) { 2284 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2285 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2286 var max = -Infinity; 2287 2288 for (var i = begin; i < end; i++) { 2289 var val = arr[i]; 2290 2291 if (isFinite(val)) { 2292 max = Math.max(val, max); 2293 } 2294 } 2295 2296 return max; 2297 }; 2298 var mean = function mean(arr) { 2299 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2300 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2301 var total = 0; 2302 var n = 0; 2303 2304 for (var i = begin; i < end; i++) { 2305 var val = arr[i]; 2306 2307 if (isFinite(val)) { 2308 total += val; 2309 n++; 2310 } 2311 } 2312 2313 return total / n; 2314 }; 2315 var median = function median(arr) { 2316 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2317 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2318 var copy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 2319 var sort = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 2320 var includeHoles = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 2321 2322 if (copy) { 2323 arr = arr.slice(begin, end); 2324 } else { 2325 if (end < arr.length) { 2326 arr.splice(end, arr.length - end); 2327 } 2328 2329 if (begin > 0) { 2330 arr.splice(0, begin); 2331 } 2332 } // all non finite (e.g. Infinity, NaN) elements must be -Infinity so they go to the start 2333 2334 2335 var off = 0; // offset from non-finite values 2336 2337 for (var i = arr.length - 1; i >= 0; i--) { 2338 var v = arr[i]; 2339 2340 if (includeHoles) { 2341 if (!isFinite(v)) { 2342 arr[i] = -Infinity; 2343 off++; 2344 } 2345 } else { 2346 // just remove it if we don't want to consider holes 2347 arr.splice(i, 1); 2348 } 2349 } 2350 2351 if (sort) { 2352 arr.sort(function (a, b) { 2353 return a - b; 2354 }); // requires copy = true if you don't want to change the orig 2355 } 2356 2357 var len = arr.length; 2358 var mid = Math.floor(len / 2); 2359 2360 if (len % 2 !== 0) { 2361 return arr[mid + 1 + off]; 2362 } else { 2363 return (arr[mid - 1 + off] + arr[mid + off]) / 2; 2364 } 2365 }; 2366 var deg2rad = function deg2rad(deg) { 2367 return Math.PI * deg / 180; 2368 }; 2369 var getAngleFromDisp = function getAngleFromDisp(dispX, dispY) { 2370 return Math.atan2(dispY, dispX) - Math.PI / 2; 2371 }; 2372 var log2 = Math.log2 || function (n) { 2373 return Math.log(n) / Math.log(2); 2374 }; 2375 var signum = function signum(x) { 2376 if (x > 0) { 2377 return 1; 2378 } else if (x < 0) { 2379 return -1; 2380 } else { 2381 return 0; 2382 } 2383 }; 2384 var dist = function dist(p1, p2) { 2385 return Math.sqrt(sqdist(p1, p2)); 2386 }; 2387 var sqdist = function sqdist(p1, p2) { 2388 var dx = p2.x - p1.x; 2389 var dy = p2.y - p1.y; 2390 return dx * dx + dy * dy; 2391 }; 2392 var inPlaceSumNormalize = function inPlaceSumNormalize(v) { 2393 var length = v.length; // First, get sum of all elements 2394 2395 var total = 0; 2396 2397 for (var i = 0; i < length; i++) { 2398 total += v[i]; 2399 } // Now, divide each by the sum of all elements 2400 2401 2402 for (var _i = 0; _i < length; _i++) { 2403 v[_i] = v[_i] / total; 2404 } 2405 2406 return v; 2407 }; 2408 2409 var qbezierAt = function qbezierAt(p0, p1, p2, t) { 2410 return (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2; 2411 }; 2412 var qbezierPtAt = function qbezierPtAt(p0, p1, p2, t) { 2413 return { 2414 x: qbezierAt(p0.x, p1.x, p2.x, t), 2415 y: qbezierAt(p0.y, p1.y, p2.y, t) 2416 }; 2417 }; 2418 var lineAt = function lineAt(p0, p1, t, d) { 2419 var vec = { 2420 x: p1.x - p0.x, 2421 y: p1.y - p0.y 2422 }; 2423 var vecDist = dist(p0, p1); 2424 var normVec = { 2425 x: vec.x / vecDist, 2426 y: vec.y / vecDist 2427 }; 2428 t = t == null ? 0 : t; 2429 d = d != null ? d : t * vecDist; 2430 return { 2431 x: p0.x + normVec.x * d, 2432 y: p0.y + normVec.y * d 2433 }; 2434 }; 2435 var bound = function bound(min, val, max) { 2436 return Math.max(min, Math.min(max, val)); 2437 }; // makes a full bb (x1, y1, x2, y2, w, h) from implicit params 2438 2439 var makeBoundingBox = function makeBoundingBox(bb) { 2440 if (bb == null) { 2441 return { 2442 x1: Infinity, 2443 y1: Infinity, 2444 x2: -Infinity, 2445 y2: -Infinity, 2446 w: 0, 2447 h: 0 2448 }; 2449 } else if (bb.x1 != null && bb.y1 != null) { 2450 if (bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1) { 2451 return { 2452 x1: bb.x1, 2453 y1: bb.y1, 2454 x2: bb.x2, 2455 y2: bb.y2, 2456 w: bb.x2 - bb.x1, 2457 h: bb.y2 - bb.y1 2458 }; 2459 } else if (bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0) { 2460 return { 2461 x1: bb.x1, 2462 y1: bb.y1, 2463 x2: bb.x1 + bb.w, 2464 y2: bb.y1 + bb.h, 2465 w: bb.w, 2466 h: bb.h 2467 }; 2468 } 2469 } 2470 }; 2471 var copyBoundingBox = function copyBoundingBox(bb) { 2472 return { 2473 x1: bb.x1, 2474 x2: bb.x2, 2475 w: bb.w, 2476 y1: bb.y1, 2477 y2: bb.y2, 2478 h: bb.h 2479 }; 2480 }; 2481 var clearBoundingBox = function clearBoundingBox(bb) { 2482 bb.x1 = Infinity; 2483 bb.y1 = Infinity; 2484 bb.x2 = -Infinity; 2485 bb.y2 = -Infinity; 2486 bb.w = 0; 2487 bb.h = 0; 2488 }; 2489 var updateBoundingBox = function updateBoundingBox(bb1, bb2) { 2490 // update bb1 with bb2 bounds 2491 bb1.x1 = Math.min(bb1.x1, bb2.x1); 2492 bb1.x2 = Math.max(bb1.x2, bb2.x2); 2493 bb1.w = bb1.x2 - bb1.x1; 2494 bb1.y1 = Math.min(bb1.y1, bb2.y1); 2495 bb1.y2 = Math.max(bb1.y2, bb2.y2); 2496 bb1.h = bb1.y2 - bb1.y1; 2497 }; 2498 var expandBoundingBoxByPoint = function expandBoundingBoxByPoint(bb, x, y) { 2499 bb.x1 = Math.min(bb.x1, x); 2500 bb.x2 = Math.max(bb.x2, x); 2501 bb.w = bb.x2 - bb.x1; 2502 bb.y1 = Math.min(bb.y1, y); 2503 bb.y2 = Math.max(bb.y2, y); 2504 bb.h = bb.y2 - bb.y1; 2505 }; 2506 var expandBoundingBox = function expandBoundingBox(bb) { 2507 var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2508 bb.x1 -= padding; 2509 bb.x2 += padding; 2510 bb.y1 -= padding; 2511 bb.y2 += padding; 2512 bb.w = bb.x2 - bb.x1; 2513 bb.h = bb.y2 - bb.y1; 2514 return bb; 2515 }; 2516 var expandBoundingBoxSides = function expandBoundingBoxSides(bb) { 2517 var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [0]; 2518 var top, right, bottom, left; 2519 2520 if (padding.length === 1) { 2521 top = right = bottom = left = padding[0]; 2522 } else if (padding.length === 2) { 2523 top = bottom = padding[0]; 2524 left = right = padding[1]; 2525 } else if (padding.length === 4) { 2526 var _padding = _slicedToArray(padding, 4); 2527 2528 top = _padding[0]; 2529 right = _padding[1]; 2530 bottom = _padding[2]; 2531 left = _padding[3]; 2532 } 2533 2534 bb.x1 -= left; 2535 bb.x2 += right; 2536 bb.y1 -= top; 2537 bb.y2 += bottom; 2538 bb.w = bb.x2 - bb.x1; 2539 bb.h = bb.y2 - bb.y1; 2540 return bb; 2541 }; 2542 2543 var assignBoundingBox = function assignBoundingBox(bb1, bb2) { 2544 bb1.x1 = bb2.x1; 2545 bb1.y1 = bb2.y1; 2546 bb1.x2 = bb2.x2; 2547 bb1.y2 = bb2.y2; 2548 bb1.w = bb1.x2 - bb1.x1; 2549 bb1.h = bb1.y2 - bb1.y1; 2550 }; 2551 var assignShiftToBoundingBox = function assignShiftToBoundingBox(bb, delta) { 2552 bb.x1 += delta.x; 2553 bb.x2 += delta.x; 2554 bb.y1 += delta.y; 2555 bb.y2 += delta.y; 2556 }; 2557 var boundingBoxesIntersect = function boundingBoxesIntersect(bb1, bb2) { 2558 // case: one bb to right of other 2559 if (bb1.x1 > bb2.x2) { 2560 return false; 2561 } 2562 2563 if (bb2.x1 > bb1.x2) { 2564 return false; 2565 } // case: one bb to left of other 2566 2567 2568 if (bb1.x2 < bb2.x1) { 2569 return false; 2570 } 2571 2572 if (bb2.x2 < bb1.x1) { 2573 return false; 2574 } // case: one bb above other 2575 2576 2577 if (bb1.y2 < bb2.y1) { 2578 return false; 2579 } 2580 2581 if (bb2.y2 < bb1.y1) { 2582 return false; 2583 } // case: one bb below other 2584 2585 2586 if (bb1.y1 > bb2.y2) { 2587 return false; 2588 } 2589 2590 if (bb2.y1 > bb1.y2) { 2591 return false; 2592 } // otherwise, must have some overlap 2593 2594 2595 return true; 2596 }; 2597 var inBoundingBox = function inBoundingBox(bb, x, y) { 2598 return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2; 2599 }; 2600 var pointInBoundingBox = function pointInBoundingBox(bb, pt) { 2601 return inBoundingBox(bb, pt.x, pt.y); 2602 }; 2603 var boundingBoxInBoundingBox = function boundingBoxInBoundingBox(bb1, bb2) { 2604 return inBoundingBox(bb1, bb2.x1, bb2.y1) && inBoundingBox(bb1, bb2.x2, bb2.y2); 2605 }; 2606 var roundRectangleIntersectLine = function roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding) { 2607 var cornerRadius = getRoundRectangleRadius(width, height); 2608 var halfWidth = width / 2; 2609 var halfHeight = height / 2; // Check intersections with straight line segments 2610 2611 var straightLineIntersections; // Top segment, left to right 2612 2613 { 2614 var topStartX = nodeX - halfWidth + cornerRadius - padding; 2615 var topStartY = nodeY - halfHeight - padding; 2616 var topEndX = nodeX + halfWidth - cornerRadius + padding; 2617 var topEndY = topStartY; 2618 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); 2619 2620 if (straightLineIntersections.length > 0) { 2621 return straightLineIntersections; 2622 } 2623 } // Right segment, top to bottom 2624 2625 { 2626 var rightStartX = nodeX + halfWidth + padding; 2627 var rightStartY = nodeY - halfHeight + cornerRadius - padding; 2628 var rightEndX = rightStartX; 2629 var rightEndY = nodeY + halfHeight - cornerRadius + padding; 2630 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false); 2631 2632 if (straightLineIntersections.length > 0) { 2633 return straightLineIntersections; 2634 } 2635 } // Bottom segment, left to right 2636 2637 { 2638 var bottomStartX = nodeX - halfWidth + cornerRadius - padding; 2639 var bottomStartY = nodeY + halfHeight + padding; 2640 var bottomEndX = nodeX + halfWidth - cornerRadius + padding; 2641 var bottomEndY = bottomStartY; 2642 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false); 2643 2644 if (straightLineIntersections.length > 0) { 2645 return straightLineIntersections; 2646 } 2647 } // Left segment, top to bottom 2648 2649 { 2650 var leftStartX = nodeX - halfWidth - padding; 2651 var leftStartY = nodeY - halfHeight + cornerRadius - padding; 2652 var leftEndX = leftStartX; 2653 var leftEndY = nodeY + halfHeight - cornerRadius + padding; 2654 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false); 2655 2656 if (straightLineIntersections.length > 0) { 2657 return straightLineIntersections; 2658 } 2659 } // Check intersections with arc segments 2660 2661 var arcIntersections; // Top Left 2662 2663 { 2664 var topLeftCenterX = nodeX - halfWidth + cornerRadius; 2665 var topLeftCenterY = nodeY - halfHeight + cornerRadius; 2666 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, topLeftCenterX, topLeftCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2667 2668 if (arcIntersections.length > 0 && arcIntersections[0] <= topLeftCenterX && arcIntersections[1] <= topLeftCenterY) { 2669 return [arcIntersections[0], arcIntersections[1]]; 2670 } 2671 } // Top Right 2672 2673 { 2674 var topRightCenterX = nodeX + halfWidth - cornerRadius; 2675 var topRightCenterY = nodeY - halfHeight + cornerRadius; 2676 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, topRightCenterX, topRightCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2677 2678 if (arcIntersections.length > 0 && arcIntersections[0] >= topRightCenterX && arcIntersections[1] <= topRightCenterY) { 2679 return [arcIntersections[0], arcIntersections[1]]; 2680 } 2681 } // Bottom Right 2682 2683 { 2684 var bottomRightCenterX = nodeX + halfWidth - cornerRadius; 2685 var bottomRightCenterY = nodeY + halfHeight - cornerRadius; 2686 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, bottomRightCenterX, bottomRightCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2687 2688 if (arcIntersections.length > 0 && arcIntersections[0] >= bottomRightCenterX && arcIntersections[1] >= bottomRightCenterY) { 2689 return [arcIntersections[0], arcIntersections[1]]; 2690 } 2691 } // Bottom Left 2692 2693 { 2694 var bottomLeftCenterX = nodeX - halfWidth + cornerRadius; 2695 var bottomLeftCenterY = nodeY + halfHeight - cornerRadius; 2696 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2697 2698 if (arcIntersections.length > 0 && arcIntersections[0] <= bottomLeftCenterX && arcIntersections[1] >= bottomLeftCenterY) { 2699 return [arcIntersections[0], arcIntersections[1]]; 2700 } 2701 } 2702 return []; // if nothing 2703 }; 2704 var inLineVicinity = function inLineVicinity(x, y, lx1, ly1, lx2, ly2, tolerance) { 2705 var t = tolerance; 2706 var x1 = Math.min(lx1, lx2); 2707 var x2 = Math.max(lx1, lx2); 2708 var y1 = Math.min(ly1, ly2); 2709 var y2 = Math.max(ly1, ly2); 2710 return x1 - t <= x && x <= x2 + t && y1 - t <= y && y <= y2 + t; 2711 }; 2712 var inBezierVicinity = function inBezierVicinity(x, y, x1, y1, x2, y2, x3, y3, tolerance) { 2713 var bb = { 2714 x1: Math.min(x1, x3, x2) - tolerance, 2715 x2: Math.max(x1, x3, x2) + tolerance, 2716 y1: Math.min(y1, y3, y2) - tolerance, 2717 y2: Math.max(y1, y3, y2) + tolerance 2718 }; // if outside the rough bounding box for the bezier, then it can't be a hit 2719 2720 if (x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2) { 2721 // console.log('bezier out of rough bb') 2722 return false; 2723 } else { 2724 // console.log('do more expensive check'); 2725 return true; 2726 } 2727 }; 2728 var solveQuadratic = function solveQuadratic(a, b, c, val) { 2729 c -= val; 2730 var r = b * b - 4 * a * c; 2731 2732 if (r < 0) { 2733 return []; 2734 } 2735 2736 var sqrtR = Math.sqrt(r); 2737 var denom = 2 * a; 2738 var root1 = (-b + sqrtR) / denom; 2739 var root2 = (-b - sqrtR) / denom; 2740 return [root1, root2]; 2741 }; 2742 var solveCubic = function solveCubic(a, b, c, d, result) { 2743 // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where 2744 // r is the real component, i is the imaginary component 2745 // An implementation of the Cardano method from the year 1545 2746 // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots 2747 var epsilon = 0.00001; // avoid division by zero while keeping the overall expression close in value 2748 2749 if (a === 0) { 2750 a = epsilon; 2751 } 2752 2753 b /= a; 2754 c /= a; 2755 d /= a; 2756 var discriminant, q, r, dum1, s, t, term1, r13; 2757 q = (3.0 * c - b * b) / 9.0; 2758 r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b)); 2759 r /= 54.0; 2760 discriminant = q * q * q + r * r; 2761 result[1] = 0; 2762 term1 = b / 3.0; 2763 2764 if (discriminant > 0) { 2765 s = r + Math.sqrt(discriminant); 2766 s = s < 0 ? -Math.pow(-s, 1.0 / 3.0) : Math.pow(s, 1.0 / 3.0); 2767 t = r - Math.sqrt(discriminant); 2768 t = t < 0 ? -Math.pow(-t, 1.0 / 3.0) : Math.pow(t, 1.0 / 3.0); 2769 result[0] = -term1 + s + t; 2770 term1 += (s + t) / 2.0; 2771 result[4] = result[2] = -term1; 2772 term1 = Math.sqrt(3.0) * (-t + s) / 2; 2773 result[3] = term1; 2774 result[5] = -term1; 2775 return; 2776 } 2777 2778 result[5] = result[3] = 0; 2779 2780 if (discriminant === 0) { 2781 r13 = r < 0 ? -Math.pow(-r, 1.0 / 3.0) : Math.pow(r, 1.0 / 3.0); 2782 result[0] = -term1 + 2.0 * r13; 2783 result[4] = result[2] = -(r13 + term1); 2784 return; 2785 } 2786 2787 q = -q; 2788 dum1 = q * q * q; 2789 dum1 = Math.acos(r / Math.sqrt(dum1)); 2790 r13 = 2.0 * Math.sqrt(q); 2791 result[0] = -term1 + r13 * Math.cos(dum1 / 3.0); 2792 result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0); 2793 result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0); 2794 return; 2795 }; 2796 var sqdistToQuadraticBezier = function sqdistToQuadraticBezier(x, y, x1, y1, x2, y2, x3, y3) { 2797 // Find minimum distance by using the minimum of the distance 2798 // function between the given point and the curve 2799 // This gives the coefficients of the resulting cubic equation 2800 // whose roots tell us where a possible minimum is 2801 // (Coefficients are divided by 4) 2802 var a = 1.0 * x1 * x1 - 4 * x1 * x2 + 2 * x1 * x3 + 4 * x2 * x2 - 4 * x2 * x3 + x3 * x3 + y1 * y1 - 4 * y1 * y2 + 2 * y1 * y3 + 4 * y2 * y2 - 4 * y2 * y3 + y3 * y3; 2803 var b = 1.0 * 9 * x1 * x2 - 3 * x1 * x1 - 3 * x1 * x3 - 6 * x2 * x2 + 3 * x2 * x3 + 9 * y1 * y2 - 3 * y1 * y1 - 3 * y1 * y3 - 6 * y2 * y2 + 3 * y2 * y3; 2804 var c = 1.0 * 3 * x1 * x1 - 6 * x1 * x2 + x1 * x3 - x1 * x + 2 * x2 * x2 + 2 * x2 * x - x3 * x + 3 * y1 * y1 - 6 * y1 * y2 + y1 * y3 - y1 * y + 2 * y2 * y2 + 2 * y2 * y - y3 * y; 2805 var d = 1.0 * x1 * x2 - x1 * x1 + x1 * x - x2 * x + y1 * y2 - y1 * y1 + y1 * y - y2 * y; // debug("coefficients: " + a / a + ", " + b / a + ", " + c / a + ", " + d / a); 2806 2807 var roots = []; // Use the cubic solving algorithm 2808 2809 solveCubic(a, b, c, d, roots); 2810 var zeroThreshold = 0.0000001; 2811 var params = []; 2812 2813 for (var index = 0; index < 6; index += 2) { 2814 if (Math.abs(roots[index + 1]) < zeroThreshold && roots[index] >= 0 && roots[index] <= 1.0) { 2815 params.push(roots[index]); 2816 } 2817 } 2818 2819 params.push(1.0); 2820 params.push(0.0); 2821 var minDistanceSquared = -1; 2822 var curX, curY, distSquared; 2823 2824 for (var i = 0; i < params.length; i++) { 2825 curX = Math.pow(1.0 - params[i], 2.0) * x1 + 2.0 * (1 - params[i]) * params[i] * x2 + params[i] * params[i] * x3; 2826 curY = Math.pow(1 - params[i], 2.0) * y1 + 2 * (1.0 - params[i]) * params[i] * y2 + params[i] * params[i] * y3; 2827 distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2); // debug('distance for param ' + params[i] + ": " + Math.sqrt(distSquared)); 2828 2829 if (minDistanceSquared >= 0) { 2830 if (distSquared < minDistanceSquared) { 2831 minDistanceSquared = distSquared; 2832 } 2833 } else { 2834 minDistanceSquared = distSquared; 2835 } 2836 } 2837 2838 return minDistanceSquared; 2839 }; 2840 var sqdistToFiniteLine = function sqdistToFiniteLine(x, y, x1, y1, x2, y2) { 2841 var offset = [x - x1, y - y1]; 2842 var line = [x2 - x1, y2 - y1]; 2843 var lineSq = line[0] * line[0] + line[1] * line[1]; 2844 var hypSq = offset[0] * offset[0] + offset[1] * offset[1]; 2845 var dotProduct = offset[0] * line[0] + offset[1] * line[1]; 2846 var adjSq = dotProduct * dotProduct / lineSq; 2847 2848 if (dotProduct < 0) { 2849 return hypSq; 2850 } 2851 2852 if (adjSq > lineSq) { 2853 return (x - x2) * (x - x2) + (y - y2) * (y - y2); 2854 } 2855 2856 return hypSq - adjSq; 2857 }; 2858 var pointInsidePolygonPoints = function pointInsidePolygonPoints(x, y, points) { 2859 var x1, y1, x2, y2; 2860 var y3; // Intersect with vertical line through (x, y) 2861 2862 var up = 0; // let down = 0; 2863 2864 for (var i = 0; i < points.length / 2; i++) { 2865 x1 = points[i * 2]; 2866 y1 = points[i * 2 + 1]; 2867 2868 if (i + 1 < points.length / 2) { 2869 x2 = points[(i + 1) * 2]; 2870 y2 = points[(i + 1) * 2 + 1]; 2871 } else { 2872 x2 = points[(i + 1 - points.length / 2) * 2]; 2873 y2 = points[(i + 1 - points.length / 2) * 2 + 1]; 2874 } 2875 2876 if (x1 == x && x2 == x) ; else if (x1 >= x && x >= x2 || x1 <= x && x <= x2) { 2877 y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1; 2878 2879 if (y3 > y) { 2880 up++; 2881 } // if( y3 < y ){ 2882 // down++; 2883 // } 2884 2885 } else { 2886 continue; 2887 } 2888 } 2889 2890 if (up % 2 === 0) { 2891 return false; 2892 } else { 2893 return true; 2894 } 2895 }; 2896 var pointInsidePolygon = function pointInsidePolygon(x, y, basePoints, centerX, centerY, width, height, direction, padding) { 2897 var transformedPoints = new Array(basePoints.length); // Gives negative angle 2898 2899 var angle; 2900 2901 if (direction[0] != null) { 2902 angle = Math.atan(direction[1] / direction[0]); 2903 2904 if (direction[0] < 0) { 2905 angle = angle + Math.PI / 2; 2906 } else { 2907 angle = -angle - Math.PI / 2; 2908 } 2909 } else { 2910 angle = direction; 2911 } 2912 2913 var cos = Math.cos(-angle); 2914 var sin = Math.sin(-angle); // console.log("base: " + basePoints); 2915 2916 for (var i = 0; i < transformedPoints.length / 2; i++) { 2917 transformedPoints[i * 2] = width / 2 * (basePoints[i * 2] * cos - basePoints[i * 2 + 1] * sin); 2918 transformedPoints[i * 2 + 1] = height / 2 * (basePoints[i * 2 + 1] * cos + basePoints[i * 2] * sin); 2919 transformedPoints[i * 2] += centerX; 2920 transformedPoints[i * 2 + 1] += centerY; 2921 } 2922 2923 var points; 2924 2925 if (padding > 0) { 2926 var expandedLineSet = expandPolygon(transformedPoints, -padding); 2927 points = joinLines(expandedLineSet); 2928 } else { 2929 points = transformedPoints; 2930 } 2931 2932 return pointInsidePolygonPoints(x, y, points); 2933 }; 2934 var pointInsideRoundPolygon = function pointInsideRoundPolygon(x, y, basePoints, centerX, centerY, width, height) { 2935 var cutPolygonPoints = new Array(basePoints.length); 2936 var halfW = width / 2; 2937 var halfH = height / 2; 2938 var cornerRadius = getRoundPolygonRadius(width, height); 2939 var squaredCornerRadius = cornerRadius * cornerRadius; 2940 2941 for (var i = 0; i < basePoints.length / 4; i++) { 2942 var sourceUv = void 0, 2943 destUv = void 0; 2944 2945 if (i === 0) { 2946 sourceUv = basePoints.length - 2; 2947 } else { 2948 sourceUv = i * 4 - 2; 2949 } 2950 2951 destUv = i * 4 + 2; 2952 var px = centerX + halfW * basePoints[i * 4]; 2953 var py = centerY + halfH * basePoints[i * 4 + 1]; 2954 var cosTheta = -basePoints[sourceUv] * basePoints[destUv] - basePoints[sourceUv + 1] * basePoints[destUv + 1]; 2955 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 2956 var cp0x = px - offset * basePoints[sourceUv]; 2957 var cp0y = py - offset * basePoints[sourceUv + 1]; 2958 var cp1x = px + offset * basePoints[destUv]; 2959 var cp1y = py + offset * basePoints[destUv + 1]; 2960 cutPolygonPoints[i * 4] = cp0x; 2961 cutPolygonPoints[i * 4 + 1] = cp0y; 2962 cutPolygonPoints[i * 4 + 2] = cp1x; 2963 cutPolygonPoints[i * 4 + 3] = cp1y; 2964 var orthx = basePoints[sourceUv + 1]; 2965 var orthy = -basePoints[sourceUv]; 2966 var cosAlpha = orthx * basePoints[destUv] + orthy * basePoints[destUv + 1]; 2967 2968 if (cosAlpha < 0) { 2969 orthx *= -1; 2970 orthy *= -1; 2971 } 2972 2973 var cx = cp0x + orthx * cornerRadius; 2974 var cy = cp0y + orthy * cornerRadius; 2975 var squaredDistance = Math.pow(cx - x, 2) + Math.pow(cy - y, 2); 2976 2977 if (squaredDistance <= squaredCornerRadius) { 2978 return true; 2979 } 2980 } 2981 2982 return pointInsidePolygonPoints(x, y, cutPolygonPoints); 2983 }; 2984 var joinLines = function joinLines(lineSet) { 2985 var vertices = new Array(lineSet.length / 2); 2986 var currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY; 2987 var nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY; 2988 2989 for (var i = 0; i < lineSet.length / 4; i++) { 2990 currentLineStartX = lineSet[i * 4]; 2991 currentLineStartY = lineSet[i * 4 + 1]; 2992 currentLineEndX = lineSet[i * 4 + 2]; 2993 currentLineEndY = lineSet[i * 4 + 3]; 2994 2995 if (i < lineSet.length / 4 - 1) { 2996 nextLineStartX = lineSet[(i + 1) * 4]; 2997 nextLineStartY = lineSet[(i + 1) * 4 + 1]; 2998 nextLineEndX = lineSet[(i + 1) * 4 + 2]; 2999 nextLineEndY = lineSet[(i + 1) * 4 + 3]; 3000 } else { 3001 nextLineStartX = lineSet[0]; 3002 nextLineStartY = lineSet[1]; 3003 nextLineEndX = lineSet[2]; 3004 nextLineEndY = lineSet[3]; 3005 } 3006 3007 var intersection = finiteLinesIntersect(currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY, nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY, true); 3008 vertices[i * 2] = intersection[0]; 3009 vertices[i * 2 + 1] = intersection[1]; 3010 } 3011 3012 return vertices; 3013 }; 3014 var expandPolygon = function expandPolygon(points, pad) { 3015 var expandedLineSet = new Array(points.length * 2); 3016 var currentPointX, currentPointY, nextPointX, nextPointY; 3017 3018 for (var i = 0; i < points.length / 2; i++) { 3019 currentPointX = points[i * 2]; 3020 currentPointY = points[i * 2 + 1]; 3021 3022 if (i < points.length / 2 - 1) { 3023 nextPointX = points[(i + 1) * 2]; 3024 nextPointY = points[(i + 1) * 2 + 1]; 3025 } else { 3026 nextPointX = points[0]; 3027 nextPointY = points[1]; 3028 } // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY] 3029 // Assume CCW polygon winding 3030 3031 3032 var offsetX = nextPointY - currentPointY; 3033 var offsetY = -(nextPointX - currentPointX); // Normalize 3034 3035 var offsetLength = Math.sqrt(offsetX * offsetX + offsetY * offsetY); 3036 var normalizedOffsetX = offsetX / offsetLength; 3037 var normalizedOffsetY = offsetY / offsetLength; 3038 expandedLineSet[i * 4] = currentPointX + normalizedOffsetX * pad; 3039 expandedLineSet[i * 4 + 1] = currentPointY + normalizedOffsetY * pad; 3040 expandedLineSet[i * 4 + 2] = nextPointX + normalizedOffsetX * pad; 3041 expandedLineSet[i * 4 + 3] = nextPointY + normalizedOffsetY * pad; 3042 } 3043 3044 return expandedLineSet; 3045 }; 3046 var intersectLineEllipse = function intersectLineEllipse(x, y, centerX, centerY, ellipseWradius, ellipseHradius) { 3047 var dispX = centerX - x; 3048 var dispY = centerY - y; 3049 dispX /= ellipseWradius; 3050 dispY /= ellipseHradius; 3051 var len = Math.sqrt(dispX * dispX + dispY * dispY); 3052 var newLength = len - 1; 3053 3054 if (newLength < 0) { 3055 return []; 3056 } 3057 3058 var lenProportion = newLength / len; 3059 return [(centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y]; 3060 }; 3061 var checkInEllipse = function checkInEllipse(x, y, width, height, centerX, centerY, padding) { 3062 x -= centerX; 3063 y -= centerY; 3064 x /= width / 2 + padding; 3065 y /= height / 2 + padding; 3066 return x * x + y * y <= 1; 3067 }; // Returns intersections of increasing distance from line's start point 3068 3069 var intersectLineCircle = function intersectLineCircle(x1, y1, x2, y2, centerX, centerY, radius) { 3070 // Calculate d, direction vector of line 3071 var d = [x2 - x1, y2 - y1]; // Direction vector of line 3072 3073 var f = [x1 - centerX, y1 - centerY]; 3074 var a = d[0] * d[0] + d[1] * d[1]; 3075 var b = 2 * (f[0] * d[0] + f[1] * d[1]); 3076 var c = f[0] * f[0] + f[1] * f[1] - radius * radius; 3077 var discriminant = b * b - 4 * a * c; 3078 3079 if (discriminant < 0) { 3080 return []; 3081 } 3082 3083 var t1 = (-b + Math.sqrt(discriminant)) / (2 * a); 3084 var t2 = (-b - Math.sqrt(discriminant)) / (2 * a); 3085 var tMin = Math.min(t1, t2); 3086 var tMax = Math.max(t1, t2); 3087 var inRangeParams = []; 3088 3089 if (tMin >= 0 && tMin <= 1) { 3090 inRangeParams.push(tMin); 3091 } 3092 3093 if (tMax >= 0 && tMax <= 1) { 3094 inRangeParams.push(tMax); 3095 } 3096 3097 if (inRangeParams.length === 0) { 3098 return []; 3099 } 3100 3101 var nearIntersectionX = inRangeParams[0] * d[0] + x1; 3102 var nearIntersectionY = inRangeParams[0] * d[1] + y1; 3103 3104 if (inRangeParams.length > 1) { 3105 if (inRangeParams[0] == inRangeParams[1]) { 3106 return [nearIntersectionX, nearIntersectionY]; 3107 } else { 3108 var farIntersectionX = inRangeParams[1] * d[0] + x1; 3109 var farIntersectionY = inRangeParams[1] * d[1] + y1; 3110 return [nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY]; 3111 } 3112 } else { 3113 return [nearIntersectionX, nearIntersectionY]; 3114 } 3115 }; 3116 var midOfThree = function midOfThree(a, b, c) { 3117 if (b <= a && a <= c || c <= a && a <= b) { 3118 return a; 3119 } else if (a <= b && b <= c || c <= b && b <= a) { 3120 return b; 3121 } else { 3122 return c; 3123 } 3124 }; // (x1,y1)=>(x2,y2) intersect with (x3,y3)=>(x4,y4) 3125 3126 var finiteLinesIntersect = function finiteLinesIntersect(x1, y1, x2, y2, x3, y3, x4, y4, infiniteLines) { 3127 var dx13 = x1 - x3; 3128 var dx21 = x2 - x1; 3129 var dx43 = x4 - x3; 3130 var dy13 = y1 - y3; 3131 var dy21 = y2 - y1; 3132 var dy43 = y4 - y3; 3133 var ua_t = dx43 * dy13 - dy43 * dx13; 3134 var ub_t = dx21 * dy13 - dy21 * dx13; 3135 var u_b = dy43 * dx21 - dx43 * dy21; 3136 3137 if (u_b !== 0) { 3138 var ua = ua_t / u_b; 3139 var ub = ub_t / u_b; 3140 var flptThreshold = 0.001; 3141 3142 var _min = 0 - flptThreshold; 3143 3144 var _max = 1 + flptThreshold; 3145 3146 if (_min <= ua && ua <= _max && _min <= ub && ub <= _max) { 3147 return [x1 + ua * dx21, y1 + ua * dy21]; 3148 } else { 3149 if (!infiniteLines) { 3150 return []; 3151 } else { 3152 return [x1 + ua * dx21, y1 + ua * dy21]; 3153 } 3154 } 3155 } else { 3156 if (ua_t === 0 || ub_t === 0) { 3157 // Parallel, coincident lines. Check if overlap 3158 // Check endpoint of second line 3159 if (midOfThree(x1, x2, x4) === x4) { 3160 return [x4, y4]; 3161 } // Check start point of second line 3162 3163 3164 if (midOfThree(x1, x2, x3) === x3) { 3165 return [x3, y3]; 3166 } // Endpoint of first line 3167 3168 3169 if (midOfThree(x3, x4, x2) === x2) { 3170 return [x2, y2]; 3171 } 3172 3173 return []; 3174 } else { 3175 // Parallel, non-coincident 3176 return []; 3177 } 3178 } 3179 }; // math.polygonIntersectLine( x, y, basePoints, centerX, centerY, width, height, padding ) 3180 // intersect a node polygon (pts transformed) 3181 // 3182 // math.polygonIntersectLine( x, y, basePoints, centerX, centerY ) 3183 // intersect the points (no transform) 3184 3185 var polygonIntersectLine = function polygonIntersectLine(x, y, basePoints, centerX, centerY, width, height, padding) { 3186 var intersections = []; 3187 var intersection; 3188 var transformedPoints = new Array(basePoints.length); 3189 var doTransform = true; 3190 3191 if (width == null) { 3192 doTransform = false; 3193 } 3194 3195 var points; 3196 3197 if (doTransform) { 3198 for (var i = 0; i < transformedPoints.length / 2; i++) { 3199 transformedPoints[i * 2] = basePoints[i * 2] * width + centerX; 3200 transformedPoints[i * 2 + 1] = basePoints[i * 2 + 1] * height + centerY; 3201 } 3202 3203 if (padding > 0) { 3204 var expandedLineSet = expandPolygon(transformedPoints, -padding); 3205 points = joinLines(expandedLineSet); 3206 } else { 3207 points = transformedPoints; 3208 } 3209 } else { 3210 points = basePoints; 3211 } 3212 3213 var currentX, currentY, nextX, nextY; 3214 3215 for (var _i2 = 0; _i2 < points.length / 2; _i2++) { 3216 currentX = points[_i2 * 2]; 3217 currentY = points[_i2 * 2 + 1]; 3218 3219 if (_i2 < points.length / 2 - 1) { 3220 nextX = points[(_i2 + 1) * 2]; 3221 nextY = points[(_i2 + 1) * 2 + 1]; 3222 } else { 3223 nextX = points[0]; 3224 nextY = points[1]; 3225 } 3226 3227 intersection = finiteLinesIntersect(x, y, centerX, centerY, currentX, currentY, nextX, nextY); 3228 3229 if (intersection.length !== 0) { 3230 intersections.push(intersection[0], intersection[1]); 3231 } 3232 } 3233 3234 return intersections; 3235 }; 3236 var roundPolygonIntersectLine = function roundPolygonIntersectLine(x, y, basePoints, centerX, centerY, width, height, padding) { 3237 var intersections = []; 3238 var intersection; 3239 var lines = new Array(basePoints.length); 3240 var halfW = width / 2; 3241 var halfH = height / 2; 3242 var cornerRadius = getRoundPolygonRadius(width, height); 3243 3244 for (var i = 0; i < basePoints.length / 4; i++) { 3245 var sourceUv = void 0, 3246 destUv = void 0; 3247 3248 if (i === 0) { 3249 sourceUv = basePoints.length - 2; 3250 } else { 3251 sourceUv = i * 4 - 2; 3252 } 3253 3254 destUv = i * 4 + 2; 3255 var px = centerX + halfW * basePoints[i * 4]; 3256 var py = centerY + halfH * basePoints[i * 4 + 1]; 3257 var cosTheta = -basePoints[sourceUv] * basePoints[destUv] - basePoints[sourceUv + 1] * basePoints[destUv + 1]; 3258 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 3259 var cp0x = px - offset * basePoints[sourceUv]; 3260 var cp0y = py - offset * basePoints[sourceUv + 1]; 3261 var cp1x = px + offset * basePoints[destUv]; 3262 var cp1y = py + offset * basePoints[destUv + 1]; 3263 3264 if (i === 0) { 3265 lines[basePoints.length - 2] = cp0x; 3266 lines[basePoints.length - 1] = cp0y; 3267 } else { 3268 lines[i * 4 - 2] = cp0x; 3269 lines[i * 4 - 1] = cp0y; 3270 } 3271 3272 lines[i * 4] = cp1x; 3273 lines[i * 4 + 1] = cp1y; 3274 var orthx = basePoints[sourceUv + 1]; 3275 var orthy = -basePoints[sourceUv]; 3276 var cosAlpha = orthx * basePoints[destUv] + orthy * basePoints[destUv + 1]; 3277 3278 if (cosAlpha < 0) { 3279 orthx *= -1; 3280 orthy *= -1; 3281 } 3282 3283 var cx = cp0x + orthx * cornerRadius; 3284 var cy = cp0y + orthy * cornerRadius; 3285 intersection = intersectLineCircle(x, y, centerX, centerY, cx, cy, cornerRadius); 3286 3287 if (intersection.length !== 0) { 3288 intersections.push(intersection[0], intersection[1]); 3289 } 3290 } 3291 3292 for (var _i3 = 0; _i3 < lines.length / 4; _i3++) { 3293 intersection = finiteLinesIntersect(x, y, centerX, centerY, lines[_i3 * 4], lines[_i3 * 4 + 1], lines[_i3 * 4 + 2], lines[_i3 * 4 + 3], false); 3294 3295 if (intersection.length !== 0) { 3296 intersections.push(intersection[0], intersection[1]); 3297 } 3298 } 3299 3300 if (intersections.length > 2) { 3301 var lowestIntersection = [intersections[0], intersections[1]]; 3302 var lowestSquaredDistance = Math.pow(lowestIntersection[0] - x, 2) + Math.pow(lowestIntersection[1] - y, 2); 3303 3304 for (var _i4 = 1; _i4 < intersections.length / 2; _i4++) { 3305 var squaredDistance = Math.pow(intersections[_i4 * 2] - x, 2) + Math.pow(intersections[_i4 * 2 + 1] - y, 2); 3306 3307 if (squaredDistance <= lowestSquaredDistance) { 3308 lowestIntersection[0] = intersections[_i4 * 2]; 3309 lowestIntersection[1] = intersections[_i4 * 2 + 1]; 3310 lowestSquaredDistance = squaredDistance; 3311 } 3312 } 3313 3314 return lowestIntersection; 3315 } 3316 3317 return intersections; 3318 }; 3319 var shortenIntersection = function shortenIntersection(intersection, offset, amount) { 3320 var disp = [intersection[0] - offset[0], intersection[1] - offset[1]]; 3321 var length = Math.sqrt(disp[0] * disp[0] + disp[1] * disp[1]); 3322 var lenRatio = (length - amount) / length; 3323 3324 if (lenRatio < 0) { 3325 lenRatio = 0.00001; 3326 } 3327 3328 return [offset[0] + lenRatio * disp[0], offset[1] + lenRatio * disp[1]]; 3329 }; 3330 var generateUnitNgonPointsFitToSquare = function generateUnitNgonPointsFitToSquare(sides, rotationRadians) { 3331 var points = generateUnitNgonPoints(sides, rotationRadians); 3332 points = fitPolygonToSquare(points); 3333 return points; 3334 }; 3335 var fitPolygonToSquare = function fitPolygonToSquare(points) { 3336 var x, y; 3337 var sides = points.length / 2; 3338 var minX = Infinity, 3339 minY = Infinity, 3340 maxX = -Infinity, 3341 maxY = -Infinity; 3342 3343 for (var i = 0; i < sides; i++) { 3344 x = points[2 * i]; 3345 y = points[2 * i + 1]; 3346 minX = Math.min(minX, x); 3347 maxX = Math.max(maxX, x); 3348 minY = Math.min(minY, y); 3349 maxY = Math.max(maxY, y); 3350 } // stretch factors 3351 3352 3353 var sx = 2 / (maxX - minX); 3354 var sy = 2 / (maxY - minY); 3355 3356 for (var _i5 = 0; _i5 < sides; _i5++) { 3357 x = points[2 * _i5] = points[2 * _i5] * sx; 3358 y = points[2 * _i5 + 1] = points[2 * _i5 + 1] * sy; 3359 minX = Math.min(minX, x); 3360 maxX = Math.max(maxX, x); 3361 minY = Math.min(minY, y); 3362 maxY = Math.max(maxY, y); 3363 } 3364 3365 if (minY < -1) { 3366 for (var _i6 = 0; _i6 < sides; _i6++) { 3367 y = points[2 * _i6 + 1] = points[2 * _i6 + 1] + (-1 - minY); 3368 } 3369 } 3370 3371 return points; 3372 }; 3373 var generateUnitNgonPoints = function generateUnitNgonPoints(sides, rotationRadians) { 3374 var increment = 1.0 / sides * 2 * Math.PI; 3375 var startAngle = sides % 2 === 0 ? Math.PI / 2.0 + increment / 2.0 : Math.PI / 2.0; 3376 startAngle += rotationRadians; 3377 var points = new Array(sides * 2); 3378 var currentAngle; 3379 3380 for (var i = 0; i < sides; i++) { 3381 currentAngle = i * increment + startAngle; 3382 points[2 * i] = Math.cos(currentAngle); // x 3383 3384 points[2 * i + 1] = Math.sin(-currentAngle); // y 3385 } 3386 3387 return points; 3388 }; // Set the default radius, unless half of width or height is smaller than default 3389 3390 var getRoundRectangleRadius = function getRoundRectangleRadius(width, height) { 3391 return Math.min(width / 4, height / 4, 8); 3392 }; // Set the default radius 3393 3394 var getRoundPolygonRadius = function getRoundPolygonRadius(width, height) { 3395 return Math.min(width / 10, height / 10, 8); 3396 }; 3397 var getCutRectangleCornerLength = function getCutRectangleCornerLength() { 3398 return 8; 3399 }; 3400 var bezierPtsToQuadCoeff = function bezierPtsToQuadCoeff(p0, p1, p2) { 3401 return [p0 - 2 * p1 + p2, 2 * (p1 - p0), p0]; 3402 }; // get curve width, height, and control point position offsets as a percentage of node height / width 3403 3404 var getBarrelCurveConstants = function getBarrelCurveConstants(width, height) { 3405 return { 3406 heightOffset: Math.min(15, 0.05 * height), 3407 widthOffset: Math.min(100, 0.25 * width), 3408 ctrlPtOffsetPct: 0.05 3409 }; 3410 }; 3411 3412 var pageRankDefaults = defaults({ 3413 dampingFactor: 0.8, 3414 precision: 0.000001, 3415 iterations: 200, 3416 weight: function weight(edge) { 3417 return 1; 3418 } 3419 }); 3420 var elesfn$7 = { 3421 pageRank: function pageRank(options) { 3422 var _pageRankDefaults = pageRankDefaults(options), 3423 dampingFactor = _pageRankDefaults.dampingFactor, 3424 precision = _pageRankDefaults.precision, 3425 iterations = _pageRankDefaults.iterations, 3426 weight = _pageRankDefaults.weight; 3427 3428 var cy = this._private.cy; 3429 3430 var _this$byGroup = this.byGroup(), 3431 nodes = _this$byGroup.nodes, 3432 edges = _this$byGroup.edges; 3433 3434 var numNodes = nodes.length; 3435 var numNodesSqd = numNodes * numNodes; 3436 var numEdges = edges.length; // Construct transposed adjacency matrix 3437 // First lets have a zeroed matrix of the right size 3438 // We'll also keep track of the sum of each column 3439 3440 var matrix = new Array(numNodesSqd); 3441 var columnSum = new Array(numNodes); 3442 var additionalProb = (1 - dampingFactor) / numNodes; // Create null matrix 3443 3444 for (var i = 0; i < numNodes; i++) { 3445 for (var j = 0; j < numNodes; j++) { 3446 var n = i * numNodes + j; 3447 matrix[n] = 0; 3448 } 3449 3450 columnSum[i] = 0; 3451 } // Now, process edges 3452 3453 3454 for (var _i = 0; _i < numEdges; _i++) { 3455 var edge = edges[_i]; 3456 var srcId = edge.data('source'); 3457 var tgtId = edge.data('target'); // Don't include loops in the matrix 3458 3459 if (srcId === tgtId) { 3460 continue; 3461 } 3462 3463 var s = nodes.indexOfId(srcId); 3464 var t = nodes.indexOfId(tgtId); 3465 var w = weight(edge); 3466 3467 var _n = t * numNodes + s; // Update matrix 3468 3469 3470 matrix[_n] += w; // Update column sum 3471 3472 columnSum[s] += w; 3473 } // Add additional probability based on damping factor 3474 // Also, take into account columns that have sum = 0 3475 3476 3477 var p = 1.0 / numNodes + additionalProb; // Shorthand 3478 // Traverse matrix, column by column 3479 3480 for (var _j = 0; _j < numNodes; _j++) { 3481 if (columnSum[_j] === 0) { 3482 // No 'links' out from node jth, assume equal probability for each possible node 3483 for (var _i2 = 0; _i2 < numNodes; _i2++) { 3484 var _n2 = _i2 * numNodes + _j; 3485 3486 matrix[_n2] = p; 3487 } 3488 } else { 3489 // Node jth has outgoing link, compute normalized probabilities 3490 for (var _i3 = 0; _i3 < numNodes; _i3++) { 3491 var _n3 = _i3 * numNodes + _j; 3492 3493 matrix[_n3] = matrix[_n3] / columnSum[_j] + additionalProb; 3494 } 3495 } 3496 } // Compute dominant eigenvector using power method 3497 3498 3499 var eigenvector = new Array(numNodes); 3500 var temp = new Array(numNodes); 3501 var previous; // Start with a vector of all 1's 3502 // Also, initialize a null vector which will be used as shorthand 3503 3504 for (var _i4 = 0; _i4 < numNodes; _i4++) { 3505 eigenvector[_i4] = 1; 3506 } 3507 3508 for (var iter = 0; iter < iterations; iter++) { 3509 // Temp array with all 0's 3510 for (var _i5 = 0; _i5 < numNodes; _i5++) { 3511 temp[_i5] = 0; 3512 } // Multiply matrix with previous result 3513 3514 3515 for (var _i6 = 0; _i6 < numNodes; _i6++) { 3516 for (var _j2 = 0; _j2 < numNodes; _j2++) { 3517 var _n4 = _i6 * numNodes + _j2; 3518 3519 temp[_i6] += matrix[_n4] * eigenvector[_j2]; 3520 } 3521 } 3522 3523 inPlaceSumNormalize(temp); 3524 previous = eigenvector; 3525 eigenvector = temp; 3526 temp = previous; 3527 var diff = 0; // Compute difference (squared module) of both vectors 3528 3529 for (var _i7 = 0; _i7 < numNodes; _i7++) { 3530 var delta = previous[_i7] - eigenvector[_i7]; 3531 diff += delta * delta; 3532 } // If difference is less than the desired threshold, stop iterating 3533 3534 3535 if (diff < precision) { 3536 break; 3537 } 3538 } // Construct result 3539 3540 3541 var res = { 3542 rank: function rank(node) { 3543 node = cy.collection(node)[0]; 3544 return eigenvector[nodes.indexOf(node)]; 3545 } 3546 }; 3547 return res; 3548 } // pageRank 3549 3550 }; // elesfn 3551 3552 var defaults$1 = defaults({ 3553 root: null, 3554 weight: function weight(edge) { 3555 return 1; 3556 }, 3557 directed: false, 3558 alpha: 0 3559 }); 3560 var elesfn$8 = { 3561 degreeCentralityNormalized: function degreeCentralityNormalized(options) { 3562 options = defaults$1(options); 3563 var cy = this.cy(); 3564 var nodes = this.nodes(); 3565 var numNodes = nodes.length; 3566 3567 if (!options.directed) { 3568 var degrees = {}; 3569 var maxDegree = 0; 3570 3571 for (var i = 0; i < numNodes; i++) { 3572 var node = nodes[i]; // add current node to the current options object and call degreeCentrality 3573 3574 options.root = node; 3575 var currDegree = this.degreeCentrality(options); 3576 3577 if (maxDegree < currDegree.degree) { 3578 maxDegree = currDegree.degree; 3579 } 3580 3581 degrees[node.id()] = currDegree.degree; 3582 } 3583 3584 return { 3585 degree: function degree(node) { 3586 if (maxDegree === 0) { 3587 return 0; 3588 } 3589 3590 if (string(node)) { 3591 // from is a selector string 3592 node = cy.filter(node); 3593 } 3594 3595 return degrees[node.id()] / maxDegree; 3596 } 3597 }; 3598 } else { 3599 var indegrees = {}; 3600 var outdegrees = {}; 3601 var maxIndegree = 0; 3602 var maxOutdegree = 0; 3603 3604 for (var _i = 0; _i < numNodes; _i++) { 3605 var _node = nodes[_i]; 3606 3607 var id = _node.id(); // add current node to the current options object and call degreeCentrality 3608 3609 3610 options.root = _node; 3611 3612 var _currDegree = this.degreeCentrality(options); 3613 3614 if (maxIndegree < _currDegree.indegree) maxIndegree = _currDegree.indegree; 3615 if (maxOutdegree < _currDegree.outdegree) maxOutdegree = _currDegree.outdegree; 3616 indegrees[id] = _currDegree.indegree; 3617 outdegrees[id] = _currDegree.outdegree; 3618 } 3619 3620 return { 3621 indegree: function indegree(node) { 3622 if (maxIndegree == 0) { 3623 return 0; 3624 } 3625 3626 if (string(node)) { 3627 // from is a selector string 3628 node = cy.filter(node); 3629 } 3630 3631 return indegrees[node.id()] / maxIndegree; 3632 }, 3633 outdegree: function outdegree(node) { 3634 if (maxOutdegree === 0) { 3635 return 0; 3636 } 3637 3638 if (string(node)) { 3639 // from is a selector string 3640 node = cy.filter(node); 3641 } 3642 3643 return outdegrees[node.id()] / maxOutdegree; 3644 } 3645 }; 3646 } 3647 }, 3648 // degreeCentralityNormalized 3649 // Implemented from the algorithm in Opsahl's paper 3650 // "Node centrality in weighted networks: Generalizing degree and shortest paths" 3651 // check the heading 2 "Degree" 3652 degreeCentrality: function degreeCentrality(options) { 3653 options = defaults$1(options); 3654 var cy = this.cy(); 3655 var callingEles = this; 3656 var _options = options, 3657 root = _options.root, 3658 weight = _options.weight, 3659 directed = _options.directed, 3660 alpha = _options.alpha; 3661 root = cy.collection(root)[0]; 3662 3663 if (!directed) { 3664 var connEdges = root.connectedEdges().intersection(callingEles); 3665 var k = connEdges.length; 3666 var s = 0; // Now, sum edge weights 3667 3668 for (var i = 0; i < connEdges.length; i++) { 3669 s += weight(connEdges[i]); 3670 } 3671 3672 return { 3673 degree: Math.pow(k, 1 - alpha) * Math.pow(s, alpha) 3674 }; 3675 } else { 3676 var edges = root.connectedEdges(); 3677 var incoming = edges.filter(function (edge) { 3678 return edge.target().same(root) && callingEles.has(edge); 3679 }); 3680 var outgoing = edges.filter(function (edge) { 3681 return edge.source().same(root) && callingEles.has(edge); 3682 }); 3683 var k_in = incoming.length; 3684 var k_out = outgoing.length; 3685 var s_in = 0; 3686 var s_out = 0; // Now, sum incoming edge weights 3687 3688 for (var _i2 = 0; _i2 < incoming.length; _i2++) { 3689 s_in += weight(incoming[_i2]); 3690 } // Now, sum outgoing edge weights 3691 3692 3693 for (var _i3 = 0; _i3 < outgoing.length; _i3++) { 3694 s_out += weight(outgoing[_i3]); 3695 } 3696 3697 return { 3698 indegree: Math.pow(k_in, 1 - alpha) * Math.pow(s_in, alpha), 3699 outdegree: Math.pow(k_out, 1 - alpha) * Math.pow(s_out, alpha) 3700 }; 3701 } 3702 } // degreeCentrality 3703 3704 }; // elesfn 3705 // nice, short mathemathical alias 3706 3707 elesfn$8.dc = elesfn$8.degreeCentrality; 3708 elesfn$8.dcn = elesfn$8.degreeCentralityNormalised = elesfn$8.degreeCentralityNormalized; 3709 3710 var defaults$2 = defaults({ 3711 harmonic: true, 3712 weight: function weight() { 3713 return 1; 3714 }, 3715 directed: false, 3716 root: null 3717 }); 3718 var elesfn$9 = { 3719 closenessCentralityNormalized: function closenessCentralityNormalized(options) { 3720 var _defaults = defaults$2(options), 3721 harmonic = _defaults.harmonic, 3722 weight = _defaults.weight, 3723 directed = _defaults.directed; 3724 3725 var cy = this.cy(); 3726 var closenesses = {}; 3727 var maxCloseness = 0; 3728 var nodes = this.nodes(); 3729 var fw = this.floydWarshall({ 3730 weight: weight, 3731 directed: directed 3732 }); // Compute closeness for every node and find the maximum closeness 3733 3734 for (var i = 0; i < nodes.length; i++) { 3735 var currCloseness = 0; 3736 var node_i = nodes[i]; 3737 3738 for (var j = 0; j < nodes.length; j++) { 3739 if (i !== j) { 3740 var d = fw.distance(node_i, nodes[j]); 3741 3742 if (harmonic) { 3743 currCloseness += 1 / d; 3744 } else { 3745 currCloseness += d; 3746 } 3747 } 3748 } 3749 3750 if (!harmonic) { 3751 currCloseness = 1 / currCloseness; 3752 } 3753 3754 if (maxCloseness < currCloseness) { 3755 maxCloseness = currCloseness; 3756 } 3757 3758 closenesses[node_i.id()] = currCloseness; 3759 } 3760 3761 return { 3762 closeness: function closeness(node) { 3763 if (maxCloseness == 0) { 3764 return 0; 3765 } 3766 3767 if (string(node)) { 3768 // from is a selector string 3769 node = cy.filter(node)[0].id(); 3770 } else { 3771 // from is a node 3772 node = node.id(); 3773 } 3774 3775 return closenesses[node] / maxCloseness; 3776 } 3777 }; 3778 }, 3779 // Implemented from pseudocode from wikipedia 3780 closenessCentrality: function closenessCentrality(options) { 3781 var _defaults2 = defaults$2(options), 3782 root = _defaults2.root, 3783 weight = _defaults2.weight, 3784 directed = _defaults2.directed, 3785 harmonic = _defaults2.harmonic; 3786 3787 root = this.filter(root)[0]; // we need distance from this node to every other node 3788 3789 var dijkstra = this.dijkstra({ 3790 root: root, 3791 weight: weight, 3792 directed: directed 3793 }); 3794 var totalDistance = 0; 3795 var nodes = this.nodes(); 3796 3797 for (var i = 0; i < nodes.length; i++) { 3798 var n = nodes[i]; 3799 3800 if (!n.same(root)) { 3801 var d = dijkstra.distanceTo(n); 3802 3803 if (harmonic) { 3804 totalDistance += 1 / d; 3805 } else { 3806 totalDistance += d; 3807 } 3808 } 3809 } 3810 3811 return harmonic ? totalDistance : 1 / totalDistance; 3812 } // closenessCentrality 3813 3814 }; // elesfn 3815 // nice, short mathemathical alias 3816 3817 elesfn$9.cc = elesfn$9.closenessCentrality; 3818 elesfn$9.ccn = elesfn$9.closenessCentralityNormalised = elesfn$9.closenessCentralityNormalized; 3819 3820 var defaults$3 = defaults({ 3821 weight: null, 3822 directed: false 3823 }); 3824 var elesfn$a = { 3825 // Implemented from the algorithm in the paper "On Variants of Shortest-Path Betweenness Centrality and their Generic Computation" by Ulrik Brandes 3826 betweennessCentrality: function betweennessCentrality(options) { 3827 var _defaults = defaults$3(options), 3828 directed = _defaults.directed, 3829 weight = _defaults.weight; 3830 3831 var weighted = weight != null; 3832 var cy = this.cy(); // starting 3833 3834 var V = this.nodes(); 3835 var A = {}; 3836 var _C = {}; 3837 var max = 0; 3838 var C = { 3839 set: function set(key, val) { 3840 _C[key] = val; 3841 3842 if (val > max) { 3843 max = val; 3844 } 3845 }, 3846 get: function get(key) { 3847 return _C[key]; 3848 } 3849 }; // A contains the neighborhoods of every node 3850 3851 for (var i = 0; i < V.length; i++) { 3852 var v = V[i]; 3853 var vid = v.id(); 3854 3855 if (directed) { 3856 A[vid] = v.outgoers().nodes(); // get outgoers of every node 3857 } else { 3858 A[vid] = v.openNeighborhood().nodes(); // get neighbors of every node 3859 } 3860 3861 C.set(vid, 0); 3862 } 3863 3864 var _loop = function _loop(s) { 3865 var sid = V[s].id(); 3866 var S = []; // stack 3867 3868 var P = {}; 3869 var g = {}; 3870 var d = {}; 3871 var Q = new Heap(function (a, b) { 3872 return d[a] - d[b]; 3873 }); // queue 3874 // init dictionaries 3875 3876 for (var _i = 0; _i < V.length; _i++) { 3877 var _vid = V[_i].id(); 3878 3879 P[_vid] = []; 3880 g[_vid] = 0; 3881 d[_vid] = Infinity; 3882 } 3883 3884 g[sid] = 1; // sigma 3885 3886 d[sid] = 0; // distance to s 3887 3888 Q.push(sid); 3889 3890 while (!Q.empty()) { 3891 var _v = Q.pop(); 3892 3893 S.push(_v); 3894 3895 if (weighted) { 3896 for (var j = 0; j < A[_v].length; j++) { 3897 var w = A[_v][j]; 3898 var vEle = cy.getElementById(_v); 3899 var edge = void 0; 3900 3901 if (vEle.edgesTo(w).length > 0) { 3902 edge = vEle.edgesTo(w)[0]; 3903 } else { 3904 edge = w.edgesTo(vEle)[0]; 3905 } 3906 3907 var edgeWeight = weight(edge); 3908 w = w.id(); 3909 3910 if (d[w] > d[_v] + edgeWeight) { 3911 d[w] = d[_v] + edgeWeight; 3912 3913 if (Q.nodes.indexOf(w) < 0) { 3914 //if w is not in Q 3915 Q.push(w); 3916 } else { 3917 // update position if w is in Q 3918 Q.updateItem(w); 3919 } 3920 3921 g[w] = 0; 3922 P[w] = []; 3923 } 3924 3925 if (d[w] == d[_v] + edgeWeight) { 3926 g[w] = g[w] + g[_v]; 3927 P[w].push(_v); 3928 } 3929 } 3930 } else { 3931 for (var _j = 0; _j < A[_v].length; _j++) { 3932 var _w = A[_v][_j].id(); 3933 3934 if (d[_w] == Infinity) { 3935 Q.push(_w); 3936 d[_w] = d[_v] + 1; 3937 } 3938 3939 if (d[_w] == d[_v] + 1) { 3940 g[_w] = g[_w] + g[_v]; 3941 3942 P[_w].push(_v); 3943 } 3944 } 3945 } 3946 } 3947 3948 var e = {}; 3949 3950 for (var _i2 = 0; _i2 < V.length; _i2++) { 3951 e[V[_i2].id()] = 0; 3952 } 3953 3954 while (S.length > 0) { 3955 var _w2 = S.pop(); 3956 3957 for (var _j2 = 0; _j2 < P[_w2].length; _j2++) { 3958 var _v2 = P[_w2][_j2]; 3959 e[_v2] = e[_v2] + g[_v2] / g[_w2] * (1 + e[_w2]); 3960 3961 if (_w2 != V[s].id()) { 3962 C.set(_w2, C.get(_w2) + e[_w2]); 3963 } 3964 } 3965 } 3966 }; 3967 3968 for (var s = 0; s < V.length; s++) { 3969 _loop(s); 3970 } 3971 3972 var ret = { 3973 betweenness: function betweenness(node) { 3974 var id = cy.collection(node).id(); 3975 return C.get(id); 3976 }, 3977 betweennessNormalized: function betweennessNormalized(node) { 3978 if (max == 0) { 3979 return 0; 3980 } 3981 3982 var id = cy.collection(node).id(); 3983 return C.get(id) / max; 3984 } 3985 }; // alias 3986 3987 ret.betweennessNormalised = ret.betweennessNormalized; 3988 return ret; 3989 } // betweennessCentrality 3990 3991 }; // elesfn 3992 // nice, short mathemathical alias 3993 3994 elesfn$a.bc = elesfn$a.betweennessCentrality; 3995 3996 // Implemented by Zoe Xi @zoexi for GSOC 2016 3997 /* eslint-disable no-unused-vars */ 3998 3999 var defaults$4 = defaults({ 4000 expandFactor: 2, 4001 // affects time of computation and cluster granularity to some extent: M * M 4002 inflateFactor: 2, 4003 // affects cluster granularity (the greater the value, the more clusters): M(i,j) / E(j) 4004 multFactor: 1, 4005 // optional self loops for each node. Use a neutral value to improve cluster computations. 4006 maxIterations: 20, 4007 // maximum number of iterations of the MCL algorithm in a single run 4008 attributes: [// attributes/features used to group nodes, ie. similarity values between nodes 4009 function (edge) { 4010 return 1; 4011 }] 4012 }); 4013 /* eslint-enable */ 4014 4015 var setOptions = function setOptions(options) { 4016 return defaults$4(options); 4017 }; 4018 /* eslint-enable */ 4019 4020 4021 var getSimilarity = function getSimilarity(edge, attributes) { 4022 var total = 0; 4023 4024 for (var i = 0; i < attributes.length; i++) { 4025 total += attributes[i](edge); 4026 } 4027 4028 return total; 4029 }; 4030 4031 var addLoops = function addLoops(M, n, val) { 4032 for (var i = 0; i < n; i++) { 4033 M[i * n + i] = val; 4034 } 4035 }; 4036 4037 var normalize = function normalize(M, n) { 4038 var sum; 4039 4040 for (var col = 0; col < n; col++) { 4041 sum = 0; 4042 4043 for (var row = 0; row < n; row++) { 4044 sum += M[row * n + col]; 4045 } 4046 4047 for (var _row = 0; _row < n; _row++) { 4048 M[_row * n + col] = M[_row * n + col] / sum; 4049 } 4050 } 4051 }; // TODO: blocked matrix multiplication? 4052 4053 4054 var mmult = function mmult(A, B, n) { 4055 var C = new Array(n * n); 4056 4057 for (var i = 0; i < n; i++) { 4058 for (var j = 0; j < n; j++) { 4059 C[i * n + j] = 0; 4060 } 4061 4062 for (var k = 0; k < n; k++) { 4063 for (var _j = 0; _j < n; _j++) { 4064 C[i * n + _j] += A[i * n + k] * B[k * n + _j]; 4065 } 4066 } 4067 } 4068 4069 return C; 4070 }; 4071 4072 var expand = function expand(M, n, expandFactor 4073 /** power **/ 4074 ) { 4075 var _M = M.slice(0); 4076 4077 for (var p = 1; p < expandFactor; p++) { 4078 M = mmult(M, _M, n); 4079 } 4080 4081 return M; 4082 }; 4083 4084 var inflate = function inflate(M, n, inflateFactor 4085 /** r **/ 4086 ) { 4087 var _M = new Array(n * n); // M(i,j) ^ inflatePower 4088 4089 4090 for (var i = 0; i < n * n; i++) { 4091 _M[i] = Math.pow(M[i], inflateFactor); 4092 } 4093 4094 normalize(_M, n); 4095 return _M; 4096 }; 4097 4098 var hasConverged = function hasConverged(M, _M, n2, roundFactor) { 4099 // Check that both matrices have the same elements (i,j) 4100 for (var i = 0; i < n2; i++) { 4101 var v1 = Math.round(M[i] * Math.pow(10, roundFactor)) / Math.pow(10, roundFactor); // truncate to 'roundFactor' decimal places 4102 4103 var v2 = Math.round(_M[i] * Math.pow(10, roundFactor)) / Math.pow(10, roundFactor); 4104 4105 if (v1 !== v2) { 4106 return false; 4107 } 4108 } 4109 4110 return true; 4111 }; 4112 4113 var assign = function assign(M, n, nodes, cy) { 4114 var clusters = []; 4115 4116 for (var i = 0; i < n; i++) { 4117 var cluster = []; 4118 4119 for (var j = 0; j < n; j++) { 4120 // Row-wise attractors and elements that they attract belong in same cluster 4121 if (Math.round(M[i * n + j] * 1000) / 1000 > 0) { 4122 cluster.push(nodes[j]); 4123 } 4124 } 4125 4126 if (cluster.length !== 0) { 4127 clusters.push(cy.collection(cluster)); 4128 } 4129 } 4130 4131 return clusters; 4132 }; 4133 4134 var isDuplicate = function isDuplicate(c1, c2) { 4135 for (var i = 0; i < c1.length; i++) { 4136 if (!c2[i] || c1[i].id() !== c2[i].id()) { 4137 return false; 4138 } 4139 } 4140 4141 return true; 4142 }; 4143 4144 var removeDuplicates = function removeDuplicates(clusters) { 4145 for (var i = 0; i < clusters.length; i++) { 4146 for (var j = 0; j < clusters.length; j++) { 4147 if (i != j && isDuplicate(clusters[i], clusters[j])) { 4148 clusters.splice(j, 1); 4149 } 4150 } 4151 } 4152 4153 return clusters; 4154 }; 4155 4156 var markovClustering = function markovClustering(options) { 4157 var nodes = this.nodes(); 4158 var edges = this.edges(); 4159 var cy = this.cy(); // Set parameters of algorithm: 4160 4161 var opts = setOptions(options); // Map each node to its position in node array 4162 4163 var id2position = {}; 4164 4165 for (var i = 0; i < nodes.length; i++) { 4166 id2position[nodes[i].id()] = i; 4167 } // Generate stochastic matrix M from input graph G (should be symmetric/undirected) 4168 4169 4170 var n = nodes.length, 4171 n2 = n * n; 4172 4173 var M = new Array(n2), 4174 _M; 4175 4176 for (var _i = 0; _i < n2; _i++) { 4177 M[_i] = 0; 4178 } 4179 4180 for (var e = 0; e < edges.length; e++) { 4181 var edge = edges[e]; 4182 var _i2 = id2position[edge.source().id()]; 4183 var j = id2position[edge.target().id()]; 4184 var sim = getSimilarity(edge, opts.attributes); 4185 M[_i2 * n + j] += sim; // G should be symmetric and undirected 4186 4187 M[j * n + _i2] += sim; 4188 } // Begin Markov cluster algorithm 4189 // Step 1: Add self loops to each node, ie. add multFactor to matrix diagonal 4190 4191 4192 addLoops(M, n, opts.multFactor); // Step 2: M = normalize( M ); 4193 4194 normalize(M, n); 4195 var isStillMoving = true; 4196 var iterations = 0; 4197 4198 while (isStillMoving && iterations < opts.maxIterations) { 4199 isStillMoving = false; // Step 3: 4200 4201 _M = expand(M, n, opts.expandFactor); // Step 4: 4202 4203 M = inflate(_M, n, opts.inflateFactor); // Step 5: check to see if ~steady state has been reached 4204 4205 if (!hasConverged(M, _M, n2, 4)) { 4206 isStillMoving = true; 4207 } 4208 4209 iterations++; 4210 } // Build clusters from matrix 4211 4212 4213 var clusters = assign(M, n, nodes, cy); // Remove duplicate clusters due to symmetry of graph and M matrix 4214 4215 clusters = removeDuplicates(clusters); 4216 return clusters; 4217 }; 4218 4219 var markovClustering$1 = { 4220 markovClustering: markovClustering, 4221 mcl: markovClustering 4222 }; 4223 4224 // Common distance metrics for clustering algorithms 4225 4226 var identity = function identity(x) { 4227 return x; 4228 }; 4229 4230 var absDiff = function absDiff(p, q) { 4231 return Math.abs(q - p); 4232 }; 4233 4234 var addAbsDiff = function addAbsDiff(total, p, q) { 4235 return total + absDiff(p, q); 4236 }; 4237 4238 var addSquaredDiff = function addSquaredDiff(total, p, q) { 4239 return total + Math.pow(q - p, 2); 4240 }; 4241 4242 var sqrt = function sqrt(x) { 4243 return Math.sqrt(x); 4244 }; 4245 4246 var maxAbsDiff = function maxAbsDiff(currentMax, p, q) { 4247 return Math.max(currentMax, absDiff(p, q)); 4248 }; 4249 4250 var getDistance = function getDistance(length, getP, getQ, init, visit) { 4251 var post = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : identity; 4252 var ret = init; 4253 var p, q; 4254 4255 for (var dim = 0; dim < length; dim++) { 4256 p = getP(dim); 4257 q = getQ(dim); 4258 ret = visit(ret, p, q); 4259 } 4260 4261 return post(ret); 4262 }; 4263 4264 var distances = { 4265 euclidean: function euclidean(length, getP, getQ) { 4266 if (length >= 2) { 4267 return getDistance(length, getP, getQ, 0, addSquaredDiff, sqrt); 4268 } else { 4269 // for single attr case, more efficient to avoid sqrt 4270 return getDistance(length, getP, getQ, 0, addAbsDiff); 4271 } 4272 }, 4273 squaredEuclidean: function squaredEuclidean(length, getP, getQ) { 4274 return getDistance(length, getP, getQ, 0, addSquaredDiff); 4275 }, 4276 manhattan: function manhattan(length, getP, getQ) { 4277 return getDistance(length, getP, getQ, 0, addAbsDiff); 4278 }, 4279 max: function max(length, getP, getQ) { 4280 return getDistance(length, getP, getQ, -Infinity, maxAbsDiff); 4281 } 4282 }; // in case the user accidentally doesn't use camel case 4283 4284 distances['squared-euclidean'] = distances['squaredEuclidean']; 4285 distances['squaredeuclidean'] = distances['squaredEuclidean']; 4286 function clusteringDistance (method, length, getP, getQ, nodeP, nodeQ) { 4287 var impl; 4288 4289 if (fn(method)) { 4290 impl = method; 4291 } else { 4292 impl = distances[method] || distances.euclidean; 4293 } 4294 4295 if (length === 0 && fn(method)) { 4296 return impl(nodeP, nodeQ); 4297 } else { 4298 return impl(length, getP, getQ, nodeP, nodeQ); 4299 } 4300 } 4301 4302 var defaults$5 = defaults({ 4303 k: 2, 4304 m: 2, 4305 sensitivityThreshold: 0.0001, 4306 distance: 'euclidean', 4307 maxIterations: 10, 4308 attributes: [], 4309 testMode: false, 4310 testCentroids: null 4311 }); 4312 4313 var setOptions$1 = function setOptions(options) { 4314 return defaults$5(options); 4315 }; 4316 /* eslint-enable */ 4317 4318 4319 var getDist = function getDist(type, node, centroid, attributes, mode) { 4320 var noNodeP = mode !== 'kMedoids'; 4321 var getP = noNodeP ? function (i) { 4322 return centroid[i]; 4323 } : function (i) { 4324 return attributes[i](centroid); 4325 }; 4326 4327 var getQ = function getQ(i) { 4328 return attributes[i](node); 4329 }; 4330 4331 var nodeP = centroid; 4332 var nodeQ = node; 4333 return clusteringDistance(type, attributes.length, getP, getQ, nodeP, nodeQ); 4334 }; 4335 4336 var randomCentroids = function randomCentroids(nodes, k, attributes) { 4337 var ndim = attributes.length; 4338 var min = new Array(ndim); 4339 var max = new Array(ndim); 4340 var centroids = new Array(k); 4341 var centroid = null; // Find min, max values for each attribute dimension 4342 4343 for (var i = 0; i < ndim; i++) { 4344 min[i] = nodes.min(attributes[i]).value; 4345 max[i] = nodes.max(attributes[i]).value; 4346 } // Build k centroids, each represented as an n-dim feature vector 4347 4348 4349 for (var c = 0; c < k; c++) { 4350 centroid = []; 4351 4352 for (var _i = 0; _i < ndim; _i++) { 4353 centroid[_i] = Math.random() * (max[_i] - min[_i]) + min[_i]; // random initial value 4354 } 4355 4356 centroids[c] = centroid; 4357 } 4358 4359 return centroids; 4360 }; 4361 4362 var classify = function classify(node, centroids, distance, attributes, type) { 4363 var min = Infinity; 4364 var index = 0; 4365 4366 for (var i = 0; i < centroids.length; i++) { 4367 var dist = getDist(distance, node, centroids[i], attributes, type); 4368 4369 if (dist < min) { 4370 min = dist; 4371 index = i; 4372 } 4373 } 4374 4375 return index; 4376 }; 4377 4378 var buildCluster = function buildCluster(centroid, nodes, assignment) { 4379 var cluster = []; 4380 var node = null; 4381 4382 for (var n = 0; n < nodes.length; n++) { 4383 node = nodes[n]; 4384 4385 if (assignment[node.id()] === centroid) { 4386 //console.log("Node " + node.id() + " is associated with medoid #: " + m); 4387 cluster.push(node); 4388 } 4389 } 4390 4391 return cluster; 4392 }; 4393 4394 var haveValuesConverged = function haveValuesConverged(v1, v2, sensitivityThreshold) { 4395 return Math.abs(v2 - v1) <= sensitivityThreshold; 4396 }; 4397 4398 var haveMatricesConverged = function haveMatricesConverged(v1, v2, sensitivityThreshold) { 4399 for (var i = 0; i < v1.length; i++) { 4400 for (var j = 0; j < v1[i].length; j++) { 4401 var diff = Math.abs(v1[i][j] - v2[i][j]); 4402 4403 if (diff > sensitivityThreshold) { 4404 return false; 4405 } 4406 } 4407 } 4408 4409 return true; 4410 }; 4411 4412 var seenBefore = function seenBefore(node, medoids, n) { 4413 for (var i = 0; i < n; i++) { 4414 if (node === medoids[i]) return true; 4415 } 4416 4417 return false; 4418 }; 4419 4420 var randomMedoids = function randomMedoids(nodes, k) { 4421 var medoids = new Array(k); // For small data sets, the probability of medoid conflict is greater, 4422 // so we need to check to see if we've already seen or chose this node before. 4423 4424 if (nodes.length < 50) { 4425 // Randomly select k medoids from the n nodes 4426 for (var i = 0; i < k; i++) { 4427 var node = nodes[Math.floor(Math.random() * nodes.length)]; // If we've already chosen this node to be a medoid, don't choose it again (for small data sets). 4428 // Instead choose a different random node. 4429 4430 while (seenBefore(node, medoids, i)) { 4431 node = nodes[Math.floor(Math.random() * nodes.length)]; 4432 } 4433 4434 medoids[i] = node; 4435 } 4436 } else { 4437 // Relatively large data set, so pretty safe to not check and just select random nodes 4438 for (var _i2 = 0; _i2 < k; _i2++) { 4439 medoids[_i2] = nodes[Math.floor(Math.random() * nodes.length)]; 4440 } 4441 } 4442 4443 return medoids; 4444 }; 4445 4446 var findCost = function findCost(potentialNewMedoid, cluster, attributes) { 4447 var cost = 0; 4448 4449 for (var n = 0; n < cluster.length; n++) { 4450 cost += getDist('manhattan', cluster[n], potentialNewMedoid, attributes, 'kMedoids'); 4451 } 4452 4453 return cost; 4454 }; 4455 4456 var kMeans = function kMeans(options) { 4457 var cy = this.cy(); 4458 var nodes = this.nodes(); 4459 var node = null; // Set parameters of algorithm: # of clusters, distance metric, etc. 4460 4461 var opts = setOptions$1(options); // Begin k-means algorithm 4462 4463 var clusters = new Array(opts.k); 4464 var assignment = {}; 4465 var centroids; // Step 1: Initialize centroid positions 4466 4467 if (opts.testMode) { 4468 if (typeof opts.testCentroids === 'number') { 4469 centroids = randomCentroids(nodes, opts.k, opts.attributes); 4470 } else if (_typeof(opts.testCentroids) === 'object') { 4471 centroids = opts.testCentroids; 4472 } else { 4473 centroids = randomCentroids(nodes, opts.k, opts.attributes); 4474 } 4475 } else { 4476 centroids = randomCentroids(nodes, opts.k, opts.attributes); 4477 } 4478 4479 var isStillMoving = true; 4480 var iterations = 0; 4481 4482 while (isStillMoving && iterations < opts.maxIterations) { 4483 // Step 2: Assign nodes to the nearest centroid 4484 for (var n = 0; n < nodes.length; n++) { 4485 node = nodes[n]; // Determine which cluster this node belongs to: node id => cluster # 4486 4487 assignment[node.id()] = classify(node, centroids, opts.distance, opts.attributes, 'kMeans'); 4488 } // Step 3: For each of the k clusters, update its centroid 4489 4490 4491 isStillMoving = false; 4492 4493 for (var c = 0; c < opts.k; c++) { 4494 // Get all nodes that belong to this cluster 4495 var cluster = buildCluster(c, nodes, assignment); 4496 4497 if (cluster.length === 0) { 4498 // If cluster is empty, break out early & move to next cluster 4499 continue; 4500 } // Update centroids by calculating avg of all nodes within the cluster. 4501 4502 4503 var ndim = opts.attributes.length; 4504 var centroid = centroids[c]; // [ dim_1, dim_2, dim_3, ... , dim_n ] 4505 4506 var newCentroid = new Array(ndim); 4507 var sum = new Array(ndim); 4508 4509 for (var d = 0; d < ndim; d++) { 4510 sum[d] = 0.0; 4511 4512 for (var i = 0; i < cluster.length; i++) { 4513 node = cluster[i]; 4514 sum[d] += opts.attributes[d](node); 4515 } 4516 4517 newCentroid[d] = sum[d] / cluster.length; // Check to see if algorithm has converged, i.e. when centroids no longer change 4518 4519 if (!haveValuesConverged(newCentroid[d], centroid[d], opts.sensitivityThreshold)) { 4520 isStillMoving = true; 4521 } 4522 } 4523 4524 centroids[c] = newCentroid; 4525 clusters[c] = cy.collection(cluster); 4526 } 4527 4528 iterations++; 4529 } 4530 4531 return clusters; 4532 }; 4533 4534 var kMedoids = function kMedoids(options) { 4535 var cy = this.cy(); 4536 var nodes = this.nodes(); 4537 var node = null; 4538 var opts = setOptions$1(options); // Begin k-medoids algorithm 4539 4540 var clusters = new Array(opts.k); 4541 var medoids; 4542 var assignment = {}; 4543 var curCost; 4544 var minCosts = new Array(opts.k); // minimum cost configuration for each cluster 4545 // Step 1: Initialize k medoids 4546 4547 if (opts.testMode) { 4548 if (typeof opts.testCentroids === 'number') ; else if (_typeof(opts.testCentroids) === 'object') { 4549 medoids = opts.testCentroids; 4550 } else { 4551 medoids = randomMedoids(nodes, opts.k); 4552 } 4553 } else { 4554 medoids = randomMedoids(nodes, opts.k); 4555 } 4556 4557 var isStillMoving = true; 4558 var iterations = 0; 4559 4560 while (isStillMoving && iterations < opts.maxIterations) { 4561 // Step 2: Assign nodes to the nearest medoid 4562 for (var n = 0; n < nodes.length; n++) { 4563 node = nodes[n]; // Determine which cluster this node belongs to: node id => cluster # 4564 4565 assignment[node.id()] = classify(node, medoids, opts.distance, opts.attributes, 'kMedoids'); 4566 } 4567 4568 isStillMoving = false; // Step 3: For each medoid m, and for each node assciated with mediod m, 4569 // select the node with the lowest configuration cost as new medoid. 4570 4571 for (var m = 0; m < medoids.length; m++) { 4572 // Get all nodes that belong to this medoid 4573 var cluster = buildCluster(m, nodes, assignment); 4574 4575 if (cluster.length === 0) { 4576 // If cluster is empty, break out early & move to next cluster 4577 continue; 4578 } 4579 4580 minCosts[m] = findCost(medoids[m], cluster, opts.attributes); // original cost 4581 // Select different medoid if its configuration has the lowest cost 4582 4583 for (var _n = 0; _n < cluster.length; _n++) { 4584 curCost = findCost(cluster[_n], cluster, opts.attributes); 4585 4586 if (curCost < minCosts[m]) { 4587 minCosts[m] = curCost; 4588 medoids[m] = cluster[_n]; 4589 isStillMoving = true; 4590 } 4591 } 4592 4593 clusters[m] = cy.collection(cluster); 4594 } 4595 4596 iterations++; 4597 } 4598 4599 return clusters; 4600 }; 4601 4602 var updateCentroids = function updateCentroids(centroids, nodes, U, weight, opts) { 4603 var numerator, denominator; 4604 4605 for (var n = 0; n < nodes.length; n++) { 4606 for (var c = 0; c < centroids.length; c++) { 4607 weight[n][c] = Math.pow(U[n][c], opts.m); 4608 } 4609 } 4610 4611 for (var _c = 0; _c < centroids.length; _c++) { 4612 for (var dim = 0; dim < opts.attributes.length; dim++) { 4613 numerator = 0; 4614 denominator = 0; 4615 4616 for (var _n2 = 0; _n2 < nodes.length; _n2++) { 4617 numerator += weight[_n2][_c] * opts.attributes[dim](nodes[_n2]); 4618 denominator += weight[_n2][_c]; 4619 } 4620 4621 centroids[_c][dim] = numerator / denominator; 4622 } 4623 } 4624 }; 4625 4626 var updateMembership = function updateMembership(U, _U, centroids, nodes, opts) { 4627 // Save previous step 4628 for (var i = 0; i < U.length; i++) { 4629 _U[i] = U[i].slice(); 4630 } 4631 4632 var sum, numerator, denominator; 4633 var pow = 2 / (opts.m - 1); 4634 4635 for (var c = 0; c < centroids.length; c++) { 4636 for (var n = 0; n < nodes.length; n++) { 4637 sum = 0; 4638 4639 for (var k = 0; k < centroids.length; k++) { 4640 // against all other centroids 4641 numerator = getDist(opts.distance, nodes[n], centroids[c], opts.attributes, 'cmeans'); 4642 denominator = getDist(opts.distance, nodes[n], centroids[k], opts.attributes, 'cmeans'); 4643 sum += Math.pow(numerator / denominator, pow); 4644 } 4645 4646 U[n][c] = 1 / sum; 4647 } 4648 } 4649 }; 4650 4651 var assign$1 = function assign(nodes, U, opts, cy) { 4652 var clusters = new Array(opts.k); 4653 4654 for (var c = 0; c < clusters.length; c++) { 4655 clusters[c] = []; 4656 } 4657 4658 var max; 4659 var index; 4660 4661 for (var n = 0; n < U.length; n++) { 4662 // for each node (U is N x C matrix) 4663 max = -Infinity; 4664 index = -1; // Determine which cluster the node is most likely to belong in 4665 4666 for (var _c2 = 0; _c2 < U[0].length; _c2++) { 4667 if (U[n][_c2] > max) { 4668 max = U[n][_c2]; 4669 index = _c2; 4670 } 4671 } 4672 4673 clusters[index].push(nodes[n]); 4674 } // Turn every array into a collection of nodes 4675 4676 4677 for (var _c3 = 0; _c3 < clusters.length; _c3++) { 4678 clusters[_c3] = cy.collection(clusters[_c3]); 4679 } 4680 4681 return clusters; 4682 }; 4683 4684 var fuzzyCMeans = function fuzzyCMeans(options) { 4685 var cy = this.cy(); 4686 var nodes = this.nodes(); 4687 var opts = setOptions$1(options); // Begin fuzzy c-means algorithm 4688 4689 var clusters; 4690 var centroids; 4691 var U; 4692 4693 var _U; 4694 4695 var weight; // Step 1: Initialize letiables. 4696 4697 _U = new Array(nodes.length); 4698 4699 for (var i = 0; i < nodes.length; i++) { 4700 // N x C matrix 4701 _U[i] = new Array(opts.k); 4702 } 4703 4704 U = new Array(nodes.length); 4705 4706 for (var _i3 = 0; _i3 < nodes.length; _i3++) { 4707 // N x C matrix 4708 U[_i3] = new Array(opts.k); 4709 } 4710 4711 for (var _i4 = 0; _i4 < nodes.length; _i4++) { 4712 var total = 0; 4713 4714 for (var j = 0; j < opts.k; j++) { 4715 U[_i4][j] = Math.random(); 4716 total += U[_i4][j]; 4717 } 4718 4719 for (var _j = 0; _j < opts.k; _j++) { 4720 U[_i4][_j] = U[_i4][_j] / total; 4721 } 4722 } 4723 4724 centroids = new Array(opts.k); 4725 4726 for (var _i5 = 0; _i5 < opts.k; _i5++) { 4727 centroids[_i5] = new Array(opts.attributes.length); 4728 } 4729 4730 weight = new Array(nodes.length); 4731 4732 for (var _i6 = 0; _i6 < nodes.length; _i6++) { 4733 // N x C matrix 4734 weight[_i6] = new Array(opts.k); 4735 } // end init FCM 4736 4737 4738 var isStillMoving = true; 4739 var iterations = 0; 4740 4741 while (isStillMoving && iterations < opts.maxIterations) { 4742 isStillMoving = false; // Step 2: Calculate the centroids for each step. 4743 4744 updateCentroids(centroids, nodes, U, weight, opts); // Step 3: Update the partition matrix U. 4745 4746 updateMembership(U, _U, centroids, nodes, opts); // Step 4: Check for convergence. 4747 4748 if (!haveMatricesConverged(U, _U, opts.sensitivityThreshold)) { 4749 isStillMoving = true; 4750 } 4751 4752 iterations++; 4753 } // Assign nodes to clusters with highest probability. 4754 4755 4756 clusters = assign$1(nodes, U, opts, cy); 4757 return { 4758 clusters: clusters, 4759 degreeOfMembership: U 4760 }; 4761 }; 4762 4763 var kClustering = { 4764 kMeans: kMeans, 4765 kMedoids: kMedoids, 4766 fuzzyCMeans: fuzzyCMeans, 4767 fcm: fuzzyCMeans 4768 }; 4769 4770 // Implemented by Zoe Xi @zoexi for GSOC 2016 4771 var defaults$6 = defaults({ 4772 distance: 'euclidean', 4773 // distance metric to compare nodes 4774 linkage: 'min', 4775 // linkage criterion : how to determine the distance between clusters of nodes 4776 mode: 'threshold', 4777 // mode:'threshold' => clusters must be threshold distance apart 4778 threshold: Infinity, 4779 // the distance threshold 4780 // mode:'dendrogram' => the nodes are organised as leaves in a tree (siblings are close), merging makes clusters 4781 addDendrogram: false, 4782 // whether to add the dendrogram to the graph for viz 4783 dendrogramDepth: 0, 4784 // depth at which dendrogram branches are merged into the returned clusters 4785 attributes: [] // array of attr functions 4786 4787 }); 4788 var linkageAliases = { 4789 'single': 'min', 4790 'complete': 'max' 4791 }; 4792 4793 var setOptions$2 = function setOptions(options) { 4794 var opts = defaults$6(options); 4795 var preferredAlias = linkageAliases[opts.linkage]; 4796 4797 if (preferredAlias != null) { 4798 opts.linkage = preferredAlias; 4799 } 4800 4801 return opts; 4802 }; 4803 4804 var mergeClosest = function mergeClosest(clusters, index, dists, mins, opts) { 4805 // Find two closest clusters from cached mins 4806 var minKey = 0; 4807 var min = Infinity; 4808 var dist; 4809 var attrs = opts.attributes; 4810 4811 var getDist = function getDist(n1, n2) { 4812 return clusteringDistance(opts.distance, attrs.length, function (i) { 4813 return attrs[i](n1); 4814 }, function (i) { 4815 return attrs[i](n2); 4816 }, n1, n2); 4817 }; 4818 4819 for (var i = 0; i < clusters.length; i++) { 4820 var key = clusters[i].key; 4821 var _dist = dists[key][mins[key]]; 4822 4823 if (_dist < min) { 4824 minKey = key; 4825 min = _dist; 4826 } 4827 } 4828 4829 if (opts.mode === 'threshold' && min >= opts.threshold || opts.mode === 'dendrogram' && clusters.length === 1) { 4830 return false; 4831 } 4832 4833 var c1 = index[minKey]; 4834 var c2 = index[mins[minKey]]; 4835 var merged; // Merge two closest clusters 4836 4837 if (opts.mode === 'dendrogram') { 4838 merged = { 4839 left: c1, 4840 right: c2, 4841 key: c1.key 4842 }; 4843 } else { 4844 merged = { 4845 value: c1.value.concat(c2.value), 4846 key: c1.key 4847 }; 4848 } 4849 4850 clusters[c1.index] = merged; 4851 clusters.splice(c2.index, 1); 4852 index[c1.key] = merged; // Update distances with new merged cluster 4853 4854 for (var _i = 0; _i < clusters.length; _i++) { 4855 var cur = clusters[_i]; 4856 4857 if (c1.key === cur.key) { 4858 dist = Infinity; 4859 } else if (opts.linkage === 'min') { 4860 dist = dists[c1.key][cur.key]; 4861 4862 if (dists[c1.key][cur.key] > dists[c2.key][cur.key]) { 4863 dist = dists[c2.key][cur.key]; 4864 } 4865 } else if (opts.linkage === 'max') { 4866 dist = dists[c1.key][cur.key]; 4867 4868 if (dists[c1.key][cur.key] < dists[c2.key][cur.key]) { 4869 dist = dists[c2.key][cur.key]; 4870 } 4871 } else if (opts.linkage === 'mean') { 4872 dist = (dists[c1.key][cur.key] * c1.size + dists[c2.key][cur.key] * c2.size) / (c1.size + c2.size); 4873 } else { 4874 if (opts.mode === 'dendrogram') dist = getDist(cur.value, c1.value);else dist = getDist(cur.value[0], c1.value[0]); 4875 } 4876 4877 dists[c1.key][cur.key] = dists[cur.key][c1.key] = dist; // distance matrix is symmetric 4878 } // Update cached mins 4879 4880 4881 for (var _i2 = 0; _i2 < clusters.length; _i2++) { 4882 var key1 = clusters[_i2].key; 4883 4884 if (mins[key1] === c1.key || mins[key1] === c2.key) { 4885 var _min = key1; 4886 4887 for (var j = 0; j < clusters.length; j++) { 4888 var key2 = clusters[j].key; 4889 4890 if (dists[key1][key2] < dists[key1][_min]) { 4891 _min = key2; 4892 } 4893 } 4894 4895 mins[key1] = _min; 4896 } 4897 4898 clusters[_i2].index = _i2; 4899 } // Clean up meta data used for clustering 4900 4901 4902 c1.key = c2.key = c1.index = c2.index = null; 4903 return true; 4904 }; 4905 4906 var getAllChildren = function getAllChildren(root, arr, cy) { 4907 if (!root) return; 4908 4909 if (root.value) { 4910 arr.push(root.value); 4911 } else { 4912 if (root.left) getAllChildren(root.left, arr); 4913 if (root.right) getAllChildren(root.right, arr); 4914 } 4915 }; 4916 4917 var buildDendrogram = function buildDendrogram(root, cy) { 4918 if (!root) return ''; 4919 4920 if (root.left && root.right) { 4921 var leftStr = buildDendrogram(root.left, cy); 4922 var rightStr = buildDendrogram(root.right, cy); 4923 var node = cy.add({ 4924 group: 'nodes', 4925 data: { 4926 id: leftStr + ',' + rightStr 4927 } 4928 }); 4929 cy.add({ 4930 group: 'edges', 4931 data: { 4932 source: leftStr, 4933 target: node.id() 4934 } 4935 }); 4936 cy.add({ 4937 group: 'edges', 4938 data: { 4939 source: rightStr, 4940 target: node.id() 4941 } 4942 }); 4943 return node.id(); 4944 } else if (root.value) { 4945 return root.value.id(); 4946 } 4947 }; 4948 4949 var buildClustersFromTree = function buildClustersFromTree(root, k, cy) { 4950 if (!root) return []; 4951 var left = [], 4952 right = [], 4953 leaves = []; 4954 4955 if (k === 0) { 4956 // don't cut tree, simply return all nodes as 1 single cluster 4957 if (root.left) getAllChildren(root.left, left); 4958 if (root.right) getAllChildren(root.right, right); 4959 leaves = left.concat(right); 4960 return [cy.collection(leaves)]; 4961 } else if (k === 1) { 4962 // cut at root 4963 if (root.value) { 4964 // leaf node 4965 return [cy.collection(root.value)]; 4966 } else { 4967 if (root.left) getAllChildren(root.left, left); 4968 if (root.right) getAllChildren(root.right, right); 4969 return [cy.collection(left), cy.collection(right)]; 4970 } 4971 } else { 4972 if (root.value) { 4973 return [cy.collection(root.value)]; 4974 } else { 4975 if (root.left) left = buildClustersFromTree(root.left, k - 1, cy); 4976 if (root.right) right = buildClustersFromTree(root.right, k - 1, cy); 4977 return left.concat(right); 4978 } 4979 } 4980 }; 4981 /* eslint-enable */ 4982 4983 4984 var hierarchicalClustering = function hierarchicalClustering(options) { 4985 var cy = this.cy(); 4986 var nodes = this.nodes(); // Set parameters of algorithm: linkage type, distance metric, etc. 4987 4988 var opts = setOptions$2(options); 4989 var attrs = opts.attributes; 4990 4991 var getDist = function getDist(n1, n2) { 4992 return clusteringDistance(opts.distance, attrs.length, function (i) { 4993 return attrs[i](n1); 4994 }, function (i) { 4995 return attrs[i](n2); 4996 }, n1, n2); 4997 }; // Begin hierarchical algorithm 4998 4999 5000 var clusters = []; 5001 var dists = []; // distances between each pair of clusters 5002 5003 var mins = []; // closest cluster for each cluster 5004 5005 var index = []; // hash of all clusters by key 5006 // In agglomerative (bottom-up) clustering, each node starts as its own cluster 5007 5008 for (var n = 0; n < nodes.length; n++) { 5009 var cluster = { 5010 value: opts.mode === 'dendrogram' ? nodes[n] : [nodes[n]], 5011 key: n, 5012 index: n 5013 }; 5014 clusters[n] = cluster; 5015 index[n] = cluster; 5016 dists[n] = []; 5017 mins[n] = 0; 5018 } // Calculate the distance between each pair of clusters 5019 5020 5021 for (var i = 0; i < clusters.length; i++) { 5022 for (var j = 0; j <= i; j++) { 5023 var dist = void 0; 5024 5025 if (opts.mode === 'dendrogram') { 5026 // modes store cluster values differently 5027 dist = i === j ? Infinity : getDist(clusters[i].value, clusters[j].value); 5028 } else { 5029 dist = i === j ? Infinity : getDist(clusters[i].value[0], clusters[j].value[0]); 5030 } 5031 5032 dists[i][j] = dist; 5033 dists[j][i] = dist; 5034 5035 if (dist < dists[i][mins[i]]) { 5036 mins[i] = j; // Cache mins: closest cluster to cluster i is cluster j 5037 } 5038 } 5039 } // Find the closest pair of clusters and merge them into a single cluster. 5040 // Update distances between new cluster and each of the old clusters, and loop until threshold reached. 5041 5042 5043 var merged = mergeClosest(clusters, index, dists, mins, opts); 5044 5045 while (merged) { 5046 merged = mergeClosest(clusters, index, dists, mins, opts); 5047 } 5048 5049 var retClusters; // Dendrogram mode builds the hierarchy and adds intermediary nodes + edges 5050 // in addition to returning the clusters. 5051 5052 if (opts.mode === 'dendrogram') { 5053 retClusters = buildClustersFromTree(clusters[0], opts.dendrogramDepth, cy); 5054 if (opts.addDendrogram) buildDendrogram(clusters[0], cy); 5055 } else { 5056 // Regular mode simply returns the clusters 5057 retClusters = new Array(clusters.length); 5058 clusters.forEach(function (cluster, i) { 5059 // Clean up meta data used for clustering 5060 cluster.key = cluster.index = null; 5061 retClusters[i] = cy.collection(cluster.value); 5062 }); 5063 } 5064 5065 return retClusters; 5066 }; 5067 5068 var hierarchicalClustering$1 = { 5069 hierarchicalClustering: hierarchicalClustering, 5070 hca: hierarchicalClustering 5071 }; 5072 5073 // Implemented by Zoe Xi @zoexi for GSOC 2016 5074 var defaults$7 = defaults({ 5075 distance: 'euclidean', 5076 // distance metric to compare attributes between two nodes 5077 preference: 'median', 5078 // suitability of a data point to serve as an exemplar 5079 damping: 0.8, 5080 // damping factor between [0.5, 1) 5081 maxIterations: 1000, 5082 // max number of iterations to run 5083 minIterations: 100, 5084 // min number of iterations to run in order for clustering to stop 5085 attributes: [// functions to quantify the similarity between any two points 5086 // e.g. node => node.data('weight') 5087 ] 5088 }); 5089 5090 var setOptions$3 = function setOptions(options) { 5091 var dmp = options.damping; 5092 var pref = options.preference; 5093 5094 if (!(0.5 <= dmp && dmp < 1)) { 5095 error("Damping must range on [0.5, 1). Got: ".concat(dmp)); 5096 } 5097 5098 var validPrefs = ['median', 'mean', 'min', 'max']; 5099 5100 if (!(validPrefs.some(function (v) { 5101 return v === pref; 5102 }) || number(pref))) { 5103 error("Preference must be one of [".concat(validPrefs.map(function (p) { 5104 return "'".concat(p, "'"); 5105 }).join(', '), "] or a number. Got: ").concat(pref)); 5106 } 5107 5108 return defaults$7(options); 5109 }; 5110 /* eslint-enable */ 5111 5112 5113 var getSimilarity$1 = function getSimilarity(type, n1, n2, attributes) { 5114 var attr = function attr(n, i) { 5115 return attributes[i](n); 5116 }; // nb negative because similarity should have an inverse relationship to distance 5117 5118 5119 return -clusteringDistance(type, attributes.length, function (i) { 5120 return attr(n1, i); 5121 }, function (i) { 5122 return attr(n2, i); 5123 }, n1, n2); 5124 }; 5125 5126 var getPreference = function getPreference(S, preference) { 5127 // larger preference = greater # of clusters 5128 var p = null; 5129 5130 if (preference === 'median') { 5131 p = median(S); 5132 } else if (preference === 'mean') { 5133 p = mean(S); 5134 } else if (preference === 'min') { 5135 p = min(S); 5136 } else if (preference === 'max') { 5137 p = max(S); 5138 } else { 5139 // Custom preference number, as set by user 5140 p = preference; 5141 } 5142 5143 return p; 5144 }; 5145 5146 var findExemplars = function findExemplars(n, R, A) { 5147 var indices = []; 5148 5149 for (var i = 0; i < n; i++) { 5150 if (R[i * n + i] + A[i * n + i] > 0) { 5151 indices.push(i); 5152 } 5153 } 5154 5155 return indices; 5156 }; 5157 5158 var assignClusters = function assignClusters(n, S, exemplars) { 5159 var clusters = []; 5160 5161 for (var i = 0; i < n; i++) { 5162 var index = -1; 5163 var max = -Infinity; 5164 5165 for (var ei = 0; ei < exemplars.length; ei++) { 5166 var e = exemplars[ei]; 5167 5168 if (S[i * n + e] > max) { 5169 index = e; 5170 max = S[i * n + e]; 5171 } 5172 } 5173 5174 if (index > 0) { 5175 clusters.push(index); 5176 } 5177 } 5178 5179 for (var _ei = 0; _ei < exemplars.length; _ei++) { 5180 clusters[exemplars[_ei]] = exemplars[_ei]; 5181 } 5182 5183 return clusters; 5184 }; 5185 5186 var assign$2 = function assign(n, S, exemplars) { 5187 var clusters = assignClusters(n, S, exemplars); 5188 5189 for (var ei = 0; ei < exemplars.length; ei++) { 5190 var ii = []; 5191 5192 for (var c = 0; c < clusters.length; c++) { 5193 if (clusters[c] === exemplars[ei]) { 5194 ii.push(c); 5195 } 5196 } 5197 5198 var maxI = -1; 5199 var maxSum = -Infinity; 5200 5201 for (var i = 0; i < ii.length; i++) { 5202 var sum = 0; 5203 5204 for (var j = 0; j < ii.length; j++) { 5205 sum += S[ii[j] * n + ii[i]]; 5206 } 5207 5208 if (sum > maxSum) { 5209 maxI = i; 5210 maxSum = sum; 5211 } 5212 } 5213 5214 exemplars[ei] = ii[maxI]; 5215 } 5216 5217 clusters = assignClusters(n, S, exemplars); 5218 return clusters; 5219 }; 5220 5221 var affinityPropagation = function affinityPropagation(options) { 5222 var cy = this.cy(); 5223 var nodes = this.nodes(); 5224 var opts = setOptions$3(options); // Map each node to its position in node array 5225 5226 var id2position = {}; 5227 5228 for (var i = 0; i < nodes.length; i++) { 5229 id2position[nodes[i].id()] = i; 5230 } // Begin affinity propagation algorithm 5231 5232 5233 var n; // number of data points 5234 5235 var n2; // size of matrices 5236 5237 var S; // similarity matrix (1D array) 5238 5239 var p; // preference/suitability of a data point to serve as an exemplar 5240 5241 var R; // responsibility matrix (1D array) 5242 5243 var A; // availability matrix (1D array) 5244 5245 n = nodes.length; 5246 n2 = n * n; // Initialize and build S similarity matrix 5247 5248 S = new Array(n2); 5249 5250 for (var _i = 0; _i < n2; _i++) { 5251 S[_i] = -Infinity; // for cases where two data points shouldn't be linked together 5252 } 5253 5254 for (var _i2 = 0; _i2 < n; _i2++) { 5255 for (var j = 0; j < n; j++) { 5256 if (_i2 !== j) { 5257 S[_i2 * n + j] = getSimilarity$1(opts.distance, nodes[_i2], nodes[j], opts.attributes); 5258 } 5259 } 5260 } // Place preferences on the diagonal of S 5261 5262 5263 p = getPreference(S, opts.preference); 5264 5265 for (var _i3 = 0; _i3 < n; _i3++) { 5266 S[_i3 * n + _i3] = p; 5267 } // Initialize R responsibility matrix 5268 5269 5270 R = new Array(n2); 5271 5272 for (var _i4 = 0; _i4 < n2; _i4++) { 5273 R[_i4] = 0.0; 5274 } // Initialize A availability matrix 5275 5276 5277 A = new Array(n2); 5278 5279 for (var _i5 = 0; _i5 < n2; _i5++) { 5280 A[_i5] = 0.0; 5281 } 5282 5283 var old = new Array(n); 5284 var Rp = new Array(n); 5285 var se = new Array(n); 5286 5287 for (var _i6 = 0; _i6 < n; _i6++) { 5288 old[_i6] = 0.0; 5289 Rp[_i6] = 0.0; 5290 se[_i6] = 0; 5291 } 5292 5293 var e = new Array(n * opts.minIterations); 5294 5295 for (var _i7 = 0; _i7 < e.length; _i7++) { 5296 e[_i7] = 0; 5297 } 5298 5299 var iter; 5300 5301 for (iter = 0; iter < opts.maxIterations; iter++) { 5302 // main algorithmic loop 5303 // Update R responsibility matrix 5304 for (var _i8 = 0; _i8 < n; _i8++) { 5305 var max = -Infinity, 5306 max2 = -Infinity, 5307 maxI = -1, 5308 AS = 0.0; 5309 5310 for (var _j = 0; _j < n; _j++) { 5311 old[_j] = R[_i8 * n + _j]; 5312 AS = A[_i8 * n + _j] + S[_i8 * n + _j]; 5313 5314 if (AS >= max) { 5315 max2 = max; 5316 max = AS; 5317 maxI = _j; 5318 } else if (AS > max2) { 5319 max2 = AS; 5320 } 5321 } 5322 5323 for (var _j2 = 0; _j2 < n; _j2++) { 5324 R[_i8 * n + _j2] = (1 - opts.damping) * (S[_i8 * n + _j2] - max) + opts.damping * old[_j2]; 5325 } 5326 5327 R[_i8 * n + maxI] = (1 - opts.damping) * (S[_i8 * n + maxI] - max2) + opts.damping * old[maxI]; 5328 } // Update A availability matrix 5329 5330 5331 for (var _i9 = 0; _i9 < n; _i9++) { 5332 var sum = 0; 5333 5334 for (var _j3 = 0; _j3 < n; _j3++) { 5335 old[_j3] = A[_j3 * n + _i9]; 5336 Rp[_j3] = Math.max(0, R[_j3 * n + _i9]); 5337 sum += Rp[_j3]; 5338 } 5339 5340 sum -= Rp[_i9]; 5341 Rp[_i9] = R[_i9 * n + _i9]; 5342 sum += Rp[_i9]; 5343 5344 for (var _j4 = 0; _j4 < n; _j4++) { 5345 A[_j4 * n + _i9] = (1 - opts.damping) * Math.min(0, sum - Rp[_j4]) + opts.damping * old[_j4]; 5346 } 5347 5348 A[_i9 * n + _i9] = (1 - opts.damping) * (sum - Rp[_i9]) + opts.damping * old[_i9]; 5349 } // Check for convergence 5350 5351 5352 var K = 0; 5353 5354 for (var _i10 = 0; _i10 < n; _i10++) { 5355 var E = A[_i10 * n + _i10] + R[_i10 * n + _i10] > 0 ? 1 : 0; 5356 e[iter % opts.minIterations * n + _i10] = E; 5357 K += E; 5358 } 5359 5360 if (K > 0 && (iter >= opts.minIterations - 1 || iter == opts.maxIterations - 1)) { 5361 var _sum = 0; 5362 5363 for (var _i11 = 0; _i11 < n; _i11++) { 5364 se[_i11] = 0; 5365 5366 for (var _j5 = 0; _j5 < opts.minIterations; _j5++) { 5367 se[_i11] += e[_j5 * n + _i11]; 5368 } 5369 5370 if (se[_i11] === 0 || se[_i11] === opts.minIterations) { 5371 _sum++; 5372 } 5373 } 5374 5375 if (_sum === n) { 5376 // then we have convergence 5377 break; 5378 } 5379 } 5380 } // Identify exemplars (cluster centers) 5381 5382 5383 var exemplarsIndices = findExemplars(n, R, A); // Assign nodes to clusters 5384 5385 var clusterIndices = assign$2(n, S, exemplarsIndices); 5386 var clusters = {}; 5387 5388 for (var c = 0; c < exemplarsIndices.length; c++) { 5389 clusters[exemplarsIndices[c]] = []; 5390 } 5391 5392 for (var _i12 = 0; _i12 < nodes.length; _i12++) { 5393 var pos = id2position[nodes[_i12].id()]; 5394 5395 var clusterIndex = clusterIndices[pos]; 5396 5397 if (clusterIndex != null) { 5398 // the node may have not been assigned a cluster if no valid attributes were specified 5399 clusters[clusterIndex].push(nodes[_i12]); 5400 } 5401 } 5402 5403 var retClusters = new Array(exemplarsIndices.length); 5404 5405 for (var _c = 0; _c < exemplarsIndices.length; _c++) { 5406 retClusters[_c] = cy.collection(clusters[exemplarsIndices[_c]]); 5407 } 5408 5409 return retClusters; 5410 }; 5411 5412 var affinityPropagation$1 = { 5413 affinityPropagation: affinityPropagation, 5414 ap: affinityPropagation 5415 }; 5416 5417 var hierholzerDefaults = defaults({ 5418 root: undefined, 5419 directed: false 5420 }); 5421 var elesfn$b = { 5422 hierholzer: function hierholzer(options) { 5423 if (!plainObject(options)) { 5424 var args = arguments; 5425 options = { 5426 root: args[0], 5427 directed: args[1] 5428 }; 5429 } 5430 5431 var _hierholzerDefaults = hierholzerDefaults(options), 5432 root = _hierholzerDefaults.root, 5433 directed = _hierholzerDefaults.directed; 5434 5435 var eles = this; 5436 var dflag = false; 5437 var oddIn; 5438 var oddOut; 5439 var startVertex; 5440 if (root) startVertex = string(root) ? this.filter(root)[0].id() : root[0].id(); 5441 var nodes = {}; 5442 var edges = {}; 5443 5444 if (directed) { 5445 eles.forEach(function (ele) { 5446 var id = ele.id(); 5447 5448 if (ele.isNode()) { 5449 var ind = ele.indegree(true); 5450 var outd = ele.outdegree(true); 5451 var d1 = ind - outd; 5452 var d2 = outd - ind; 5453 5454 if (d1 == 1) { 5455 if (oddIn) dflag = true;else oddIn = id; 5456 } else if (d2 == 1) { 5457 if (oddOut) dflag = true;else oddOut = id; 5458 } else if (d2 > 1 || d1 > 1) { 5459 dflag = true; 5460 } 5461 5462 nodes[id] = []; 5463 ele.outgoers().forEach(function (e) { 5464 if (e.isEdge()) nodes[id].push(e.id()); 5465 }); 5466 } else { 5467 edges[id] = [undefined, ele.target().id()]; 5468 } 5469 }); 5470 } else { 5471 eles.forEach(function (ele) { 5472 var id = ele.id(); 5473 5474 if (ele.isNode()) { 5475 var d = ele.degree(true); 5476 5477 if (d % 2) { 5478 if (!oddIn) oddIn = id;else if (!oddOut) oddOut = id;else dflag = true; 5479 } 5480 5481 nodes[id] = []; 5482 ele.connectedEdges().forEach(function (e) { 5483 return nodes[id].push(e.id()); 5484 }); 5485 } else { 5486 edges[id] = [ele.source().id(), ele.target().id()]; 5487 } 5488 }); 5489 } 5490 5491 var result = { 5492 found: false, 5493 trail: undefined 5494 }; 5495 if (dflag) return result;else if (oddOut && oddIn) { 5496 if (directed) { 5497 if (startVertex && oddOut != startVertex) { 5498 return result; 5499 } 5500 5501 startVertex = oddOut; 5502 } else { 5503 if (startVertex && oddOut != startVertex && oddIn != startVertex) { 5504 return result; 5505 } else if (!startVertex) { 5506 startVertex = oddOut; 5507 } 5508 } 5509 } else { 5510 if (!startVertex) startVertex = eles[0].id(); 5511 } 5512 5513 var walk = function walk(v) { 5514 var currentNode = v; 5515 var subtour = [v]; 5516 var adj, adjTail, adjHead; 5517 5518 while (nodes[currentNode].length) { 5519 adj = nodes[currentNode].shift(); 5520 adjTail = edges[adj][0]; 5521 adjHead = edges[adj][1]; 5522 5523 if (currentNode != adjHead) { 5524 nodes[adjHead] = nodes[adjHead].filter(function (e) { 5525 return e != adj; 5526 }); 5527 currentNode = adjHead; 5528 } else if (!directed && currentNode != adjTail) { 5529 nodes[adjTail] = nodes[adjTail].filter(function (e) { 5530 return e != adj; 5531 }); 5532 currentNode = adjTail; 5533 } 5534 5535 subtour.unshift(adj); 5536 subtour.unshift(currentNode); 5537 } 5538 5539 return subtour; 5540 }; 5541 5542 var trail = []; 5543 var subtour = []; 5544 subtour = walk(startVertex); 5545 5546 while (subtour.length != 1) { 5547 if (nodes[subtour[0]].length == 0) { 5548 trail.unshift(eles.getElementById(subtour.shift())); 5549 trail.unshift(eles.getElementById(subtour.shift())); 5550 } else { 5551 subtour = walk(subtour.shift()).concat(subtour); 5552 } 5553 } 5554 5555 trail.unshift(eles.getElementById(subtour.shift())); // final node 5556 5557 for (var d in nodes) { 5558 if (nodes[d].length) { 5559 return result; 5560 } 5561 } 5562 5563 result.found = true; 5564 result.trail = this.spawn(trail); 5565 return result; 5566 } 5567 }; 5568 5569 var hopcroftTarjanBiconnected = function hopcroftTarjanBiconnected() { 5570 var eles = this; 5571 var nodes = {}; 5572 var id = 0; 5573 var edgeCount = 0; 5574 var components = []; 5575 var stack = []; 5576 var visitedEdges = {}; 5577 5578 var buildComponent = function buildComponent(x, y) { 5579 var i = stack.length - 1; 5580 var cutset = []; 5581 var component = eles.spawn(); 5582 5583 while (stack[i].x != x || stack[i].y != y) { 5584 cutset.push(stack.pop().edge); 5585 i--; 5586 } 5587 5588 cutset.push(stack.pop().edge); 5589 cutset.forEach(function (edge) { 5590 var connectedNodes = edge.connectedNodes().intersection(eles); 5591 component.merge(edge); 5592 connectedNodes.forEach(function (node) { 5593 var nodeId = node.id(); 5594 var connectedEdges = node.connectedEdges().intersection(eles); 5595 component.merge(node); 5596 5597 if (!nodes[nodeId].cutVertex) { 5598 component.merge(connectedEdges); 5599 } else { 5600 component.merge(connectedEdges.filter(function (edge) { 5601 return edge.isLoop(); 5602 })); 5603 } 5604 }); 5605 }); 5606 components.push(component); 5607 }; 5608 5609 var biconnectedSearch = function biconnectedSearch(root, currentNode, parent) { 5610 if (root === parent) edgeCount += 1; 5611 nodes[currentNode] = { 5612 id: id, 5613 low: id++, 5614 cutVertex: false 5615 }; 5616 var edges = eles.getElementById(currentNode).connectedEdges().intersection(eles); 5617 5618 if (edges.size() === 0) { 5619 components.push(eles.spawn(eles.getElementById(currentNode))); 5620 } else { 5621 var sourceId, targetId, otherNodeId, edgeId; 5622 edges.forEach(function (edge) { 5623 sourceId = edge.source().id(); 5624 targetId = edge.target().id(); 5625 otherNodeId = sourceId === currentNode ? targetId : sourceId; 5626 5627 if (otherNodeId !== parent) { 5628 edgeId = edge.id(); 5629 5630 if (!visitedEdges[edgeId]) { 5631 visitedEdges[edgeId] = true; 5632 stack.push({ 5633 x: currentNode, 5634 y: otherNodeId, 5635 edge: edge 5636 }); 5637 } 5638 5639 if (!(otherNodeId in nodes)) { 5640 biconnectedSearch(root, otherNodeId, currentNode); 5641 nodes[currentNode].low = Math.min(nodes[currentNode].low, nodes[otherNodeId].low); 5642 5643 if (nodes[currentNode].id <= nodes[otherNodeId].low) { 5644 nodes[currentNode].cutVertex = true; 5645 buildComponent(currentNode, otherNodeId); 5646 } 5647 } else { 5648 nodes[currentNode].low = Math.min(nodes[currentNode].low, nodes[otherNodeId].id); 5649 } 5650 } 5651 }); 5652 } 5653 }; 5654 5655 eles.forEach(function (ele) { 5656 if (ele.isNode()) { 5657 var nodeId = ele.id(); 5658 5659 if (!(nodeId in nodes)) { 5660 edgeCount = 0; 5661 biconnectedSearch(nodeId, nodeId); 5662 nodes[nodeId].cutVertex = edgeCount > 1; 5663 } 5664 } 5665 }); 5666 var cutVertices = Object.keys(nodes).filter(function (id) { 5667 return nodes[id].cutVertex; 5668 }).map(function (id) { 5669 return eles.getElementById(id); 5670 }); 5671 return { 5672 cut: eles.spawn(cutVertices), 5673 components: components 5674 }; 5675 }; 5676 5677 var hopcroftTarjanBiconnected$1 = { 5678 hopcroftTarjanBiconnected: hopcroftTarjanBiconnected, 5679 htbc: hopcroftTarjanBiconnected, 5680 htb: hopcroftTarjanBiconnected, 5681 hopcroftTarjanBiconnectedComponents: hopcroftTarjanBiconnected 5682 }; 5683 5684 var elesfn$c = {}; 5685 [elesfn, elesfn$1, elesfn$2, elesfn$3, elesfn$4, elesfn$5, elesfn$6, elesfn$7, elesfn$8, elesfn$9, elesfn$a, markovClustering$1, kClustering, hierarchicalClustering$1, affinityPropagation$1, elesfn$b, hopcroftTarjanBiconnected$1].forEach(function (props) { 5686 extend(elesfn$c, props); 5687 }); 5688 5689 /*! 5690 Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable 5691 Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) 5692 Licensed under The MIT License (http://opensource.org/licenses/MIT) 5693 */ 5694 5695 /* promise states [Promises/A+ 2.1] */ 5696 var STATE_PENDING = 0; 5697 /* [Promises/A+ 2.1.1] */ 5698 5699 var STATE_FULFILLED = 1; 5700 /* [Promises/A+ 2.1.2] */ 5701 5702 var STATE_REJECTED = 2; 5703 /* [Promises/A+ 2.1.3] */ 5704 5705 /* promise object constructor */ 5706 5707 var api = function api(executor) { 5708 /* optionally support non-constructor/plain-function call */ 5709 if (!(this instanceof api)) return new api(executor); 5710 /* initialize object */ 5711 5712 this.id = 'Thenable/1.0.7'; 5713 this.state = STATE_PENDING; 5714 /* initial state */ 5715 5716 this.fulfillValue = undefined; 5717 /* initial value */ 5718 5719 /* [Promises/A+ 1.3, 2.1.2.2] */ 5720 5721 this.rejectReason = undefined; 5722 /* initial reason */ 5723 5724 /* [Promises/A+ 1.5, 2.1.3.2] */ 5725 5726 this.onFulfilled = []; 5727 /* initial handlers */ 5728 5729 this.onRejected = []; 5730 /* initial handlers */ 5731 5732 /* provide optional information-hiding proxy */ 5733 5734 this.proxy = { 5735 then: this.then.bind(this) 5736 }; 5737 /* support optional executor function */ 5738 5739 if (typeof executor === 'function') executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); 5740 }; 5741 /* promise API methods */ 5742 5743 5744 api.prototype = { 5745 /* promise resolving methods */ 5746 fulfill: function fulfill(value) { 5747 return deliver(this, STATE_FULFILLED, 'fulfillValue', value); 5748 }, 5749 reject: function reject(value) { 5750 return deliver(this, STATE_REJECTED, 'rejectReason', value); 5751 }, 5752 5753 /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ 5754 then: function then(onFulfilled, onRejected) { 5755 var curr = this; 5756 var next = new api(); 5757 /* [Promises/A+ 2.2.7] */ 5758 5759 curr.onFulfilled.push(resolver(onFulfilled, next, 'fulfill')); 5760 /* [Promises/A+ 2.2.2/2.2.6] */ 5761 5762 curr.onRejected.push(resolver(onRejected, next, 'reject')); 5763 /* [Promises/A+ 2.2.3/2.2.6] */ 5764 5765 execute(curr); 5766 return next.proxy; 5767 /* [Promises/A+ 2.2.7, 3.3] */ 5768 } 5769 }; 5770 /* deliver an action */ 5771 5772 var deliver = function deliver(curr, state, name, value) { 5773 if (curr.state === STATE_PENDING) { 5774 curr.state = state; 5775 /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ 5776 5777 curr[name] = value; 5778 /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ 5779 5780 execute(curr); 5781 } 5782 5783 return curr; 5784 }; 5785 /* execute all handlers */ 5786 5787 5788 var execute = function execute(curr) { 5789 if (curr.state === STATE_FULFILLED) execute_handlers(curr, 'onFulfilled', curr.fulfillValue);else if (curr.state === STATE_REJECTED) execute_handlers(curr, 'onRejected', curr.rejectReason); 5790 }; 5791 /* execute particular set of handlers */ 5792 5793 5794 var execute_handlers = function execute_handlers(curr, name, value) { 5795 /* global setImmediate: true */ 5796 5797 /* global setTimeout: true */ 5798 5799 /* short-circuit processing */ 5800 if (curr[name].length === 0) return; 5801 /* iterate over all handlers, exactly once */ 5802 5803 var handlers = curr[name]; 5804 curr[name] = []; 5805 /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ 5806 5807 var func = function func() { 5808 for (var i = 0; i < handlers.length; i++) { 5809 handlers[i](value); 5810 } 5811 /* [Promises/A+ 2.2.5] */ 5812 5813 }; 5814 /* execute procedure asynchronously */ 5815 5816 /* [Promises/A+ 2.2.4, 3.1] */ 5817 5818 5819 if (typeof setImmediate === 'function') setImmediate(func);else setTimeout(func, 0); 5820 }; 5821 /* generate a resolver function */ 5822 5823 5824 var resolver = function resolver(cb, next, method) { 5825 return function (value) { 5826 if (typeof cb !== 'function') 5827 /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ 5828 next[method].call(next, value); 5829 /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ 5830 else { 5831 var result; 5832 5833 try { 5834 result = cb(value); 5835 } 5836 /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ 5837 catch (e) { 5838 next.reject(e); 5839 /* [Promises/A+ 2.2.7.2] */ 5840 5841 return; 5842 } 5843 5844 resolve(next, result); 5845 /* [Promises/A+ 2.2.7.1] */ 5846 } 5847 }; 5848 }; 5849 /* "Promise Resolution Procedure" */ 5850 5851 /* [Promises/A+ 2.3] */ 5852 5853 5854 var resolve = function resolve(promise, x) { 5855 /* sanity check arguments */ 5856 5857 /* [Promises/A+ 2.3.1] */ 5858 if (promise === x || promise.proxy === x) { 5859 promise.reject(new TypeError('cannot resolve promise with itself')); 5860 return; 5861 } 5862 /* surgically check for a "then" method 5863 (mainly to just call the "getter" of "then" only once) */ 5864 5865 5866 var then; 5867 5868 if (_typeof(x) === 'object' && x !== null || typeof x === 'function') { 5869 try { 5870 then = x.then; 5871 } 5872 /* [Promises/A+ 2.3.3.1, 3.5] */ 5873 catch (e) { 5874 promise.reject(e); 5875 /* [Promises/A+ 2.3.3.2] */ 5876 5877 return; 5878 } 5879 } 5880 /* handle own Thenables [Promises/A+ 2.3.2] 5881 and similar "thenables" [Promises/A+ 2.3.3] */ 5882 5883 5884 if (typeof then === 'function') { 5885 var resolved = false; 5886 5887 try { 5888 /* call retrieved "then" method */ 5889 5890 /* [Promises/A+ 2.3.3.3] */ 5891 then.call(x, 5892 /* resolvePromise */ 5893 5894 /* [Promises/A+ 2.3.3.3.1] */ 5895 function (y) { 5896 if (resolved) return; 5897 resolved = true; 5898 /* [Promises/A+ 2.3.3.3.3] */ 5899 5900 if (y === x) 5901 /* [Promises/A+ 3.6] */ 5902 promise.reject(new TypeError('circular thenable chain'));else resolve(promise, y); 5903 }, 5904 /* rejectPromise */ 5905 5906 /* [Promises/A+ 2.3.3.3.2] */ 5907 function (r) { 5908 if (resolved) return; 5909 resolved = true; 5910 /* [Promises/A+ 2.3.3.3.3] */ 5911 5912 promise.reject(r); 5913 }); 5914 } catch (e) { 5915 if (!resolved) 5916 /* [Promises/A+ 2.3.3.3.3] */ 5917 promise.reject(e); 5918 /* [Promises/A+ 2.3.3.3.4] */ 5919 } 5920 5921 return; 5922 } 5923 /* handle other values */ 5924 5925 5926 promise.fulfill(x); 5927 /* [Promises/A+ 2.3.4, 2.3.3.4] */ 5928 }; // so we always have Promise.all() 5929 5930 5931 api.all = function (ps) { 5932 return new api(function (resolveAll, rejectAll) { 5933 var vals = new Array(ps.length); 5934 var doneCount = 0; 5935 5936 var fulfill = function fulfill(i, val) { 5937 vals[i] = val; 5938 doneCount++; 5939 5940 if (doneCount === ps.length) { 5941 resolveAll(vals); 5942 } 5943 }; 5944 5945 for (var i = 0; i < ps.length; i++) { 5946 (function (i) { 5947 var p = ps[i]; 5948 var isPromise = p != null && p.then != null; 5949 5950 if (isPromise) { 5951 p.then(function (val) { 5952 fulfill(i, val); 5953 }, function (err) { 5954 rejectAll(err); 5955 }); 5956 } else { 5957 var val = p; 5958 fulfill(i, val); 5959 } 5960 })(i); 5961 } 5962 }); 5963 }; 5964 5965 api.resolve = function (val) { 5966 return new api(function (resolve, reject) { 5967 resolve(val); 5968 }); 5969 }; 5970 5971 api.reject = function (val) { 5972 return new api(function (resolve, reject) { 5973 reject(val); 5974 }); 5975 }; 5976 5977 var Promise$1 = typeof Promise !== 'undefined' ? Promise : api; // eslint-disable-line no-undef 5978 5979 var Animation = function Animation(target, opts, opts2) { 5980 var isCore = core(target); 5981 var isEle = !isCore; 5982 5983 var _p = this._private = extend({ 5984 duration: 1000 5985 }, opts, opts2); 5986 5987 _p.target = target; 5988 _p.style = _p.style || _p.css; 5989 _p.started = false; 5990 _p.playing = false; 5991 _p.hooked = false; 5992 _p.applying = false; 5993 _p.progress = 0; 5994 _p.completes = []; 5995 _p.frames = []; 5996 5997 if (_p.complete && fn(_p.complete)) { 5998 _p.completes.push(_p.complete); 5999 } 6000 6001 if (isEle) { 6002 var pos = target.position(); 6003 _p.startPosition = _p.startPosition || { 6004 x: pos.x, 6005 y: pos.y 6006 }; 6007 _p.startStyle = _p.startStyle || target.cy().style().getAnimationStartStyle(target, _p.style); 6008 } 6009 6010 if (isCore) { 6011 var pan = target.pan(); 6012 _p.startPan = { 6013 x: pan.x, 6014 y: pan.y 6015 }; 6016 _p.startZoom = target.zoom(); 6017 } // for future timeline/animations impl 6018 6019 6020 this.length = 1; 6021 this[0] = this; 6022 }; 6023 6024 var anifn = Animation.prototype; 6025 extend(anifn, { 6026 instanceString: function instanceString() { 6027 return 'animation'; 6028 }, 6029 hook: function hook() { 6030 var _p = this._private; 6031 6032 if (!_p.hooked) { 6033 // add to target's animation queue 6034 var q; 6035 var tAni = _p.target._private.animation; 6036 6037 if (_p.queue) { 6038 q = tAni.queue; 6039 } else { 6040 q = tAni.current; 6041 } 6042 6043 q.push(this); // add to the animation loop pool 6044 6045 if (elementOrCollection(_p.target)) { 6046 _p.target.cy().addToAnimationPool(_p.target); 6047 } 6048 6049 _p.hooked = true; 6050 } 6051 6052 return this; 6053 }, 6054 play: function play() { 6055 var _p = this._private; // autorewind 6056 6057 if (_p.progress === 1) { 6058 _p.progress = 0; 6059 } 6060 6061 _p.playing = true; 6062 _p.started = false; // needs to be started by animation loop 6063 6064 _p.stopped = false; 6065 this.hook(); // the animation loop will start the animation... 6066 6067 return this; 6068 }, 6069 playing: function playing() { 6070 return this._private.playing; 6071 }, 6072 apply: function apply() { 6073 var _p = this._private; 6074 _p.applying = true; 6075 _p.started = false; // needs to be started by animation loop 6076 6077 _p.stopped = false; 6078 this.hook(); // the animation loop will apply the animation at this progress 6079 6080 return this; 6081 }, 6082 applying: function applying() { 6083 return this._private.applying; 6084 }, 6085 pause: function pause() { 6086 var _p = this._private; 6087 _p.playing = false; 6088 _p.started = false; 6089 return this; 6090 }, 6091 stop: function stop() { 6092 var _p = this._private; 6093 _p.playing = false; 6094 _p.started = false; 6095 _p.stopped = true; // to be removed from animation queues 6096 6097 return this; 6098 }, 6099 rewind: function rewind() { 6100 return this.progress(0); 6101 }, 6102 fastforward: function fastforward() { 6103 return this.progress(1); 6104 }, 6105 time: function time(t) { 6106 var _p = this._private; 6107 6108 if (t === undefined) { 6109 return _p.progress * _p.duration; 6110 } else { 6111 return this.progress(t / _p.duration); 6112 } 6113 }, 6114 progress: function progress(p) { 6115 var _p = this._private; 6116 var wasPlaying = _p.playing; 6117 6118 if (p === undefined) { 6119 return _p.progress; 6120 } else { 6121 if (wasPlaying) { 6122 this.pause(); 6123 } 6124 6125 _p.progress = p; 6126 _p.started = false; 6127 6128 if (wasPlaying) { 6129 this.play(); 6130 } 6131 } 6132 6133 return this; 6134 }, 6135 completed: function completed() { 6136 return this._private.progress === 1; 6137 }, 6138 reverse: function reverse() { 6139 var _p = this._private; 6140 var wasPlaying = _p.playing; 6141 6142 if (wasPlaying) { 6143 this.pause(); 6144 } 6145 6146 _p.progress = 1 - _p.progress; 6147 _p.started = false; 6148 6149 var swap = function swap(a, b) { 6150 var _pa = _p[a]; 6151 6152 if (_pa == null) { 6153 return; 6154 } 6155 6156 _p[a] = _p[b]; 6157 _p[b] = _pa; 6158 }; 6159 6160 swap('zoom', 'startZoom'); 6161 swap('pan', 'startPan'); 6162 swap('position', 'startPosition'); // swap styles 6163 6164 if (_p.style) { 6165 for (var i = 0; i < _p.style.length; i++) { 6166 var prop = _p.style[i]; 6167 var name = prop.name; 6168 var startStyleProp = _p.startStyle[name]; 6169 _p.startStyle[name] = prop; 6170 _p.style[i] = startStyleProp; 6171 } 6172 } 6173 6174 if (wasPlaying) { 6175 this.play(); 6176 } 6177 6178 return this; 6179 }, 6180 promise: function promise(type) { 6181 var _p = this._private; 6182 var arr; 6183 6184 switch (type) { 6185 case 'frame': 6186 arr = _p.frames; 6187 break; 6188 6189 default: 6190 case 'complete': 6191 case 'completed': 6192 arr = _p.completes; 6193 } 6194 6195 return new Promise$1(function (resolve, reject) { 6196 arr.push(function () { 6197 resolve(); 6198 }); 6199 }); 6200 } 6201 }); 6202 anifn.complete = anifn.completed; 6203 anifn.run = anifn.play; 6204 anifn.running = anifn.playing; 6205 6206 var define = { 6207 animated: function animated() { 6208 return function animatedImpl() { 6209 var self = this; 6210 var selfIsArrayLike = self.length !== undefined; 6211 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6212 6213 var cy = this._private.cy || this; 6214 6215 if (!cy.styleEnabled()) { 6216 return false; 6217 } 6218 6219 var ele = all[0]; 6220 6221 if (ele) { 6222 return ele._private.animation.current.length > 0; 6223 } 6224 }; 6225 }, 6226 // animated 6227 clearQueue: function clearQueue() { 6228 return function clearQueueImpl() { 6229 var self = this; 6230 var selfIsArrayLike = self.length !== undefined; 6231 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6232 6233 var cy = this._private.cy || this; 6234 6235 if (!cy.styleEnabled()) { 6236 return this; 6237 } 6238 6239 for (var i = 0; i < all.length; i++) { 6240 var ele = all[i]; 6241 ele._private.animation.queue = []; 6242 } 6243 6244 return this; 6245 }; 6246 }, 6247 // clearQueue 6248 delay: function delay() { 6249 return function delayImpl(time, complete) { 6250 var cy = this._private.cy || this; 6251 6252 if (!cy.styleEnabled()) { 6253 return this; 6254 } 6255 6256 return this.animate({ 6257 delay: time, 6258 duration: time, 6259 complete: complete 6260 }); 6261 }; 6262 }, 6263 // delay 6264 delayAnimation: function delayAnimation() { 6265 return function delayAnimationImpl(time, complete) { 6266 var cy = this._private.cy || this; 6267 6268 if (!cy.styleEnabled()) { 6269 return this; 6270 } 6271 6272 return this.animation({ 6273 delay: time, 6274 duration: time, 6275 complete: complete 6276 }); 6277 }; 6278 }, 6279 // delay 6280 animation: function animation() { 6281 return function animationImpl(properties, params) { 6282 var self = this; 6283 var selfIsArrayLike = self.length !== undefined; 6284 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6285 6286 var cy = this._private.cy || this; 6287 var isCore = !selfIsArrayLike; 6288 var isEles = !isCore; 6289 6290 if (!cy.styleEnabled()) { 6291 return this; 6292 } 6293 6294 var style = cy.style(); 6295 properties = extend({}, properties, params); 6296 var propertiesEmpty = Object.keys(properties).length === 0; 6297 6298 if (propertiesEmpty) { 6299 return new Animation(all[0], properties); // nothing to animate 6300 } 6301 6302 if (properties.duration === undefined) { 6303 properties.duration = 400; 6304 } 6305 6306 switch (properties.duration) { 6307 case 'slow': 6308 properties.duration = 600; 6309 break; 6310 6311 case 'fast': 6312 properties.duration = 200; 6313 break; 6314 } 6315 6316 if (isEles) { 6317 properties.style = style.getPropsList(properties.style || properties.css); 6318 properties.css = undefined; 6319 } 6320 6321 if (isEles && properties.renderedPosition != null) { 6322 var rpos = properties.renderedPosition; 6323 var pan = cy.pan(); 6324 var zoom = cy.zoom(); 6325 properties.position = renderedToModelPosition(rpos, zoom, pan); 6326 } // override pan w/ panBy if set 6327 6328 6329 if (isCore && properties.panBy != null) { 6330 var panBy = properties.panBy; 6331 var cyPan = cy.pan(); 6332 properties.pan = { 6333 x: cyPan.x + panBy.x, 6334 y: cyPan.y + panBy.y 6335 }; 6336 } // override pan w/ center if set 6337 6338 6339 var center = properties.center || properties.centre; 6340 6341 if (isCore && center != null) { 6342 var centerPan = cy.getCenterPan(center.eles, properties.zoom); 6343 6344 if (centerPan != null) { 6345 properties.pan = centerPan; 6346 } 6347 } // override pan & zoom w/ fit if set 6348 6349 6350 if (isCore && properties.fit != null) { 6351 var fit = properties.fit; 6352 var fitVp = cy.getFitViewport(fit.eles || fit.boundingBox, fit.padding); 6353 6354 if (fitVp != null) { 6355 properties.pan = fitVp.pan; 6356 properties.zoom = fitVp.zoom; 6357 } 6358 } // override zoom (& potentially pan) w/ zoom obj if set 6359 6360 6361 if (isCore && plainObject(properties.zoom)) { 6362 var vp = cy.getZoomedViewport(properties.zoom); 6363 6364 if (vp != null) { 6365 if (vp.zoomed) { 6366 properties.zoom = vp.zoom; 6367 } 6368 6369 if (vp.panned) { 6370 properties.pan = vp.pan; 6371 } 6372 } else { 6373 properties.zoom = null; // an inavalid zoom (e.g. no delta) gets automatically destroyed 6374 } 6375 } 6376 6377 return new Animation(all[0], properties); 6378 }; 6379 }, 6380 // animate 6381 animate: function animate() { 6382 return function animateImpl(properties, params) { 6383 var self = this; 6384 var selfIsArrayLike = self.length !== undefined; 6385 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6386 6387 var cy = this._private.cy || this; 6388 6389 if (!cy.styleEnabled()) { 6390 return this; 6391 } 6392 6393 if (params) { 6394 properties = extend({}, properties, params); 6395 } // manually hook and run the animation 6396 6397 6398 for (var i = 0; i < all.length; i++) { 6399 var ele = all[i]; 6400 var queue = ele.animated() && (properties.queue === undefined || properties.queue); 6401 var ani = ele.animation(properties, queue ? { 6402 queue: true 6403 } : undefined); 6404 ani.play(); 6405 } 6406 6407 return this; // chaining 6408 }; 6409 }, 6410 // animate 6411 stop: function stop() { 6412 return function stopImpl(clearQueue, jumpToEnd) { 6413 var self = this; 6414 var selfIsArrayLike = self.length !== undefined; 6415 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6416 6417 var cy = this._private.cy || this; 6418 6419 if (!cy.styleEnabled()) { 6420 return this; 6421 } 6422 6423 for (var i = 0; i < all.length; i++) { 6424 var ele = all[i]; 6425 var _p = ele._private; 6426 var anis = _p.animation.current; 6427 6428 for (var j = 0; j < anis.length; j++) { 6429 var ani = anis[j]; 6430 var ani_p = ani._private; 6431 6432 if (jumpToEnd) { 6433 // next iteration of the animation loop, the animation 6434 // will go straight to the end and be removed 6435 ani_p.duration = 0; 6436 } 6437 } // clear the queue of future animations 6438 6439 6440 if (clearQueue) { 6441 _p.animation.queue = []; 6442 } 6443 6444 if (!jumpToEnd) { 6445 _p.animation.current = []; 6446 } 6447 } // we have to notify (the animation loop doesn't do it for us on `stop`) 6448 6449 6450 cy.notify('draw'); 6451 return this; 6452 }; 6453 } // stop 6454 6455 }; // define 6456 6457 var define$1 = { 6458 // access data field 6459 data: function data(params) { 6460 var defaults = { 6461 field: 'data', 6462 bindingEvent: 'data', 6463 allowBinding: false, 6464 allowSetting: false, 6465 allowGetting: false, 6466 settingEvent: 'data', 6467 settingTriggersEvent: false, 6468 triggerFnName: 'trigger', 6469 immutableKeys: {}, 6470 // key => true if immutable 6471 updateStyle: false, 6472 beforeGet: function beforeGet(self) {}, 6473 beforeSet: function beforeSet(self, obj) {}, 6474 onSet: function onSet(self) {}, 6475 canSet: function canSet(self) { 6476 return true; 6477 } 6478 }; 6479 params = extend({}, defaults, params); 6480 return function dataImpl(name, value) { 6481 var p = params; 6482 var self = this; 6483 var selfIsArrayLike = self.length !== undefined; 6484 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6485 6486 var single = selfIsArrayLike ? self[0] : self; // .data('foo', ...) 6487 6488 if (string(name)) { 6489 // set or get property 6490 // .data('foo') 6491 if (p.allowGetting && value === undefined) { 6492 // get 6493 var ret; 6494 6495 if (single) { 6496 p.beforeGet(single); 6497 ret = single._private[p.field][name]; 6498 } 6499 6500 return ret; // .data('foo', 'bar') 6501 } else if (p.allowSetting && value !== undefined) { 6502 // set 6503 var valid = !p.immutableKeys[name]; 6504 6505 if (valid) { 6506 var change = _defineProperty({}, name, value); 6507 6508 p.beforeSet(self, change); 6509 6510 for (var i = 0, l = all.length; i < l; i++) { 6511 var ele = all[i]; 6512 6513 if (p.canSet(ele)) { 6514 ele._private[p.field][name] = value; 6515 } 6516 } // update mappers if asked 6517 6518 6519 if (p.updateStyle) { 6520 self.updateStyle(); 6521 } // call onSet callback 6522 6523 6524 p.onSet(self); 6525 6526 if (p.settingTriggersEvent) { 6527 self[p.triggerFnName](p.settingEvent); 6528 } 6529 } 6530 } // .data({ 'foo': 'bar' }) 6531 6532 } else if (p.allowSetting && plainObject(name)) { 6533 // extend 6534 var obj = name; 6535 var k, v; 6536 var keys = Object.keys(obj); 6537 p.beforeSet(self, obj); 6538 6539 for (var _i = 0; _i < keys.length; _i++) { 6540 k = keys[_i]; 6541 v = obj[k]; 6542 6543 var _valid = !p.immutableKeys[k]; 6544 6545 if (_valid) { 6546 for (var j = 0; j < all.length; j++) { 6547 var _ele = all[j]; 6548 6549 if (p.canSet(_ele)) { 6550 _ele._private[p.field][k] = v; 6551 } 6552 } 6553 } 6554 } // update mappers if asked 6555 6556 6557 if (p.updateStyle) { 6558 self.updateStyle(); 6559 } // call onSet callback 6560 6561 6562 p.onSet(self); 6563 6564 if (p.settingTriggersEvent) { 6565 self[p.triggerFnName](p.settingEvent); 6566 } // .data(function(){ ... }) 6567 6568 } else if (p.allowBinding && fn(name)) { 6569 // bind to event 6570 var fn$1 = name; 6571 self.on(p.bindingEvent, fn$1); // .data() 6572 } else if (p.allowGetting && name === undefined) { 6573 // get whole object 6574 var _ret; 6575 6576 if (single) { 6577 p.beforeGet(single); 6578 _ret = single._private[p.field]; 6579 } 6580 6581 return _ret; 6582 } 6583 6584 return self; // maintain chainability 6585 }; // function 6586 }, 6587 // data 6588 // remove data field 6589 removeData: function removeData(params) { 6590 var defaults = { 6591 field: 'data', 6592 event: 'data', 6593 triggerFnName: 'trigger', 6594 triggerEvent: false, 6595 immutableKeys: {} // key => true if immutable 6596 6597 }; 6598 params = extend({}, defaults, params); 6599 return function removeDataImpl(names) { 6600 var p = params; 6601 var self = this; 6602 var selfIsArrayLike = self.length !== undefined; 6603 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6604 // .removeData('foo bar') 6605 6606 if (string(names)) { 6607 // then get the list of keys, and delete them 6608 var keys = names.split(/\s+/); 6609 var l = keys.length; 6610 6611 for (var i = 0; i < l; i++) { 6612 // delete each non-empty key 6613 var key = keys[i]; 6614 6615 if (emptyString(key)) { 6616 continue; 6617 } 6618 6619 var valid = !p.immutableKeys[key]; // not valid if immutable 6620 6621 if (valid) { 6622 for (var i_a = 0, l_a = all.length; i_a < l_a; i_a++) { 6623 all[i_a]._private[p.field][key] = undefined; 6624 } 6625 } 6626 } 6627 6628 if (p.triggerEvent) { 6629 self[p.triggerFnName](p.event); 6630 } // .removeData() 6631 6632 } else if (names === undefined) { 6633 // then delete all keys 6634 for (var _i_a = 0, _l_a = all.length; _i_a < _l_a; _i_a++) { 6635 var _privateFields = all[_i_a]._private[p.field]; 6636 6637 var _keys = Object.keys(_privateFields); 6638 6639 for (var _i2 = 0; _i2 < _keys.length; _i2++) { 6640 var _key = _keys[_i2]; 6641 var validKeyToDelete = !p.immutableKeys[_key]; 6642 6643 if (validKeyToDelete) { 6644 _privateFields[_key] = undefined; 6645 } 6646 } 6647 } 6648 6649 if (p.triggerEvent) { 6650 self[p.triggerFnName](p.event); 6651 } 6652 } 6653 6654 return self; // maintain chaining 6655 }; // function 6656 } // removeData 6657 6658 }; // define 6659 6660 var define$2 = { 6661 eventAliasesOn: function eventAliasesOn(proto) { 6662 var p = proto; 6663 p.addListener = p.listen = p.bind = p.on; 6664 p.unlisten = p.unbind = p.off = p.removeListener; 6665 p.trigger = p.emit; // this is just a wrapper alias of .on() 6666 6667 p.pon = p.promiseOn = function (events, selector) { 6668 var self = this; 6669 var args = Array.prototype.slice.call(arguments, 0); 6670 return new Promise$1(function (resolve, reject) { 6671 var callback = function callback(e) { 6672 self.off.apply(self, offArgs); 6673 resolve(e); 6674 }; 6675 6676 var onArgs = args.concat([callback]); 6677 var offArgs = onArgs.concat([]); 6678 self.on.apply(self, onArgs); 6679 }); 6680 }; 6681 } 6682 }; // define 6683 6684 // use this module to cherry pick functions into your prototype 6685 var define$3 = {}; 6686 [define, define$1, define$2].forEach(function (m) { 6687 extend(define$3, m); 6688 }); 6689 6690 var elesfn$d = { 6691 animate: define$3.animate(), 6692 animation: define$3.animation(), 6693 animated: define$3.animated(), 6694 clearQueue: define$3.clearQueue(), 6695 delay: define$3.delay(), 6696 delayAnimation: define$3.delayAnimation(), 6697 stop: define$3.stop() 6698 }; 6699 6700 var elesfn$e = { 6701 classes: function classes(_classes) { 6702 var self = this; 6703 6704 if (_classes === undefined) { 6705 var ret = []; 6706 6707 self[0]._private.classes.forEach(function (cls) { 6708 return ret.push(cls); 6709 }); 6710 6711 return ret; 6712 } else if (!array(_classes)) { 6713 // extract classes from string 6714 _classes = (_classes || '').match(/\S+/g) || []; 6715 } 6716 6717 var changed = []; 6718 var classesSet = new Set$1(_classes); // check and update each ele 6719 6720 for (var j = 0; j < self.length; j++) { 6721 var ele = self[j]; 6722 var _p = ele._private; 6723 var eleClasses = _p.classes; 6724 var changedEle = false; // check if ele has all of the passed classes 6725 6726 for (var i = 0; i < _classes.length; i++) { 6727 var cls = _classes[i]; 6728 var eleHasClass = eleClasses.has(cls); 6729 6730 if (!eleHasClass) { 6731 changedEle = true; 6732 break; 6733 } 6734 } // check if ele has classes outside of those passed 6735 6736 6737 if (!changedEle) { 6738 changedEle = eleClasses.size !== _classes.length; 6739 } 6740 6741 if (changedEle) { 6742 _p.classes = classesSet; 6743 changed.push(ele); 6744 } 6745 } // trigger update style on those eles that had class changes 6746 6747 6748 if (changed.length > 0) { 6749 this.spawn(changed).updateStyle().emit('class'); 6750 } 6751 6752 return self; 6753 }, 6754 addClass: function addClass(classes) { 6755 return this.toggleClass(classes, true); 6756 }, 6757 hasClass: function hasClass(className) { 6758 var ele = this[0]; 6759 return ele != null && ele._private.classes.has(className); 6760 }, 6761 toggleClass: function toggleClass(classes, toggle) { 6762 if (!array(classes)) { 6763 // extract classes from string 6764 classes = classes.match(/\S+/g) || []; 6765 } 6766 6767 var self = this; 6768 var toggleUndefd = toggle === undefined; 6769 var changed = []; // eles who had classes changed 6770 6771 for (var i = 0, il = self.length; i < il; i++) { 6772 var ele = self[i]; 6773 var eleClasses = ele._private.classes; 6774 var changedEle = false; 6775 6776 for (var j = 0; j < classes.length; j++) { 6777 var cls = classes[j]; 6778 var hasClass = eleClasses.has(cls); 6779 var changedNow = false; 6780 6781 if (toggle || toggleUndefd && !hasClass) { 6782 eleClasses.add(cls); 6783 changedNow = true; 6784 } else if (!toggle || toggleUndefd && hasClass) { 6785 eleClasses["delete"](cls); 6786 changedNow = true; 6787 } 6788 6789 if (!changedEle && changedNow) { 6790 changed.push(ele); 6791 changedEle = true; 6792 } 6793 } // for j classes 6794 6795 } // for i eles 6796 // trigger update style on those eles that had class changes 6797 6798 6799 if (changed.length > 0) { 6800 this.spawn(changed).updateStyle().emit('class'); 6801 } 6802 6803 return self; 6804 }, 6805 removeClass: function removeClass(classes) { 6806 return this.toggleClass(classes, false); 6807 }, 6808 flashClass: function flashClass(classes, duration) { 6809 var self = this; 6810 6811 if (duration == null) { 6812 duration = 250; 6813 } else if (duration === 0) { 6814 return self; // nothing to do really 6815 } 6816 6817 self.addClass(classes); 6818 setTimeout(function () { 6819 self.removeClass(classes); 6820 }, duration); 6821 return self; 6822 } 6823 }; 6824 elesfn$e.className = elesfn$e.classNames = elesfn$e.classes; 6825 6826 var tokens = { 6827 metaChar: '[\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]', 6828 // chars we need to escape in let names, etc 6829 comparatorOp: '=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=', 6830 // binary comparison op (used in data selectors) 6831 boolOp: '\\?|\\!|\\^', 6832 // boolean (unary) operators (used in data selectors) 6833 string: '"(?:\\\\"|[^"])*"' + '|' + "'(?:\\\\'|[^'])*'", 6834 // string literals (used in data selectors) -- doublequotes | singlequotes 6835 number: number$1, 6836 // number literal (used in data selectors) --- e.g. 0.1234, 1234, 12e123 6837 meta: 'degree|indegree|outdegree', 6838 // allowed metadata fields (i.e. allowed functions to use from Collection) 6839 separator: '\\s*,\\s*', 6840 // queries are separated by commas, e.g. edge[foo = 'bar'], node.someClass 6841 descendant: '\\s+', 6842 child: '\\s+>\\s+', 6843 subject: '\\$', 6844 group: 'node|edge|\\*', 6845 directedEdge: '\\s+->\\s+', 6846 undirectedEdge: '\\s+<->\\s+' 6847 }; 6848 tokens.variable = '(?:[\\w-]|(?:\\\\' + tokens.metaChar + '))+'; // a variable name 6849 6850 tokens.value = tokens.string + '|' + tokens.number; // a value literal, either a string or number 6851 6852 tokens.className = tokens.variable; // a class name (follows variable conventions) 6853 6854 tokens.id = tokens.variable; // an element id (follows variable conventions) 6855 6856 (function () { 6857 var ops, op, i; // add @ variants to comparatorOp 6858 6859 ops = tokens.comparatorOp.split('|'); 6860 6861 for (i = 0; i < ops.length; i++) { 6862 op = ops[i]; 6863 tokens.comparatorOp += '|@' + op; 6864 } // add ! variants to comparatorOp 6865 6866 6867 ops = tokens.comparatorOp.split('|'); 6868 6869 for (i = 0; i < ops.length; i++) { 6870 op = ops[i]; 6871 6872 if (op.indexOf('!') >= 0) { 6873 continue; 6874 } // skip ops that explicitly contain ! 6875 6876 6877 if (op === '=') { 6878 continue; 6879 } // skip = b/c != is explicitly defined 6880 6881 6882 tokens.comparatorOp += '|\\!' + op; 6883 } 6884 })(); 6885 6886 /** 6887 * Make a new query object 6888 * 6889 * @prop type {Type} The type enum (int) of the query 6890 * @prop checks List of checks to make against an ele to test for a match 6891 */ 6892 var newQuery = function newQuery() { 6893 return { 6894 checks: [] 6895 }; 6896 }; 6897 6898 /** 6899 * A check type enum-like object. Uses integer values for fast match() lookup. 6900 * The ordering does not matter as long as the ints are unique. 6901 */ 6902 var Type = { 6903 /** E.g. node */ 6904 GROUP: 0, 6905 6906 /** A collection of elements */ 6907 COLLECTION: 1, 6908 6909 /** A filter(ele) function */ 6910 FILTER: 2, 6911 6912 /** E.g. [foo > 1] */ 6913 DATA_COMPARE: 3, 6914 6915 /** E.g. [foo] */ 6916 DATA_EXIST: 4, 6917 6918 /** E.g. [?foo] */ 6919 DATA_BOOL: 5, 6920 6921 /** E.g. [[degree > 2]] */ 6922 META_COMPARE: 6, 6923 6924 /** E.g. :selected */ 6925 STATE: 7, 6926 6927 /** E.g. #foo */ 6928 ID: 8, 6929 6930 /** E.g. .foo */ 6931 CLASS: 9, 6932 6933 /** E.g. #foo <-> #bar */ 6934 UNDIRECTED_EDGE: 10, 6935 6936 /** E.g. #foo -> #bar */ 6937 DIRECTED_EDGE: 11, 6938 6939 /** E.g. $#foo -> #bar */ 6940 NODE_SOURCE: 12, 6941 6942 /** E.g. #foo -> $#bar */ 6943 NODE_TARGET: 13, 6944 6945 /** E.g. $#foo <-> #bar */ 6946 NODE_NEIGHBOR: 14, 6947 6948 /** E.g. #foo > #bar */ 6949 CHILD: 15, 6950 6951 /** E.g. #foo #bar */ 6952 DESCENDANT: 16, 6953 6954 /** E.g. $#foo > #bar */ 6955 PARENT: 17, 6956 6957 /** E.g. $#foo #bar */ 6958 ANCESTOR: 18, 6959 6960 /** E.g. #foo > $bar > #baz */ 6961 COMPOUND_SPLIT: 19, 6962 6963 /** Always matches, useful placeholder for subject in `COMPOUND_SPLIT` */ 6964 TRUE: 20 6965 }; 6966 6967 var stateSelectors = [{ 6968 selector: ':selected', 6969 matches: function matches(ele) { 6970 return ele.selected(); 6971 } 6972 }, { 6973 selector: ':unselected', 6974 matches: function matches(ele) { 6975 return !ele.selected(); 6976 } 6977 }, { 6978 selector: ':selectable', 6979 matches: function matches(ele) { 6980 return ele.selectable(); 6981 } 6982 }, { 6983 selector: ':unselectable', 6984 matches: function matches(ele) { 6985 return !ele.selectable(); 6986 } 6987 }, { 6988 selector: ':locked', 6989 matches: function matches(ele) { 6990 return ele.locked(); 6991 } 6992 }, { 6993 selector: ':unlocked', 6994 matches: function matches(ele) { 6995 return !ele.locked(); 6996 } 6997 }, { 6998 selector: ':visible', 6999 matches: function matches(ele) { 7000 return ele.visible(); 7001 } 7002 }, { 7003 selector: ':hidden', 7004 matches: function matches(ele) { 7005 return !ele.visible(); 7006 } 7007 }, { 7008 selector: ':transparent', 7009 matches: function matches(ele) { 7010 return ele.transparent(); 7011 } 7012 }, { 7013 selector: ':grabbed', 7014 matches: function matches(ele) { 7015 return ele.grabbed(); 7016 } 7017 }, { 7018 selector: ':free', 7019 matches: function matches(ele) { 7020 return !ele.grabbed(); 7021 } 7022 }, { 7023 selector: ':removed', 7024 matches: function matches(ele) { 7025 return ele.removed(); 7026 } 7027 }, { 7028 selector: ':inside', 7029 matches: function matches(ele) { 7030 return !ele.removed(); 7031 } 7032 }, { 7033 selector: ':grabbable', 7034 matches: function matches(ele) { 7035 return ele.grabbable(); 7036 } 7037 }, { 7038 selector: ':ungrabbable', 7039 matches: function matches(ele) { 7040 return !ele.grabbable(); 7041 } 7042 }, { 7043 selector: ':animated', 7044 matches: function matches(ele) { 7045 return ele.animated(); 7046 } 7047 }, { 7048 selector: ':unanimated', 7049 matches: function matches(ele) { 7050 return !ele.animated(); 7051 } 7052 }, { 7053 selector: ':parent', 7054 matches: function matches(ele) { 7055 return ele.isParent(); 7056 } 7057 }, { 7058 selector: ':childless', 7059 matches: function matches(ele) { 7060 return ele.isChildless(); 7061 } 7062 }, { 7063 selector: ':child', 7064 matches: function matches(ele) { 7065 return ele.isChild(); 7066 } 7067 }, { 7068 selector: ':orphan', 7069 matches: function matches(ele) { 7070 return ele.isOrphan(); 7071 } 7072 }, { 7073 selector: ':nonorphan', 7074 matches: function matches(ele) { 7075 return ele.isChild(); 7076 } 7077 }, { 7078 selector: ':compound', 7079 matches: function matches(ele) { 7080 if (ele.isNode()) { 7081 return ele.isParent(); 7082 } else { 7083 return ele.source().isParent() || ele.target().isParent(); 7084 } 7085 } 7086 }, { 7087 selector: ':loop', 7088 matches: function matches(ele) { 7089 return ele.isLoop(); 7090 } 7091 }, { 7092 selector: ':simple', 7093 matches: function matches(ele) { 7094 return ele.isSimple(); 7095 } 7096 }, { 7097 selector: ':active', 7098 matches: function matches(ele) { 7099 return ele.active(); 7100 } 7101 }, { 7102 selector: ':inactive', 7103 matches: function matches(ele) { 7104 return !ele.active(); 7105 } 7106 }, { 7107 selector: ':backgrounding', 7108 matches: function matches(ele) { 7109 return ele.backgrounding(); 7110 } 7111 }, { 7112 selector: ':nonbackgrounding', 7113 matches: function matches(ele) { 7114 return !ele.backgrounding(); 7115 } 7116 }].sort(function (a, b) { 7117 // n.b. selectors that are starting substrings of others must have the longer ones first 7118 return descending(a.selector, b.selector); 7119 }); 7120 7121 var lookup = function () { 7122 var selToFn = {}; 7123 var s; 7124 7125 for (var i = 0; i < stateSelectors.length; i++) { 7126 s = stateSelectors[i]; 7127 selToFn[s.selector] = s.matches; 7128 } 7129 7130 return selToFn; 7131 }(); 7132 7133 var stateSelectorMatches = function stateSelectorMatches(sel, ele) { 7134 return lookup[sel](ele); 7135 }; 7136 var stateSelectorRegex = '(' + stateSelectors.map(function (s) { 7137 return s.selector; 7138 }).join('|') + ')'; 7139 7140 // so that values get compared properly in Selector.filter() 7141 7142 var cleanMetaChars = function cleanMetaChars(str) { 7143 return str.replace(new RegExp('\\\\(' + tokens.metaChar + ')', 'g'), function (match, $1) { 7144 return $1; 7145 }); 7146 }; 7147 7148 var replaceLastQuery = function replaceLastQuery(selector, examiningQuery, replacementQuery) { 7149 selector[selector.length - 1] = replacementQuery; 7150 }; // NOTE: add new expression syntax here to have it recognised by the parser; 7151 // - a query contains all adjacent (i.e. no separator in between) expressions; 7152 // - the current query is stored in selector[i] 7153 // - you need to check the query objects in match() for it actually filter properly, but that's pretty straight forward 7154 7155 7156 var exprs = [{ 7157 name: 'group', 7158 // just used for identifying when debugging 7159 query: true, 7160 regex: '(' + tokens.group + ')', 7161 populate: function populate(selector, query, _ref) { 7162 var _ref2 = _slicedToArray(_ref, 1), 7163 group = _ref2[0]; 7164 7165 query.checks.push({ 7166 type: Type.GROUP, 7167 value: group === '*' ? group : group + 's' 7168 }); 7169 } 7170 }, { 7171 name: 'state', 7172 query: true, 7173 regex: stateSelectorRegex, 7174 populate: function populate(selector, query, _ref3) { 7175 var _ref4 = _slicedToArray(_ref3, 1), 7176 state = _ref4[0]; 7177 7178 query.checks.push({ 7179 type: Type.STATE, 7180 value: state 7181 }); 7182 } 7183 }, { 7184 name: 'id', 7185 query: true, 7186 regex: '\\#(' + tokens.id + ')', 7187 populate: function populate(selector, query, _ref5) { 7188 var _ref6 = _slicedToArray(_ref5, 1), 7189 id = _ref6[0]; 7190 7191 query.checks.push({ 7192 type: Type.ID, 7193 value: cleanMetaChars(id) 7194 }); 7195 } 7196 }, { 7197 name: 'className', 7198 query: true, 7199 regex: '\\.(' + tokens.className + ')', 7200 populate: function populate(selector, query, _ref7) { 7201 var _ref8 = _slicedToArray(_ref7, 1), 7202 className = _ref8[0]; 7203 7204 query.checks.push({ 7205 type: Type.CLASS, 7206 value: cleanMetaChars(className) 7207 }); 7208 } 7209 }, { 7210 name: 'dataExists', 7211 query: true, 7212 regex: '\\[\\s*(' + tokens.variable + ')\\s*\\]', 7213 populate: function populate(selector, query, _ref9) { 7214 var _ref10 = _slicedToArray(_ref9, 1), 7215 variable = _ref10[0]; 7216 7217 query.checks.push({ 7218 type: Type.DATA_EXIST, 7219 field: cleanMetaChars(variable) 7220 }); 7221 } 7222 }, { 7223 name: 'dataCompare', 7224 query: true, 7225 regex: '\\[\\s*(' + tokens.variable + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.value + ')\\s*\\]', 7226 populate: function populate(selector, query, _ref11) { 7227 var _ref12 = _slicedToArray(_ref11, 3), 7228 variable = _ref12[0], 7229 comparatorOp = _ref12[1], 7230 value = _ref12[2]; 7231 7232 var valueIsString = new RegExp('^' + tokens.string + '$').exec(value) != null; 7233 7234 if (valueIsString) { 7235 value = value.substring(1, value.length - 1); 7236 } else { 7237 value = parseFloat(value); 7238 } 7239 7240 query.checks.push({ 7241 type: Type.DATA_COMPARE, 7242 field: cleanMetaChars(variable), 7243 operator: comparatorOp, 7244 value: value 7245 }); 7246 } 7247 }, { 7248 name: 'dataBool', 7249 query: true, 7250 regex: '\\[\\s*(' + tokens.boolOp + ')\\s*(' + tokens.variable + ')\\s*\\]', 7251 populate: function populate(selector, query, _ref13) { 7252 var _ref14 = _slicedToArray(_ref13, 2), 7253 boolOp = _ref14[0], 7254 variable = _ref14[1]; 7255 7256 query.checks.push({ 7257 type: Type.DATA_BOOL, 7258 field: cleanMetaChars(variable), 7259 operator: boolOp 7260 }); 7261 } 7262 }, { 7263 name: 'metaCompare', 7264 query: true, 7265 regex: '\\[\\[\\s*(' + tokens.meta + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.number + ')\\s*\\]\\]', 7266 populate: function populate(selector, query, _ref15) { 7267 var _ref16 = _slicedToArray(_ref15, 3), 7268 meta = _ref16[0], 7269 comparatorOp = _ref16[1], 7270 number = _ref16[2]; 7271 7272 query.checks.push({ 7273 type: Type.META_COMPARE, 7274 field: cleanMetaChars(meta), 7275 operator: comparatorOp, 7276 value: parseFloat(number) 7277 }); 7278 } 7279 }, { 7280 name: 'nextQuery', 7281 separator: true, 7282 regex: tokens.separator, 7283 populate: function populate(selector, query) { 7284 var currentSubject = selector.currentSubject; 7285 var edgeCount = selector.edgeCount; 7286 var compoundCount = selector.compoundCount; 7287 var lastQ = selector[selector.length - 1]; 7288 7289 if (currentSubject != null) { 7290 lastQ.subject = currentSubject; 7291 selector.currentSubject = null; 7292 } 7293 7294 lastQ.edgeCount = edgeCount; 7295 lastQ.compoundCount = compoundCount; 7296 selector.edgeCount = 0; 7297 selector.compoundCount = 0; // go on to next query 7298 7299 var nextQuery = selector[selector.length++] = newQuery(); 7300 return nextQuery; // this is the new query to be filled by the following exprs 7301 } 7302 }, { 7303 name: 'directedEdge', 7304 separator: true, 7305 regex: tokens.directedEdge, 7306 populate: function populate(selector, query) { 7307 if (selector.currentSubject == null) { 7308 // undirected edge 7309 var edgeQuery = newQuery(); 7310 var source = query; 7311 var target = newQuery(); 7312 edgeQuery.checks.push({ 7313 type: Type.DIRECTED_EDGE, 7314 source: source, 7315 target: target 7316 }); // the query in the selector should be the edge rather than the source 7317 7318 replaceLastQuery(selector, query, edgeQuery); 7319 selector.edgeCount++; // we're now populating the target query with expressions that follow 7320 7321 return target; 7322 } else { 7323 // source/target 7324 var srcTgtQ = newQuery(); 7325 var _source = query; 7326 7327 var _target = newQuery(); 7328 7329 srcTgtQ.checks.push({ 7330 type: Type.NODE_SOURCE, 7331 source: _source, 7332 target: _target 7333 }); // the query in the selector should be the neighbourhood rather than the node 7334 7335 replaceLastQuery(selector, query, srcTgtQ); 7336 selector.edgeCount++; 7337 return _target; // now populating the target with the following expressions 7338 } 7339 } 7340 }, { 7341 name: 'undirectedEdge', 7342 separator: true, 7343 regex: tokens.undirectedEdge, 7344 populate: function populate(selector, query) { 7345 if (selector.currentSubject == null) { 7346 // undirected edge 7347 var edgeQuery = newQuery(); 7348 var source = query; 7349 var target = newQuery(); 7350 edgeQuery.checks.push({ 7351 type: Type.UNDIRECTED_EDGE, 7352 nodes: [source, target] 7353 }); // the query in the selector should be the edge rather than the source 7354 7355 replaceLastQuery(selector, query, edgeQuery); 7356 selector.edgeCount++; // we're now populating the target query with expressions that follow 7357 7358 return target; 7359 } else { 7360 // neighbourhood 7361 var nhoodQ = newQuery(); 7362 var node = query; 7363 var neighbor = newQuery(); 7364 nhoodQ.checks.push({ 7365 type: Type.NODE_NEIGHBOR, 7366 node: node, 7367 neighbor: neighbor 7368 }); // the query in the selector should be the neighbourhood rather than the node 7369 7370 replaceLastQuery(selector, query, nhoodQ); 7371 return neighbor; // now populating the neighbor with following expressions 7372 } 7373 } 7374 }, { 7375 name: 'child', 7376 separator: true, 7377 regex: tokens.child, 7378 populate: function populate(selector, query) { 7379 if (selector.currentSubject == null) { 7380 // default: child query 7381 var parentChildQuery = newQuery(); 7382 var child = newQuery(); 7383 var parent = selector[selector.length - 1]; 7384 parentChildQuery.checks.push({ 7385 type: Type.CHILD, 7386 parent: parent, 7387 child: child 7388 }); // the query in the selector should be the '>' itself 7389 7390 replaceLastQuery(selector, query, parentChildQuery); 7391 selector.compoundCount++; // we're now populating the child query with expressions that follow 7392 7393 return child; 7394 } else if (selector.currentSubject === query) { 7395 // compound split query 7396 var compound = newQuery(); 7397 var left = selector[selector.length - 1]; 7398 var right = newQuery(); 7399 var subject = newQuery(); 7400 7401 var _child = newQuery(); 7402 7403 var _parent = newQuery(); // set up the root compound q 7404 7405 7406 compound.checks.push({ 7407 type: Type.COMPOUND_SPLIT, 7408 left: left, 7409 right: right, 7410 subject: subject 7411 }); // populate the subject and replace the q at the old spot (within left) with TRUE 7412 7413 subject.checks = query.checks; // take the checks from the left 7414 7415 query.checks = [{ 7416 type: Type.TRUE 7417 }]; // checks under left refs the subject implicitly 7418 // set up the right q 7419 7420 _parent.checks.push({ 7421 type: Type.TRUE 7422 }); // parent implicitly refs the subject 7423 7424 7425 right.checks.push({ 7426 type: Type.PARENT, 7427 // type is swapped on right side queries 7428 parent: _parent, 7429 child: _child // empty for now 7430 7431 }); 7432 replaceLastQuery(selector, left, compound); // update the ref since we moved things around for `query` 7433 7434 selector.currentSubject = subject; 7435 selector.compoundCount++; 7436 return _child; // now populating the right side's child 7437 } else { 7438 // parent query 7439 // info for parent query 7440 var _parent2 = newQuery(); 7441 7442 var _child2 = newQuery(); 7443 7444 var pcQChecks = [{ 7445 type: Type.PARENT, 7446 parent: _parent2, 7447 child: _child2 7448 }]; // the parent-child query takes the place of the query previously being populated 7449 7450 _parent2.checks = query.checks; // the previous query contains the checks for the parent 7451 7452 query.checks = pcQChecks; // pc query takes over 7453 7454 selector.compoundCount++; 7455 return _child2; // we're now populating the child 7456 } 7457 } 7458 }, { 7459 name: 'descendant', 7460 separator: true, 7461 regex: tokens.descendant, 7462 populate: function populate(selector, query) { 7463 if (selector.currentSubject == null) { 7464 // default: descendant query 7465 var ancChQuery = newQuery(); 7466 var descendant = newQuery(); 7467 var ancestor = selector[selector.length - 1]; 7468 ancChQuery.checks.push({ 7469 type: Type.DESCENDANT, 7470 ancestor: ancestor, 7471 descendant: descendant 7472 }); // the query in the selector should be the '>' itself 7473 7474 replaceLastQuery(selector, query, ancChQuery); 7475 selector.compoundCount++; // we're now populating the descendant query with expressions that follow 7476 7477 return descendant; 7478 } else if (selector.currentSubject === query) { 7479 // compound split query 7480 var compound = newQuery(); 7481 var left = selector[selector.length - 1]; 7482 var right = newQuery(); 7483 var subject = newQuery(); 7484 7485 var _descendant = newQuery(); 7486 7487 var _ancestor = newQuery(); // set up the root compound q 7488 7489 7490 compound.checks.push({ 7491 type: Type.COMPOUND_SPLIT, 7492 left: left, 7493 right: right, 7494 subject: subject 7495 }); // populate the subject and replace the q at the old spot (within left) with TRUE 7496 7497 subject.checks = query.checks; // take the checks from the left 7498 7499 query.checks = [{ 7500 type: Type.TRUE 7501 }]; // checks under left refs the subject implicitly 7502 // set up the right q 7503 7504 _ancestor.checks.push({ 7505 type: Type.TRUE 7506 }); // ancestor implicitly refs the subject 7507 7508 7509 right.checks.push({ 7510 type: Type.ANCESTOR, 7511 // type is swapped on right side queries 7512 ancestor: _ancestor, 7513 descendant: _descendant // empty for now 7514 7515 }); 7516 replaceLastQuery(selector, left, compound); // update the ref since we moved things around for `query` 7517 7518 selector.currentSubject = subject; 7519 selector.compoundCount++; 7520 return _descendant; // now populating the right side's descendant 7521 } else { 7522 // ancestor query 7523 // info for parent query 7524 var _ancestor2 = newQuery(); 7525 7526 var _descendant2 = newQuery(); 7527 7528 var adQChecks = [{ 7529 type: Type.ANCESTOR, 7530 ancestor: _ancestor2, 7531 descendant: _descendant2 7532 }]; // the parent-child query takes the place of the query previously being populated 7533 7534 _ancestor2.checks = query.checks; // the previous query contains the checks for the parent 7535 7536 query.checks = adQChecks; // pc query takes over 7537 7538 selector.compoundCount++; 7539 return _descendant2; // we're now populating the child 7540 } 7541 } 7542 }, { 7543 name: 'subject', 7544 modifier: true, 7545 regex: tokens.subject, 7546 populate: function populate(selector, query) { 7547 if (selector.currentSubject != null && selector.currentSubject !== query) { 7548 warn('Redefinition of subject in selector `' + selector.toString() + '`'); 7549 return false; 7550 } 7551 7552 selector.currentSubject = query; 7553 var topQ = selector[selector.length - 1]; 7554 var topChk = topQ.checks[0]; 7555 var topType = topChk == null ? null : topChk.type; 7556 7557 if (topType === Type.DIRECTED_EDGE) { 7558 // directed edge with subject on the target 7559 // change to target node check 7560 topChk.type = Type.NODE_TARGET; 7561 } else if (topType === Type.UNDIRECTED_EDGE) { 7562 // undirected edge with subject on the second node 7563 // change to neighbor check 7564 topChk.type = Type.NODE_NEIGHBOR; 7565 topChk.node = topChk.nodes[1]; // second node is subject 7566 7567 topChk.neighbor = topChk.nodes[0]; // clean up unused fields for new type 7568 7569 topChk.nodes = null; 7570 } 7571 } 7572 }]; 7573 exprs.forEach(function (e) { 7574 return e.regexObj = new RegExp('^' + e.regex); 7575 }); 7576 7577 /** 7578 * Of all the expressions, find the first match in the remaining text. 7579 * @param {string} remaining The remaining text to parse 7580 * @returns The matched expression and the newly remaining text `{ expr, match, name, remaining }` 7581 */ 7582 7583 var consumeExpr = function consumeExpr(remaining) { 7584 var expr; 7585 var match; 7586 var name; 7587 7588 for (var j = 0; j < exprs.length; j++) { 7589 var e = exprs[j]; 7590 var n = e.name; 7591 var m = remaining.match(e.regexObj); 7592 7593 if (m != null) { 7594 match = m; 7595 expr = e; 7596 name = n; 7597 var consumed = m[0]; 7598 remaining = remaining.substring(consumed.length); 7599 break; // we've consumed one expr, so we can return now 7600 } 7601 } 7602 7603 return { 7604 expr: expr, 7605 match: match, 7606 name: name, 7607 remaining: remaining 7608 }; 7609 }; 7610 /** 7611 * Consume all the leading whitespace 7612 * @param {string} remaining The text to consume 7613 * @returns The text with the leading whitespace removed 7614 */ 7615 7616 7617 var consumeWhitespace = function consumeWhitespace(remaining) { 7618 var match = remaining.match(/^\s+/); 7619 7620 if (match) { 7621 var consumed = match[0]; 7622 remaining = remaining.substring(consumed.length); 7623 } 7624 7625 return remaining; 7626 }; 7627 /** 7628 * Parse the string and store the parsed representation in the Selector. 7629 * @param {string} selector The selector string 7630 * @returns `true` if the selector was successfully parsed, `false` otherwise 7631 */ 7632 7633 7634 var parse = function parse(selector) { 7635 var self = this; 7636 var remaining = self.inputText = selector; 7637 var currentQuery = self[0] = newQuery(); 7638 self.length = 1; 7639 remaining = consumeWhitespace(remaining); // get rid of leading whitespace 7640 7641 for (;;) { 7642 var exprInfo = consumeExpr(remaining); 7643 7644 if (exprInfo.expr == null) { 7645 warn('The selector `' + selector + '`is invalid'); 7646 return false; 7647 } else { 7648 var args = exprInfo.match.slice(1); // let the token populate the selector object in currentQuery 7649 7650 var ret = exprInfo.expr.populate(self, currentQuery, args); 7651 7652 if (ret === false) { 7653 return false; // exit if population failed 7654 } else if (ret != null) { 7655 currentQuery = ret; // change the current query to be filled if the expr specifies 7656 } 7657 } 7658 7659 remaining = exprInfo.remaining; // we're done when there's nothing left to parse 7660 7661 if (remaining.match(/^\s*$/)) { 7662 break; 7663 } 7664 } 7665 7666 var lastQ = self[self.length - 1]; 7667 7668 if (self.currentSubject != null) { 7669 lastQ.subject = self.currentSubject; 7670 } 7671 7672 lastQ.edgeCount = self.edgeCount; 7673 lastQ.compoundCount = self.compoundCount; 7674 7675 for (var i = 0; i < self.length; i++) { 7676 var q = self[i]; // in future, this could potentially be allowed if there were operator precedence and detection of invalid combinations 7677 7678 if (q.compoundCount > 0 && q.edgeCount > 0) { 7679 warn('The selector `' + selector + '` is invalid because it uses both a compound selector and an edge selector'); 7680 return false; 7681 } 7682 7683 if (q.edgeCount > 1) { 7684 warn('The selector `' + selector + '` is invalid because it uses multiple edge selectors'); 7685 return false; 7686 } else if (q.edgeCount === 1) { 7687 warn('The selector `' + selector + '` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.'); 7688 } 7689 } 7690 7691 return true; // success 7692 }; 7693 /** 7694 * Get the selector represented as a string. This value uses default formatting, 7695 * so things like spacing may differ from the input text passed to the constructor. 7696 * @returns {string} The selector string 7697 */ 7698 7699 7700 var toString = function toString() { 7701 if (this.toStringCache != null) { 7702 return this.toStringCache; 7703 } 7704 7705 var clean = function clean(obj) { 7706 if (obj == null) { 7707 return ''; 7708 } else { 7709 return obj; 7710 } 7711 }; 7712 7713 var cleanVal = function cleanVal(val) { 7714 if (string(val)) { 7715 return '"' + val + '"'; 7716 } else { 7717 return clean(val); 7718 } 7719 }; 7720 7721 var space = function space(val) { 7722 return ' ' + val + ' '; 7723 }; 7724 7725 var checkToString = function checkToString(check, subject) { 7726 var type = check.type, 7727 value = check.value; 7728 7729 switch (type) { 7730 case Type.GROUP: 7731 { 7732 var group = clean(value); 7733 return group.substring(0, group.length - 1); 7734 } 7735 7736 case Type.DATA_COMPARE: 7737 { 7738 var field = check.field, 7739 operator = check.operator; 7740 return '[' + field + space(clean(operator)) + cleanVal(value) + ']'; 7741 } 7742 7743 case Type.DATA_BOOL: 7744 { 7745 var _operator = check.operator, 7746 _field = check.field; 7747 return '[' + clean(_operator) + _field + ']'; 7748 } 7749 7750 case Type.DATA_EXIST: 7751 { 7752 var _field2 = check.field; 7753 return '[' + _field2 + ']'; 7754 } 7755 7756 case Type.META_COMPARE: 7757 { 7758 var _operator2 = check.operator, 7759 _field3 = check.field; 7760 return '[[' + _field3 + space(clean(_operator2)) + cleanVal(value) + ']]'; 7761 } 7762 7763 case Type.STATE: 7764 { 7765 return value; 7766 } 7767 7768 case Type.ID: 7769 { 7770 return '#' + value; 7771 } 7772 7773 case Type.CLASS: 7774 { 7775 return '.' + value; 7776 } 7777 7778 case Type.PARENT: 7779 case Type.CHILD: 7780 { 7781 return queryToString(check.parent, subject) + space('>') + queryToString(check.child, subject); 7782 } 7783 7784 case Type.ANCESTOR: 7785 case Type.DESCENDANT: 7786 { 7787 return queryToString(check.ancestor, subject) + ' ' + queryToString(check.descendant, subject); 7788 } 7789 7790 case Type.COMPOUND_SPLIT: 7791 { 7792 var lhs = queryToString(check.left, subject); 7793 var sub = queryToString(check.subject, subject); 7794 var rhs = queryToString(check.right, subject); 7795 return lhs + (lhs.length > 0 ? ' ' : '') + sub + rhs; 7796 } 7797 7798 case Type.TRUE: 7799 { 7800 return ''; 7801 } 7802 } 7803 }; 7804 7805 var queryToString = function queryToString(query, subject) { 7806 return query.checks.reduce(function (str, chk, i) { 7807 return str + (subject === query && i === 0 ? '$' : '') + checkToString(chk, subject); 7808 }, ''); 7809 }; 7810 7811 var str = ''; 7812 7813 for (var i = 0; i < this.length; i++) { 7814 var query = this[i]; 7815 str += queryToString(query, query.subject); 7816 7817 if (this.length > 1 && i < this.length - 1) { 7818 str += ', '; 7819 } 7820 } 7821 7822 this.toStringCache = str; 7823 return str; 7824 }; 7825 var parse$1 = { 7826 parse: parse, 7827 toString: toString 7828 }; 7829 7830 var valCmp = function valCmp(fieldVal, operator, value) { 7831 var matches; 7832 var isFieldStr = string(fieldVal); 7833 var isFieldNum = number(fieldVal); 7834 var isValStr = string(value); 7835 var fieldStr, valStr; 7836 var caseInsensitive = false; 7837 var notExpr = false; 7838 var isIneqCmp = false; 7839 7840 if (operator.indexOf('!') >= 0) { 7841 operator = operator.replace('!', ''); 7842 notExpr = true; 7843 } 7844 7845 if (operator.indexOf('@') >= 0) { 7846 operator = operator.replace('@', ''); 7847 caseInsensitive = true; 7848 } 7849 7850 if (isFieldStr || isValStr || caseInsensitive) { 7851 fieldStr = !isFieldStr && !isFieldNum ? '' : '' + fieldVal; 7852 valStr = '' + value; 7853 } // if we're doing a case insensitive comparison, then we're using a STRING comparison 7854 // even if we're comparing numbers 7855 7856 7857 if (caseInsensitive) { 7858 fieldVal = fieldStr = fieldStr.toLowerCase(); 7859 value = valStr = valStr.toLowerCase(); 7860 } 7861 7862 switch (operator) { 7863 case '*=': 7864 matches = fieldStr.indexOf(valStr) >= 0; 7865 break; 7866 7867 case '$=': 7868 matches = fieldStr.indexOf(valStr, fieldStr.length - valStr.length) >= 0; 7869 break; 7870 7871 case '^=': 7872 matches = fieldStr.indexOf(valStr) === 0; 7873 break; 7874 7875 case '=': 7876 matches = fieldVal === value; 7877 break; 7878 7879 case '>': 7880 isIneqCmp = true; 7881 matches = fieldVal > value; 7882 break; 7883 7884 case '>=': 7885 isIneqCmp = true; 7886 matches = fieldVal >= value; 7887 break; 7888 7889 case '<': 7890 isIneqCmp = true; 7891 matches = fieldVal < value; 7892 break; 7893 7894 case '<=': 7895 isIneqCmp = true; 7896 matches = fieldVal <= value; 7897 break; 7898 7899 default: 7900 matches = false; 7901 break; 7902 } // apply the not op, but null vals for inequalities should always stay non-matching 7903 7904 7905 if (notExpr && (fieldVal != null || !isIneqCmp)) { 7906 matches = !matches; 7907 } 7908 7909 return matches; 7910 }; 7911 var boolCmp = function boolCmp(fieldVal, operator) { 7912 switch (operator) { 7913 case '?': 7914 return fieldVal ? true : false; 7915 7916 case '!': 7917 return fieldVal ? false : true; 7918 7919 case '^': 7920 return fieldVal === undefined; 7921 } 7922 }; 7923 var existCmp = function existCmp(fieldVal) { 7924 return fieldVal !== undefined; 7925 }; 7926 var data = function data(ele, field) { 7927 return ele.data(field); 7928 }; 7929 var meta = function meta(ele, field) { 7930 return ele[field](); 7931 }; 7932 7933 /** A lookup of `match(check, ele)` functions by `Type` int */ 7934 7935 var match = []; 7936 /** 7937 * Returns whether the query matches for the element 7938 * @param query The `{ type, value, ... }` query object 7939 * @param ele The element to compare against 7940 */ 7941 7942 var matches = function matches(query, ele) { 7943 return query.checks.every(function (chk) { 7944 return match[chk.type](chk, ele); 7945 }); 7946 }; 7947 7948 match[Type.GROUP] = function (check, ele) { 7949 var group = check.value; 7950 return group === '*' || group === ele.group(); 7951 }; 7952 7953 match[Type.STATE] = function (check, ele) { 7954 var stateSelector = check.value; 7955 return stateSelectorMatches(stateSelector, ele); 7956 }; 7957 7958 match[Type.ID] = function (check, ele) { 7959 var id = check.value; 7960 return ele.id() === id; 7961 }; 7962 7963 match[Type.CLASS] = function (check, ele) { 7964 var cls = check.value; 7965 return ele.hasClass(cls); 7966 }; 7967 7968 match[Type.META_COMPARE] = function (check, ele) { 7969 var field = check.field, 7970 operator = check.operator, 7971 value = check.value; 7972 return valCmp(meta(ele, field), operator, value); 7973 }; 7974 7975 match[Type.DATA_COMPARE] = function (check, ele) { 7976 var field = check.field, 7977 operator = check.operator, 7978 value = check.value; 7979 return valCmp(data(ele, field), operator, value); 7980 }; 7981 7982 match[Type.DATA_BOOL] = function (check, ele) { 7983 var field = check.field, 7984 operator = check.operator; 7985 return boolCmp(data(ele, field), operator); 7986 }; 7987 7988 match[Type.DATA_EXIST] = function (check, ele) { 7989 var field = check.field, 7990 operator = check.operator; 7991 return existCmp(data(ele, field)); 7992 }; 7993 7994 match[Type.UNDIRECTED_EDGE] = function (check, ele) { 7995 var qA = check.nodes[0]; 7996 var qB = check.nodes[1]; 7997 var src = ele.source(); 7998 var tgt = ele.target(); 7999 return matches(qA, src) && matches(qB, tgt) || matches(qB, src) && matches(qA, tgt); 8000 }; 8001 8002 match[Type.NODE_NEIGHBOR] = function (check, ele) { 8003 return matches(check.node, ele) && ele.neighborhood().some(function (n) { 8004 return n.isNode() && matches(check.neighbor, n); 8005 }); 8006 }; 8007 8008 match[Type.DIRECTED_EDGE] = function (check, ele) { 8009 return matches(check.source, ele.source()) && matches(check.target, ele.target()); 8010 }; 8011 8012 match[Type.NODE_SOURCE] = function (check, ele) { 8013 return matches(check.source, ele) && ele.outgoers().some(function (n) { 8014 return n.isNode() && matches(check.target, n); 8015 }); 8016 }; 8017 8018 match[Type.NODE_TARGET] = function (check, ele) { 8019 return matches(check.target, ele) && ele.incomers().some(function (n) { 8020 return n.isNode() && matches(check.source, n); 8021 }); 8022 }; 8023 8024 match[Type.CHILD] = function (check, ele) { 8025 return matches(check.child, ele) && matches(check.parent, ele.parent()); 8026 }; 8027 8028 match[Type.PARENT] = function (check, ele) { 8029 return matches(check.parent, ele) && ele.children().some(function (c) { 8030 return matches(check.child, c); 8031 }); 8032 }; 8033 8034 match[Type.DESCENDANT] = function (check, ele) { 8035 return matches(check.descendant, ele) && ele.ancestors().some(function (a) { 8036 return matches(check.ancestor, a); 8037 }); 8038 }; 8039 8040 match[Type.ANCESTOR] = function (check, ele) { 8041 return matches(check.ancestor, ele) && ele.descendants().some(function (d) { 8042 return matches(check.descendant, d); 8043 }); 8044 }; 8045 8046 match[Type.COMPOUND_SPLIT] = function (check, ele) { 8047 return matches(check.subject, ele) && matches(check.left, ele) && matches(check.right, ele); 8048 }; 8049 8050 match[Type.TRUE] = function () { 8051 return true; 8052 }; 8053 8054 match[Type.COLLECTION] = function (check, ele) { 8055 var collection = check.value; 8056 return collection.has(ele); 8057 }; 8058 8059 match[Type.FILTER] = function (check, ele) { 8060 var filter = check.value; 8061 return filter(ele); 8062 }; 8063 8064 var filter = function filter(collection) { 8065 var self = this; // for 1 id #foo queries, just get the element 8066 8067 if (self.length === 1 && self[0].checks.length === 1 && self[0].checks[0].type === Type.ID) { 8068 return collection.getElementById(self[0].checks[0].value).collection(); 8069 } 8070 8071 var selectorFunction = function selectorFunction(element) { 8072 for (var j = 0; j < self.length; j++) { 8073 var query = self[j]; 8074 8075 if (matches(query, element)) { 8076 return true; 8077 } 8078 } 8079 8080 return false; 8081 }; 8082 8083 if (self.text() == null) { 8084 selectorFunction = function selectorFunction() { 8085 return true; 8086 }; 8087 } 8088 8089 return collection.filter(selectorFunction); 8090 }; // filter 8091 // does selector match a single element? 8092 8093 8094 var matches$1 = function matches$1(ele) { 8095 var self = this; 8096 8097 for (var j = 0; j < self.length; j++) { 8098 var query = self[j]; 8099 8100 if (matches(query, ele)) { 8101 return true; 8102 } 8103 } 8104 8105 return false; 8106 }; // matches 8107 8108 8109 var matching = { 8110 matches: matches$1, 8111 filter: filter 8112 }; 8113 8114 var Selector = function Selector(selector) { 8115 this.inputText = selector; 8116 this.currentSubject = null; 8117 this.compoundCount = 0; 8118 this.edgeCount = 0; 8119 this.length = 0; 8120 8121 if (selector == null || string(selector) && selector.match(/^\s*$/)) ; else if (elementOrCollection(selector)) { 8122 this.addQuery({ 8123 checks: [{ 8124 type: Type.COLLECTION, 8125 value: selector.collection() 8126 }] 8127 }); 8128 } else if (fn(selector)) { 8129 this.addQuery({ 8130 checks: [{ 8131 type: Type.FILTER, 8132 value: selector 8133 }] 8134 }); 8135 } else if (string(selector)) { 8136 if (!this.parse(selector)) { 8137 this.invalid = true; 8138 } 8139 } else { 8140 error('A selector must be created from a string; found '); 8141 } 8142 }; 8143 8144 var selfn = Selector.prototype; 8145 [parse$1, matching].forEach(function (p) { 8146 return extend(selfn, p); 8147 }); 8148 8149 selfn.text = function () { 8150 return this.inputText; 8151 }; 8152 8153 selfn.size = function () { 8154 return this.length; 8155 }; 8156 8157 selfn.eq = function (i) { 8158 return this[i]; 8159 }; 8160 8161 selfn.sameText = function (otherSel) { 8162 return !this.invalid && !otherSel.invalid && this.text() === otherSel.text(); 8163 }; 8164 8165 selfn.addQuery = function (q) { 8166 this[this.length++] = q; 8167 }; 8168 8169 selfn.selector = selfn.toString; 8170 8171 var elesfn$f = { 8172 allAre: function allAre(selector) { 8173 var selObj = new Selector(selector); 8174 return this.every(function (ele) { 8175 return selObj.matches(ele); 8176 }); 8177 }, 8178 is: function is(selector) { 8179 var selObj = new Selector(selector); 8180 return this.some(function (ele) { 8181 return selObj.matches(ele); 8182 }); 8183 }, 8184 some: function some(fn, thisArg) { 8185 for (var i = 0; i < this.length; i++) { 8186 var ret = !thisArg ? fn(this[i], i, this) : fn.apply(thisArg, [this[i], i, this]); 8187 8188 if (ret) { 8189 return true; 8190 } 8191 } 8192 8193 return false; 8194 }, 8195 every: function every(fn, thisArg) { 8196 for (var i = 0; i < this.length; i++) { 8197 var ret = !thisArg ? fn(this[i], i, this) : fn.apply(thisArg, [this[i], i, this]); 8198 8199 if (!ret) { 8200 return false; 8201 } 8202 } 8203 8204 return true; 8205 }, 8206 same: function same(collection) { 8207 // cheap collection ref check 8208 if (this === collection) { 8209 return true; 8210 } 8211 8212 collection = this.cy().collection(collection); 8213 var thisLength = this.length; 8214 var collectionLength = collection.length; // cheap length check 8215 8216 if (thisLength !== collectionLength) { 8217 return false; 8218 } // cheap element ref check 8219 8220 8221 if (thisLength === 1) { 8222 return this[0] === collection[0]; 8223 } 8224 8225 return this.every(function (ele) { 8226 return collection.hasElementWithId(ele.id()); 8227 }); 8228 }, 8229 anySame: function anySame(collection) { 8230 collection = this.cy().collection(collection); 8231 return this.some(function (ele) { 8232 return collection.hasElementWithId(ele.id()); 8233 }); 8234 }, 8235 allAreNeighbors: function allAreNeighbors(collection) { 8236 collection = this.cy().collection(collection); 8237 var nhood = this.neighborhood(); 8238 return collection.every(function (ele) { 8239 return nhood.hasElementWithId(ele.id()); 8240 }); 8241 }, 8242 contains: function contains(collection) { 8243 collection = this.cy().collection(collection); 8244 var self = this; 8245 return collection.every(function (ele) { 8246 return self.hasElementWithId(ele.id()); 8247 }); 8248 } 8249 }; 8250 elesfn$f.allAreNeighbours = elesfn$f.allAreNeighbors; 8251 elesfn$f.has = elesfn$f.contains; 8252 elesfn$f.equal = elesfn$f.equals = elesfn$f.same; 8253 8254 var cache = function cache(fn, name) { 8255 return function traversalCache(arg1, arg2, arg3, arg4) { 8256 var selectorOrEles = arg1; 8257 var eles = this; 8258 var key; 8259 8260 if (selectorOrEles == null) { 8261 key = ''; 8262 } else if (elementOrCollection(selectorOrEles) && selectorOrEles.length === 1) { 8263 key = selectorOrEles.id(); 8264 } 8265 8266 if (eles.length === 1 && key) { 8267 var _p = eles[0]._private; 8268 var tch = _p.traversalCache = _p.traversalCache || {}; 8269 var ch = tch[name] = tch[name] || []; 8270 var hash = hashString(key); 8271 var cacheHit = ch[hash]; 8272 8273 if (cacheHit) { 8274 return cacheHit; 8275 } else { 8276 return ch[hash] = fn.call(eles, arg1, arg2, arg3, arg4); 8277 } 8278 } else { 8279 return fn.call(eles, arg1, arg2, arg3, arg4); 8280 } 8281 }; 8282 }; 8283 8284 var elesfn$g = { 8285 parent: function parent(selector) { 8286 var parents = []; // optimisation for single ele call 8287 8288 if (this.length === 1) { 8289 var parent = this[0]._private.parent; 8290 8291 if (parent) { 8292 return parent; 8293 } 8294 } 8295 8296 for (var i = 0; i < this.length; i++) { 8297 var ele = this[i]; 8298 var _parent = ele._private.parent; 8299 8300 if (_parent) { 8301 parents.push(_parent); 8302 } 8303 } 8304 8305 return this.spawn(parents, { 8306 unique: true 8307 }).filter(selector); 8308 }, 8309 parents: function parents(selector) { 8310 var parents = []; 8311 var eles = this.parent(); 8312 8313 while (eles.nonempty()) { 8314 for (var i = 0; i < eles.length; i++) { 8315 var ele = eles[i]; 8316 parents.push(ele); 8317 } 8318 8319 eles = eles.parent(); 8320 } 8321 8322 return this.spawn(parents, { 8323 unique: true 8324 }).filter(selector); 8325 }, 8326 commonAncestors: function commonAncestors(selector) { 8327 var ancestors; 8328 8329 for (var i = 0; i < this.length; i++) { 8330 var ele = this[i]; 8331 var parents = ele.parents(); 8332 ancestors = ancestors || parents; 8333 ancestors = ancestors.intersect(parents); // current list must be common with current ele parents set 8334 } 8335 8336 return ancestors.filter(selector); 8337 }, 8338 orphans: function orphans(selector) { 8339 return this.stdFilter(function (ele) { 8340 return ele.isOrphan(); 8341 }).filter(selector); 8342 }, 8343 nonorphans: function nonorphans(selector) { 8344 return this.stdFilter(function (ele) { 8345 return ele.isChild(); 8346 }).filter(selector); 8347 }, 8348 children: cache(function (selector) { 8349 var children = []; 8350 8351 for (var i = 0; i < this.length; i++) { 8352 var ele = this[i]; 8353 var eleChildren = ele._private.children; 8354 8355 for (var j = 0; j < eleChildren.length; j++) { 8356 children.push(eleChildren[j]); 8357 } 8358 } 8359 8360 return this.spawn(children, { 8361 unique: true 8362 }).filter(selector); 8363 }, 'children'), 8364 siblings: function siblings(selector) { 8365 return this.parent().children().not(this).filter(selector); 8366 }, 8367 isParent: function isParent() { 8368 var ele = this[0]; 8369 8370 if (ele) { 8371 return ele.isNode() && ele._private.children.length !== 0; 8372 } 8373 }, 8374 isChildless: function isChildless() { 8375 var ele = this[0]; 8376 8377 if (ele) { 8378 return ele.isNode() && ele._private.children.length === 0; 8379 } 8380 }, 8381 isChild: function isChild() { 8382 var ele = this[0]; 8383 8384 if (ele) { 8385 return ele.isNode() && ele._private.parent != null; 8386 } 8387 }, 8388 isOrphan: function isOrphan() { 8389 var ele = this[0]; 8390 8391 if (ele) { 8392 return ele.isNode() && ele._private.parent == null; 8393 } 8394 }, 8395 descendants: function descendants(selector) { 8396 var elements = []; 8397 8398 function add(eles) { 8399 for (var i = 0; i < eles.length; i++) { 8400 var ele = eles[i]; 8401 elements.push(ele); 8402 8403 if (ele.children().nonempty()) { 8404 add(ele.children()); 8405 } 8406 } 8407 } 8408 8409 add(this.children()); 8410 return this.spawn(elements, { 8411 unique: true 8412 }).filter(selector); 8413 } 8414 }; 8415 8416 function forEachCompound(eles, fn, includeSelf, recursiveStep) { 8417 var q = []; 8418 var did = new Set$1(); 8419 var cy = eles.cy(); 8420 var hasCompounds = cy.hasCompoundNodes(); 8421 8422 for (var i = 0; i < eles.length; i++) { 8423 var ele = eles[i]; 8424 8425 if (includeSelf) { 8426 q.push(ele); 8427 } else if (hasCompounds) { 8428 recursiveStep(q, did, ele); 8429 } 8430 } 8431 8432 while (q.length > 0) { 8433 var _ele = q.shift(); 8434 8435 fn(_ele); 8436 did.add(_ele.id()); 8437 8438 if (hasCompounds) { 8439 recursiveStep(q, did, _ele); 8440 } 8441 } 8442 8443 return eles; 8444 } 8445 8446 function addChildren(q, did, ele) { 8447 if (ele.isParent()) { 8448 var children = ele._private.children; 8449 8450 for (var i = 0; i < children.length; i++) { 8451 var child = children[i]; 8452 8453 if (!did.has(child.id())) { 8454 q.push(child); 8455 } 8456 } 8457 } 8458 } // very efficient version of eles.add( eles.descendants() ).forEach() 8459 // for internal use 8460 8461 8462 elesfn$g.forEachDown = function (fn) { 8463 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 8464 return forEachCompound(this, fn, includeSelf, addChildren); 8465 }; 8466 8467 function addParent(q, did, ele) { 8468 if (ele.isChild()) { 8469 var parent = ele._private.parent; 8470 8471 if (!did.has(parent.id())) { 8472 q.push(parent); 8473 } 8474 } 8475 } 8476 8477 elesfn$g.forEachUp = function (fn) { 8478 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 8479 return forEachCompound(this, fn, includeSelf, addParent); 8480 }; 8481 8482 function addParentAndChildren(q, did, ele) { 8483 addParent(q, did, ele); 8484 addChildren(q, did, ele); 8485 } 8486 8487 elesfn$g.forEachUpAndDown = function (fn) { 8488 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 8489 return forEachCompound(this, fn, includeSelf, addParentAndChildren); 8490 }; // aliases 8491 8492 8493 elesfn$g.ancestors = elesfn$g.parents; 8494 8495 var fn$1, elesfn$h; 8496 fn$1 = elesfn$h = { 8497 data: define$3.data({ 8498 field: 'data', 8499 bindingEvent: 'data', 8500 allowBinding: true, 8501 allowSetting: true, 8502 settingEvent: 'data', 8503 settingTriggersEvent: true, 8504 triggerFnName: 'trigger', 8505 allowGetting: true, 8506 immutableKeys: { 8507 'id': true, 8508 'source': true, 8509 'target': true, 8510 'parent': true 8511 }, 8512 updateStyle: true 8513 }), 8514 removeData: define$3.removeData({ 8515 field: 'data', 8516 event: 'data', 8517 triggerFnName: 'trigger', 8518 triggerEvent: true, 8519 immutableKeys: { 8520 'id': true, 8521 'source': true, 8522 'target': true, 8523 'parent': true 8524 }, 8525 updateStyle: true 8526 }), 8527 scratch: define$3.data({ 8528 field: 'scratch', 8529 bindingEvent: 'scratch', 8530 allowBinding: true, 8531 allowSetting: true, 8532 settingEvent: 'scratch', 8533 settingTriggersEvent: true, 8534 triggerFnName: 'trigger', 8535 allowGetting: true, 8536 updateStyle: true 8537 }), 8538 removeScratch: define$3.removeData({ 8539 field: 'scratch', 8540 event: 'scratch', 8541 triggerFnName: 'trigger', 8542 triggerEvent: true, 8543 updateStyle: true 8544 }), 8545 rscratch: define$3.data({ 8546 field: 'rscratch', 8547 allowBinding: false, 8548 allowSetting: true, 8549 settingTriggersEvent: false, 8550 allowGetting: true 8551 }), 8552 removeRscratch: define$3.removeData({ 8553 field: 'rscratch', 8554 triggerEvent: false 8555 }), 8556 id: function id() { 8557 var ele = this[0]; 8558 8559 if (ele) { 8560 return ele._private.data.id; 8561 } 8562 } 8563 }; // aliases 8564 8565 fn$1.attr = fn$1.data; 8566 fn$1.removeAttr = fn$1.removeData; 8567 var data$1 = elesfn$h; 8568 8569 var elesfn$i = {}; 8570 8571 function defineDegreeFunction(callback) { 8572 return function (includeLoops) { 8573 var self = this; 8574 8575 if (includeLoops === undefined) { 8576 includeLoops = true; 8577 } 8578 8579 if (self.length === 0) { 8580 return; 8581 } 8582 8583 if (self.isNode() && !self.removed()) { 8584 var degree = 0; 8585 var node = self[0]; 8586 var connectedEdges = node._private.edges; 8587 8588 for (var i = 0; i < connectedEdges.length; i++) { 8589 var edge = connectedEdges[i]; 8590 8591 if (!includeLoops && edge.isLoop()) { 8592 continue; 8593 } 8594 8595 degree += callback(node, edge); 8596 } 8597 8598 return degree; 8599 } else { 8600 return; 8601 } 8602 }; 8603 } 8604 8605 extend(elesfn$i, { 8606 degree: defineDegreeFunction(function (node, edge) { 8607 if (edge.source().same(edge.target())) { 8608 return 2; 8609 } else { 8610 return 1; 8611 } 8612 }), 8613 indegree: defineDegreeFunction(function (node, edge) { 8614 if (edge.target().same(node)) { 8615 return 1; 8616 } else { 8617 return 0; 8618 } 8619 }), 8620 outdegree: defineDegreeFunction(function (node, edge) { 8621 if (edge.source().same(node)) { 8622 return 1; 8623 } else { 8624 return 0; 8625 } 8626 }) 8627 }); 8628 8629 function defineDegreeBoundsFunction(degreeFn, callback) { 8630 return function (includeLoops) { 8631 var ret; 8632 var nodes = this.nodes(); 8633 8634 for (var i = 0; i < nodes.length; i++) { 8635 var ele = nodes[i]; 8636 var degree = ele[degreeFn](includeLoops); 8637 8638 if (degree !== undefined && (ret === undefined || callback(degree, ret))) { 8639 ret = degree; 8640 } 8641 } 8642 8643 return ret; 8644 }; 8645 } 8646 8647 extend(elesfn$i, { 8648 minDegree: defineDegreeBoundsFunction('degree', function (degree, min) { 8649 return degree < min; 8650 }), 8651 maxDegree: defineDegreeBoundsFunction('degree', function (degree, max) { 8652 return degree > max; 8653 }), 8654 minIndegree: defineDegreeBoundsFunction('indegree', function (degree, min) { 8655 return degree < min; 8656 }), 8657 maxIndegree: defineDegreeBoundsFunction('indegree', function (degree, max) { 8658 return degree > max; 8659 }), 8660 minOutdegree: defineDegreeBoundsFunction('outdegree', function (degree, min) { 8661 return degree < min; 8662 }), 8663 maxOutdegree: defineDegreeBoundsFunction('outdegree', function (degree, max) { 8664 return degree > max; 8665 }) 8666 }); 8667 extend(elesfn$i, { 8668 totalDegree: function totalDegree(includeLoops) { 8669 var total = 0; 8670 var nodes = this.nodes(); 8671 8672 for (var i = 0; i < nodes.length; i++) { 8673 total += nodes[i].degree(includeLoops); 8674 } 8675 8676 return total; 8677 } 8678 }); 8679 8680 var fn$2, elesfn$j; 8681 8682 var beforePositionSet = function beforePositionSet(eles, newPos, silent) { 8683 for (var i = 0; i < eles.length; i++) { 8684 var ele = eles[i]; 8685 8686 if (!ele.locked()) { 8687 var oldPos = ele._private.position; 8688 var delta = { 8689 x: newPos.x != null ? newPos.x - oldPos.x : 0, 8690 y: newPos.y != null ? newPos.y - oldPos.y : 0 8691 }; 8692 8693 if (ele.isParent() && !(delta.x === 0 && delta.y === 0)) { 8694 ele.children().shift(delta, silent); 8695 } 8696 8697 ele.shiftCachedBoundingBox(delta); 8698 } 8699 } 8700 }; 8701 8702 var positionDef = { 8703 field: 'position', 8704 bindingEvent: 'position', 8705 allowBinding: true, 8706 allowSetting: true, 8707 settingEvent: 'position', 8708 settingTriggersEvent: true, 8709 triggerFnName: 'emitAndNotify', 8710 allowGetting: true, 8711 validKeys: ['x', 'y'], 8712 beforeGet: function beforeGet(ele) { 8713 ele.updateCompoundBounds(); 8714 }, 8715 beforeSet: function beforeSet(eles, newPos) { 8716 beforePositionSet(eles, newPos, false); 8717 }, 8718 onSet: function onSet(eles) { 8719 eles.dirtyCompoundBoundsCache(); 8720 }, 8721 canSet: function canSet(ele) { 8722 return !ele.locked(); 8723 } 8724 }; 8725 fn$2 = elesfn$j = { 8726 position: define$3.data(positionDef), 8727 // position but no notification to renderer 8728 silentPosition: define$3.data(extend({}, positionDef, { 8729 allowBinding: false, 8730 allowSetting: true, 8731 settingTriggersEvent: false, 8732 allowGetting: false, 8733 beforeSet: function beforeSet(eles, newPos) { 8734 beforePositionSet(eles, newPos, true); 8735 } 8736 })), 8737 positions: function positions(pos, silent) { 8738 if (plainObject(pos)) { 8739 if (silent) { 8740 this.silentPosition(pos); 8741 } else { 8742 this.position(pos); 8743 } 8744 } else if (fn(pos)) { 8745 var _fn = pos; 8746 var cy = this.cy(); 8747 cy.startBatch(); 8748 8749 for (var i = 0; i < this.length; i++) { 8750 var ele = this[i]; 8751 8752 var _pos = void 0; 8753 8754 if (_pos = _fn(ele, i)) { 8755 if (silent) { 8756 ele.silentPosition(_pos); 8757 } else { 8758 ele.position(_pos); 8759 } 8760 } 8761 } 8762 8763 cy.endBatch(); 8764 } 8765 8766 return this; // chaining 8767 }, 8768 silentPositions: function silentPositions(pos) { 8769 return this.positions(pos, true); 8770 }, 8771 shift: function shift(dim, val, silent) { 8772 var delta; 8773 8774 if (plainObject(dim)) { 8775 delta = { 8776 x: number(dim.x) ? dim.x : 0, 8777 y: number(dim.y) ? dim.y : 0 8778 }; 8779 silent = val; 8780 } else if (string(dim) && number(val)) { 8781 delta = { 8782 x: 0, 8783 y: 0 8784 }; 8785 delta[dim] = val; 8786 } 8787 8788 if (delta != null) { 8789 var cy = this.cy(); 8790 cy.startBatch(); 8791 8792 for (var i = 0; i < this.length; i++) { 8793 var ele = this[i]; 8794 var pos = ele.position(); 8795 var newPos = { 8796 x: pos.x + delta.x, 8797 y: pos.y + delta.y 8798 }; 8799 8800 if (silent) { 8801 ele.silentPosition(newPos); 8802 } else { 8803 ele.position(newPos); 8804 } 8805 } 8806 8807 cy.endBatch(); 8808 } 8809 8810 return this; 8811 }, 8812 silentShift: function silentShift(dim, val) { 8813 if (plainObject(dim)) { 8814 this.shift(dim, true); 8815 } else if (string(dim) && number(val)) { 8816 this.shift(dim, val, true); 8817 } 8818 8819 return this; 8820 }, 8821 // get/set the rendered (i.e. on screen) positon of the element 8822 renderedPosition: function renderedPosition(dim, val) { 8823 var ele = this[0]; 8824 var cy = this.cy(); 8825 var zoom = cy.zoom(); 8826 var pan = cy.pan(); 8827 var rpos = plainObject(dim) ? dim : undefined; 8828 var setting = rpos !== undefined || val !== undefined && string(dim); 8829 8830 if (ele && ele.isNode()) { 8831 // must have an element and must be a node to return position 8832 if (setting) { 8833 for (var i = 0; i < this.length; i++) { 8834 var _ele = this[i]; 8835 8836 if (val !== undefined) { 8837 // set one dimension 8838 _ele.position(dim, (val - pan[dim]) / zoom); 8839 } else if (rpos !== undefined) { 8840 // set whole position 8841 _ele.position(renderedToModelPosition(rpos, zoom, pan)); 8842 } 8843 } 8844 } else { 8845 // getting 8846 var pos = ele.position(); 8847 rpos = modelToRenderedPosition(pos, zoom, pan); 8848 8849 if (dim === undefined) { 8850 // then return the whole rendered position 8851 return rpos; 8852 } else { 8853 // then return the specified dimension 8854 return rpos[dim]; 8855 } 8856 } 8857 } else if (!setting) { 8858 return undefined; // for empty collection case 8859 } 8860 8861 return this; // chaining 8862 }, 8863 // get/set the position relative to the parent 8864 relativePosition: function relativePosition(dim, val) { 8865 var ele = this[0]; 8866 var cy = this.cy(); 8867 var ppos = plainObject(dim) ? dim : undefined; 8868 var setting = ppos !== undefined || val !== undefined && string(dim); 8869 var hasCompoundNodes = cy.hasCompoundNodes(); 8870 8871 if (ele && ele.isNode()) { 8872 // must have an element and must be a node to return position 8873 if (setting) { 8874 for (var i = 0; i < this.length; i++) { 8875 var _ele2 = this[i]; 8876 var parent = hasCompoundNodes ? _ele2.parent() : null; 8877 var hasParent = parent && parent.length > 0; 8878 var relativeToParent = hasParent; 8879 8880 if (hasParent) { 8881 parent = parent[0]; 8882 } 8883 8884 var origin = relativeToParent ? parent.position() : { 8885 x: 0, 8886 y: 0 8887 }; 8888 8889 if (val !== undefined) { 8890 // set one dimension 8891 _ele2.position(dim, val + origin[dim]); 8892 } else if (ppos !== undefined) { 8893 // set whole position 8894 _ele2.position({ 8895 x: ppos.x + origin.x, 8896 y: ppos.y + origin.y 8897 }); 8898 } 8899 } 8900 } else { 8901 // getting 8902 var pos = ele.position(); 8903 8904 var _parent = hasCompoundNodes ? ele.parent() : null; 8905 8906 var _hasParent = _parent && _parent.length > 0; 8907 8908 var _relativeToParent = _hasParent; 8909 8910 if (_hasParent) { 8911 _parent = _parent[0]; 8912 } 8913 8914 var _origin = _relativeToParent ? _parent.position() : { 8915 x: 0, 8916 y: 0 8917 }; 8918 8919 ppos = { 8920 x: pos.x - _origin.x, 8921 y: pos.y - _origin.y 8922 }; 8923 8924 if (dim === undefined) { 8925 // then return the whole rendered position 8926 return ppos; 8927 } else { 8928 // then return the specified dimension 8929 return ppos[dim]; 8930 } 8931 } 8932 } else if (!setting) { 8933 return undefined; // for empty collection case 8934 } 8935 8936 return this; // chaining 8937 } 8938 }; // aliases 8939 8940 fn$2.modelPosition = fn$2.point = fn$2.position; 8941 fn$2.modelPositions = fn$2.points = fn$2.positions; 8942 fn$2.renderedPoint = fn$2.renderedPosition; 8943 fn$2.relativePoint = fn$2.relativePosition; 8944 var position = elesfn$j; 8945 8946 var fn$3, elesfn$k; 8947 fn$3 = elesfn$k = {}; 8948 8949 elesfn$k.renderedBoundingBox = function (options) { 8950 var bb = this.boundingBox(options); 8951 var cy = this.cy(); 8952 var zoom = cy.zoom(); 8953 var pan = cy.pan(); 8954 var x1 = bb.x1 * zoom + pan.x; 8955 var x2 = bb.x2 * zoom + pan.x; 8956 var y1 = bb.y1 * zoom + pan.y; 8957 var y2 = bb.y2 * zoom + pan.y; 8958 return { 8959 x1: x1, 8960 x2: x2, 8961 y1: y1, 8962 y2: y2, 8963 w: x2 - x1, 8964 h: y2 - y1 8965 }; 8966 }; 8967 8968 elesfn$k.dirtyCompoundBoundsCache = function () { 8969 var cy = this.cy(); 8970 8971 if (!cy.styleEnabled() || !cy.hasCompoundNodes()) { 8972 return this; 8973 } 8974 8975 this.forEachUp(function (ele) { 8976 if (ele.isParent()) { 8977 var _p = ele._private; 8978 _p.compoundBoundsClean = false; 8979 _p.bbCache = null; 8980 ele.emitAndNotify('bounds'); 8981 } 8982 }); 8983 return this; 8984 }; 8985 8986 elesfn$k.updateCompoundBounds = function () { 8987 var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 8988 var cy = this.cy(); // not possible to do on non-compound graphs or with the style disabled 8989 8990 if (!cy.styleEnabled() || !cy.hasCompoundNodes()) { 8991 return this; 8992 } // save cycles when batching -- but bounds will be stale (or not exist yet) 8993 8994 8995 if (!force && cy.batching()) { 8996 return this; 8997 } 8998 8999 function update(parent) { 9000 if (!parent.isParent()) { 9001 return; 9002 } 9003 9004 var _p = parent._private; 9005 var children = parent.children(); 9006 var includeLabels = parent.pstyle('compound-sizing-wrt-labels').value === 'include'; 9007 var min = { 9008 width: { 9009 val: parent.pstyle('min-width').pfValue, 9010 left: parent.pstyle('min-width-bias-left'), 9011 right: parent.pstyle('min-width-bias-right') 9012 }, 9013 height: { 9014 val: parent.pstyle('min-height').pfValue, 9015 top: parent.pstyle('min-height-bias-top'), 9016 bottom: parent.pstyle('min-height-bias-bottom') 9017 } 9018 }; 9019 var bb = children.boundingBox({ 9020 includeLabels: includeLabels, 9021 includeOverlays: false, 9022 // updating the compound bounds happens outside of the regular 9023 // cache cycle (i.e. before fired events) 9024 useCache: false 9025 }); 9026 var pos = _p.position; // if children take up zero area then keep position and fall back on stylesheet w/h 9027 9028 if (bb.w === 0 || bb.h === 0) { 9029 bb = { 9030 w: parent.pstyle('width').pfValue, 9031 h: parent.pstyle('height').pfValue 9032 }; 9033 bb.x1 = pos.x - bb.w / 2; 9034 bb.x2 = pos.x + bb.w / 2; 9035 bb.y1 = pos.y - bb.h / 2; 9036 bb.y2 = pos.y + bb.h / 2; 9037 } 9038 9039 function computeBiasValues(propDiff, propBias, propBiasComplement) { 9040 var biasDiff = 0; 9041 var biasComplementDiff = 0; 9042 var biasTotal = propBias + propBiasComplement; 9043 9044 if (propDiff > 0 && biasTotal > 0) { 9045 biasDiff = propBias / biasTotal * propDiff; 9046 biasComplementDiff = propBiasComplement / biasTotal * propDiff; 9047 } 9048 9049 return { 9050 biasDiff: biasDiff, 9051 biasComplementDiff: biasComplementDiff 9052 }; 9053 } 9054 9055 function computePaddingValues(width, height, paddingObject, relativeTo) { 9056 // Assuming percentage is number from 0 to 1 9057 if (paddingObject.units === '%') { 9058 switch (relativeTo) { 9059 case 'width': 9060 return width > 0 ? paddingObject.pfValue * width : 0; 9061 9062 case 'height': 9063 return height > 0 ? paddingObject.pfValue * height : 0; 9064 9065 case 'average': 9066 return width > 0 && height > 0 ? paddingObject.pfValue * (width + height) / 2 : 0; 9067 9068 case 'min': 9069 return width > 0 && height > 0 ? width > height ? paddingObject.pfValue * height : paddingObject.pfValue * width : 0; 9070 9071 case 'max': 9072 return width > 0 && height > 0 ? width > height ? paddingObject.pfValue * width : paddingObject.pfValue * height : 0; 9073 9074 default: 9075 return 0; 9076 } 9077 } else if (paddingObject.units === 'px') { 9078 return paddingObject.pfValue; 9079 } else { 9080 return 0; 9081 } 9082 } 9083 9084 var leftVal = min.width.left.value; 9085 9086 if (min.width.left.units === 'px' && min.width.val > 0) { 9087 leftVal = leftVal * 100 / min.width.val; 9088 } 9089 9090 var rightVal = min.width.right.value; 9091 9092 if (min.width.right.units === 'px' && min.width.val > 0) { 9093 rightVal = rightVal * 100 / min.width.val; 9094 } 9095 9096 var topVal = min.height.top.value; 9097 9098 if (min.height.top.units === 'px' && min.height.val > 0) { 9099 topVal = topVal * 100 / min.height.val; 9100 } 9101 9102 var bottomVal = min.height.bottom.value; 9103 9104 if (min.height.bottom.units === 'px' && min.height.val > 0) { 9105 bottomVal = bottomVal * 100 / min.height.val; 9106 } 9107 9108 var widthBiasDiffs = computeBiasValues(min.width.val - bb.w, leftVal, rightVal); 9109 var diffLeft = widthBiasDiffs.biasDiff; 9110 var diffRight = widthBiasDiffs.biasComplementDiff; 9111 var heightBiasDiffs = computeBiasValues(min.height.val - bb.h, topVal, bottomVal); 9112 var diffTop = heightBiasDiffs.biasDiff; 9113 var diffBottom = heightBiasDiffs.biasComplementDiff; 9114 _p.autoPadding = computePaddingValues(bb.w, bb.h, parent.pstyle('padding'), parent.pstyle('padding-relative-to').value); 9115 _p.autoWidth = Math.max(bb.w, min.width.val); 9116 pos.x = (-diffLeft + bb.x1 + bb.x2 + diffRight) / 2; 9117 _p.autoHeight = Math.max(bb.h, min.height.val); 9118 pos.y = (-diffTop + bb.y1 + bb.y2 + diffBottom) / 2; 9119 } 9120 9121 for (var i = 0; i < this.length; i++) { 9122 var ele = this[i]; 9123 var _p = ele._private; 9124 9125 if (!_p.compoundBoundsClean) { 9126 update(ele); 9127 9128 if (!cy.batching()) { 9129 _p.compoundBoundsClean = true; 9130 } 9131 } 9132 } 9133 9134 return this; 9135 }; 9136 9137 var noninf = function noninf(x) { 9138 if (x === Infinity || x === -Infinity) { 9139 return 0; 9140 } 9141 9142 return x; 9143 }; 9144 9145 var updateBounds = function updateBounds(b, x1, y1, x2, y2) { 9146 // don't update with zero area boxes 9147 if (x2 - x1 === 0 || y2 - y1 === 0) { 9148 return; 9149 } // don't update with null dim 9150 9151 9152 if (x1 == null || y1 == null || x2 == null || y2 == null) { 9153 return; 9154 } 9155 9156 b.x1 = x1 < b.x1 ? x1 : b.x1; 9157 b.x2 = x2 > b.x2 ? x2 : b.x2; 9158 b.y1 = y1 < b.y1 ? y1 : b.y1; 9159 b.y2 = y2 > b.y2 ? y2 : b.y2; 9160 b.w = b.x2 - b.x1; 9161 b.h = b.y2 - b.y1; 9162 }; 9163 9164 var updateBoundsFromBox = function updateBoundsFromBox(b, b2) { 9165 if (b2 == null) { 9166 return b; 9167 } 9168 9169 return updateBounds(b, b2.x1, b2.y1, b2.x2, b2.y2); 9170 }; 9171 9172 var prefixedProperty = function prefixedProperty(obj, field, prefix) { 9173 return getPrefixedProperty(obj, field, prefix); 9174 }; 9175 9176 var updateBoundsFromArrow = function updateBoundsFromArrow(bounds, ele, prefix) { 9177 if (ele.cy().headless()) { 9178 return; 9179 } 9180 9181 var _p = ele._private; 9182 var rstyle = _p.rstyle; 9183 var halfArW = rstyle.arrowWidth / 2; 9184 var arrowType = ele.pstyle(prefix + '-arrow-shape').value; 9185 var x; 9186 var y; 9187 9188 if (arrowType !== 'none') { 9189 if (prefix === 'source') { 9190 x = rstyle.srcX; 9191 y = rstyle.srcY; 9192 } else if (prefix === 'target') { 9193 x = rstyle.tgtX; 9194 y = rstyle.tgtY; 9195 } else { 9196 x = rstyle.midX; 9197 y = rstyle.midY; 9198 } // always store the individual arrow bounds 9199 9200 9201 var bbs = _p.arrowBounds = _p.arrowBounds || {}; 9202 var bb = bbs[prefix] = bbs[prefix] || {}; 9203 bb.x1 = x - halfArW; 9204 bb.y1 = y - halfArW; 9205 bb.x2 = x + halfArW; 9206 bb.y2 = y + halfArW; 9207 bb.w = bb.x2 - bb.x1; 9208 bb.h = bb.y2 - bb.y1; 9209 expandBoundingBox(bb, 1); 9210 updateBounds(bounds, bb.x1, bb.y1, bb.x2, bb.y2); 9211 } 9212 }; 9213 9214 var updateBoundsFromLabel = function updateBoundsFromLabel(bounds, ele, prefix) { 9215 if (ele.cy().headless()) { 9216 return; 9217 } 9218 9219 var prefixDash; 9220 9221 if (prefix) { 9222 prefixDash = prefix + '-'; 9223 } else { 9224 prefixDash = ''; 9225 } 9226 9227 var _p = ele._private; 9228 var rstyle = _p.rstyle; 9229 var label = ele.pstyle(prefixDash + 'label').strValue; 9230 9231 if (label) { 9232 var halign = ele.pstyle('text-halign'); 9233 var valign = ele.pstyle('text-valign'); 9234 var labelWidth = prefixedProperty(rstyle, 'labelWidth', prefix); 9235 var labelHeight = prefixedProperty(rstyle, 'labelHeight', prefix); 9236 var labelX = prefixedProperty(rstyle, 'labelX', prefix); 9237 var labelY = prefixedProperty(rstyle, 'labelY', prefix); 9238 var marginX = ele.pstyle(prefixDash + 'text-margin-x').pfValue; 9239 var marginY = ele.pstyle(prefixDash + 'text-margin-y').pfValue; 9240 var isEdge = ele.isEdge(); 9241 var rotation = ele.pstyle(prefixDash + 'text-rotation'); 9242 var outlineWidth = ele.pstyle('text-outline-width').pfValue; 9243 var borderWidth = ele.pstyle('text-border-width').pfValue; 9244 var halfBorderWidth = borderWidth / 2; 9245 var padding = ele.pstyle('text-background-padding').pfValue; 9246 var lh = labelHeight; 9247 var lw = labelWidth; 9248 var lw_2 = lw / 2; 9249 var lh_2 = lh / 2; 9250 var lx1, lx2, ly1, ly2; 9251 9252 if (isEdge) { 9253 lx1 = labelX - lw_2; 9254 lx2 = labelX + lw_2; 9255 ly1 = labelY - lh_2; 9256 ly2 = labelY + lh_2; 9257 } else { 9258 switch (halign.value) { 9259 case 'left': 9260 lx1 = labelX - lw; 9261 lx2 = labelX; 9262 break; 9263 9264 case 'center': 9265 lx1 = labelX - lw_2; 9266 lx2 = labelX + lw_2; 9267 break; 9268 9269 case 'right': 9270 lx1 = labelX; 9271 lx2 = labelX + lw; 9272 break; 9273 } 9274 9275 switch (valign.value) { 9276 case 'top': 9277 ly1 = labelY - lh; 9278 ly2 = labelY; 9279 break; 9280 9281 case 'center': 9282 ly1 = labelY - lh_2; 9283 ly2 = labelY + lh_2; 9284 break; 9285 9286 case 'bottom': 9287 ly1 = labelY; 9288 ly2 = labelY + lh; 9289 break; 9290 } 9291 } // shift by margin and expand by outline and border 9292 9293 9294 lx1 += marginX - Math.max(outlineWidth, halfBorderWidth) - padding; 9295 lx2 += marginX + Math.max(outlineWidth, halfBorderWidth) + padding; 9296 ly1 += marginY - Math.max(outlineWidth, halfBorderWidth) - padding; 9297 ly2 += marginY + Math.max(outlineWidth, halfBorderWidth) + padding; // always store the unrotated label bounds separately 9298 9299 var bbPrefix = prefix || 'main'; 9300 var bbs = _p.labelBounds; 9301 var bb = bbs[bbPrefix] = bbs[bbPrefix] || {}; 9302 bb.x1 = lx1; 9303 bb.y1 = ly1; 9304 bb.x2 = lx2; 9305 bb.y2 = ly2; 9306 bb.w = lx2 - lx1; 9307 bb.h = ly2 - ly1; 9308 expandBoundingBox(bb, 1); // expand to work around browser dimension inaccuracies 9309 9310 var isAutorotate = isEdge && rotation.strValue === 'autorotate'; 9311 var isPfValue = rotation.pfValue != null && rotation.pfValue !== 0; 9312 9313 if (isAutorotate || isPfValue) { 9314 var theta = isAutorotate ? prefixedProperty(_p.rstyle, 'labelAngle', prefix) : rotation.pfValue; 9315 var cos = Math.cos(theta); 9316 var sin = Math.sin(theta); // rotation point (default value for center-center) 9317 9318 var xo = (lx1 + lx2) / 2; 9319 var yo = (ly1 + ly2) / 2; 9320 9321 if (!isEdge) { 9322 switch (halign.value) { 9323 case 'left': 9324 xo = lx2; 9325 break; 9326 9327 case 'right': 9328 xo = lx1; 9329 break; 9330 } 9331 9332 switch (valign.value) { 9333 case 'top': 9334 yo = ly2; 9335 break; 9336 9337 case 'bottom': 9338 yo = ly1; 9339 break; 9340 } 9341 } 9342 9343 var rotate = function rotate(x, y) { 9344 x = x - xo; 9345 y = y - yo; 9346 return { 9347 x: x * cos - y * sin + xo, 9348 y: x * sin + y * cos + yo 9349 }; 9350 }; 9351 9352 var px1y1 = rotate(lx1, ly1); 9353 var px1y2 = rotate(lx1, ly2); 9354 var px2y1 = rotate(lx2, ly1); 9355 var px2y2 = rotate(lx2, ly2); 9356 lx1 = Math.min(px1y1.x, px1y2.x, px2y1.x, px2y2.x); 9357 lx2 = Math.max(px1y1.x, px1y2.x, px2y1.x, px2y2.x); 9358 ly1 = Math.min(px1y1.y, px1y2.y, px2y1.y, px2y2.y); 9359 ly2 = Math.max(px1y1.y, px1y2.y, px2y1.y, px2y2.y); 9360 } 9361 9362 var bbPrefixRot = bbPrefix + 'Rot'; 9363 var bbRot = bbs[bbPrefixRot] = bbs[bbPrefixRot] || {}; 9364 bbRot.x1 = lx1; 9365 bbRot.y1 = ly1; 9366 bbRot.x2 = lx2; 9367 bbRot.y2 = ly2; 9368 bbRot.w = lx2 - lx1; 9369 bbRot.h = ly2 - ly1; 9370 updateBounds(bounds, lx1, ly1, lx2, ly2); 9371 updateBounds(_p.labelBounds.all, lx1, ly1, lx2, ly2); 9372 } 9373 9374 return bounds; 9375 }; // get the bounding box of the elements (in raw model position) 9376 9377 9378 var boundingBoxImpl = function boundingBoxImpl(ele, options) { 9379 var cy = ele._private.cy; 9380 var styleEnabled = cy.styleEnabled(); 9381 var headless = cy.headless(); 9382 var bounds = makeBoundingBox(); 9383 var _p = ele._private; 9384 var isNode = ele.isNode(); 9385 var isEdge = ele.isEdge(); 9386 var ex1, ex2, ey1, ey2; // extrema of body / lines 9387 9388 var x, y; // node pos 9389 9390 var rstyle = _p.rstyle; 9391 var manualExpansion = isNode && styleEnabled ? ele.pstyle('bounds-expansion').pfValue : [0]; // must use `display` prop only, as reading `compound.width()` causes recursion 9392 // (other factors like width values will be considered later in this function anyway) 9393 9394 var isDisplayed = function isDisplayed(ele) { 9395 return ele.pstyle('display').value !== 'none'; 9396 }; 9397 9398 var displayed = !styleEnabled || isDisplayed(ele) // must take into account connected nodes b/c of implicit edge hiding on display:none node 9399 && (!isEdge || isDisplayed(ele.source()) && isDisplayed(ele.target())); 9400 9401 if (displayed) { 9402 // displayed suffices, since we will find zero area eles anyway 9403 var overlayOpacity = 0; 9404 var overlayPadding = 0; 9405 9406 if (styleEnabled && options.includeOverlays) { 9407 overlayOpacity = ele.pstyle('overlay-opacity').value; 9408 9409 if (overlayOpacity !== 0) { 9410 overlayPadding = ele.pstyle('overlay-padding').value; 9411 } 9412 } 9413 9414 var w = 0; 9415 var wHalf = 0; 9416 9417 if (styleEnabled) { 9418 w = ele.pstyle('width').pfValue; 9419 wHalf = w / 2; 9420 } 9421 9422 if (isNode && options.includeNodes) { 9423 var pos = ele.position(); 9424 x = pos.x; 9425 y = pos.y; 9426 9427 var _w = ele.outerWidth(); 9428 9429 var halfW = _w / 2; 9430 var h = ele.outerHeight(); 9431 var halfH = h / 2; // handle node dimensions 9432 ///////////////////////// 9433 9434 ex1 = x - halfW; 9435 ex2 = x + halfW; 9436 ey1 = y - halfH; 9437 ey2 = y + halfH; 9438 updateBounds(bounds, ex1, ey1, ex2, ey2); 9439 } else if (isEdge && options.includeEdges) { 9440 if (styleEnabled && !headless) { 9441 var curveStyle = ele.pstyle('curve-style').strValue; // handle edge dimensions (rough box estimate) 9442 ////////////////////////////////////////////// 9443 9444 ex1 = Math.min(rstyle.srcX, rstyle.midX, rstyle.tgtX); 9445 ex2 = Math.max(rstyle.srcX, rstyle.midX, rstyle.tgtX); 9446 ey1 = Math.min(rstyle.srcY, rstyle.midY, rstyle.tgtY); 9447 ey2 = Math.max(rstyle.srcY, rstyle.midY, rstyle.tgtY); // take into account edge width 9448 9449 ex1 -= wHalf; 9450 ex2 += wHalf; 9451 ey1 -= wHalf; 9452 ey2 += wHalf; 9453 updateBounds(bounds, ex1, ey1, ex2, ey2); // precise edges 9454 //////////////// 9455 9456 if (curveStyle === 'haystack') { 9457 var hpts = rstyle.haystackPts; 9458 9459 if (hpts && hpts.length === 2) { 9460 ex1 = hpts[0].x; 9461 ey1 = hpts[0].y; 9462 ex2 = hpts[1].x; 9463 ey2 = hpts[1].y; 9464 9465 if (ex1 > ex2) { 9466 var temp = ex1; 9467 ex1 = ex2; 9468 ex2 = temp; 9469 } 9470 9471 if (ey1 > ey2) { 9472 var _temp = ey1; 9473 ey1 = ey2; 9474 ey2 = _temp; 9475 } 9476 9477 updateBounds(bounds, ex1 - wHalf, ey1 - wHalf, ex2 + wHalf, ey2 + wHalf); 9478 } 9479 } else if (curveStyle === 'bezier' || curveStyle === 'unbundled-bezier' || curveStyle === 'segments' || curveStyle === 'taxi') { 9480 var pts; 9481 9482 switch (curveStyle) { 9483 case 'bezier': 9484 case 'unbundled-bezier': 9485 pts = rstyle.bezierPts; 9486 break; 9487 9488 case 'segments': 9489 case 'taxi': 9490 pts = rstyle.linePts; 9491 break; 9492 } 9493 9494 if (pts != null) { 9495 for (var j = 0; j < pts.length; j++) { 9496 var pt = pts[j]; 9497 ex1 = pt.x - wHalf; 9498 ex2 = pt.x + wHalf; 9499 ey1 = pt.y - wHalf; 9500 ey2 = pt.y + wHalf; 9501 updateBounds(bounds, ex1, ey1, ex2, ey2); 9502 } 9503 } 9504 } // bezier-like or segment-like edge 9505 9506 } else { 9507 // headless or style disabled 9508 // fallback on source and target positions 9509 ////////////////////////////////////////// 9510 var n1 = ele.source(); 9511 var n1pos = n1.position(); 9512 var n2 = ele.target(); 9513 var n2pos = n2.position(); 9514 ex1 = n1pos.x; 9515 ex2 = n2pos.x; 9516 ey1 = n1pos.y; 9517 ey2 = n2pos.y; 9518 9519 if (ex1 > ex2) { 9520 var _temp2 = ex1; 9521 ex1 = ex2; 9522 ex2 = _temp2; 9523 } 9524 9525 if (ey1 > ey2) { 9526 var _temp3 = ey1; 9527 ey1 = ey2; 9528 ey2 = _temp3; 9529 } // take into account edge width 9530 9531 9532 ex1 -= wHalf; 9533 ex2 += wHalf; 9534 ey1 -= wHalf; 9535 ey2 += wHalf; 9536 updateBounds(bounds, ex1, ey1, ex2, ey2); 9537 } // headless or style disabled 9538 9539 } // edges 9540 // handle edge arrow size 9541 ///////////////////////// 9542 9543 9544 if (styleEnabled && options.includeEdges && isEdge) { 9545 updateBoundsFromArrow(bounds, ele, 'mid-source'); 9546 updateBoundsFromArrow(bounds, ele, 'mid-target'); 9547 updateBoundsFromArrow(bounds, ele, 'source'); 9548 updateBoundsFromArrow(bounds, ele, 'target'); 9549 } // ghost 9550 //////// 9551 9552 9553 if (styleEnabled) { 9554 var ghost = ele.pstyle('ghost').value === 'yes'; 9555 9556 if (ghost) { 9557 var gx = ele.pstyle('ghost-offset-x').pfValue; 9558 var gy = ele.pstyle('ghost-offset-y').pfValue; 9559 updateBounds(bounds, bounds.x1 + gx, bounds.y1 + gy, bounds.x2 + gx, bounds.y2 + gy); 9560 } 9561 } // always store the body bounds separately from the labels 9562 9563 9564 var bbBody = _p.bodyBounds = _p.bodyBounds || {}; 9565 assignBoundingBox(bbBody, bounds); 9566 expandBoundingBoxSides(bbBody, manualExpansion); 9567 expandBoundingBox(bbBody, 1); // expand to work around browser dimension inaccuracies 9568 // overlay 9569 ////////// 9570 9571 if (styleEnabled) { 9572 ex1 = bounds.x1; 9573 ex2 = bounds.x2; 9574 ey1 = bounds.y1; 9575 ey2 = bounds.y2; 9576 updateBounds(bounds, ex1 - overlayPadding, ey1 - overlayPadding, ex2 + overlayPadding, ey2 + overlayPadding); 9577 } // always store the body bounds separately from the labels 9578 9579 9580 var bbOverlay = _p.overlayBounds = _p.overlayBounds || {}; 9581 assignBoundingBox(bbOverlay, bounds); 9582 expandBoundingBoxSides(bbOverlay, manualExpansion); 9583 expandBoundingBox(bbOverlay, 1); // expand to work around browser dimension inaccuracies 9584 // handle label dimensions 9585 ////////////////////////// 9586 9587 var bbLabels = _p.labelBounds = _p.labelBounds || {}; 9588 9589 if (bbLabels.all != null) { 9590 clearBoundingBox(bbLabels.all); 9591 } else { 9592 bbLabels.all = makeBoundingBox(); 9593 } 9594 9595 if (styleEnabled && options.includeLabels) { 9596 if (options.includeMainLabels) { 9597 updateBoundsFromLabel(bounds, ele, null); 9598 } 9599 9600 if (isEdge) { 9601 if (options.includeSourceLabels) { 9602 updateBoundsFromLabel(bounds, ele, 'source'); 9603 } 9604 9605 if (options.includeTargetLabels) { 9606 updateBoundsFromLabel(bounds, ele, 'target'); 9607 } 9608 } 9609 } // style enabled for labels 9610 9611 } // if displayed 9612 9613 9614 bounds.x1 = noninf(bounds.x1); 9615 bounds.y1 = noninf(bounds.y1); 9616 bounds.x2 = noninf(bounds.x2); 9617 bounds.y2 = noninf(bounds.y2); 9618 bounds.w = noninf(bounds.x2 - bounds.x1); 9619 bounds.h = noninf(bounds.y2 - bounds.y1); 9620 9621 if (bounds.w > 0 && bounds.h > 0 && displayed) { 9622 expandBoundingBoxSides(bounds, manualExpansion); // expand bounds by 1 because antialiasing can increase the visual/effective size by 1 on all sides 9623 9624 expandBoundingBox(bounds, 1); 9625 } 9626 9627 return bounds; 9628 }; 9629 9630 var getKey = function getKey(opts) { 9631 var i = 0; 9632 9633 var tf = function tf(val) { 9634 return (val ? 1 : 0) << i++; 9635 }; 9636 9637 var key = 0; 9638 key += tf(opts.incudeNodes); 9639 key += tf(opts.includeEdges); 9640 key += tf(opts.includeLabels); 9641 key += tf(opts.includeMainLabels); 9642 key += tf(opts.includeSourceLabels); 9643 key += tf(opts.includeTargetLabels); 9644 key += tf(opts.includeOverlays); 9645 return key; 9646 }; 9647 9648 var getBoundingBoxPosKey = function getBoundingBoxPosKey(ele) { 9649 if (ele.isEdge()) { 9650 var p1 = ele.source().position(); 9651 var p2 = ele.target().position(); 9652 9653 var r = function r(x) { 9654 return Math.round(x); 9655 }; 9656 9657 return hashIntsArray([r(p1.x), r(p1.y), r(p2.x), r(p2.y)]); 9658 } else { 9659 return 0; 9660 } 9661 }; 9662 9663 var cachedBoundingBoxImpl = function cachedBoundingBoxImpl(ele, opts) { 9664 var _p = ele._private; 9665 var bb; 9666 var isEdge = ele.isEdge(); 9667 var key = opts == null ? defBbOptsKey : getKey(opts); 9668 var usingDefOpts = key === defBbOptsKey; 9669 var currPosKey = getBoundingBoxPosKey(ele); 9670 var isPosKeySame = _p.bbCachePosKey === currPosKey; 9671 var useCache = opts.useCache && isPosKeySame; 9672 9673 var isDirty = function isDirty(ele) { 9674 return ele._private.bbCache == null; 9675 }; 9676 9677 var needRecalc = !useCache || isDirty(ele) || isEdge && isDirty(ele.source()) || isDirty(ele.target()); 9678 9679 if (needRecalc) { 9680 if (!isPosKeySame) { 9681 ele.recalculateRenderedStyle(); 9682 } 9683 9684 bb = boundingBoxImpl(ele, defBbOpts); 9685 _p.bbCache = bb; 9686 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; 9687 _p.bbCachePosKey = currPosKey; 9688 } else { 9689 bb = _p.bbCache; 9690 } 9691 9692 if (!needRecalc && (_p.bbCacheShift.x !== 0 || _p.bbCacheShift.y !== 0)) { 9693 var shift = assignShiftToBoundingBox; 9694 var delta = _p.bbCacheShift; 9695 9696 var safeShift = function safeShift(bb, delta) { 9697 if (bb != null) { 9698 shift(bb, delta); 9699 } 9700 }; 9701 9702 shift(bb, delta); 9703 var bodyBounds = _p.bodyBounds, 9704 overlayBounds = _p.overlayBounds, 9705 labelBounds = _p.labelBounds, 9706 arrowBounds = _p.arrowBounds; 9707 safeShift(bodyBounds, delta); 9708 safeShift(overlayBounds, delta); 9709 9710 if (arrowBounds != null) { 9711 safeShift(arrowBounds.source, delta); 9712 safeShift(arrowBounds.target, delta); 9713 safeShift(arrowBounds['mid-source'], delta); 9714 safeShift(arrowBounds['mid-target'], delta); 9715 } 9716 9717 if (labelBounds != null) { 9718 safeShift(labelBounds.main, delta); 9719 safeShift(labelBounds.all, delta); 9720 safeShift(labelBounds.source, delta); 9721 safeShift(labelBounds.target, delta); 9722 } 9723 } // always reset the shift, because we either applied the shift or cleared it by doing a fresh recalc 9724 9725 9726 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; // not using def opts => need to build up bb from combination of sub bbs 9727 9728 if (!usingDefOpts) { 9729 var isNode = ele.isNode(); 9730 bb = makeBoundingBox(); 9731 9732 if (opts.includeNodes && isNode || opts.includeEdges && !isNode) { 9733 if (opts.includeOverlays) { 9734 updateBoundsFromBox(bb, _p.overlayBounds); 9735 } else { 9736 updateBoundsFromBox(bb, _p.bodyBounds); 9737 } 9738 } 9739 9740 if (opts.includeLabels) { 9741 if (opts.includeMainLabels && (!isEdge || opts.includeSourceLabels && opts.includeTargetLabels)) { 9742 updateBoundsFromBox(bb, _p.labelBounds.all); 9743 } else { 9744 if (opts.includeMainLabels) { 9745 updateBoundsFromBox(bb, _p.labelBounds.mainRot); 9746 } 9747 9748 if (opts.includeSourceLabels) { 9749 updateBoundsFromBox(bb, _p.labelBounds.sourceRot); 9750 } 9751 9752 if (opts.includeTargetLabels) { 9753 updateBoundsFromBox(bb, _p.labelBounds.targetRot); 9754 } 9755 } 9756 } 9757 9758 bb.w = bb.x2 - bb.x1; 9759 bb.h = bb.y2 - bb.y1; 9760 } 9761 9762 return bb; 9763 }; 9764 9765 var defBbOpts = { 9766 includeNodes: true, 9767 includeEdges: true, 9768 includeLabels: true, 9769 includeMainLabels: true, 9770 includeSourceLabels: true, 9771 includeTargetLabels: true, 9772 includeOverlays: true, 9773 useCache: true 9774 }; 9775 var defBbOptsKey = getKey(defBbOpts); 9776 var filledBbOpts = defaults(defBbOpts); 9777 9778 elesfn$k.boundingBox = function (options) { 9779 var bounds; // the main usecase is ele.boundingBox() for a single element with no/def options 9780 // specified s.t. the cache is used, so check for this case to make it faster by 9781 // avoiding the overhead of the rest of the function 9782 9783 if (this.length === 1 && this[0]._private.bbCache != null && (options === undefined || options.useCache === undefined || options.useCache === true)) { 9784 if (options === undefined) { 9785 options = defBbOpts; 9786 } else { 9787 options = filledBbOpts(options); 9788 } 9789 9790 bounds = cachedBoundingBoxImpl(this[0], options); 9791 } else { 9792 bounds = makeBoundingBox(); 9793 options = options || defBbOpts; 9794 var opts = filledBbOpts(options); 9795 var eles = this; 9796 var cy = eles.cy(); 9797 var styleEnabled = cy.styleEnabled(); 9798 9799 if (styleEnabled) { 9800 for (var i = 0; i < eles.length; i++) { 9801 var ele = eles[i]; 9802 var _p = ele._private; 9803 var currPosKey = getBoundingBoxPosKey(ele); 9804 var isPosKeySame = _p.bbCachePosKey === currPosKey; 9805 var useCache = opts.useCache && isPosKeySame; 9806 ele.recalculateRenderedStyle(useCache); 9807 } 9808 } 9809 9810 this.updateCompoundBounds(); 9811 9812 for (var _i = 0; _i < eles.length; _i++) { 9813 var _ele = eles[_i]; 9814 updateBoundsFromBox(bounds, cachedBoundingBoxImpl(_ele, opts)); 9815 } 9816 } 9817 9818 bounds.x1 = noninf(bounds.x1); 9819 bounds.y1 = noninf(bounds.y1); 9820 bounds.x2 = noninf(bounds.x2); 9821 bounds.y2 = noninf(bounds.y2); 9822 bounds.w = noninf(bounds.x2 - bounds.x1); 9823 bounds.h = noninf(bounds.y2 - bounds.y1); 9824 return bounds; 9825 }; 9826 9827 elesfn$k.dirtyBoundingBoxCache = function () { 9828 for (var i = 0; i < this.length; i++) { 9829 var _p = this[i]._private; 9830 _p.bbCache = null; 9831 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; 9832 _p.bbCachePosKey = null; 9833 _p.bodyBounds = null; 9834 _p.overlayBounds = null; 9835 _p.labelBounds.all = null; 9836 _p.labelBounds.source = null; 9837 _p.labelBounds.target = null; 9838 _p.labelBounds.main = null; 9839 _p.labelBounds.sourceRot = null; 9840 _p.labelBounds.targetRot = null; 9841 _p.labelBounds.mainRot = null; 9842 _p.arrowBounds.source = null; 9843 _p.arrowBounds.target = null; 9844 _p.arrowBounds['mid-source'] = null; 9845 _p.arrowBounds['mid-target'] = null; 9846 } 9847 9848 this.emitAndNotify('bounds'); 9849 return this; 9850 }; 9851 9852 elesfn$k.shiftCachedBoundingBox = function (delta) { 9853 for (var i = 0; i < this.length; i++) { 9854 var ele = this[i]; 9855 var _p = ele._private; 9856 var bb = _p.bbCache; 9857 9858 if (bb != null) { 9859 _p.bbCacheShift.x += delta.x; 9860 _p.bbCacheShift.y += delta.y; 9861 } 9862 } 9863 9864 this.emitAndNotify('bounds'); 9865 return this; 9866 }; // private helper to get bounding box for custom node positions 9867 // - good for perf in certain cases but currently requires dirtying the rendered style 9868 // - would be better to not modify the nodes but the nodes are read directly everywhere in the renderer... 9869 // - try to use for only things like discrete layouts where the node position would change anyway 9870 9871 9872 elesfn$k.boundingBoxAt = function (fn) { 9873 var nodes = this.nodes(); 9874 var cy = this.cy(); 9875 var hasCompoundNodes = cy.hasCompoundNodes(); 9876 9877 if (hasCompoundNodes) { 9878 nodes = nodes.filter(function (node) { 9879 return !node.isParent(); 9880 }); 9881 } 9882 9883 if (plainObject(fn)) { 9884 var obj = fn; 9885 9886 fn = function fn() { 9887 return obj; 9888 }; 9889 } 9890 9891 var storeOldPos = function storeOldPos(node, i) { 9892 return node._private.bbAtOldPos = fn(node, i); 9893 }; 9894 9895 var getOldPos = function getOldPos(node) { 9896 return node._private.bbAtOldPos; 9897 }; 9898 9899 cy.startBatch(); 9900 nodes.forEach(storeOldPos).silentPositions(fn); 9901 9902 if (hasCompoundNodes) { 9903 this.updateCompoundBounds(true); // force update b/c we're inside a batch cycle 9904 } 9905 9906 var bb = copyBoundingBox(this.boundingBox({ 9907 useCache: false 9908 })); 9909 nodes.silentPositions(getOldPos); 9910 cy.endBatch(); 9911 return bb; 9912 }; 9913 9914 fn$3.boundingbox = fn$3.bb = fn$3.boundingBox; 9915 fn$3.renderedBoundingbox = fn$3.renderedBoundingBox; 9916 var bounds = elesfn$k; 9917 9918 var fn$4, elesfn$l; 9919 fn$4 = elesfn$l = {}; 9920 9921 var defineDimFns = function defineDimFns(opts) { 9922 opts.uppercaseName = capitalize(opts.name); 9923 opts.autoName = 'auto' + opts.uppercaseName; 9924 opts.labelName = 'label' + opts.uppercaseName; 9925 opts.outerName = 'outer' + opts.uppercaseName; 9926 opts.uppercaseOuterName = capitalize(opts.outerName); 9927 9928 fn$4[opts.name] = function dimImpl() { 9929 var ele = this[0]; 9930 var _p = ele._private; 9931 var cy = _p.cy; 9932 var styleEnabled = cy._private.styleEnabled; 9933 9934 if (ele) { 9935 if (styleEnabled) { 9936 if (ele.isParent()) { 9937 ele.updateCompoundBounds(); 9938 return _p[opts.autoName] || 0; 9939 } 9940 9941 var d = ele.pstyle(opts.name); 9942 9943 switch (d.strValue) { 9944 case 'label': 9945 ele.recalculateRenderedStyle(); 9946 return _p.rstyle[opts.labelName] || 0; 9947 9948 default: 9949 return d.pfValue; 9950 } 9951 } else { 9952 return 1; 9953 } 9954 } 9955 }; 9956 9957 fn$4['outer' + opts.uppercaseName] = function outerDimImpl() { 9958 var ele = this[0]; 9959 var _p = ele._private; 9960 var cy = _p.cy; 9961 var styleEnabled = cy._private.styleEnabled; 9962 9963 if (ele) { 9964 if (styleEnabled) { 9965 var dim = ele[opts.name](); 9966 var border = ele.pstyle('border-width').pfValue; // n.b. 1/2 each side 9967 9968 var padding = 2 * ele.padding(); 9969 return dim + border + padding; 9970 } else { 9971 return 1; 9972 } 9973 } 9974 }; 9975 9976 fn$4['rendered' + opts.uppercaseName] = function renderedDimImpl() { 9977 var ele = this[0]; 9978 9979 if (ele) { 9980 var d = ele[opts.name](); 9981 return d * this.cy().zoom(); 9982 } 9983 }; 9984 9985 fn$4['rendered' + opts.uppercaseOuterName] = function renderedOuterDimImpl() { 9986 var ele = this[0]; 9987 9988 if (ele) { 9989 var od = ele[opts.outerName](); 9990 return od * this.cy().zoom(); 9991 } 9992 }; 9993 }; 9994 9995 defineDimFns({ 9996 name: 'width' 9997 }); 9998 defineDimFns({ 9999 name: 'height' 10000 }); 10001 10002 elesfn$l.padding = function () { 10003 var ele = this[0]; 10004 var _p = ele._private; 10005 10006 if (ele.isParent()) { 10007 ele.updateCompoundBounds(); 10008 10009 if (_p.autoPadding !== undefined) { 10010 return _p.autoPadding; 10011 } else { 10012 return ele.pstyle('padding').pfValue; 10013 } 10014 } else { 10015 return ele.pstyle('padding').pfValue; 10016 } 10017 }; 10018 10019 elesfn$l.paddedHeight = function () { 10020 var ele = this[0]; 10021 return ele.height() + 2 * ele.padding(); 10022 }; 10023 10024 elesfn$l.paddedWidth = function () { 10025 var ele = this[0]; 10026 return ele.width() + 2 * ele.padding(); 10027 }; 10028 10029 var widthHeight = elesfn$l; 10030 10031 var ifEdge = function ifEdge(ele, getValue) { 10032 if (ele.isEdge()) { 10033 return getValue(ele); 10034 } 10035 }; 10036 10037 var ifEdgeRenderedPosition = function ifEdgeRenderedPosition(ele, getPoint) { 10038 if (ele.isEdge()) { 10039 var cy = ele.cy(); 10040 return modelToRenderedPosition(getPoint(ele), cy.zoom(), cy.pan()); 10041 } 10042 }; 10043 10044 var ifEdgeRenderedPositions = function ifEdgeRenderedPositions(ele, getPoints) { 10045 if (ele.isEdge()) { 10046 var cy = ele.cy(); 10047 var pan = cy.pan(); 10048 var zoom = cy.zoom(); 10049 return getPoints(ele).map(function (p) { 10050 return modelToRenderedPosition(p, zoom, pan); 10051 }); 10052 } 10053 }; 10054 10055 var controlPoints = function controlPoints(ele) { 10056 return ele.renderer().getControlPoints(ele); 10057 }; 10058 10059 var segmentPoints = function segmentPoints(ele) { 10060 return ele.renderer().getSegmentPoints(ele); 10061 }; 10062 10063 var sourceEndpoint = function sourceEndpoint(ele) { 10064 return ele.renderer().getSourceEndpoint(ele); 10065 }; 10066 10067 var targetEndpoint = function targetEndpoint(ele) { 10068 return ele.renderer().getTargetEndpoint(ele); 10069 }; 10070 10071 var midpoint = function midpoint(ele) { 10072 return ele.renderer().getEdgeMidpoint(ele); 10073 }; 10074 10075 var pts = { 10076 controlPoints: { 10077 get: controlPoints, 10078 mult: true 10079 }, 10080 segmentPoints: { 10081 get: segmentPoints, 10082 mult: true 10083 }, 10084 sourceEndpoint: { 10085 get: sourceEndpoint 10086 }, 10087 targetEndpoint: { 10088 get: targetEndpoint 10089 }, 10090 midpoint: { 10091 get: midpoint 10092 } 10093 }; 10094 10095 var renderedName = function renderedName(name) { 10096 return 'rendered' + name[0].toUpperCase() + name.substr(1); 10097 }; 10098 10099 var edgePoints = Object.keys(pts).reduce(function (obj, name) { 10100 var spec = pts[name]; 10101 var rName = renderedName(name); 10102 10103 obj[name] = function () { 10104 return ifEdge(this, spec.get); 10105 }; 10106 10107 if (spec.mult) { 10108 obj[rName] = function () { 10109 return ifEdgeRenderedPositions(this, spec.get); 10110 }; 10111 } else { 10112 obj[rName] = function () { 10113 return ifEdgeRenderedPosition(this, spec.get); 10114 }; 10115 } 10116 10117 return obj; 10118 }, {}); 10119 10120 var dimensions = extend({}, position, bounds, widthHeight, edgePoints); 10121 10122 /*! 10123 Event object based on jQuery events, MIT license 10124 10125 https://jquery.org/license/ 10126 https://tldrlegal.com/license/mit-license 10127 https://github.com/jquery/jquery/blob/master/src/event.js 10128 */ 10129 var Event = function Event(src, props) { 10130 this.recycle(src, props); 10131 }; 10132 10133 function returnFalse() { 10134 return false; 10135 } 10136 10137 function returnTrue() { 10138 return true; 10139 } // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 10140 10141 10142 Event.prototype = { 10143 instanceString: function instanceString() { 10144 return 'event'; 10145 }, 10146 recycle: function recycle(src, props) { 10147 this.isImmediatePropagationStopped = this.isPropagationStopped = this.isDefaultPrevented = returnFalse; 10148 10149 if (src != null && src.preventDefault) { 10150 // Browser Event object 10151 this.type = src.type; // Events bubbling up the document may have been marked as prevented 10152 // by a handler lower down the tree; reflect the correct value. 10153 10154 this.isDefaultPrevented = src.defaultPrevented ? returnTrue : returnFalse; 10155 } else if (src != null && src.type) { 10156 // Plain object containing all event details 10157 props = src; 10158 } else { 10159 // Event string 10160 this.type = src; 10161 } // Put explicitly provided properties onto the event object 10162 10163 10164 if (props != null) { 10165 // more efficient to manually copy fields we use 10166 this.originalEvent = props.originalEvent; 10167 this.type = props.type != null ? props.type : this.type; 10168 this.cy = props.cy; 10169 this.target = props.target; 10170 this.position = props.position; 10171 this.renderedPosition = props.renderedPosition; 10172 this.namespace = props.namespace; 10173 this.layout = props.layout; 10174 } 10175 10176 if (this.cy != null && this.position != null && this.renderedPosition == null) { 10177 // create a rendered position based on the passed position 10178 var pos = this.position; 10179 var zoom = this.cy.zoom(); 10180 var pan = this.cy.pan(); 10181 this.renderedPosition = { 10182 x: pos.x * zoom + pan.x, 10183 y: pos.y * zoom + pan.y 10184 }; 10185 } // Create a timestamp if incoming event doesn't have one 10186 10187 10188 this.timeStamp = src && src.timeStamp || Date.now(); 10189 }, 10190 preventDefault: function preventDefault() { 10191 this.isDefaultPrevented = returnTrue; 10192 var e = this.originalEvent; 10193 10194 if (!e) { 10195 return; 10196 } // if preventDefault exists run it on the original event 10197 10198 10199 if (e.preventDefault) { 10200 e.preventDefault(); 10201 } 10202 }, 10203 stopPropagation: function stopPropagation() { 10204 this.isPropagationStopped = returnTrue; 10205 var e = this.originalEvent; 10206 10207 if (!e) { 10208 return; 10209 } // if stopPropagation exists run it on the original event 10210 10211 10212 if (e.stopPropagation) { 10213 e.stopPropagation(); 10214 } 10215 }, 10216 stopImmediatePropagation: function stopImmediatePropagation() { 10217 this.isImmediatePropagationStopped = returnTrue; 10218 this.stopPropagation(); 10219 }, 10220 isDefaultPrevented: returnFalse, 10221 isPropagationStopped: returnFalse, 10222 isImmediatePropagationStopped: returnFalse 10223 }; 10224 10225 var eventRegex = /^([^.]+)(\.(?:[^.]+))?$/; // regex for matching event strings (e.g. "click.namespace") 10226 10227 var universalNamespace = '.*'; // matches as if no namespace specified and prevents users from unbinding accidentally 10228 10229 var defaults$8 = { 10230 qualifierCompare: function qualifierCompare(q1, q2) { 10231 return q1 === q2; 10232 }, 10233 eventMatches: function eventMatches() 10234 /*context, listener, eventObj*/ 10235 { 10236 return true; 10237 }, 10238 addEventFields: function addEventFields() 10239 /*context, evt*/ 10240 {}, 10241 callbackContext: function callbackContext(context 10242 /*, listener, eventObj*/ 10243 ) { 10244 return context; 10245 }, 10246 beforeEmit: function beforeEmit() 10247 /* context, listener, eventObj */ 10248 {}, 10249 afterEmit: function afterEmit() 10250 /* context, listener, eventObj */ 10251 {}, 10252 bubble: function bubble() 10253 /*context*/ 10254 { 10255 return false; 10256 }, 10257 parent: function parent() 10258 /*context*/ 10259 { 10260 return null; 10261 }, 10262 context: null 10263 }; 10264 var defaultsKeys = Object.keys(defaults$8); 10265 var emptyOpts = {}; 10266 10267 function Emitter() { 10268 var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : emptyOpts; 10269 var context = arguments.length > 1 ? arguments[1] : undefined; 10270 10271 // micro-optimisation vs Object.assign() -- reduces Element instantiation time 10272 for (var i = 0; i < defaultsKeys.length; i++) { 10273 var key = defaultsKeys[i]; 10274 this[key] = opts[key] || defaults$8[key]; 10275 } 10276 10277 this.context = context || this.context; 10278 this.listeners = []; 10279 this.emitting = 0; 10280 } 10281 10282 var p = Emitter.prototype; 10283 10284 var forEachEvent = function forEachEvent(self, handler, events, qualifier, callback, conf, confOverrides) { 10285 if (fn(qualifier)) { 10286 callback = qualifier; 10287 qualifier = null; 10288 } 10289 10290 if (confOverrides) { 10291 if (conf == null) { 10292 conf = confOverrides; 10293 } else { 10294 conf = extend({}, conf, confOverrides); 10295 } 10296 } 10297 10298 var eventList = array(events) ? events : events.split(/\s+/); 10299 10300 for (var i = 0; i < eventList.length; i++) { 10301 var evt = eventList[i]; 10302 10303 if (emptyString(evt)) { 10304 continue; 10305 } 10306 10307 var match = evt.match(eventRegex); // type[.namespace] 10308 10309 if (match) { 10310 var type = match[1]; 10311 var namespace = match[2] ? match[2] : null; 10312 var ret = handler(self, evt, type, namespace, qualifier, callback, conf); 10313 10314 if (ret === false) { 10315 break; 10316 } // allow exiting early 10317 10318 } 10319 } 10320 }; 10321 10322 var makeEventObj = function makeEventObj(self, obj) { 10323 self.addEventFields(self.context, obj); 10324 return new Event(obj.type, obj); 10325 }; 10326 10327 var forEachEventObj = function forEachEventObj(self, handler, events) { 10328 if (event(events)) { 10329 handler(self, events); 10330 return; 10331 } else if (plainObject(events)) { 10332 handler(self, makeEventObj(self, events)); 10333 return; 10334 } 10335 10336 var eventList = array(events) ? events : events.split(/\s+/); 10337 10338 for (var i = 0; i < eventList.length; i++) { 10339 var evt = eventList[i]; 10340 10341 if (emptyString(evt)) { 10342 continue; 10343 } 10344 10345 var match = evt.match(eventRegex); // type[.namespace] 10346 10347 if (match) { 10348 var type = match[1]; 10349 var namespace = match[2] ? match[2] : null; 10350 var eventObj = makeEventObj(self, { 10351 type: type, 10352 namespace: namespace, 10353 target: self.context 10354 }); 10355 handler(self, eventObj); 10356 } 10357 } 10358 }; 10359 10360 p.on = p.addListener = function (events, qualifier, callback, conf, confOverrides) { 10361 forEachEvent(this, function (self, event, type, namespace, qualifier, callback, conf) { 10362 if (fn(callback)) { 10363 self.listeners.push({ 10364 event: event, 10365 // full event string 10366 callback: callback, 10367 // callback to run 10368 type: type, 10369 // the event type (e.g. 'click') 10370 namespace: namespace, 10371 // the event namespace (e.g. ".foo") 10372 qualifier: qualifier, 10373 // a restriction on whether to match this emitter 10374 conf: conf // additional configuration 10375 10376 }); 10377 } 10378 }, events, qualifier, callback, conf, confOverrides); 10379 return this; 10380 }; 10381 10382 p.one = function (events, qualifier, callback, conf) { 10383 return this.on(events, qualifier, callback, conf, { 10384 one: true 10385 }); 10386 }; 10387 10388 p.removeListener = p.off = function (events, qualifier, callback, conf) { 10389 var _this = this; 10390 10391 if (this.emitting !== 0) { 10392 this.listeners = copyArray(this.listeners); 10393 } 10394 10395 var listeners = this.listeners; 10396 10397 var _loop = function _loop(i) { 10398 var listener = listeners[i]; 10399 forEachEvent(_this, function (self, event, type, namespace, qualifier, callback 10400 /*, conf*/ 10401 ) { 10402 if ((listener.type === type || events === '*') && (!namespace && listener.namespace !== '.*' || listener.namespace === namespace) && (!qualifier || self.qualifierCompare(listener.qualifier, qualifier)) && (!callback || listener.callback === callback)) { 10403 listeners.splice(i, 1); 10404 return false; 10405 } 10406 }, events, qualifier, callback, conf); 10407 }; 10408 10409 for (var i = listeners.length - 1; i >= 0; i--) { 10410 _loop(i); 10411 } 10412 10413 return this; 10414 }; 10415 10416 p.removeAllListeners = function () { 10417 return this.removeListener('*'); 10418 }; 10419 10420 p.emit = p.trigger = function (events, extraParams, manualCallback) { 10421 var listeners = this.listeners; 10422 var numListenersBeforeEmit = listeners.length; 10423 this.emitting++; 10424 10425 if (!array(extraParams)) { 10426 extraParams = [extraParams]; 10427 } 10428 10429 forEachEventObj(this, function (self, eventObj) { 10430 if (manualCallback != null) { 10431 listeners = [{ 10432 event: eventObj.event, 10433 type: eventObj.type, 10434 namespace: eventObj.namespace, 10435 callback: manualCallback 10436 }]; 10437 numListenersBeforeEmit = listeners.length; 10438 } 10439 10440 var _loop2 = function _loop2(i) { 10441 var listener = listeners[i]; 10442 10443 if (listener.type === eventObj.type && (!listener.namespace || listener.namespace === eventObj.namespace || listener.namespace === universalNamespace) && self.eventMatches(self.context, listener, eventObj)) { 10444 var args = [eventObj]; 10445 10446 if (extraParams != null) { 10447 push(args, extraParams); 10448 } 10449 10450 self.beforeEmit(self.context, listener, eventObj); 10451 10452 if (listener.conf && listener.conf.one) { 10453 self.listeners = self.listeners.filter(function (l) { 10454 return l !== listener; 10455 }); 10456 } 10457 10458 var context = self.callbackContext(self.context, listener, eventObj); 10459 var ret = listener.callback.apply(context, args); 10460 self.afterEmit(self.context, listener, eventObj); 10461 10462 if (ret === false) { 10463 eventObj.stopPropagation(); 10464 eventObj.preventDefault(); 10465 } 10466 } // if listener matches 10467 10468 }; 10469 10470 for (var i = 0; i < numListenersBeforeEmit; i++) { 10471 _loop2(i); 10472 } // for listener 10473 10474 10475 if (self.bubble(self.context) && !eventObj.isPropagationStopped()) { 10476 self.parent(self.context).emit(eventObj, extraParams); 10477 } 10478 }, events); 10479 this.emitting--; 10480 return this; 10481 }; 10482 10483 var emitterOptions = { 10484 qualifierCompare: function qualifierCompare(selector1, selector2) { 10485 if (selector1 == null || selector2 == null) { 10486 return selector1 == null && selector2 == null; 10487 } else { 10488 return selector1.sameText(selector2); 10489 } 10490 }, 10491 eventMatches: function eventMatches(ele, listener, eventObj) { 10492 var selector = listener.qualifier; 10493 10494 if (selector != null) { 10495 return ele !== eventObj.target && element(eventObj.target) && selector.matches(eventObj.target); 10496 } 10497 10498 return true; 10499 }, 10500 addEventFields: function addEventFields(ele, evt) { 10501 evt.cy = ele.cy(); 10502 evt.target = ele; 10503 }, 10504 callbackContext: function callbackContext(ele, listener, eventObj) { 10505 return listener.qualifier != null ? eventObj.target : ele; 10506 }, 10507 beforeEmit: function beforeEmit(context, listener 10508 /*, eventObj*/ 10509 ) { 10510 if (listener.conf && listener.conf.once) { 10511 listener.conf.onceCollection.removeListener(listener.event, listener.qualifier, listener.callback); 10512 } 10513 }, 10514 bubble: function bubble() { 10515 return true; 10516 }, 10517 parent: function parent(ele) { 10518 return ele.isChild() ? ele.parent() : ele.cy(); 10519 } 10520 }; 10521 10522 var argSelector = function argSelector(arg) { 10523 if (string(arg)) { 10524 return new Selector(arg); 10525 } else { 10526 return arg; 10527 } 10528 }; 10529 10530 var elesfn$m = { 10531 createEmitter: function createEmitter() { 10532 for (var i = 0; i < this.length; i++) { 10533 var ele = this[i]; 10534 var _p = ele._private; 10535 10536 if (!_p.emitter) { 10537 _p.emitter = new Emitter(emitterOptions, ele); 10538 } 10539 } 10540 10541 return this; 10542 }, 10543 emitter: function emitter() { 10544 return this._private.emitter; 10545 }, 10546 on: function on(events, selector, callback) { 10547 var argSel = argSelector(selector); 10548 10549 for (var i = 0; i < this.length; i++) { 10550 var ele = this[i]; 10551 ele.emitter().on(events, argSel, callback); 10552 } 10553 10554 return this; 10555 }, 10556 removeListener: function removeListener(events, selector, callback) { 10557 var argSel = argSelector(selector); 10558 10559 for (var i = 0; i < this.length; i++) { 10560 var ele = this[i]; 10561 ele.emitter().removeListener(events, argSel, callback); 10562 } 10563 10564 return this; 10565 }, 10566 removeAllListeners: function removeAllListeners() { 10567 for (var i = 0; i < this.length; i++) { 10568 var ele = this[i]; 10569 ele.emitter().removeAllListeners(); 10570 } 10571 10572 return this; 10573 }, 10574 one: function one(events, selector, callback) { 10575 var argSel = argSelector(selector); 10576 10577 for (var i = 0; i < this.length; i++) { 10578 var ele = this[i]; 10579 ele.emitter().one(events, argSel, callback); 10580 } 10581 10582 return this; 10583 }, 10584 once: function once(events, selector, callback) { 10585 var argSel = argSelector(selector); 10586 10587 for (var i = 0; i < this.length; i++) { 10588 var ele = this[i]; 10589 ele.emitter().on(events, argSel, callback, { 10590 once: true, 10591 onceCollection: this 10592 }); 10593 } 10594 }, 10595 emit: function emit(events, extraParams) { 10596 for (var i = 0; i < this.length; i++) { 10597 var ele = this[i]; 10598 ele.emitter().emit(events, extraParams); 10599 } 10600 10601 return this; 10602 }, 10603 emitAndNotify: function emitAndNotify(event, extraParams) { 10604 // for internal use only 10605 if (this.length === 0) { 10606 return; 10607 } // empty collections don't need to notify anything 10608 // notify renderer 10609 10610 10611 this.cy().notify(event, this); 10612 this.emit(event, extraParams); 10613 return this; 10614 } 10615 }; 10616 define$3.eventAliasesOn(elesfn$m); 10617 10618 var elesfn$n = { 10619 nodes: function nodes(selector) { 10620 return this.filter(function (ele) { 10621 return ele.isNode(); 10622 }).filter(selector); 10623 }, 10624 edges: function edges(selector) { 10625 return this.filter(function (ele) { 10626 return ele.isEdge(); 10627 }).filter(selector); 10628 }, 10629 // internal helper to get nodes and edges as separate collections with single iteration over elements 10630 byGroup: function byGroup() { 10631 var nodes = this.spawn(); 10632 var edges = this.spawn(); 10633 10634 for (var i = 0; i < this.length; i++) { 10635 var ele = this[i]; 10636 10637 if (ele.isNode()) { 10638 nodes.merge(ele); 10639 } else { 10640 edges.merge(ele); 10641 } 10642 } 10643 10644 return { 10645 nodes: nodes, 10646 edges: edges 10647 }; 10648 }, 10649 filter: function filter(_filter, thisArg) { 10650 if (_filter === undefined) { 10651 // check this first b/c it's the most common/performant case 10652 return this; 10653 } else if (string(_filter) || elementOrCollection(_filter)) { 10654 return new Selector(_filter).filter(this); 10655 } else if (fn(_filter)) { 10656 var filterEles = this.spawn(); 10657 var eles = this; 10658 10659 for (var i = 0; i < eles.length; i++) { 10660 var ele = eles[i]; 10661 var include = thisArg ? _filter.apply(thisArg, [ele, i, eles]) : _filter(ele, i, eles); 10662 10663 if (include) { 10664 filterEles.merge(ele); 10665 } 10666 } 10667 10668 return filterEles; 10669 } 10670 10671 return this.spawn(); // if not handled by above, give 'em an empty collection 10672 }, 10673 not: function not(toRemove) { 10674 if (!toRemove) { 10675 return this; 10676 } else { 10677 if (string(toRemove)) { 10678 toRemove = this.filter(toRemove); 10679 } 10680 10681 var elements = []; 10682 var rMap = toRemove._private.map; 10683 10684 for (var i = 0; i < this.length; i++) { 10685 var element = this[i]; 10686 var remove = rMap.has(element.id()); 10687 10688 if (!remove) { 10689 elements.push(element); 10690 } 10691 } 10692 10693 return this.spawn(elements); 10694 } 10695 }, 10696 absoluteComplement: function absoluteComplement() { 10697 var cy = this.cy(); 10698 return cy.mutableElements().not(this); 10699 }, 10700 intersect: function intersect(other) { 10701 // if a selector is specified, then filter by it instead 10702 if (string(other)) { 10703 var selector = other; 10704 return this.filter(selector); 10705 } 10706 10707 var elements = []; 10708 var col1 = this; 10709 var col2 = other; 10710 var col1Smaller = this.length < other.length; 10711 var map2 = col1Smaller ? col2._private.map : col1._private.map; 10712 var col = col1Smaller ? col1 : col2; 10713 10714 for (var i = 0; i < col.length; i++) { 10715 var id = col[i]._private.data.id; 10716 var entry = map2.get(id); 10717 10718 if (entry) { 10719 elements.push(entry.ele); 10720 } 10721 } 10722 10723 return this.spawn(elements); 10724 }, 10725 xor: function xor(other) { 10726 var cy = this._private.cy; 10727 10728 if (string(other)) { 10729 other = cy.$(other); 10730 } 10731 10732 var elements = []; 10733 var col1 = this; 10734 var col2 = other; 10735 10736 var add = function add(col, other) { 10737 for (var i = 0; i < col.length; i++) { 10738 var ele = col[i]; 10739 var id = ele._private.data.id; 10740 var inOther = other.hasElementWithId(id); 10741 10742 if (!inOther) { 10743 elements.push(ele); 10744 } 10745 } 10746 }; 10747 10748 add(col1, col2); 10749 add(col2, col1); 10750 return this.spawn(elements); 10751 }, 10752 diff: function diff(other) { 10753 var cy = this._private.cy; 10754 10755 if (string(other)) { 10756 other = cy.$(other); 10757 } 10758 10759 var left = []; 10760 var right = []; 10761 var both = []; 10762 var col1 = this; 10763 var col2 = other; 10764 10765 var add = function add(col, other, retEles) { 10766 for (var i = 0; i < col.length; i++) { 10767 var ele = col[i]; 10768 var id = ele._private.data.id; 10769 var inOther = other.hasElementWithId(id); 10770 10771 if (inOther) { 10772 both.push(ele); 10773 } else { 10774 retEles.push(ele); 10775 } 10776 } 10777 }; 10778 10779 add(col1, col2, left); 10780 add(col2, col1, right); 10781 return { 10782 left: this.spawn(left, { 10783 unique: true 10784 }), 10785 right: this.spawn(right, { 10786 unique: true 10787 }), 10788 both: this.spawn(both, { 10789 unique: true 10790 }) 10791 }; 10792 }, 10793 add: function add(toAdd) { 10794 var cy = this._private.cy; 10795 10796 if (!toAdd) { 10797 return this; 10798 } 10799 10800 if (string(toAdd)) { 10801 var selector = toAdd; 10802 toAdd = cy.mutableElements().filter(selector); 10803 } 10804 10805 var elements = []; 10806 10807 for (var i = 0; i < this.length; i++) { 10808 elements.push(this[i]); 10809 } 10810 10811 var map = this._private.map; 10812 10813 for (var _i = 0; _i < toAdd.length; _i++) { 10814 var add = !map.has(toAdd[_i].id()); 10815 10816 if (add) { 10817 elements.push(toAdd[_i]); 10818 } 10819 } 10820 10821 return this.spawn(elements); 10822 }, 10823 // in place merge on calling collection 10824 merge: function merge(toAdd) { 10825 var _p = this._private; 10826 var cy = _p.cy; 10827 10828 if (!toAdd) { 10829 return this; 10830 } 10831 10832 if (toAdd && string(toAdd)) { 10833 var selector = toAdd; 10834 toAdd = cy.mutableElements().filter(selector); 10835 } 10836 10837 var map = _p.map; 10838 10839 for (var i = 0; i < toAdd.length; i++) { 10840 var toAddEle = toAdd[i]; 10841 var id = toAddEle._private.data.id; 10842 var add = !map.has(id); 10843 10844 if (add) { 10845 var index = this.length++; 10846 this[index] = toAddEle; 10847 map.set(id, { 10848 ele: toAddEle, 10849 index: index 10850 }); 10851 } else { 10852 // replace 10853 var _index = map.get(id).index; 10854 this[_index] = toAddEle; 10855 map.set(id, { 10856 ele: toAddEle, 10857 index: _index 10858 }); 10859 } 10860 } 10861 10862 return this; // chaining 10863 }, 10864 unmergeAt: function unmergeAt(i) { 10865 var ele = this[i]; 10866 var id = ele.id(); 10867 var _p = this._private; 10868 var map = _p.map; // remove ele 10869 10870 this[i] = undefined; 10871 map["delete"](id); 10872 var unmergedLastEle = i === this.length - 1; // replace empty spot with last ele in collection 10873 10874 if (this.length > 1 && !unmergedLastEle) { 10875 var lastEleI = this.length - 1; 10876 var lastEle = this[lastEleI]; 10877 var lastEleId = lastEle._private.data.id; 10878 this[lastEleI] = undefined; 10879 this[i] = lastEle; 10880 map.set(lastEleId, { 10881 ele: lastEle, 10882 index: i 10883 }); 10884 } // the collection is now 1 ele smaller 10885 10886 10887 this.length--; 10888 return this; 10889 }, 10890 // remove single ele in place in calling collection 10891 unmergeOne: function unmergeOne(ele) { 10892 ele = ele[0]; 10893 var _p = this._private; 10894 var id = ele._private.data.id; 10895 var map = _p.map; 10896 var entry = map.get(id); 10897 10898 if (!entry) { 10899 return this; // no need to remove 10900 } 10901 10902 var i = entry.index; 10903 this.unmergeAt(i); 10904 return this; 10905 }, 10906 // remove eles in place on calling collection 10907 unmerge: function unmerge(toRemove) { 10908 var cy = this._private.cy; 10909 10910 if (!toRemove) { 10911 return this; 10912 } 10913 10914 if (toRemove && string(toRemove)) { 10915 var selector = toRemove; 10916 toRemove = cy.mutableElements().filter(selector); 10917 } 10918 10919 for (var i = 0; i < toRemove.length; i++) { 10920 this.unmergeOne(toRemove[i]); 10921 } 10922 10923 return this; // chaining 10924 }, 10925 unmergeBy: function unmergeBy(toRmFn) { 10926 for (var i = this.length - 1; i >= 0; i--) { 10927 var ele = this[i]; 10928 10929 if (toRmFn(ele)) { 10930 this.unmergeAt(i); 10931 } 10932 } 10933 10934 return this; 10935 }, 10936 map: function map(mapFn, thisArg) { 10937 var arr = []; 10938 var eles = this; 10939 10940 for (var i = 0; i < eles.length; i++) { 10941 var ele = eles[i]; 10942 var ret = thisArg ? mapFn.apply(thisArg, [ele, i, eles]) : mapFn(ele, i, eles); 10943 arr.push(ret); 10944 } 10945 10946 return arr; 10947 }, 10948 reduce: function reduce(fn, initialValue) { 10949 var val = initialValue; 10950 var eles = this; 10951 10952 for (var i = 0; i < eles.length; i++) { 10953 val = fn(val, eles[i], i, eles); 10954 } 10955 10956 return val; 10957 }, 10958 max: function max(valFn, thisArg) { 10959 var max = -Infinity; 10960 var maxEle; 10961 var eles = this; 10962 10963 for (var i = 0; i < eles.length; i++) { 10964 var ele = eles[i]; 10965 var val = thisArg ? valFn.apply(thisArg, [ele, i, eles]) : valFn(ele, i, eles); 10966 10967 if (val > max) { 10968 max = val; 10969 maxEle = ele; 10970 } 10971 } 10972 10973 return { 10974 value: max, 10975 ele: maxEle 10976 }; 10977 }, 10978 min: function min(valFn, thisArg) { 10979 var min = Infinity; 10980 var minEle; 10981 var eles = this; 10982 10983 for (var i = 0; i < eles.length; i++) { 10984 var ele = eles[i]; 10985 var val = thisArg ? valFn.apply(thisArg, [ele, i, eles]) : valFn(ele, i, eles); 10986 10987 if (val < min) { 10988 min = val; 10989 minEle = ele; 10990 } 10991 } 10992 10993 return { 10994 value: min, 10995 ele: minEle 10996 }; 10997 } 10998 }; // aliases 10999 11000 var fn$5 = elesfn$n; 11001 fn$5['u'] = fn$5['|'] = fn$5['+'] = fn$5.union = fn$5.or = fn$5.add; 11002 fn$5['\\'] = fn$5['!'] = fn$5['-'] = fn$5.difference = fn$5.relativeComplement = fn$5.subtract = fn$5.not; 11003 fn$5['n'] = fn$5['&'] = fn$5['.'] = fn$5.and = fn$5.intersection = fn$5.intersect; 11004 fn$5['^'] = fn$5['(+)'] = fn$5['(-)'] = fn$5.symmetricDifference = fn$5.symdiff = fn$5.xor; 11005 fn$5.fnFilter = fn$5.filterFn = fn$5.stdFilter = fn$5.filter; 11006 fn$5.complement = fn$5.abscomp = fn$5.absoluteComplement; 11007 11008 var elesfn$o = { 11009 isNode: function isNode() { 11010 return this.group() === 'nodes'; 11011 }, 11012 isEdge: function isEdge() { 11013 return this.group() === 'edges'; 11014 }, 11015 isLoop: function isLoop() { 11016 return this.isEdge() && this.source()[0] === this.target()[0]; 11017 }, 11018 isSimple: function isSimple() { 11019 return this.isEdge() && this.source()[0] !== this.target()[0]; 11020 }, 11021 group: function group() { 11022 var ele = this[0]; 11023 11024 if (ele) { 11025 return ele._private.group; 11026 } 11027 } 11028 }; 11029 11030 /** 11031 * Elements are drawn in a specific order based on compound depth (low to high), the element type (nodes above edges), 11032 * and z-index (low to high). These styles affect how this applies: 11033 * 11034 * z-compound-depth: May be `bottom | orphan | auto | top`. The first drawn is `bottom`, then `orphan` which is the 11035 * same depth as the root of the compound graph, followed by the default value `auto` which draws in order from 11036 * root to leaves of the compound graph. The last drawn is `top`. 11037 * z-index-compare: May be `auto | manual`. The default value is `auto` which always draws edges under nodes. 11038 * `manual` ignores this convention and draws based on the `z-index` value setting. 11039 * z-index: An integer value that affects the relative draw order of elements. In general, an element with a higher 11040 * `z-index` will be drawn on top of an element with a lower `z-index`. 11041 */ 11042 11043 var zIndexSort = function zIndexSort(a, b) { 11044 var cy = a.cy(); 11045 var hasCompoundNodes = cy.hasCompoundNodes(); 11046 11047 function getDepth(ele) { 11048 var style = ele.pstyle('z-compound-depth'); 11049 11050 if (style.value === 'auto') { 11051 return hasCompoundNodes ? ele.zDepth() : 0; 11052 } else if (style.value === 'bottom') { 11053 return -1; 11054 } else if (style.value === 'top') { 11055 return MAX_INT; 11056 } // 'orphan' 11057 11058 11059 return 0; 11060 } 11061 11062 var depthDiff = getDepth(a) - getDepth(b); 11063 11064 if (depthDiff !== 0) { 11065 return depthDiff; 11066 } 11067 11068 function getEleDepth(ele) { 11069 var style = ele.pstyle('z-index-compare'); 11070 11071 if (style.value === 'auto') { 11072 return ele.isNode() ? 1 : 0; 11073 } // 'manual' 11074 11075 11076 return 0; 11077 } 11078 11079 var eleDiff = getEleDepth(a) - getEleDepth(b); 11080 11081 if (eleDiff !== 0) { 11082 return eleDiff; 11083 } 11084 11085 var zDiff = a.pstyle('z-index').value - b.pstyle('z-index').value; 11086 11087 if (zDiff !== 0) { 11088 return zDiff; 11089 } // compare indices in the core (order added to graph w/ last on top) 11090 11091 11092 return a.poolIndex() - b.poolIndex(); 11093 }; 11094 11095 var elesfn$p = { 11096 forEach: function forEach(fn$1, thisArg) { 11097 if (fn(fn$1)) { 11098 var N = this.length; 11099 11100 for (var i = 0; i < N; i++) { 11101 var ele = this[i]; 11102 var ret = thisArg ? fn$1.apply(thisArg, [ele, i, this]) : fn$1(ele, i, this); 11103 11104 if (ret === false) { 11105 break; 11106 } // exit each early on return false 11107 11108 } 11109 } 11110 11111 return this; 11112 }, 11113 toArray: function toArray() { 11114 var array = []; 11115 11116 for (var i = 0; i < this.length; i++) { 11117 array.push(this[i]); 11118 } 11119 11120 return array; 11121 }, 11122 slice: function slice(start, end) { 11123 var array = []; 11124 var thisSize = this.length; 11125 11126 if (end == null) { 11127 end = thisSize; 11128 } 11129 11130 if (start == null) { 11131 start = 0; 11132 } 11133 11134 if (start < 0) { 11135 start = thisSize + start; 11136 } 11137 11138 if (end < 0) { 11139 end = thisSize + end; 11140 } 11141 11142 for (var i = start; i >= 0 && i < end && i < thisSize; i++) { 11143 array.push(this[i]); 11144 } 11145 11146 return this.spawn(array); 11147 }, 11148 size: function size() { 11149 return this.length; 11150 }, 11151 eq: function eq(i) { 11152 return this[i] || this.spawn(); 11153 }, 11154 first: function first() { 11155 return this[0] || this.spawn(); 11156 }, 11157 last: function last() { 11158 return this[this.length - 1] || this.spawn(); 11159 }, 11160 empty: function empty() { 11161 return this.length === 0; 11162 }, 11163 nonempty: function nonempty() { 11164 return !this.empty(); 11165 }, 11166 sort: function sort(sortFn) { 11167 if (!fn(sortFn)) { 11168 return this; 11169 } 11170 11171 var sorted = this.toArray().sort(sortFn); 11172 return this.spawn(sorted); 11173 }, 11174 sortByZIndex: function sortByZIndex() { 11175 return this.sort(zIndexSort); 11176 }, 11177 zDepth: function zDepth() { 11178 var ele = this[0]; 11179 11180 if (!ele) { 11181 return undefined; 11182 } // let cy = ele.cy(); 11183 11184 11185 var _p = ele._private; 11186 var group = _p.group; 11187 11188 if (group === 'nodes') { 11189 var depth = _p.data.parent ? ele.parents().size() : 0; 11190 11191 if (!ele.isParent()) { 11192 return MAX_INT - 1; // childless nodes always on top 11193 } 11194 11195 return depth; 11196 } else { 11197 var src = _p.source; 11198 var tgt = _p.target; 11199 var srcDepth = src.zDepth(); 11200 var tgtDepth = tgt.zDepth(); 11201 return Math.max(srcDepth, tgtDepth, 0); // depth of deepest parent 11202 } 11203 } 11204 }; 11205 elesfn$p.each = elesfn$p.forEach; 11206 11207 var defineSymbolIterator = function defineSymbolIterator() { 11208 var typeofUndef = "undefined" ; 11209 var isIteratorSupported = (typeof Symbol === "undefined" ? "undefined" : _typeof(Symbol)) != typeofUndef && _typeof(Symbol.iterator) != typeofUndef; // eslint-disable-line no-undef 11210 11211 if (isIteratorSupported) { 11212 elesfn$p[Symbol.iterator] = function () { 11213 var _this = this; 11214 11215 // eslint-disable-line no-undef 11216 var entry = { 11217 value: undefined, 11218 done: false 11219 }; 11220 var i = 0; 11221 var length = this.length; 11222 return _defineProperty({ 11223 next: function next() { 11224 if (i < length) { 11225 entry.value = _this[i++]; 11226 } else { 11227 entry.value = undefined; 11228 entry.done = true; 11229 } 11230 11231 return entry; 11232 } 11233 }, Symbol.iterator, function () { 11234 // eslint-disable-line no-undef 11235 return this; 11236 }); 11237 }; 11238 } 11239 }; 11240 11241 defineSymbolIterator(); 11242 11243 var getLayoutDimensionOptions = defaults({ 11244 nodeDimensionsIncludeLabels: false 11245 }); 11246 var elesfn$q = { 11247 // Calculates and returns node dimensions { x, y } based on options given 11248 layoutDimensions: function layoutDimensions(options) { 11249 options = getLayoutDimensionOptions(options); 11250 var dims; 11251 11252 if (!this.takesUpSpace()) { 11253 dims = { 11254 w: 0, 11255 h: 0 11256 }; 11257 } else if (options.nodeDimensionsIncludeLabels) { 11258 var bbDim = this.boundingBox(); 11259 dims = { 11260 w: bbDim.w, 11261 h: bbDim.h 11262 }; 11263 } else { 11264 dims = { 11265 w: this.outerWidth(), 11266 h: this.outerHeight() 11267 }; 11268 } // sanitise the dimensions for external layouts (avoid division by zero) 11269 11270 11271 if (dims.w === 0 || dims.h === 0) { 11272 dims.w = dims.h = 1; 11273 } 11274 11275 return dims; 11276 }, 11277 // using standard layout options, apply position function (w/ or w/o animation) 11278 layoutPositions: function layoutPositions(layout, options, fn) { 11279 var nodes = this.nodes(); 11280 var cy = this.cy(); 11281 var layoutEles = options.eles; // nodes & edges 11282 11283 var getMemoizeKey = function getMemoizeKey(node) { 11284 return node.id(); 11285 }; 11286 11287 var fnMem = memoize(fn, getMemoizeKey); // memoized version of position function 11288 11289 layout.emit({ 11290 type: 'layoutstart', 11291 layout: layout 11292 }); 11293 layout.animations = []; 11294 11295 var calculateSpacing = function calculateSpacing(spacing, nodesBb, pos) { 11296 var center = { 11297 x: nodesBb.x1 + nodesBb.w / 2, 11298 y: nodesBb.y1 + nodesBb.h / 2 11299 }; 11300 var spacingVector = { 11301 // scale from center of bounding box (not necessarily 0,0) 11302 x: (pos.x - center.x) * spacing, 11303 y: (pos.y - center.y) * spacing 11304 }; 11305 return { 11306 x: center.x + spacingVector.x, 11307 y: center.y + spacingVector.y 11308 }; 11309 }; 11310 11311 var useSpacingFactor = options.spacingFactor && options.spacingFactor !== 1; 11312 11313 var spacingBb = function spacingBb() { 11314 if (!useSpacingFactor) { 11315 return null; 11316 } 11317 11318 var bb = makeBoundingBox(); 11319 11320 for (var i = 0; i < nodes.length; i++) { 11321 var node = nodes[i]; 11322 var pos = fnMem(node, i); 11323 expandBoundingBoxByPoint(bb, pos.x, pos.y); 11324 } 11325 11326 return bb; 11327 }; 11328 11329 var bb = spacingBb(); 11330 var getFinalPos = memoize(function (node, i) { 11331 var newPos = fnMem(node, i); 11332 11333 if (useSpacingFactor) { 11334 var spacing = Math.abs(options.spacingFactor); 11335 newPos = calculateSpacing(spacing, bb, newPos); 11336 } 11337 11338 if (options.transform != null) { 11339 newPos = options.transform(node, newPos); 11340 } 11341 11342 return newPos; 11343 }, getMemoizeKey); 11344 11345 if (options.animate) { 11346 for (var i = 0; i < nodes.length; i++) { 11347 var node = nodes[i]; 11348 var newPos = getFinalPos(node, i); 11349 var animateNode = options.animateFilter == null || options.animateFilter(node, i); 11350 11351 if (animateNode) { 11352 var ani = node.animation({ 11353 position: newPos, 11354 duration: options.animationDuration, 11355 easing: options.animationEasing 11356 }); 11357 layout.animations.push(ani); 11358 } else { 11359 node.position(newPos); 11360 } 11361 } 11362 11363 if (options.fit) { 11364 var fitAni = cy.animation({ 11365 fit: { 11366 boundingBox: layoutEles.boundingBoxAt(getFinalPos), 11367 padding: options.padding 11368 }, 11369 duration: options.animationDuration, 11370 easing: options.animationEasing 11371 }); 11372 layout.animations.push(fitAni); 11373 } else if (options.zoom !== undefined && options.pan !== undefined) { 11374 var zoomPanAni = cy.animation({ 11375 zoom: options.zoom, 11376 pan: options.pan, 11377 duration: options.animationDuration, 11378 easing: options.animationEasing 11379 }); 11380 layout.animations.push(zoomPanAni); 11381 } 11382 11383 layout.animations.forEach(function (ani) { 11384 return ani.play(); 11385 }); 11386 layout.one('layoutready', options.ready); 11387 layout.emit({ 11388 type: 'layoutready', 11389 layout: layout 11390 }); 11391 Promise$1.all(layout.animations.map(function (ani) { 11392 return ani.promise(); 11393 })).then(function () { 11394 layout.one('layoutstop', options.stop); 11395 layout.emit({ 11396 type: 'layoutstop', 11397 layout: layout 11398 }); 11399 }); 11400 } else { 11401 nodes.positions(getFinalPos); 11402 11403 if (options.fit) { 11404 cy.fit(options.eles, options.padding); 11405 } 11406 11407 if (options.zoom != null) { 11408 cy.zoom(options.zoom); 11409 } 11410 11411 if (options.pan) { 11412 cy.pan(options.pan); 11413 } 11414 11415 layout.one('layoutready', options.ready); 11416 layout.emit({ 11417 type: 'layoutready', 11418 layout: layout 11419 }); 11420 layout.one('layoutstop', options.stop); 11421 layout.emit({ 11422 type: 'layoutstop', 11423 layout: layout 11424 }); 11425 } 11426 11427 return this; // chaining 11428 }, 11429 layout: function layout(options) { 11430 var cy = this.cy(); 11431 return cy.makeLayout(extend({}, options, { 11432 eles: this 11433 })); 11434 } 11435 }; // aliases: 11436 11437 elesfn$q.createLayout = elesfn$q.makeLayout = elesfn$q.layout; 11438 11439 function styleCache(key, fn, ele) { 11440 var _p = ele._private; 11441 var cache = _p.styleCache = _p.styleCache || []; 11442 var val; 11443 11444 if ((val = cache[key]) != null) { 11445 return val; 11446 } else { 11447 val = cache[key] = fn(ele); 11448 return val; 11449 } 11450 } 11451 11452 function cacheStyleFunction(key, fn) { 11453 key = hashString(key); 11454 return function cachedStyleFunction(ele) { 11455 return styleCache(key, fn, ele); 11456 }; 11457 } 11458 11459 function cachePrototypeStyleFunction(key, fn) { 11460 key = hashString(key); 11461 11462 var selfFn = function selfFn(ele) { 11463 return fn.call(ele); 11464 }; 11465 11466 return function cachedPrototypeStyleFunction() { 11467 var ele = this[0]; 11468 11469 if (ele) { 11470 return styleCache(key, selfFn, ele); 11471 } 11472 }; 11473 } 11474 11475 var elesfn$r = { 11476 recalculateRenderedStyle: function recalculateRenderedStyle(useCache) { 11477 var cy = this.cy(); 11478 var renderer = cy.renderer(); 11479 var styleEnabled = cy.styleEnabled(); 11480 11481 if (renderer && styleEnabled) { 11482 renderer.recalculateRenderedStyle(this, useCache); 11483 } 11484 11485 return this; 11486 }, 11487 dirtyStyleCache: function dirtyStyleCache() { 11488 var cy = this.cy(); 11489 11490 var dirty = function dirty(ele) { 11491 return ele._private.styleCache = null; 11492 }; 11493 11494 if (cy.hasCompoundNodes()) { 11495 var eles; 11496 eles = this.spawnSelf().merge(this.descendants()).merge(this.parents()); 11497 eles.merge(eles.connectedEdges()); 11498 eles.forEach(dirty); 11499 } else { 11500 this.forEach(function (ele) { 11501 dirty(ele); 11502 ele.connectedEdges().forEach(dirty); 11503 }); 11504 } 11505 11506 return this; 11507 }, 11508 // fully updates (recalculates) the style for the elements 11509 updateStyle: function updateStyle(notifyRenderer) { 11510 var cy = this._private.cy; 11511 11512 if (!cy.styleEnabled()) { 11513 return this; 11514 } 11515 11516 if (cy.batching()) { 11517 var bEles = cy._private.batchStyleEles; 11518 bEles.merge(this); 11519 return this; // chaining and exit early when batching 11520 } 11521 11522 var hasCompounds = cy.hasCompoundNodes(); 11523 var style = cy.style(); 11524 var updatedEles = this; 11525 notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; 11526 11527 if (hasCompounds) { 11528 // then add everything up and down for compound selector checks 11529 updatedEles = this.spawnSelf().merge(this.descendants()).merge(this.parents()); 11530 } 11531 11532 var changedEles = style.apply(updatedEles); 11533 11534 if (notifyRenderer) { 11535 changedEles.emitAndNotify('style'); // let renderer know we changed style 11536 } else { 11537 changedEles.emit('style'); // just fire the event 11538 } 11539 11540 return this; // chaining 11541 }, 11542 // get the internal parsed style object for the specified property 11543 parsedStyle: function parsedStyle(property) { 11544 var includeNonDefault = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 11545 var ele = this[0]; 11546 var cy = ele.cy(); 11547 11548 if (!cy.styleEnabled()) { 11549 return; 11550 } 11551 11552 if (ele) { 11553 var overriddenStyle = ele._private.style[property]; 11554 11555 if (overriddenStyle != null) { 11556 return overriddenStyle; 11557 } else if (includeNonDefault) { 11558 return cy.style().getDefaultProperty(property); 11559 } else { 11560 return null; 11561 } 11562 } 11563 }, 11564 numericStyle: function numericStyle(property) { 11565 var ele = this[0]; 11566 11567 if (!ele.cy().styleEnabled()) { 11568 return; 11569 } 11570 11571 if (ele) { 11572 var pstyle = ele.pstyle(property); 11573 return pstyle.pfValue !== undefined ? pstyle.pfValue : pstyle.value; 11574 } 11575 }, 11576 numericStyleUnits: function numericStyleUnits(property) { 11577 var ele = this[0]; 11578 11579 if (!ele.cy().styleEnabled()) { 11580 return; 11581 } 11582 11583 if (ele) { 11584 return ele.pstyle(property).units; 11585 } 11586 }, 11587 // get the specified css property as a rendered value (i.e. on-screen value) 11588 // or get the whole rendered style if no property specified (NB doesn't allow setting) 11589 renderedStyle: function renderedStyle(property) { 11590 var cy = this.cy(); 11591 11592 if (!cy.styleEnabled()) { 11593 return this; 11594 } 11595 11596 var ele = this[0]; 11597 11598 if (ele) { 11599 return cy.style().getRenderedStyle(ele, property); 11600 } 11601 }, 11602 // read the calculated css style of the element or override the style (via a bypass) 11603 style: function style(name, value) { 11604 var cy = this.cy(); 11605 11606 if (!cy.styleEnabled()) { 11607 return this; 11608 } 11609 11610 var updateTransitions = false; 11611 var style = cy.style(); 11612 11613 if (plainObject(name)) { 11614 // then extend the bypass 11615 var props = name; 11616 style.applyBypass(this, props, updateTransitions); 11617 this.emitAndNotify('style'); // let the renderer know we've updated style 11618 } else if (string(name)) { 11619 if (value === undefined) { 11620 // then get the property from the style 11621 var ele = this[0]; 11622 11623 if (ele) { 11624 return style.getStylePropertyValue(ele, name); 11625 } else { 11626 // empty collection => can't get any value 11627 return; 11628 } 11629 } else { 11630 // then set the bypass with the property value 11631 style.applyBypass(this, name, value, updateTransitions); 11632 this.emitAndNotify('style'); // let the renderer know we've updated style 11633 } 11634 } else if (name === undefined) { 11635 var _ele = this[0]; 11636 11637 if (_ele) { 11638 return style.getRawStyle(_ele); 11639 } else { 11640 // empty collection => can't get any value 11641 return; 11642 } 11643 } 11644 11645 return this; // chaining 11646 }, 11647 removeStyle: function removeStyle(names) { 11648 var cy = this.cy(); 11649 11650 if (!cy.styleEnabled()) { 11651 return this; 11652 } 11653 11654 var updateTransitions = false; 11655 var style = cy.style(); 11656 var eles = this; 11657 11658 if (names === undefined) { 11659 for (var i = 0; i < eles.length; i++) { 11660 var ele = eles[i]; 11661 style.removeAllBypasses(ele, updateTransitions); 11662 } 11663 } else { 11664 names = names.split(/\s+/); 11665 11666 for (var _i = 0; _i < eles.length; _i++) { 11667 var _ele2 = eles[_i]; 11668 style.removeBypasses(_ele2, names, updateTransitions); 11669 } 11670 } 11671 11672 this.emitAndNotify('style'); // let the renderer know we've updated style 11673 11674 return this; // chaining 11675 }, 11676 show: function show() { 11677 this.css('display', 'element'); 11678 return this; // chaining 11679 }, 11680 hide: function hide() { 11681 this.css('display', 'none'); 11682 return this; // chaining 11683 }, 11684 effectiveOpacity: function effectiveOpacity() { 11685 var cy = this.cy(); 11686 11687 if (!cy.styleEnabled()) { 11688 return 1; 11689 } 11690 11691 var hasCompoundNodes = cy.hasCompoundNodes(); 11692 var ele = this[0]; 11693 11694 if (ele) { 11695 var _p = ele._private; 11696 var parentOpacity = ele.pstyle('opacity').value; 11697 11698 if (!hasCompoundNodes) { 11699 return parentOpacity; 11700 } 11701 11702 var parents = !_p.data.parent ? null : ele.parents(); 11703 11704 if (parents) { 11705 for (var i = 0; i < parents.length; i++) { 11706 var parent = parents[i]; 11707 var opacity = parent.pstyle('opacity').value; 11708 parentOpacity = opacity * parentOpacity; 11709 } 11710 } 11711 11712 return parentOpacity; 11713 } 11714 }, 11715 transparent: function transparent() { 11716 var cy = this.cy(); 11717 11718 if (!cy.styleEnabled()) { 11719 return false; 11720 } 11721 11722 var ele = this[0]; 11723 var hasCompoundNodes = ele.cy().hasCompoundNodes(); 11724 11725 if (ele) { 11726 if (!hasCompoundNodes) { 11727 return ele.pstyle('opacity').value === 0; 11728 } else { 11729 return ele.effectiveOpacity() === 0; 11730 } 11731 } 11732 }, 11733 backgrounding: function backgrounding() { 11734 var cy = this.cy(); 11735 11736 if (!cy.styleEnabled()) { 11737 return false; 11738 } 11739 11740 var ele = this[0]; 11741 return ele._private.backgrounding ? true : false; 11742 } 11743 }; 11744 11745 function checkCompound(ele, parentOk) { 11746 var _p = ele._private; 11747 var parents = _p.data.parent ? ele.parents() : null; 11748 11749 if (parents) { 11750 for (var i = 0; i < parents.length; i++) { 11751 var parent = parents[i]; 11752 11753 if (!parentOk(parent)) { 11754 return false; 11755 } 11756 } 11757 } 11758 11759 return true; 11760 } 11761 11762 function defineDerivedStateFunction(specs) { 11763 var ok = specs.ok; 11764 var edgeOkViaNode = specs.edgeOkViaNode || specs.ok; 11765 var parentOk = specs.parentOk || specs.ok; 11766 return function () { 11767 var cy = this.cy(); 11768 11769 if (!cy.styleEnabled()) { 11770 return true; 11771 } 11772 11773 var ele = this[0]; 11774 var hasCompoundNodes = cy.hasCompoundNodes(); 11775 11776 if (ele) { 11777 var _p = ele._private; 11778 11779 if (!ok(ele)) { 11780 return false; 11781 } 11782 11783 if (ele.isNode()) { 11784 return !hasCompoundNodes || checkCompound(ele, parentOk); 11785 } else { 11786 var src = _p.source; 11787 var tgt = _p.target; 11788 return edgeOkViaNode(src) && (!hasCompoundNodes || checkCompound(src, edgeOkViaNode)) && (src === tgt || edgeOkViaNode(tgt) && (!hasCompoundNodes || checkCompound(tgt, edgeOkViaNode))); 11789 } 11790 } 11791 }; 11792 } 11793 11794 var eleTakesUpSpace = cacheStyleFunction('eleTakesUpSpace', function (ele) { 11795 return ele.pstyle('display').value === 'element' && ele.width() !== 0 && (ele.isNode() ? ele.height() !== 0 : true); 11796 }); 11797 elesfn$r.takesUpSpace = cachePrototypeStyleFunction('takesUpSpace', defineDerivedStateFunction({ 11798 ok: eleTakesUpSpace 11799 })); 11800 var eleInteractive = cacheStyleFunction('eleInteractive', function (ele) { 11801 return ele.pstyle('events').value === 'yes' && ele.pstyle('visibility').value === 'visible' && eleTakesUpSpace(ele); 11802 }); 11803 var parentInteractive = cacheStyleFunction('parentInteractive', function (parent) { 11804 return parent.pstyle('visibility').value === 'visible' && eleTakesUpSpace(parent); 11805 }); 11806 elesfn$r.interactive = cachePrototypeStyleFunction('interactive', defineDerivedStateFunction({ 11807 ok: eleInteractive, 11808 parentOk: parentInteractive, 11809 edgeOkViaNode: eleTakesUpSpace 11810 })); 11811 11812 elesfn$r.noninteractive = function () { 11813 var ele = this[0]; 11814 11815 if (ele) { 11816 return !ele.interactive(); 11817 } 11818 }; 11819 11820 var eleVisible = cacheStyleFunction('eleVisible', function (ele) { 11821 return ele.pstyle('visibility').value === 'visible' && ele.pstyle('opacity').pfValue !== 0 && eleTakesUpSpace(ele); 11822 }); 11823 var edgeVisibleViaNode = eleTakesUpSpace; 11824 elesfn$r.visible = cachePrototypeStyleFunction('visible', defineDerivedStateFunction({ 11825 ok: eleVisible, 11826 edgeOkViaNode: edgeVisibleViaNode 11827 })); 11828 11829 elesfn$r.hidden = function () { 11830 var ele = this[0]; 11831 11832 if (ele) { 11833 return !ele.visible(); 11834 } 11835 }; 11836 11837 elesfn$r.isBundledBezier = cachePrototypeStyleFunction('isBundledBezier', function () { 11838 if (!this.cy().styleEnabled()) { 11839 return false; 11840 } 11841 11842 return !this.removed() && this.pstyle('curve-style').value === 'bezier' && this.takesUpSpace(); 11843 }); 11844 elesfn$r.bypass = elesfn$r.css = elesfn$r.style; 11845 elesfn$r.renderedCss = elesfn$r.renderedStyle; 11846 elesfn$r.removeBypass = elesfn$r.removeCss = elesfn$r.removeStyle; 11847 elesfn$r.pstyle = elesfn$r.parsedStyle; 11848 11849 var elesfn$s = {}; 11850 11851 function defineSwitchFunction(params) { 11852 return function () { 11853 var args = arguments; 11854 var changedEles = []; // e.g. cy.nodes().select( data, handler ) 11855 11856 if (args.length === 2) { 11857 var data = args[0]; 11858 var handler = args[1]; 11859 this.on(params.event, data, handler); 11860 } // e.g. cy.nodes().select( handler ) 11861 else if (args.length === 1 && fn(args[0])) { 11862 var _handler = args[0]; 11863 this.on(params.event, _handler); 11864 } // e.g. cy.nodes().select() 11865 // e.g. (private) cy.nodes().select(['tapselect']) 11866 else if (args.length === 0 || args.length === 1 && array(args[0])) { 11867 var addlEvents = args.length === 1 ? args[0] : null; 11868 11869 for (var i = 0; i < this.length; i++) { 11870 var ele = this[i]; 11871 var able = !params.ableField || ele._private[params.ableField]; 11872 var changed = ele._private[params.field] != params.value; 11873 11874 if (params.overrideAble) { 11875 var overrideAble = params.overrideAble(ele); 11876 11877 if (overrideAble !== undefined) { 11878 able = overrideAble; 11879 11880 if (!overrideAble) { 11881 return this; 11882 } // to save cycles assume not able for all on override 11883 11884 } 11885 } 11886 11887 if (able) { 11888 ele._private[params.field] = params.value; 11889 11890 if (changed) { 11891 changedEles.push(ele); 11892 } 11893 } 11894 } 11895 11896 var changedColl = this.spawn(changedEles); 11897 changedColl.updateStyle(); // change of state => possible change of style 11898 11899 changedColl.emit(params.event); 11900 11901 if (addlEvents) { 11902 changedColl.emit(addlEvents); 11903 } 11904 } 11905 11906 return this; 11907 }; 11908 } 11909 11910 function defineSwitchSet(params) { 11911 elesfn$s[params.field] = function () { 11912 var ele = this[0]; 11913 11914 if (ele) { 11915 if (params.overrideField) { 11916 var val = params.overrideField(ele); 11917 11918 if (val !== undefined) { 11919 return val; 11920 } 11921 } 11922 11923 return ele._private[params.field]; 11924 } 11925 }; 11926 11927 elesfn$s[params.on] = defineSwitchFunction({ 11928 event: params.on, 11929 field: params.field, 11930 ableField: params.ableField, 11931 overrideAble: params.overrideAble, 11932 value: true 11933 }); 11934 elesfn$s[params.off] = defineSwitchFunction({ 11935 event: params.off, 11936 field: params.field, 11937 ableField: params.ableField, 11938 overrideAble: params.overrideAble, 11939 value: false 11940 }); 11941 } 11942 11943 defineSwitchSet({ 11944 field: 'locked', 11945 overrideField: function overrideField(ele) { 11946 return ele.cy().autolock() ? true : undefined; 11947 }, 11948 on: 'lock', 11949 off: 'unlock' 11950 }); 11951 defineSwitchSet({ 11952 field: 'grabbable', 11953 overrideField: function overrideField(ele) { 11954 return ele.cy().autoungrabify() || ele.pannable() ? false : undefined; 11955 }, 11956 on: 'grabify', 11957 off: 'ungrabify' 11958 }); 11959 defineSwitchSet({ 11960 field: 'selected', 11961 ableField: 'selectable', 11962 overrideAble: function overrideAble(ele) { 11963 return ele.cy().autounselectify() ? false : undefined; 11964 }, 11965 on: 'select', 11966 off: 'unselect' 11967 }); 11968 defineSwitchSet({ 11969 field: 'selectable', 11970 overrideField: function overrideField(ele) { 11971 return ele.cy().autounselectify() ? false : undefined; 11972 }, 11973 on: 'selectify', 11974 off: 'unselectify' 11975 }); 11976 elesfn$s.deselect = elesfn$s.unselect; 11977 11978 elesfn$s.grabbed = function () { 11979 var ele = this[0]; 11980 11981 if (ele) { 11982 return ele._private.grabbed; 11983 } 11984 }; 11985 11986 defineSwitchSet({ 11987 field: 'active', 11988 on: 'activate', 11989 off: 'unactivate' 11990 }); 11991 defineSwitchSet({ 11992 field: 'pannable', 11993 on: 'panify', 11994 off: 'unpanify' 11995 }); 11996 11997 elesfn$s.inactive = function () { 11998 var ele = this[0]; 11999 12000 if (ele) { 12001 return !ele._private.active; 12002 } 12003 }; 12004 12005 var elesfn$t = {}; // DAG functions 12006 //////////////// 12007 12008 var defineDagExtremity = function defineDagExtremity(params) { 12009 return function dagExtremityImpl(selector) { 12010 var eles = this; 12011 var ret = []; 12012 12013 for (var i = 0; i < eles.length; i++) { 12014 var ele = eles[i]; 12015 12016 if (!ele.isNode()) { 12017 continue; 12018 } 12019 12020 var disqualified = false; 12021 var edges = ele.connectedEdges(); 12022 12023 for (var j = 0; j < edges.length; j++) { 12024 var edge = edges[j]; 12025 var src = edge.source(); 12026 var tgt = edge.target(); 12027 12028 if (params.noIncomingEdges && tgt === ele && src !== ele || params.noOutgoingEdges && src === ele && tgt !== ele) { 12029 disqualified = true; 12030 break; 12031 } 12032 } 12033 12034 if (!disqualified) { 12035 ret.push(ele); 12036 } 12037 } 12038 12039 return this.spawn(ret, { 12040 unique: true 12041 }).filter(selector); 12042 }; 12043 }; 12044 12045 var defineDagOneHop = function defineDagOneHop(params) { 12046 return function (selector) { 12047 var eles = this; 12048 var oEles = []; 12049 12050 for (var i = 0; i < eles.length; i++) { 12051 var ele = eles[i]; 12052 12053 if (!ele.isNode()) { 12054 continue; 12055 } 12056 12057 var edges = ele.connectedEdges(); 12058 12059 for (var j = 0; j < edges.length; j++) { 12060 var edge = edges[j]; 12061 var src = edge.source(); 12062 var tgt = edge.target(); 12063 12064 if (params.outgoing && src === ele) { 12065 oEles.push(edge); 12066 oEles.push(tgt); 12067 } else if (params.incoming && tgt === ele) { 12068 oEles.push(edge); 12069 oEles.push(src); 12070 } 12071 } 12072 } 12073 12074 return this.spawn(oEles, { 12075 unique: true 12076 }).filter(selector); 12077 }; 12078 }; 12079 12080 var defineDagAllHops = function defineDagAllHops(params) { 12081 return function (selector) { 12082 var eles = this; 12083 var sEles = []; 12084 var sElesIds = {}; 12085 12086 for (;;) { 12087 var next = params.outgoing ? eles.outgoers() : eles.incomers(); 12088 12089 if (next.length === 0) { 12090 break; 12091 } // done if none left 12092 12093 12094 var newNext = false; 12095 12096 for (var i = 0; i < next.length; i++) { 12097 var n = next[i]; 12098 var nid = n.id(); 12099 12100 if (!sElesIds[nid]) { 12101 sElesIds[nid] = true; 12102 sEles.push(n); 12103 newNext = true; 12104 } 12105 } 12106 12107 if (!newNext) { 12108 break; 12109 } // done if touched all outgoers already 12110 12111 12112 eles = next; 12113 } 12114 12115 return this.spawn(sEles, { 12116 unique: true 12117 }).filter(selector); 12118 }; 12119 }; 12120 12121 elesfn$t.clearTraversalCache = function () { 12122 for (var i = 0; i < this.length; i++) { 12123 this[i]._private.traversalCache = null; 12124 } 12125 }; 12126 12127 extend(elesfn$t, { 12128 // get the root nodes in the DAG 12129 roots: defineDagExtremity({ 12130 noIncomingEdges: true 12131 }), 12132 // get the leaf nodes in the DAG 12133 leaves: defineDagExtremity({ 12134 noOutgoingEdges: true 12135 }), 12136 // normally called children in graph theory 12137 // these nodes =edges=> outgoing nodes 12138 outgoers: cache(defineDagOneHop({ 12139 outgoing: true 12140 }), 'outgoers'), 12141 // aka DAG descendants 12142 successors: defineDagAllHops({ 12143 outgoing: true 12144 }), 12145 // normally called parents in graph theory 12146 // these nodes <=edges= incoming nodes 12147 incomers: cache(defineDagOneHop({ 12148 incoming: true 12149 }), 'incomers'), 12150 // aka DAG ancestors 12151 predecessors: defineDagAllHops({ 12152 incoming: true 12153 }) 12154 }); // Neighbourhood functions 12155 ////////////////////////// 12156 12157 extend(elesfn$t, { 12158 neighborhood: cache(function (selector) { 12159 var elements = []; 12160 var nodes = this.nodes(); 12161 12162 for (var i = 0; i < nodes.length; i++) { 12163 // for all nodes 12164 var node = nodes[i]; 12165 var connectedEdges = node.connectedEdges(); // for each connected edge, add the edge and the other node 12166 12167 for (var j = 0; j < connectedEdges.length; j++) { 12168 var edge = connectedEdges[j]; 12169 var src = edge.source(); 12170 var tgt = edge.target(); 12171 var otherNode = node === src ? tgt : src; // need check in case of loop 12172 12173 if (otherNode.length > 0) { 12174 elements.push(otherNode[0]); // add node 1 hop away 12175 } // add connected edge 12176 12177 12178 elements.push(edge[0]); 12179 } 12180 } 12181 12182 return this.spawn(elements, { 12183 unique: true 12184 }).filter(selector); 12185 }, 'neighborhood'), 12186 closedNeighborhood: function closedNeighborhood(selector) { 12187 return this.neighborhood().add(this).filter(selector); 12188 }, 12189 openNeighborhood: function openNeighborhood(selector) { 12190 return this.neighborhood(selector); 12191 } 12192 }); // aliases 12193 12194 elesfn$t.neighbourhood = elesfn$t.neighborhood; 12195 elesfn$t.closedNeighbourhood = elesfn$t.closedNeighborhood; 12196 elesfn$t.openNeighbourhood = elesfn$t.openNeighborhood; // Edge functions 12197 ///////////////// 12198 12199 extend(elesfn$t, { 12200 source: cache(function sourceImpl(selector) { 12201 var ele = this[0]; 12202 var src; 12203 12204 if (ele) { 12205 src = ele._private.source || ele.cy().collection(); 12206 } 12207 12208 return src && selector ? src.filter(selector) : src; 12209 }, 'source'), 12210 target: cache(function targetImpl(selector) { 12211 var ele = this[0]; 12212 var tgt; 12213 12214 if (ele) { 12215 tgt = ele._private.target || ele.cy().collection(); 12216 } 12217 12218 return tgt && selector ? tgt.filter(selector) : tgt; 12219 }, 'target'), 12220 sources: defineSourceFunction({ 12221 attr: 'source' 12222 }), 12223 targets: defineSourceFunction({ 12224 attr: 'target' 12225 }) 12226 }); 12227 12228 function defineSourceFunction(params) { 12229 return function sourceImpl(selector) { 12230 var sources = []; 12231 12232 for (var i = 0; i < this.length; i++) { 12233 var ele = this[i]; 12234 var src = ele._private[params.attr]; 12235 12236 if (src) { 12237 sources.push(src); 12238 } 12239 } 12240 12241 return this.spawn(sources, { 12242 unique: true 12243 }).filter(selector); 12244 }; 12245 } 12246 12247 extend(elesfn$t, { 12248 edgesWith: cache(defineEdgesWithFunction(), 'edgesWith'), 12249 edgesTo: cache(defineEdgesWithFunction({ 12250 thisIsSrc: true 12251 }), 'edgesTo') 12252 }); 12253 12254 function defineEdgesWithFunction(params) { 12255 return function edgesWithImpl(otherNodes) { 12256 var elements = []; 12257 var cy = this._private.cy; 12258 var p = params || {}; // get elements if a selector is specified 12259 12260 if (string(otherNodes)) { 12261 otherNodes = cy.$(otherNodes); 12262 } 12263 12264 for (var h = 0; h < otherNodes.length; h++) { 12265 var edges = otherNodes[h]._private.edges; 12266 12267 for (var i = 0; i < edges.length; i++) { 12268 var edge = edges[i]; 12269 var edgeData = edge._private.data; 12270 var thisToOther = this.hasElementWithId(edgeData.source) && otherNodes.hasElementWithId(edgeData.target); 12271 var otherToThis = otherNodes.hasElementWithId(edgeData.source) && this.hasElementWithId(edgeData.target); 12272 var edgeConnectsThisAndOther = thisToOther || otherToThis; 12273 12274 if (!edgeConnectsThisAndOther) { 12275 continue; 12276 } 12277 12278 if (p.thisIsSrc || p.thisIsTgt) { 12279 if (p.thisIsSrc && !thisToOther) { 12280 continue; 12281 } 12282 12283 if (p.thisIsTgt && !otherToThis) { 12284 continue; 12285 } 12286 } 12287 12288 elements.push(edge); 12289 } 12290 } 12291 12292 return this.spawn(elements, { 12293 unique: true 12294 }); 12295 }; 12296 } 12297 12298 extend(elesfn$t, { 12299 connectedEdges: cache(function (selector) { 12300 var retEles = []; 12301 var eles = this; 12302 12303 for (var i = 0; i < eles.length; i++) { 12304 var node = eles[i]; 12305 12306 if (!node.isNode()) { 12307 continue; 12308 } 12309 12310 var edges = node._private.edges; 12311 12312 for (var j = 0; j < edges.length; j++) { 12313 var edge = edges[j]; 12314 retEles.push(edge); 12315 } 12316 } 12317 12318 return this.spawn(retEles, { 12319 unique: true 12320 }).filter(selector); 12321 }, 'connectedEdges'), 12322 connectedNodes: cache(function (selector) { 12323 var retEles = []; 12324 var eles = this; 12325 12326 for (var i = 0; i < eles.length; i++) { 12327 var edge = eles[i]; 12328 12329 if (!edge.isEdge()) { 12330 continue; 12331 } 12332 12333 retEles.push(edge.source()[0]); 12334 retEles.push(edge.target()[0]); 12335 } 12336 12337 return this.spawn(retEles, { 12338 unique: true 12339 }).filter(selector); 12340 }, 'connectedNodes'), 12341 parallelEdges: cache(defineParallelEdgesFunction(), 'parallelEdges'), 12342 codirectedEdges: cache(defineParallelEdgesFunction({ 12343 codirected: true 12344 }), 'codirectedEdges') 12345 }); 12346 12347 function defineParallelEdgesFunction(params) { 12348 var defaults = { 12349 codirected: false 12350 }; 12351 params = extend({}, defaults, params); 12352 return function parallelEdgesImpl(selector) { 12353 // micro-optimised for renderer 12354 var elements = []; 12355 var edges = this.edges(); 12356 var p = params; // look at all the edges in the collection 12357 12358 for (var i = 0; i < edges.length; i++) { 12359 var edge1 = edges[i]; 12360 var edge1_p = edge1._private; 12361 var src1 = edge1_p.source; 12362 var srcid1 = src1._private.data.id; 12363 var tgtid1 = edge1_p.data.target; 12364 var srcEdges1 = src1._private.edges; // look at edges connected to the src node of this edge 12365 12366 for (var j = 0; j < srcEdges1.length; j++) { 12367 var edge2 = srcEdges1[j]; 12368 var edge2data = edge2._private.data; 12369 var tgtid2 = edge2data.target; 12370 var srcid2 = edge2data.source; 12371 var codirected = tgtid2 === tgtid1 && srcid2 === srcid1; 12372 var oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2; 12373 12374 if (p.codirected && codirected || !p.codirected && (codirected || oppdirected)) { 12375 elements.push(edge2); 12376 } 12377 } 12378 } 12379 12380 return this.spawn(elements, { 12381 unique: true 12382 }).filter(selector); 12383 }; 12384 } // Misc functions 12385 ///////////////// 12386 12387 12388 extend(elesfn$t, { 12389 components: function components(root) { 12390 var self = this; 12391 var cy = self.cy(); 12392 var visited = cy.collection(); 12393 var unvisited = root == null ? self.nodes() : root.nodes(); 12394 var components = []; 12395 12396 if (root != null && unvisited.empty()) { 12397 // root may contain only edges 12398 unvisited = root.sources(); // doesn't matter which node to use (undirected), so just use the source sides 12399 } 12400 12401 var visitInComponent = function visitInComponent(node, component) { 12402 visited.merge(node); 12403 unvisited.unmerge(node); 12404 component.merge(node); 12405 }; 12406 12407 if (unvisited.empty()) { 12408 return self.spawn(); 12409 } 12410 12411 var _loop = function _loop() { 12412 // each iteration yields a component 12413 var cmpt = cy.collection(); 12414 components.push(cmpt); 12415 var root = unvisited[0]; 12416 visitInComponent(root, cmpt); 12417 self.bfs({ 12418 directed: false, 12419 roots: root, 12420 visit: function visit(v) { 12421 return visitInComponent(v, cmpt); 12422 } 12423 }); 12424 cmpt.forEach(function (node) { 12425 node.connectedEdges().forEach(function (e) { 12426 // connectedEdges() usually cached 12427 if (self.has(e) && cmpt.has(e.source()) && cmpt.has(e.target())) { 12428 // has() is cheap 12429 cmpt.merge(e); // forEach() only considers nodes -- sets N at call time 12430 } 12431 }); 12432 }); 12433 }; 12434 12435 do { 12436 _loop(); 12437 } while (unvisited.length > 0); 12438 12439 return components; 12440 }, 12441 component: function component() { 12442 var ele = this[0]; 12443 return ele.cy().mutableElements().components(ele)[0]; 12444 } 12445 }); 12446 elesfn$t.componentsOf = elesfn$t.components; 12447 12448 var idFactory = { 12449 generate: function generate(cy, element, tryThisId) { 12450 var id = tryThisId != null ? tryThisId : uuid(); 12451 12452 while (cy.hasElementWithId(id)) { 12453 id = uuid(); 12454 } 12455 12456 return id; 12457 } 12458 }; // represents a set of nodes, edges, or both together 12459 12460 var Collection = function Collection(cy, elements, options) { 12461 if (cy === undefined || !core(cy)) { 12462 error('A collection must have a reference to the core'); 12463 return; 12464 } 12465 12466 var map = new Map$1(); 12467 var createdElements = false; 12468 12469 if (!elements) { 12470 elements = []; 12471 } else if (elements.length > 0 && plainObject(elements[0]) && !element(elements[0])) { 12472 createdElements = true; // make elements from json and restore all at once later 12473 12474 var eles = []; 12475 var elesIds = new Set$1(); 12476 12477 for (var i = 0, l = elements.length; i < l; i++) { 12478 var json = elements[i]; 12479 12480 if (json.data == null) { 12481 json.data = {}; 12482 } 12483 12484 var _data = json.data; // make sure newly created elements have valid ids 12485 12486 if (_data.id == null) { 12487 _data.id = idFactory.generate(cy, json); 12488 } else if (cy.hasElementWithId(_data.id) || elesIds.has(_data.id)) { 12489 continue; // can't create element if prior id already exists 12490 } 12491 12492 var ele = new Element(cy, json, false); 12493 eles.push(ele); 12494 elesIds.add(_data.id); 12495 } 12496 12497 elements = eles; 12498 } 12499 12500 this.length = 0; 12501 12502 for (var _i = 0, _l = elements.length; _i < _l; _i++) { 12503 var element$1 = elements[_i][0]; // [0] in case elements is an array of collections, rather than array of elements 12504 12505 if (element$1 == null) { 12506 continue; 12507 } 12508 12509 var id = element$1._private.data.id; 12510 12511 if (options == null || options.unique && !map.has(id)) { 12512 map.set(id, { 12513 index: this.length, 12514 ele: element$1 12515 }); 12516 this[this.length] = element$1; 12517 this.length++; 12518 } 12519 } 12520 12521 this._private = { 12522 cy: cy, 12523 map: map 12524 }; // restore the elements if we created them from json 12525 12526 if (createdElements) { 12527 this.restore(); 12528 } 12529 }; // Functions 12530 //////////////////////////////////////////////////////////////////////////////////////////////////// 12531 // keep the prototypes in sync (an element has the same functions as a collection) 12532 // and use elefn and elesfn as shorthands to the prototypes 12533 12534 12535 var elesfn$u = Element.prototype = Collection.prototype; 12536 12537 elesfn$u.instanceString = function () { 12538 return 'collection'; 12539 }; 12540 12541 elesfn$u.spawn = function (cy, eles, opts) { 12542 if (!core(cy)) { 12543 // cy is optional 12544 opts = eles; 12545 eles = cy; 12546 cy = this.cy(); 12547 } 12548 12549 return new Collection(cy, eles, opts); 12550 }; 12551 12552 elesfn$u.spawnSelf = function () { 12553 return this.spawn(this); 12554 }; 12555 12556 elesfn$u.cy = function () { 12557 return this._private.cy; 12558 }; 12559 12560 elesfn$u.renderer = function () { 12561 return this._private.cy.renderer(); 12562 }; 12563 12564 elesfn$u.element = function () { 12565 return this[0]; 12566 }; 12567 12568 elesfn$u.collection = function () { 12569 if (collection(this)) { 12570 return this; 12571 } else { 12572 // an element 12573 return new Collection(this._private.cy, [this]); 12574 } 12575 }; 12576 12577 elesfn$u.unique = function () { 12578 return new Collection(this._private.cy, this, { 12579 unique: true 12580 }); 12581 }; 12582 12583 elesfn$u.hasElementWithId = function (id) { 12584 id = '' + id; // id must be string 12585 12586 return this._private.map.has(id); 12587 }; 12588 12589 elesfn$u.getElementById = function (id) { 12590 id = '' + id; // id must be string 12591 12592 var cy = this._private.cy; 12593 12594 var entry = this._private.map.get(id); 12595 12596 return entry ? entry.ele : new Collection(cy); // get ele or empty collection 12597 }; 12598 12599 elesfn$u.$id = elesfn$u.getElementById; 12600 12601 elesfn$u.poolIndex = function () { 12602 var cy = this._private.cy; 12603 var eles = cy._private.elements; 12604 var id = this[0]._private.data.id; 12605 return eles._private.map.get(id).index; 12606 }; 12607 12608 elesfn$u.indexOf = function (ele) { 12609 var id = ele[0]._private.data.id; 12610 return this._private.map.get(id).index; 12611 }; 12612 12613 elesfn$u.indexOfId = function (id) { 12614 id = '' + id; // id must be string 12615 12616 return this._private.map.get(id).index; 12617 }; 12618 12619 elesfn$u.json = function (obj) { 12620 var ele = this.element(); 12621 var cy = this.cy(); 12622 12623 if (ele == null && obj) { 12624 return this; 12625 } // can't set to no eles 12626 12627 12628 if (ele == null) { 12629 return undefined; 12630 } // can't get from no eles 12631 12632 12633 var p = ele._private; 12634 12635 if (plainObject(obj)) { 12636 // set 12637 cy.startBatch(); 12638 12639 if (obj.data) { 12640 ele.data(obj.data); 12641 var _data2 = p.data; 12642 12643 if (ele.isEdge()) { 12644 // source and target are immutable via data() 12645 var move = false; 12646 var spec = {}; 12647 var src = obj.data.source; 12648 var tgt = obj.data.target; 12649 12650 if (src != null && src != _data2.source) { 12651 spec.source = '' + src; // id must be string 12652 12653 move = true; 12654 } 12655 12656 if (tgt != null && tgt != _data2.target) { 12657 spec.target = '' + tgt; // id must be string 12658 12659 move = true; 12660 } 12661 12662 if (move) { 12663 ele = ele.move(spec); 12664 } 12665 } else { 12666 // parent is immutable via data() 12667 var newParentValSpecd = 'parent' in obj.data; 12668 var parent = obj.data.parent; 12669 12670 if (newParentValSpecd && (parent != null || _data2.parent != null) && parent != _data2.parent) { 12671 if (parent === undefined) { 12672 // can't set undefined imperatively, so use null 12673 parent = null; 12674 } 12675 12676 if (parent != null) { 12677 parent = '' + parent; // id must be string 12678 } 12679 12680 ele = ele.move({ 12681 parent: parent 12682 }); 12683 } 12684 } 12685 } 12686 12687 if (obj.position) { 12688 ele.position(obj.position); 12689 } // ignore group -- immutable 12690 12691 12692 var checkSwitch = function checkSwitch(k, trueFnName, falseFnName) { 12693 var obj_k = obj[k]; 12694 12695 if (obj_k != null && obj_k !== p[k]) { 12696 if (obj_k) { 12697 ele[trueFnName](); 12698 } else { 12699 ele[falseFnName](); 12700 } 12701 } 12702 }; 12703 12704 checkSwitch('removed', 'remove', 'restore'); 12705 checkSwitch('selected', 'select', 'unselect'); 12706 checkSwitch('selectable', 'selectify', 'unselectify'); 12707 checkSwitch('locked', 'lock', 'unlock'); 12708 checkSwitch('grabbable', 'grabify', 'ungrabify'); 12709 checkSwitch('pannable', 'panify', 'unpanify'); 12710 12711 if (obj.classes != null) { 12712 ele.classes(obj.classes); 12713 } 12714 12715 cy.endBatch(); 12716 return this; 12717 } else if (obj === undefined) { 12718 // get 12719 var json = { 12720 data: copy(p.data), 12721 position: copy(p.position), 12722 group: p.group, 12723 removed: p.removed, 12724 selected: p.selected, 12725 selectable: p.selectable, 12726 locked: p.locked, 12727 grabbable: p.grabbable, 12728 pannable: p.pannable, 12729 classes: null 12730 }; 12731 json.classes = ''; 12732 var i = 0; 12733 p.classes.forEach(function (cls) { 12734 return json.classes += i++ === 0 ? cls : ' ' + cls; 12735 }); 12736 return json; 12737 } 12738 }; 12739 12740 elesfn$u.jsons = function () { 12741 var jsons = []; 12742 12743 for (var i = 0; i < this.length; i++) { 12744 var ele = this[i]; 12745 var json = ele.json(); 12746 jsons.push(json); 12747 } 12748 12749 return jsons; 12750 }; 12751 12752 elesfn$u.clone = function () { 12753 var cy = this.cy(); 12754 var elesArr = []; 12755 12756 for (var i = 0; i < this.length; i++) { 12757 var ele = this[i]; 12758 var json = ele.json(); 12759 var clone = new Element(cy, json, false); // NB no restore 12760 12761 elesArr.push(clone); 12762 } 12763 12764 return new Collection(cy, elesArr); 12765 }; 12766 12767 elesfn$u.copy = elesfn$u.clone; 12768 12769 elesfn$u.restore = function () { 12770 var notifyRenderer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 12771 var addToPool = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 12772 var self = this; 12773 var cy = self.cy(); 12774 var cy_p = cy._private; // create arrays of nodes and edges, since we need to 12775 // restore the nodes first 12776 12777 var nodes = []; 12778 var edges = []; 12779 var elements; 12780 12781 for (var _i2 = 0, l = self.length; _i2 < l; _i2++) { 12782 var ele = self[_i2]; 12783 12784 if (addToPool && !ele.removed()) { 12785 // don't need to handle this ele 12786 continue; 12787 } // keep nodes first in the array and edges after 12788 12789 12790 if (ele.isNode()) { 12791 // put to front of array if node 12792 nodes.push(ele); 12793 } else { 12794 // put to end of array if edge 12795 edges.push(ele); 12796 } 12797 } 12798 12799 elements = nodes.concat(edges); 12800 var i; 12801 12802 var removeFromElements = function removeFromElements() { 12803 elements.splice(i, 1); 12804 i--; 12805 }; // now, restore each element 12806 12807 12808 for (i = 0; i < elements.length; i++) { 12809 var _ele = elements[i]; 12810 var _private = _ele._private; 12811 var _data3 = _private.data; // the traversal cache should start fresh when ele is added 12812 12813 _ele.clearTraversalCache(); // set id and validate 12814 12815 12816 if (!addToPool && !_private.removed) ; else if (_data3.id === undefined) { 12817 _data3.id = idFactory.generate(cy, _ele); 12818 } else if (number(_data3.id)) { 12819 _data3.id = '' + _data3.id; // now it's a string 12820 } else if (emptyString(_data3.id) || !string(_data3.id)) { 12821 error('Can not create element with invalid string ID `' + _data3.id + '`'); // can't create element if it has empty string as id or non-string id 12822 12823 removeFromElements(); 12824 continue; 12825 } else if (cy.hasElementWithId(_data3.id)) { 12826 error('Can not create second element with ID `' + _data3.id + '`'); // can't create element if one already has that id 12827 12828 removeFromElements(); 12829 continue; 12830 } 12831 12832 var id = _data3.id; // id is finalised, now let's keep a ref 12833 12834 if (_ele.isNode()) { 12835 // extra checks for nodes 12836 var pos = _private.position; // make sure the nodes have a defined position 12837 12838 if (pos.x == null) { 12839 pos.x = 0; 12840 } 12841 12842 if (pos.y == null) { 12843 pos.y = 0; 12844 } 12845 } 12846 12847 if (_ele.isEdge()) { 12848 // extra checks for edges 12849 var edge = _ele; 12850 var fields = ['source', 'target']; 12851 var fieldsLength = fields.length; 12852 var badSourceOrTarget = false; 12853 12854 for (var j = 0; j < fieldsLength; j++) { 12855 var field = fields[j]; 12856 var val = _data3[field]; 12857 12858 if (number(val)) { 12859 val = _data3[field] = '' + _data3[field]; // now string 12860 } 12861 12862 if (val == null || val === '') { 12863 // can't create if source or target is not defined properly 12864 error('Can not create edge `' + id + '` with unspecified ' + field); 12865 badSourceOrTarget = true; 12866 } else if (!cy.hasElementWithId(val)) { 12867 // can't create edge if one of its nodes doesn't exist 12868 error('Can not create edge `' + id + '` with nonexistant ' + field + ' `' + val + '`'); 12869 badSourceOrTarget = true; 12870 } 12871 } 12872 12873 if (badSourceOrTarget) { 12874 removeFromElements(); 12875 continue; 12876 } // can't create this 12877 12878 12879 var src = cy.getElementById(_data3.source); 12880 var tgt = cy.getElementById(_data3.target); // only one edge in node if loop 12881 12882 if (src.same(tgt)) { 12883 src._private.edges.push(edge); 12884 } else { 12885 src._private.edges.push(edge); 12886 12887 tgt._private.edges.push(edge); 12888 } 12889 12890 edge._private.source = src; 12891 edge._private.target = tgt; 12892 } // if is edge 12893 // create mock ids / indexes maps for element so it can be used like collections 12894 12895 12896 _private.map = new Map$1(); 12897 12898 _private.map.set(id, { 12899 ele: _ele, 12900 index: 0 12901 }); 12902 12903 _private.removed = false; 12904 12905 if (addToPool) { 12906 cy.addToPool(_ele); 12907 } 12908 } // for each element 12909 // do compound node sanity checks 12910 12911 12912 for (var _i3 = 0; _i3 < nodes.length; _i3++) { 12913 // each node 12914 var node = nodes[_i3]; 12915 var _data4 = node._private.data; 12916 12917 if (number(_data4.parent)) { 12918 // then automake string 12919 _data4.parent = '' + _data4.parent; 12920 } 12921 12922 var parentId = _data4.parent; 12923 var specifiedParent = parentId != null; 12924 12925 if (specifiedParent) { 12926 var parent = cy.getElementById(parentId); 12927 12928 if (parent.empty()) { 12929 // non-existant parent; just remove it 12930 _data4.parent = undefined; 12931 } else { 12932 var selfAsParent = false; 12933 var ancestor = parent; 12934 12935 while (!ancestor.empty()) { 12936 if (node.same(ancestor)) { 12937 // mark self as parent and remove from data 12938 selfAsParent = true; 12939 _data4.parent = undefined; // remove parent reference 12940 // exit or we loop forever 12941 12942 break; 12943 } 12944 12945 ancestor = ancestor.parent(); 12946 } 12947 12948 if (!selfAsParent) { 12949 // connect with children 12950 parent[0]._private.children.push(node); 12951 12952 node._private.parent = parent[0]; // let the core know we have a compound graph 12953 12954 cy_p.hasCompoundNodes = true; 12955 } 12956 } // else 12957 12958 } // if specified parent 12959 12960 } // for each node 12961 12962 12963 if (elements.length > 0) { 12964 var restored = new Collection(cy, elements); 12965 12966 for (var _i4 = 0; _i4 < restored.length; _i4++) { 12967 var _ele2 = restored[_i4]; 12968 12969 if (_ele2.isNode()) { 12970 continue; 12971 } // adding an edge invalidates the traversal caches for the parallel edges 12972 12973 12974 _ele2.parallelEdges().clearTraversalCache(); // adding an edge invalidates the traversal cache for the connected nodes 12975 12976 12977 _ele2.source().clearTraversalCache(); 12978 12979 _ele2.target().clearTraversalCache(); 12980 } 12981 12982 var toUpdateStyle; 12983 12984 if (cy_p.hasCompoundNodes) { 12985 toUpdateStyle = cy.collection().merge(restored).merge(restored.connectedNodes()).merge(restored.parent()); 12986 } else { 12987 toUpdateStyle = restored; 12988 } 12989 12990 toUpdateStyle.dirtyCompoundBoundsCache().dirtyBoundingBoxCache().updateStyle(notifyRenderer); 12991 12992 if (notifyRenderer) { 12993 restored.emitAndNotify('add'); 12994 } else if (addToPool) { 12995 restored.emit('add'); 12996 } 12997 } 12998 12999 return self; // chainability 13000 }; 13001 13002 elesfn$u.removed = function () { 13003 var ele = this[0]; 13004 return ele && ele._private.removed; 13005 }; 13006 13007 elesfn$u.inside = function () { 13008 var ele = this[0]; 13009 return ele && !ele._private.removed; 13010 }; 13011 13012 elesfn$u.remove = function () { 13013 var notifyRenderer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 13014 var removeFromPool = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 13015 var self = this; 13016 var elesToRemove = []; 13017 var elesToRemoveIds = {}; 13018 var cy = self._private.cy; // add connected edges 13019 13020 function addConnectedEdges(node) { 13021 var edges = node._private.edges; 13022 13023 for (var i = 0; i < edges.length; i++) { 13024 add(edges[i]); 13025 } 13026 } // add descendant nodes 13027 13028 13029 function addChildren(node) { 13030 var children = node._private.children; 13031 13032 for (var i = 0; i < children.length; i++) { 13033 add(children[i]); 13034 } 13035 } 13036 13037 function add(ele) { 13038 var alreadyAdded = elesToRemoveIds[ele.id()]; 13039 13040 if (removeFromPool && ele.removed() || alreadyAdded) { 13041 return; 13042 } else { 13043 elesToRemoveIds[ele.id()] = true; 13044 } 13045 13046 if (ele.isNode()) { 13047 elesToRemove.push(ele); // nodes are removed last 13048 13049 addConnectedEdges(ele); 13050 addChildren(ele); 13051 } else { 13052 elesToRemove.unshift(ele); // edges are removed first 13053 } 13054 } // make the list of elements to remove 13055 // (may be removing more than specified due to connected edges etc) 13056 13057 13058 for (var i = 0, l = self.length; i < l; i++) { 13059 var ele = self[i]; 13060 add(ele); 13061 } 13062 13063 function removeEdgeRef(node, edge) { 13064 var connectedEdges = node._private.edges; 13065 removeFromArray(connectedEdges, edge); // removing an edges invalidates the traversal cache for its nodes 13066 13067 node.clearTraversalCache(); 13068 } 13069 13070 function removeParallelRef(pllEdge) { 13071 // removing an edge invalidates the traversal caches for the parallel edges 13072 pllEdge.clearTraversalCache(); 13073 } 13074 13075 var alteredParents = []; 13076 alteredParents.ids = {}; 13077 13078 function removeChildRef(parent, ele) { 13079 ele = ele[0]; 13080 parent = parent[0]; 13081 var children = parent._private.children; 13082 var pid = parent.id(); 13083 removeFromArray(children, ele); // remove parent => child ref 13084 13085 ele._private.parent = null; // remove child => parent ref 13086 13087 if (!alteredParents.ids[pid]) { 13088 alteredParents.ids[pid] = true; 13089 alteredParents.push(parent); 13090 } 13091 } 13092 13093 self.dirtyCompoundBoundsCache(); 13094 13095 if (removeFromPool) { 13096 cy.removeFromPool(elesToRemove); // remove from core pool 13097 } 13098 13099 for (var _i5 = 0; _i5 < elesToRemove.length; _i5++) { 13100 var _ele3 = elesToRemove[_i5]; 13101 13102 if (_ele3.isEdge()) { 13103 // remove references to this edge in its connected nodes 13104 var src = _ele3.source()[0]; 13105 13106 var tgt = _ele3.target()[0]; 13107 13108 removeEdgeRef(src, _ele3); 13109 removeEdgeRef(tgt, _ele3); 13110 13111 var pllEdges = _ele3.parallelEdges(); 13112 13113 for (var j = 0; j < pllEdges.length; j++) { 13114 var pllEdge = pllEdges[j]; 13115 removeParallelRef(pllEdge); 13116 13117 if (pllEdge.isBundledBezier()) { 13118 pllEdge.dirtyBoundingBoxCache(); 13119 } 13120 } 13121 } else { 13122 // remove reference to parent 13123 var parent = _ele3.parent(); 13124 13125 if (parent.length !== 0) { 13126 removeChildRef(parent, _ele3); 13127 } 13128 } 13129 13130 if (removeFromPool) { 13131 // mark as removed 13132 _ele3._private.removed = true; 13133 } 13134 } // check to see if we have a compound graph or not 13135 13136 13137 var elesStillInside = cy._private.elements; 13138 cy._private.hasCompoundNodes = false; 13139 13140 for (var _i6 = 0; _i6 < elesStillInside.length; _i6++) { 13141 var _ele4 = elesStillInside[_i6]; 13142 13143 if (_ele4.isParent()) { 13144 cy._private.hasCompoundNodes = true; 13145 break; 13146 } 13147 } 13148 13149 var removedElements = new Collection(this.cy(), elesToRemove); 13150 13151 if (removedElements.size() > 0) { 13152 // must manually notify since trigger won't do this automatically once removed 13153 if (notifyRenderer) { 13154 removedElements.emitAndNotify('remove'); 13155 } else if (removeFromPool) { 13156 removedElements.emit('remove'); 13157 } 13158 } // the parents who were modified by the removal need their style updated 13159 13160 13161 for (var _i7 = 0; _i7 < alteredParents.length; _i7++) { 13162 var _ele5 = alteredParents[_i7]; 13163 13164 if (!removeFromPool || !_ele5.removed()) { 13165 _ele5.updateStyle(); 13166 } 13167 } 13168 13169 return removedElements; 13170 }; 13171 13172 elesfn$u.move = function (struct) { 13173 var cy = this._private.cy; 13174 var eles = this; // just clean up refs, caches, etc. in the same way as when removing and then restoring 13175 // (our calls to remove/restore do not remove from the graph or make events) 13176 13177 var notifyRenderer = false; 13178 var modifyPool = false; 13179 13180 var toString = function toString(id) { 13181 return id == null ? id : '' + id; 13182 }; // id must be string 13183 13184 13185 if (struct.source !== undefined || struct.target !== undefined) { 13186 var srcId = toString(struct.source); 13187 var tgtId = toString(struct.target); 13188 var srcExists = srcId != null && cy.hasElementWithId(srcId); 13189 var tgtExists = tgtId != null && cy.hasElementWithId(tgtId); 13190 13191 if (srcExists || tgtExists) { 13192 cy.batch(function () { 13193 // avoid duplicate style updates 13194 eles.remove(notifyRenderer, modifyPool); // clean up refs etc. 13195 13196 eles.emitAndNotify('moveout'); 13197 13198 for (var i = 0; i < eles.length; i++) { 13199 var ele = eles[i]; 13200 var _data5 = ele._private.data; 13201 13202 if (ele.isEdge()) { 13203 if (srcExists) { 13204 _data5.source = srcId; 13205 } 13206 13207 if (tgtExists) { 13208 _data5.target = tgtId; 13209 } 13210 } 13211 } 13212 13213 eles.restore(notifyRenderer, modifyPool); // make new refs, style, etc. 13214 }); 13215 eles.emitAndNotify('move'); 13216 } 13217 } else if (struct.parent !== undefined) { 13218 // move node to new parent 13219 var parentId = toString(struct.parent); 13220 var parentExists = parentId === null || cy.hasElementWithId(parentId); 13221 13222 if (parentExists) { 13223 var pidToAssign = parentId === null ? undefined : parentId; 13224 cy.batch(function () { 13225 // avoid duplicate style updates 13226 var updated = eles.remove(notifyRenderer, modifyPool); // clean up refs etc. 13227 13228 updated.emitAndNotify('moveout'); 13229 13230 for (var i = 0; i < eles.length; i++) { 13231 var ele = eles[i]; 13232 var _data6 = ele._private.data; 13233 13234 if (ele.isNode()) { 13235 _data6.parent = pidToAssign; 13236 } 13237 } 13238 13239 updated.restore(notifyRenderer, modifyPool); // make new refs, style, etc. 13240 }); 13241 eles.emitAndNotify('move'); 13242 } 13243 } 13244 13245 return this; 13246 }; 13247 13248 [elesfn$c, elesfn$d, elesfn$e, elesfn$f, elesfn$g, data$1, elesfn$i, dimensions, elesfn$m, elesfn$n, elesfn$o, elesfn$p, elesfn$q, elesfn$r, elesfn$s, elesfn$t].forEach(function (props) { 13249 extend(elesfn$u, props); 13250 }); 13251 13252 var corefn = { 13253 add: function add(opts) { 13254 var elements; 13255 var cy = this; // add the elements 13256 13257 if (elementOrCollection(opts)) { 13258 var eles = opts; 13259 13260 if (eles._private.cy === cy) { 13261 // same instance => just restore 13262 elements = eles.restore(); 13263 } else { 13264 // otherwise, copy from json 13265 var jsons = []; 13266 13267 for (var i = 0; i < eles.length; i++) { 13268 var ele = eles[i]; 13269 jsons.push(ele.json()); 13270 } 13271 13272 elements = new Collection(cy, jsons); 13273 } 13274 } // specify an array of options 13275 else if (array(opts)) { 13276 var _jsons = opts; 13277 elements = new Collection(cy, _jsons); 13278 } // specify via opts.nodes and opts.edges 13279 else if (plainObject(opts) && (array(opts.nodes) || array(opts.edges))) { 13280 var elesByGroup = opts; 13281 var _jsons2 = []; 13282 var grs = ['nodes', 'edges']; 13283 13284 for (var _i = 0, il = grs.length; _i < il; _i++) { 13285 var group = grs[_i]; 13286 var elesArray = elesByGroup[group]; 13287 13288 if (array(elesArray)) { 13289 for (var j = 0, jl = elesArray.length; j < jl; j++) { 13290 var json = extend({ 13291 group: group 13292 }, elesArray[j]); 13293 13294 _jsons2.push(json); 13295 } 13296 } 13297 } 13298 13299 elements = new Collection(cy, _jsons2); 13300 } // specify options for one element 13301 else { 13302 var _json = opts; 13303 elements = new Element(cy, _json).collection(); 13304 } 13305 13306 return elements; 13307 }, 13308 remove: function remove(collection) { 13309 if (elementOrCollection(collection)) ; else if (string(collection)) { 13310 var selector = collection; 13311 collection = this.$(selector); 13312 } 13313 13314 return collection.remove(); 13315 } 13316 }; 13317 13318 /* global Float32Array */ 13319 13320 /*! Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 13321 function generateCubicBezier(mX1, mY1, mX2, mY2) { 13322 var NEWTON_ITERATIONS = 4, 13323 NEWTON_MIN_SLOPE = 0.001, 13324 SUBDIVISION_PRECISION = 0.0000001, 13325 SUBDIVISION_MAX_ITERATIONS = 10, 13326 kSplineTableSize = 11, 13327 kSampleStepSize = 1.0 / (kSplineTableSize - 1.0), 13328 float32ArraySupported = typeof Float32Array !== 'undefined'; 13329 /* Must contain four arguments. */ 13330 13331 if (arguments.length !== 4) { 13332 return false; 13333 } 13334 /* Arguments must be numbers. */ 13335 13336 13337 for (var i = 0; i < 4; ++i) { 13338 if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) { 13339 return false; 13340 } 13341 } 13342 /* X values must be in the [0, 1] range. */ 13343 13344 13345 mX1 = Math.min(mX1, 1); 13346 mX2 = Math.min(mX2, 1); 13347 mX1 = Math.max(mX1, 0); 13348 mX2 = Math.max(mX2, 0); 13349 var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); 13350 13351 function A(aA1, aA2) { 13352 return 1.0 - 3.0 * aA2 + 3.0 * aA1; 13353 } 13354 13355 function B(aA1, aA2) { 13356 return 3.0 * aA2 - 6.0 * aA1; 13357 } 13358 13359 function C(aA1) { 13360 return 3.0 * aA1; 13361 } 13362 13363 function calcBezier(aT, aA1, aA2) { 13364 return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; 13365 } 13366 13367 function getSlope(aT, aA1, aA2) { 13368 return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); 13369 } 13370 13371 function newtonRaphsonIterate(aX, aGuessT) { 13372 for (var _i = 0; _i < NEWTON_ITERATIONS; ++_i) { 13373 var currentSlope = getSlope(aGuessT, mX1, mX2); 13374 13375 if (currentSlope === 0.0) { 13376 return aGuessT; 13377 } 13378 13379 var currentX = calcBezier(aGuessT, mX1, mX2) - aX; 13380 aGuessT -= currentX / currentSlope; 13381 } 13382 13383 return aGuessT; 13384 } 13385 13386 function calcSampleValues() { 13387 for (var _i2 = 0; _i2 < kSplineTableSize; ++_i2) { 13388 mSampleValues[_i2] = calcBezier(_i2 * kSampleStepSize, mX1, mX2); 13389 } 13390 } 13391 13392 function binarySubdivide(aX, aA, aB) { 13393 var currentX, 13394 currentT, 13395 i = 0; 13396 13397 do { 13398 currentT = aA + (aB - aA) / 2.0; 13399 currentX = calcBezier(currentT, mX1, mX2) - aX; 13400 13401 if (currentX > 0.0) { 13402 aB = currentT; 13403 } else { 13404 aA = currentT; 13405 } 13406 } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); 13407 13408 return currentT; 13409 } 13410 13411 function getTForX(aX) { 13412 var intervalStart = 0.0, 13413 currentSample = 1, 13414 lastSample = kSplineTableSize - 1; 13415 13416 for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { 13417 intervalStart += kSampleStepSize; 13418 } 13419 13420 --currentSample; 13421 var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]), 13422 guessForT = intervalStart + dist * kSampleStepSize, 13423 initialSlope = getSlope(guessForT, mX1, mX2); 13424 13425 if (initialSlope >= NEWTON_MIN_SLOPE) { 13426 return newtonRaphsonIterate(aX, guessForT); 13427 } else if (initialSlope === 0.0) { 13428 return guessForT; 13429 } else { 13430 return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize); 13431 } 13432 } 13433 13434 var _precomputed = false; 13435 13436 function precompute() { 13437 _precomputed = true; 13438 13439 if (mX1 !== mY1 || mX2 !== mY2) { 13440 calcSampleValues(); 13441 } 13442 } 13443 13444 var f = function f(aX) { 13445 if (!_precomputed) { 13446 precompute(); 13447 } 13448 13449 if (mX1 === mY1 && mX2 === mY2) { 13450 return aX; 13451 } 13452 13453 if (aX === 0) { 13454 return 0; 13455 } 13456 13457 if (aX === 1) { 13458 return 1; 13459 } 13460 13461 return calcBezier(getTForX(aX), mY1, mY2); 13462 }; 13463 13464 f.getControlPoints = function () { 13465 return [{ 13466 x: mX1, 13467 y: mY1 13468 }, { 13469 x: mX2, 13470 y: mY2 13471 }]; 13472 }; 13473 13474 var str = "generateBezier(" + [mX1, mY1, mX2, mY2] + ")"; 13475 13476 f.toString = function () { 13477 return str; 13478 }; 13479 13480 return f; 13481 } 13482 13483 /*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 13484 13485 /* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass 13486 then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */ 13487 var generateSpringRK4 = function () { 13488 function springAccelerationForState(state) { 13489 return -state.tension * state.x - state.friction * state.v; 13490 } 13491 13492 function springEvaluateStateWithDerivative(initialState, dt, derivative) { 13493 var state = { 13494 x: initialState.x + derivative.dx * dt, 13495 v: initialState.v + derivative.dv * dt, 13496 tension: initialState.tension, 13497 friction: initialState.friction 13498 }; 13499 return { 13500 dx: state.v, 13501 dv: springAccelerationForState(state) 13502 }; 13503 } 13504 13505 function springIntegrateState(state, dt) { 13506 var a = { 13507 dx: state.v, 13508 dv: springAccelerationForState(state) 13509 }, 13510 b = springEvaluateStateWithDerivative(state, dt * 0.5, a), 13511 c = springEvaluateStateWithDerivative(state, dt * 0.5, b), 13512 d = springEvaluateStateWithDerivative(state, dt, c), 13513 dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx), 13514 dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv); 13515 state.x = state.x + dxdt * dt; 13516 state.v = state.v + dvdt * dt; 13517 return state; 13518 } 13519 13520 return function springRK4Factory(tension, friction, duration) { 13521 var initState = { 13522 x: -1, 13523 v: 0, 13524 tension: null, 13525 friction: null 13526 }, 13527 path = [0], 13528 time_lapsed = 0, 13529 tolerance = 1 / 10000, 13530 DT = 16 / 1000, 13531 have_duration, 13532 dt, 13533 last_state; 13534 tension = parseFloat(tension) || 500; 13535 friction = parseFloat(friction) || 20; 13536 duration = duration || null; 13537 initState.tension = tension; 13538 initState.friction = friction; 13539 have_duration = duration !== null; 13540 /* Calculate the actual time it takes for this animation to complete with the provided conditions. */ 13541 13542 if (have_duration) { 13543 /* Run the simulation without a duration. */ 13544 time_lapsed = springRK4Factory(tension, friction); 13545 /* Compute the adjusted time delta. */ 13546 13547 dt = time_lapsed / duration * DT; 13548 } else { 13549 dt = DT; 13550 } 13551 13552 for (;;) { 13553 /* Next/step function .*/ 13554 last_state = springIntegrateState(last_state || initState, dt); 13555 /* Store the position. */ 13556 13557 path.push(1 + last_state.x); 13558 time_lapsed += 16; 13559 /* If the change threshold is reached, break. */ 13560 13561 if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) { 13562 break; 13563 } 13564 } 13565 /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the 13566 computed path and returns a snapshot of the position according to a given percentComplete. */ 13567 13568 13569 return !have_duration ? time_lapsed : function (percentComplete) { 13570 return path[percentComplete * (path.length - 1) | 0]; 13571 }; 13572 }; 13573 }(); 13574 13575 var cubicBezier = function cubicBezier(t1, p1, t2, p2) { 13576 var bezier = generateCubicBezier(t1, p1, t2, p2); 13577 return function (start, end, percent) { 13578 return start + (end - start) * bezier(percent); 13579 }; 13580 }; 13581 13582 var easings = { 13583 'linear': function linear(start, end, percent) { 13584 return start + (end - start) * percent; 13585 }, 13586 // default easings 13587 'ease': cubicBezier(0.25, 0.1, 0.25, 1), 13588 'ease-in': cubicBezier(0.42, 0, 1, 1), 13589 'ease-out': cubicBezier(0, 0, 0.58, 1), 13590 'ease-in-out': cubicBezier(0.42, 0, 0.58, 1), 13591 // sine 13592 'ease-in-sine': cubicBezier(0.47, 0, 0.745, 0.715), 13593 'ease-out-sine': cubicBezier(0.39, 0.575, 0.565, 1), 13594 'ease-in-out-sine': cubicBezier(0.445, 0.05, 0.55, 0.95), 13595 // quad 13596 'ease-in-quad': cubicBezier(0.55, 0.085, 0.68, 0.53), 13597 'ease-out-quad': cubicBezier(0.25, 0.46, 0.45, 0.94), 13598 'ease-in-out-quad': cubicBezier(0.455, 0.03, 0.515, 0.955), 13599 // cubic 13600 'ease-in-cubic': cubicBezier(0.55, 0.055, 0.675, 0.19), 13601 'ease-out-cubic': cubicBezier(0.215, 0.61, 0.355, 1), 13602 'ease-in-out-cubic': cubicBezier(0.645, 0.045, 0.355, 1), 13603 // quart 13604 'ease-in-quart': cubicBezier(0.895, 0.03, 0.685, 0.22), 13605 'ease-out-quart': cubicBezier(0.165, 0.84, 0.44, 1), 13606 'ease-in-out-quart': cubicBezier(0.77, 0, 0.175, 1), 13607 // quint 13608 'ease-in-quint': cubicBezier(0.755, 0.05, 0.855, 0.06), 13609 'ease-out-quint': cubicBezier(0.23, 1, 0.32, 1), 13610 'ease-in-out-quint': cubicBezier(0.86, 0, 0.07, 1), 13611 // expo 13612 'ease-in-expo': cubicBezier(0.95, 0.05, 0.795, 0.035), 13613 'ease-out-expo': cubicBezier(0.19, 1, 0.22, 1), 13614 'ease-in-out-expo': cubicBezier(1, 0, 0, 1), 13615 // circ 13616 'ease-in-circ': cubicBezier(0.6, 0.04, 0.98, 0.335), 13617 'ease-out-circ': cubicBezier(0.075, 0.82, 0.165, 1), 13618 'ease-in-out-circ': cubicBezier(0.785, 0.135, 0.15, 0.86), 13619 // user param easings... 13620 'spring': function spring(tension, friction, duration) { 13621 if (duration === 0) { 13622 // can't get a spring w/ duration 0 13623 return easings.linear; // duration 0 => jump to end so impl doesn't matter 13624 } 13625 13626 var spring = generateSpringRK4(tension, friction, duration); 13627 return function (start, end, percent) { 13628 return start + (end - start) * spring(percent); 13629 }; 13630 }, 13631 'cubic-bezier': cubicBezier 13632 }; 13633 13634 function getEasedValue(type, start, end, percent, easingFn) { 13635 if (percent === 1) { 13636 return end; 13637 } 13638 13639 if (start === end) { 13640 return end; 13641 } 13642 13643 var val = easingFn(start, end, percent); 13644 13645 if (type == null) { 13646 return val; 13647 } 13648 13649 if (type.roundValue || type.color) { 13650 val = Math.round(val); 13651 } 13652 13653 if (type.min !== undefined) { 13654 val = Math.max(val, type.min); 13655 } 13656 13657 if (type.max !== undefined) { 13658 val = Math.min(val, type.max); 13659 } 13660 13661 return val; 13662 } 13663 13664 function getValue(prop, spec) { 13665 if (prop.pfValue != null || prop.value != null) { 13666 if (prop.pfValue != null && (spec == null || spec.type.units !== '%')) { 13667 return prop.pfValue; 13668 } else { 13669 return prop.value; 13670 } 13671 } else { 13672 return prop; 13673 } 13674 } 13675 13676 function ease(startProp, endProp, percent, easingFn, propSpec) { 13677 var type = propSpec != null ? propSpec.type : null; 13678 13679 if (percent < 0) { 13680 percent = 0; 13681 } else if (percent > 1) { 13682 percent = 1; 13683 } 13684 13685 var start = getValue(startProp, propSpec); 13686 var end = getValue(endProp, propSpec); 13687 13688 if (number(start) && number(end)) { 13689 return getEasedValue(type, start, end, percent, easingFn); 13690 } else if (array(start) && array(end)) { 13691 var easedArr = []; 13692 13693 for (var i = 0; i < end.length; i++) { 13694 var si = start[i]; 13695 var ei = end[i]; 13696 13697 if (si != null && ei != null) { 13698 var val = getEasedValue(type, si, ei, percent, easingFn); 13699 easedArr.push(val); 13700 } else { 13701 easedArr.push(ei); 13702 } 13703 } 13704 13705 return easedArr; 13706 } 13707 13708 return undefined; 13709 } 13710 13711 function step(self, ani, now, isCore) { 13712 var isEles = !isCore; 13713 var _p = self._private; 13714 var ani_p = ani._private; 13715 var pEasing = ani_p.easing; 13716 var startTime = ani_p.startTime; 13717 var cy = isCore ? self : self.cy(); 13718 var style = cy.style(); 13719 13720 if (!ani_p.easingImpl) { 13721 if (pEasing == null) { 13722 // use default 13723 ani_p.easingImpl = easings['linear']; 13724 } else { 13725 // then define w/ name 13726 var easingVals; 13727 13728 if (string(pEasing)) { 13729 var easingProp = style.parse('transition-timing-function', pEasing); 13730 easingVals = easingProp.value; 13731 } else { 13732 // then assume preparsed array 13733 easingVals = pEasing; 13734 } 13735 13736 var name, args; 13737 13738 if (string(easingVals)) { 13739 name = easingVals; 13740 args = []; 13741 } else { 13742 name = easingVals[1]; 13743 args = easingVals.slice(2).map(function (n) { 13744 return +n; 13745 }); 13746 } 13747 13748 if (args.length > 0) { 13749 // create with args 13750 if (name === 'spring') { 13751 args.push(ani_p.duration); // need duration to generate spring 13752 } 13753 13754 ani_p.easingImpl = easings[name].apply(null, args); 13755 } else { 13756 // static impl by name 13757 ani_p.easingImpl = easings[name]; 13758 } 13759 } 13760 } 13761 13762 var easing = ani_p.easingImpl; 13763 var percent; 13764 13765 if (ani_p.duration === 0) { 13766 percent = 1; 13767 } else { 13768 percent = (now - startTime) / ani_p.duration; 13769 } 13770 13771 if (ani_p.applying) { 13772 percent = ani_p.progress; 13773 } 13774 13775 if (percent < 0) { 13776 percent = 0; 13777 } else if (percent > 1) { 13778 percent = 1; 13779 } 13780 13781 if (ani_p.delay == null) { 13782 // then update 13783 var startPos = ani_p.startPosition; 13784 var endPos = ani_p.position; 13785 13786 if (endPos && isEles && !self.locked()) { 13787 var newPos = {}; 13788 13789 if (valid(startPos.x, endPos.x)) { 13790 newPos.x = ease(startPos.x, endPos.x, percent, easing); 13791 } 13792 13793 if (valid(startPos.y, endPos.y)) { 13794 newPos.y = ease(startPos.y, endPos.y, percent, easing); 13795 } 13796 13797 self.position(newPos); 13798 } 13799 13800 var startPan = ani_p.startPan; 13801 var endPan = ani_p.pan; 13802 var pan = _p.pan; 13803 var animatingPan = endPan != null && isCore; 13804 13805 if (animatingPan) { 13806 if (valid(startPan.x, endPan.x)) { 13807 pan.x = ease(startPan.x, endPan.x, percent, easing); 13808 } 13809 13810 if (valid(startPan.y, endPan.y)) { 13811 pan.y = ease(startPan.y, endPan.y, percent, easing); 13812 } 13813 13814 self.emit('pan'); 13815 } 13816 13817 var startZoom = ani_p.startZoom; 13818 var endZoom = ani_p.zoom; 13819 var animatingZoom = endZoom != null && isCore; 13820 13821 if (animatingZoom) { 13822 if (valid(startZoom, endZoom)) { 13823 _p.zoom = bound(_p.minZoom, ease(startZoom, endZoom, percent, easing), _p.maxZoom); 13824 } 13825 13826 self.emit('zoom'); 13827 } 13828 13829 if (animatingPan || animatingZoom) { 13830 self.emit('viewport'); 13831 } 13832 13833 var props = ani_p.style; 13834 13835 if (props && props.length > 0 && isEles) { 13836 for (var i = 0; i < props.length; i++) { 13837 var prop = props[i]; 13838 var _name = prop.name; 13839 var end = prop; 13840 var start = ani_p.startStyle[_name]; 13841 var propSpec = style.properties[start.name]; 13842 var easedVal = ease(start, end, percent, easing, propSpec); 13843 style.overrideBypass(self, _name, easedVal); 13844 } // for props 13845 13846 13847 self.emit('style'); 13848 } // if 13849 13850 } 13851 13852 ani_p.progress = percent; 13853 return percent; 13854 } 13855 13856 function valid(start, end) { 13857 if (start == null || end == null) { 13858 return false; 13859 } 13860 13861 if (number(start) && number(end)) { 13862 return true; 13863 } else if (start && end) { 13864 return true; 13865 } 13866 13867 return false; 13868 } 13869 13870 function startAnimation(self, ani, now, isCore) { 13871 var ani_p = ani._private; 13872 ani_p.started = true; 13873 ani_p.startTime = now - ani_p.progress * ani_p.duration; 13874 } 13875 13876 function stepAll(now, cy) { 13877 var eles = cy._private.aniEles; 13878 var doneEles = []; 13879 13880 function stepOne(ele, isCore) { 13881 var _p = ele._private; 13882 var current = _p.animation.current; 13883 var queue = _p.animation.queue; 13884 var ranAnis = false; // cancel all animations on display:none ele 13885 13886 if (!isCore && ele.pstyle('display').value === 'none') { 13887 // put all current and queue animations in this tick's current list 13888 // and empty the lists for the element 13889 current = current.splice(0, current.length).concat(queue.splice(0, queue.length)); // stop all animations 13890 13891 for (var i = 0; i < current.length; i++) { 13892 current[i].stop(); 13893 } 13894 } // if nothing currently animating, get something from the queue 13895 13896 13897 if (current.length === 0) { 13898 var next = queue.shift(); 13899 13900 if (next) { 13901 current.push(next); 13902 } 13903 } 13904 13905 var callbacks = function callbacks(_callbacks) { 13906 for (var j = _callbacks.length - 1; j >= 0; j--) { 13907 var cb = _callbacks[j]; 13908 cb(); 13909 } 13910 13911 _callbacks.splice(0, _callbacks.length); 13912 }; // step and remove if done 13913 13914 13915 for (var _i = current.length - 1; _i >= 0; _i--) { 13916 var ani = current[_i]; 13917 var ani_p = ani._private; 13918 13919 if (ani_p.stopped) { 13920 current.splice(_i, 1); 13921 ani_p.hooked = false; 13922 ani_p.playing = false; 13923 ani_p.started = false; 13924 callbacks(ani_p.frames); 13925 continue; 13926 } 13927 13928 if (!ani_p.playing && !ani_p.applying) { 13929 continue; 13930 } // an apply() while playing shouldn't do anything 13931 13932 13933 if (ani_p.playing && ani_p.applying) { 13934 ani_p.applying = false; 13935 } 13936 13937 if (!ani_p.started) { 13938 startAnimation(ele, ani, now); 13939 } 13940 13941 step(ele, ani, now, isCore); 13942 13943 if (ani_p.applying) { 13944 ani_p.applying = false; 13945 } 13946 13947 callbacks(ani_p.frames); 13948 13949 if (ani_p.step != null) { 13950 ani_p.step(now); 13951 } 13952 13953 if (ani.completed()) { 13954 current.splice(_i, 1); 13955 ani_p.hooked = false; 13956 ani_p.playing = false; 13957 ani_p.started = false; 13958 callbacks(ani_p.completes); 13959 } 13960 13961 ranAnis = true; 13962 } 13963 13964 if (!isCore && current.length === 0 && queue.length === 0) { 13965 doneEles.push(ele); 13966 } 13967 13968 return ranAnis; 13969 } // stepElement 13970 // handle all eles 13971 13972 13973 var ranEleAni = false; 13974 13975 for (var e = 0; e < eles.length; e++) { 13976 var ele = eles[e]; 13977 var handledThisEle = stepOne(ele); 13978 ranEleAni = ranEleAni || handledThisEle; 13979 } // each element 13980 13981 13982 var ranCoreAni = stepOne(cy, true); // notify renderer 13983 13984 if (ranEleAni || ranCoreAni) { 13985 if (eles.length > 0) { 13986 cy.notify('draw', eles); 13987 } else { 13988 cy.notify('draw'); 13989 } 13990 } // remove elements from list of currently animating if its queues are empty 13991 13992 13993 eles.unmerge(doneEles); 13994 cy.emit('step'); 13995 } // stepAll 13996 13997 var corefn$1 = { 13998 // pull in animation functions 13999 animate: define$3.animate(), 14000 animation: define$3.animation(), 14001 animated: define$3.animated(), 14002 clearQueue: define$3.clearQueue(), 14003 delay: define$3.delay(), 14004 delayAnimation: define$3.delayAnimation(), 14005 stop: define$3.stop(), 14006 addToAnimationPool: function addToAnimationPool(eles) { 14007 var cy = this; 14008 14009 if (!cy.styleEnabled()) { 14010 return; 14011 } // save cycles when no style used 14012 14013 14014 cy._private.aniEles.merge(eles); 14015 }, 14016 stopAnimationLoop: function stopAnimationLoop() { 14017 this._private.animationsRunning = false; 14018 }, 14019 startAnimationLoop: function startAnimationLoop() { 14020 var cy = this; 14021 cy._private.animationsRunning = true; 14022 14023 if (!cy.styleEnabled()) { 14024 return; 14025 } // save cycles when no style used 14026 // NB the animation loop will exec in headless environments if style enabled 14027 // and explicit cy.destroy() is necessary to stop the loop 14028 14029 14030 function headlessStep() { 14031 if (!cy._private.animationsRunning) { 14032 return; 14033 } 14034 14035 requestAnimationFrame(function animationStep(now) { 14036 stepAll(now, cy); 14037 headlessStep(); 14038 }); 14039 } 14040 14041 var renderer = cy.renderer(); 14042 14043 if (renderer && renderer.beforeRender) { 14044 // let the renderer schedule animations 14045 renderer.beforeRender(function rendererAnimationStep(willDraw, now) { 14046 stepAll(now, cy); 14047 }, renderer.beforeRenderPriorities.animations); 14048 } else { 14049 // manage the animation loop ourselves 14050 headlessStep(); // first call 14051 } 14052 } 14053 }; 14054 14055 var emitterOptions$1 = { 14056 qualifierCompare: function qualifierCompare(selector1, selector2) { 14057 if (selector1 == null || selector2 == null) { 14058 return selector1 == null && selector2 == null; 14059 } else { 14060 return selector1.sameText(selector2); 14061 } 14062 }, 14063 eventMatches: function eventMatches(cy, listener, eventObj) { 14064 var selector = listener.qualifier; 14065 14066 if (selector != null) { 14067 return cy !== eventObj.target && element(eventObj.target) && selector.matches(eventObj.target); 14068 } 14069 14070 return true; 14071 }, 14072 addEventFields: function addEventFields(cy, evt) { 14073 evt.cy = cy; 14074 evt.target = cy; 14075 }, 14076 callbackContext: function callbackContext(cy, listener, eventObj) { 14077 return listener.qualifier != null ? eventObj.target : cy; 14078 } 14079 }; 14080 14081 var argSelector$1 = function argSelector(arg) { 14082 if (string(arg)) { 14083 return new Selector(arg); 14084 } else { 14085 return arg; 14086 } 14087 }; 14088 14089 var elesfn$v = { 14090 createEmitter: function createEmitter() { 14091 var _p = this._private; 14092 14093 if (!_p.emitter) { 14094 _p.emitter = new Emitter(emitterOptions$1, this); 14095 } 14096 14097 return this; 14098 }, 14099 emitter: function emitter() { 14100 return this._private.emitter; 14101 }, 14102 on: function on(events, selector, callback) { 14103 this.emitter().on(events, argSelector$1(selector), callback); 14104 return this; 14105 }, 14106 removeListener: function removeListener(events, selector, callback) { 14107 this.emitter().removeListener(events, argSelector$1(selector), callback); 14108 return this; 14109 }, 14110 removeAllListeners: function removeAllListeners() { 14111 this.emitter().removeAllListeners(); 14112 return this; 14113 }, 14114 one: function one(events, selector, callback) { 14115 this.emitter().one(events, argSelector$1(selector), callback); 14116 return this; 14117 }, 14118 once: function once(events, selector, callback) { 14119 this.emitter().one(events, argSelector$1(selector), callback); 14120 return this; 14121 }, 14122 emit: function emit(events, extraParams) { 14123 this.emitter().emit(events, extraParams); 14124 return this; 14125 }, 14126 emitAndNotify: function emitAndNotify(event, eles) { 14127 this.emit(event); 14128 this.notify(event, eles); 14129 return this; 14130 } 14131 }; 14132 define$3.eventAliasesOn(elesfn$v); 14133 14134 var corefn$2 = { 14135 png: function png(options) { 14136 var renderer = this._private.renderer; 14137 options = options || {}; 14138 return renderer.png(options); 14139 }, 14140 jpg: function jpg(options) { 14141 var renderer = this._private.renderer; 14142 options = options || {}; 14143 options.bg = options.bg || '#fff'; 14144 return renderer.jpg(options); 14145 } 14146 }; 14147 corefn$2.jpeg = corefn$2.jpg; 14148 14149 var corefn$3 = { 14150 layout: function layout(options) { 14151 var cy = this; 14152 14153 if (options == null) { 14154 error('Layout options must be specified to make a layout'); 14155 return; 14156 } 14157 14158 if (options.name == null) { 14159 error('A `name` must be specified to make a layout'); 14160 return; 14161 } 14162 14163 var name = options.name; 14164 var Layout = cy.extension('layout', name); 14165 14166 if (Layout == null) { 14167 error('No such layout `' + name + '` found. Did you forget to import it and `cytoscape.use()` it?'); 14168 return; 14169 } 14170 14171 var eles; 14172 14173 if (string(options.eles)) { 14174 eles = cy.$(options.eles); 14175 } else { 14176 eles = options.eles != null ? options.eles : cy.$(); 14177 } 14178 14179 var layout = new Layout(extend({}, options, { 14180 cy: cy, 14181 eles: eles 14182 })); 14183 return layout; 14184 } 14185 }; 14186 corefn$3.createLayout = corefn$3.makeLayout = corefn$3.layout; 14187 14188 var corefn$4 = { 14189 notify: function notify(eventName, eventEles) { 14190 var _p = this._private; 14191 14192 if (this.batching()) { 14193 _p.batchNotifications = _p.batchNotifications || {}; 14194 var eles = _p.batchNotifications[eventName] = _p.batchNotifications[eventName] || this.collection(); 14195 14196 if (eventEles != null) { 14197 eles.merge(eventEles); 14198 } 14199 14200 return; // notifications are disabled during batching 14201 } 14202 14203 if (!_p.notificationsEnabled) { 14204 return; 14205 } // exit on disabled 14206 14207 14208 var renderer = this.renderer(); // exit if destroy() called on core or renderer in between frames #1499 #1528 14209 14210 if (this.destroyed() || !renderer) { 14211 return; 14212 } 14213 14214 renderer.notify(eventName, eventEles); 14215 }, 14216 notifications: function notifications(bool) { 14217 var p = this._private; 14218 14219 if (bool === undefined) { 14220 return p.notificationsEnabled; 14221 } else { 14222 p.notificationsEnabled = bool ? true : false; 14223 } 14224 14225 return this; 14226 }, 14227 noNotifications: function noNotifications(callback) { 14228 this.notifications(false); 14229 callback(); 14230 this.notifications(true); 14231 }, 14232 batching: function batching() { 14233 return this._private.batchCount > 0; 14234 }, 14235 startBatch: function startBatch() { 14236 var _p = this._private; 14237 14238 if (_p.batchCount == null) { 14239 _p.batchCount = 0; 14240 } 14241 14242 if (_p.batchCount === 0) { 14243 _p.batchStyleEles = this.collection(); 14244 _p.batchNotifications = {}; 14245 } 14246 14247 _p.batchCount++; 14248 return this; 14249 }, 14250 endBatch: function endBatch() { 14251 var _p = this._private; 14252 14253 if (_p.batchCount === 0) { 14254 return this; 14255 } 14256 14257 _p.batchCount--; 14258 14259 if (_p.batchCount === 0) { 14260 // update style for dirty eles 14261 _p.batchStyleEles.updateStyle(); 14262 14263 var renderer = this.renderer(); // notify the renderer of queued eles and event types 14264 14265 Object.keys(_p.batchNotifications).forEach(function (eventName) { 14266 var eles = _p.batchNotifications[eventName]; 14267 14268 if (eles.empty()) { 14269 renderer.notify(eventName); 14270 } else { 14271 renderer.notify(eventName, eles); 14272 } 14273 }); 14274 } 14275 14276 return this; 14277 }, 14278 batch: function batch(callback) { 14279 this.startBatch(); 14280 callback(); 14281 this.endBatch(); 14282 return this; 14283 }, 14284 // for backwards compatibility 14285 batchData: function batchData(map) { 14286 var cy = this; 14287 return this.batch(function () { 14288 var ids = Object.keys(map); 14289 14290 for (var i = 0; i < ids.length; i++) { 14291 var id = ids[i]; 14292 var data = map[id]; 14293 var ele = cy.getElementById(id); 14294 ele.data(data); 14295 } 14296 }); 14297 } 14298 }; 14299 14300 var rendererDefaults = defaults({ 14301 hideEdgesOnViewport: false, 14302 textureOnViewport: false, 14303 motionBlur: false, 14304 motionBlurOpacity: 0.05, 14305 pixelRatio: undefined, 14306 desktopTapThreshold: 4, 14307 touchTapThreshold: 8, 14308 wheelSensitivity: 1, 14309 debug: false, 14310 showFps: false 14311 }); 14312 var corefn$5 = { 14313 renderTo: function renderTo(context, zoom, pan, pxRatio) { 14314 var r = this._private.renderer; 14315 r.renderTo(context, zoom, pan, pxRatio); 14316 return this; 14317 }, 14318 renderer: function renderer() { 14319 return this._private.renderer; 14320 }, 14321 forceRender: function forceRender() { 14322 this.notify('draw'); 14323 return this; 14324 }, 14325 resize: function resize() { 14326 this.invalidateSize(); 14327 this.emitAndNotify('resize'); 14328 return this; 14329 }, 14330 initRenderer: function initRenderer(options) { 14331 var cy = this; 14332 var RendererProto = cy.extension('renderer', options.name); 14333 14334 if (RendererProto == null) { 14335 error("Can not initialise: No such renderer `".concat(options.name, "` found. Did you forget to import it and `cytoscape.use()` it?")); 14336 return; 14337 } 14338 14339 if (options.wheelSensitivity !== undefined) { 14340 warn("You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine."); 14341 } 14342 14343 var rOpts = rendererDefaults(options); 14344 rOpts.cy = cy; 14345 cy._private.renderer = new RendererProto(rOpts); 14346 this.notify('init'); 14347 }, 14348 destroyRenderer: function destroyRenderer() { 14349 var cy = this; 14350 cy.notify('destroy'); // destroy the renderer 14351 14352 var domEle = cy.container(); 14353 14354 if (domEle) { 14355 domEle._cyreg = null; 14356 14357 while (domEle.childNodes.length > 0) { 14358 domEle.removeChild(domEle.childNodes[0]); 14359 } 14360 } 14361 14362 cy._private.renderer = null; // to be extra safe, remove the ref 14363 14364 cy.mutableElements().forEach(function (ele) { 14365 var _p = ele._private; 14366 _p.rscratch = {}; 14367 _p.rstyle = {}; 14368 _p.animation.current = []; 14369 _p.animation.queue = []; 14370 }); 14371 }, 14372 onRender: function onRender(fn) { 14373 return this.on('render', fn); 14374 }, 14375 offRender: function offRender(fn) { 14376 return this.off('render', fn); 14377 } 14378 }; 14379 corefn$5.invalidateDimensions = corefn$5.resize; 14380 14381 var corefn$6 = { 14382 // get a collection 14383 // - empty collection on no args 14384 // - collection of elements in the graph on selector arg 14385 // - guarantee a returned collection when elements or collection specified 14386 collection: function collection(eles, opts) { 14387 if (string(eles)) { 14388 return this.$(eles); 14389 } else if (elementOrCollection(eles)) { 14390 return eles.collection(); 14391 } else if (array(eles)) { 14392 return new Collection(this, eles, opts); 14393 } 14394 14395 return new Collection(this); 14396 }, 14397 nodes: function nodes(selector) { 14398 var nodes = this.$(function (ele) { 14399 return ele.isNode(); 14400 }); 14401 14402 if (selector) { 14403 return nodes.filter(selector); 14404 } 14405 14406 return nodes; 14407 }, 14408 edges: function edges(selector) { 14409 var edges = this.$(function (ele) { 14410 return ele.isEdge(); 14411 }); 14412 14413 if (selector) { 14414 return edges.filter(selector); 14415 } 14416 14417 return edges; 14418 }, 14419 // search the graph like jQuery 14420 $: function $(selector) { 14421 var eles = this._private.elements; 14422 14423 if (selector) { 14424 return eles.filter(selector); 14425 } else { 14426 return eles.spawnSelf(); 14427 } 14428 }, 14429 mutableElements: function mutableElements() { 14430 return this._private.elements; 14431 } 14432 }; // aliases 14433 14434 corefn$6.elements = corefn$6.filter = corefn$6.$; 14435 14436 var styfn = {}; // keys for style blocks, e.g. ttfftt 14437 14438 var TRUE = 't'; 14439 var FALSE = 'f'; // (potentially expensive calculation) 14440 // apply the style to the element based on 14441 // - its bypass 14442 // - what selectors match it 14443 14444 styfn.apply = function (eles) { 14445 var self = this; 14446 var _p = self._private; 14447 var cy = _p.cy; 14448 var updatedEles = cy.collection(); 14449 14450 if (_p.newStyle) { 14451 // clear style caches 14452 _p.contextStyles = {}; 14453 _p.propDiffs = {}; 14454 self.cleanElements(eles, true); 14455 } 14456 14457 for (var ie = 0; ie < eles.length; ie++) { 14458 var ele = eles[ie]; 14459 var cxtMeta = self.getContextMeta(ele); 14460 14461 if (cxtMeta.empty) { 14462 continue; 14463 } 14464 14465 var cxtStyle = self.getContextStyle(cxtMeta); 14466 var app = self.applyContextStyle(cxtMeta, cxtStyle, ele); 14467 14468 if (!_p.newStyle) { 14469 self.updateTransitions(ele, app.diffProps); 14470 } 14471 14472 var hintsDiff = self.updateStyleHints(ele); 14473 14474 if (hintsDiff) { 14475 updatedEles.merge(ele); 14476 } 14477 } // for elements 14478 14479 14480 _p.newStyle = false; 14481 return updatedEles; 14482 }; 14483 14484 styfn.getPropertiesDiff = function (oldCxtKey, newCxtKey) { 14485 var self = this; 14486 var cache = self._private.propDiffs = self._private.propDiffs || {}; 14487 var dualCxtKey = oldCxtKey + '-' + newCxtKey; 14488 var cachedVal = cache[dualCxtKey]; 14489 14490 if (cachedVal) { 14491 return cachedVal; 14492 } 14493 14494 var diffProps = []; 14495 var addedProp = {}; 14496 14497 for (var i = 0; i < self.length; i++) { 14498 var cxt = self[i]; 14499 var oldHasCxt = oldCxtKey[i] === TRUE; 14500 var newHasCxt = newCxtKey[i] === TRUE; 14501 var cxtHasDiffed = oldHasCxt !== newHasCxt; 14502 var cxtHasMappedProps = cxt.mappedProperties.length > 0; 14503 14504 if (cxtHasDiffed || newHasCxt && cxtHasMappedProps) { 14505 var props = void 0; 14506 14507 if (cxtHasDiffed && cxtHasMappedProps) { 14508 props = cxt.properties; // suffices b/c mappedProperties is a subset of properties 14509 } else if (cxtHasDiffed) { 14510 props = cxt.properties; // need to check them all 14511 } else if (cxtHasMappedProps) { 14512 props = cxt.mappedProperties; // only need to check mapped 14513 } 14514 14515 for (var j = 0; j < props.length; j++) { 14516 var prop = props[j]; 14517 var name = prop.name; // if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter 14518 // (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result 14519 // is cached) 14520 14521 var laterCxtOverrides = false; 14522 14523 for (var k = i + 1; k < self.length; k++) { 14524 var laterCxt = self[k]; 14525 var hasLaterCxt = newCxtKey[k] === TRUE; 14526 14527 if (!hasLaterCxt) { 14528 continue; 14529 } // can't override unless the context is active 14530 14531 14532 laterCxtOverrides = laterCxt.properties[prop.name] != null; 14533 14534 if (laterCxtOverrides) { 14535 break; 14536 } // exit early as long as one later context overrides 14537 14538 } 14539 14540 if (!addedProp[name] && !laterCxtOverrides) { 14541 addedProp[name] = true; 14542 diffProps.push(name); 14543 } 14544 } // for props 14545 14546 } // if 14547 14548 } // for contexts 14549 14550 14551 cache[dualCxtKey] = diffProps; 14552 return diffProps; 14553 }; 14554 14555 styfn.getContextMeta = function (ele) { 14556 var self = this; 14557 var cxtKey = ''; 14558 var diffProps; 14559 var prevKey = ele._private.styleCxtKey || ''; 14560 14561 if (self._private.newStyle) { 14562 prevKey = ''; // since we need to apply all style if a fresh stylesheet 14563 } // get the cxt key 14564 14565 14566 for (var i = 0; i < self.length; i++) { 14567 var context = self[i]; 14568 var contextSelectorMatches = context.selector && context.selector.matches(ele); // NB: context.selector may be null for 'core' 14569 14570 if (contextSelectorMatches) { 14571 cxtKey += TRUE; 14572 } else { 14573 cxtKey += FALSE; 14574 } 14575 } // for context 14576 14577 14578 diffProps = self.getPropertiesDiff(prevKey, cxtKey); 14579 ele._private.styleCxtKey = cxtKey; 14580 return { 14581 key: cxtKey, 14582 diffPropNames: diffProps, 14583 empty: diffProps.length === 0 14584 }; 14585 }; // gets a computed ele style object based on matched contexts 14586 14587 14588 styfn.getContextStyle = function (cxtMeta) { 14589 var cxtKey = cxtMeta.key; 14590 var self = this; 14591 var cxtStyles = this._private.contextStyles = this._private.contextStyles || {}; // if already computed style, returned cached copy 14592 14593 if (cxtStyles[cxtKey]) { 14594 return cxtStyles[cxtKey]; 14595 } 14596 14597 var style = { 14598 _private: { 14599 key: cxtKey 14600 } 14601 }; 14602 14603 for (var i = 0; i < self.length; i++) { 14604 var cxt = self[i]; 14605 var hasCxt = cxtKey[i] === TRUE; 14606 14607 if (!hasCxt) { 14608 continue; 14609 } 14610 14611 for (var j = 0; j < cxt.properties.length; j++) { 14612 var prop = cxt.properties[j]; 14613 style[prop.name] = prop; 14614 } 14615 } 14616 14617 cxtStyles[cxtKey] = style; 14618 return style; 14619 }; 14620 14621 styfn.applyContextStyle = function (cxtMeta, cxtStyle, ele) { 14622 var self = this; 14623 var diffProps = cxtMeta.diffPropNames; 14624 var retDiffProps = {}; 14625 var types = self.types; 14626 14627 for (var i = 0; i < diffProps.length; i++) { 14628 var diffPropName = diffProps[i]; 14629 var cxtProp = cxtStyle[diffPropName]; 14630 var eleProp = ele.pstyle(diffPropName); 14631 14632 if (!cxtProp) { 14633 // no context prop means delete 14634 if (!eleProp) { 14635 continue; // no existing prop means nothing needs to be removed 14636 // nb affects initial application on mapped values like control-point-distances 14637 } else if (eleProp.bypass) { 14638 cxtProp = { 14639 name: diffPropName, 14640 deleteBypassed: true 14641 }; 14642 } else { 14643 cxtProp = { 14644 name: diffPropName, 14645 "delete": true 14646 }; 14647 } 14648 } // save cycles when the context prop doesn't need to be applied 14649 14650 14651 if (eleProp === cxtProp) { 14652 continue; 14653 } // save cycles when a mapped context prop doesn't need to be applied 14654 14655 14656 if (cxtProp.mapped === types.fn // context prop is function mapper 14657 && eleProp != null // some props can be null even by default (e.g. a prop that overrides another one) 14658 && eleProp.mapping != null // ele prop is a concrete value from from a mapper 14659 && eleProp.mapping.value === cxtProp.value // the current prop on the ele is a flat prop value for the function mapper 14660 ) { 14661 // NB don't write to cxtProp, as it's shared among eles (stored in stylesheet) 14662 var mapping = eleProp.mapping; // can write to mapping, as it's a per-ele copy 14663 14664 var fnValue = mapping.fnValue = cxtProp.value(ele); // temporarily cache the value in case of a miss 14665 14666 if (fnValue === mapping.prevFnValue) { 14667 continue; 14668 } 14669 } 14670 14671 var retDiffProp = retDiffProps[diffPropName] = { 14672 prev: eleProp 14673 }; 14674 self.applyParsedProperty(ele, cxtProp); 14675 retDiffProp.next = ele.pstyle(diffPropName); 14676 14677 if (retDiffProp.next && retDiffProp.next.bypass) { 14678 retDiffProp.next = retDiffProp.next.bypassed; 14679 } 14680 } 14681 14682 return { 14683 diffProps: retDiffProps 14684 }; 14685 }; 14686 14687 styfn.updateStyleHints = function (ele) { 14688 var _p = ele._private; 14689 var self = this; 14690 var propNames = self.propertyGroupNames; 14691 var propGrKeys = self.propertyGroupKeys; 14692 14693 var propHash = function propHash(ele, propNames, seedKey) { 14694 return self.getPropertiesHash(ele, propNames, seedKey); 14695 }; 14696 14697 var oldStyleKey = _p.styleKey; 14698 14699 if (ele.removed()) { 14700 return false; 14701 } 14702 14703 var isNode = _p.group === 'nodes'; // get the style key hashes per prop group 14704 // but lazily -- only use non-default prop values to reduce the number of hashes 14705 // 14706 14707 var overriddenStyles = ele._private.style; 14708 propNames = Object.keys(overriddenStyles); 14709 14710 for (var i = 0; i < propGrKeys.length; i++) { 14711 var grKey = propGrKeys[i]; 14712 _p.styleKeys[grKey] = 0; 14713 } 14714 14715 var updateGrKey = function updateGrKey(val, grKey) { 14716 return _p.styleKeys[grKey] = hashInt(val, _p.styleKeys[grKey]); 14717 }; 14718 14719 var updateGrKeyWStr = function updateGrKeyWStr(strVal, grKey) { 14720 for (var j = 0; j < strVal.length; j++) { 14721 updateGrKey(strVal.charCodeAt(j), grKey); 14722 } 14723 }; // - hashing works on 32 bit ints b/c we use bitwise ops 14724 // - small numbers get cut off (e.g. 0.123 is seen as 0 by the hashing function) 14725 // - raise up small numbers so more significant digits are seen by hashing 14726 // - make small numbers larger than a normal value to avoid collisions 14727 // - works in practice and it's relatively cheap 14728 14729 14730 var N = 2000000000; 14731 14732 var cleanNum = function cleanNum(val) { 14733 return -128 < val && val < 128 && Math.floor(val) !== val ? N - (val * 1024 | 0) : val; 14734 }; 14735 14736 for (var _i = 0; _i < propNames.length; _i++) { 14737 var name = propNames[_i]; 14738 var parsedProp = overriddenStyles[name]; 14739 14740 if (parsedProp == null) { 14741 continue; 14742 } 14743 14744 var propInfo = this.properties[name]; 14745 var type = propInfo.type; 14746 var _grKey = propInfo.groupKey; 14747 var normalizedNumberVal = void 0; 14748 14749 if (propInfo.hashOverride != null) { 14750 normalizedNumberVal = propInfo.hashOverride(ele, parsedProp); 14751 } else if (parsedProp.pfValue != null) { 14752 normalizedNumberVal = parsedProp.pfValue; 14753 } // might not be a number if it allows enums 14754 14755 14756 var numberVal = propInfo.enums == null ? parsedProp.value : null; 14757 var haveNormNum = normalizedNumberVal != null; 14758 var haveUnitedNum = numberVal != null; 14759 var haveNum = haveNormNum || haveUnitedNum; 14760 var units = parsedProp.units; // numbers are cheaper to hash than strings 14761 // 1 hash op vs n hash ops (for length n string) 14762 14763 if (type.number && haveNum) { 14764 var v = haveNormNum ? normalizedNumberVal : numberVal; 14765 14766 if (type.multiple) { 14767 for (var _i2 = 0; _i2 < v.length; _i2++) { 14768 updateGrKey(cleanNum(v[_i2]), _grKey); 14769 } 14770 } else { 14771 updateGrKey(cleanNum(v), _grKey); 14772 } 14773 14774 if (!haveNormNum && units != null) { 14775 updateGrKeyWStr(units, _grKey); 14776 } 14777 } else { 14778 updateGrKeyWStr(parsedProp.strValue, _grKey); 14779 } 14780 } // overall style key 14781 // 14782 14783 14784 var hash = 0; 14785 14786 for (var _i3 = 0; _i3 < propGrKeys.length; _i3++) { 14787 var _grKey2 = propGrKeys[_i3]; 14788 var grHash = _p.styleKeys[_grKey2]; 14789 hash = hashInt(grHash, hash); 14790 } 14791 14792 _p.styleKey = hash; // label dims 14793 // 14794 14795 var labelDimsKey = _p.labelDimsKey = _p.styleKeys.labelDimensions; 14796 _p.labelKey = propHash(ele, ['label'], labelDimsKey); 14797 _p.labelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.labelKey); 14798 14799 if (!isNode) { 14800 _p.sourceLabelKey = propHash(ele, ['source-label'], labelDimsKey); 14801 _p.sourceLabelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.sourceLabelKey); 14802 _p.targetLabelKey = propHash(ele, ['target-label'], labelDimsKey); 14803 _p.targetLabelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.targetLabelKey); 14804 } // node 14805 // 14806 14807 14808 if (isNode) { 14809 var _p$styleKeys = _p.styleKeys, 14810 nodeBody = _p$styleKeys.nodeBody, 14811 nodeBorder = _p$styleKeys.nodeBorder, 14812 backgroundImage = _p$styleKeys.backgroundImage, 14813 compound = _p$styleKeys.compound, 14814 pie = _p$styleKeys.pie; 14815 _p.nodeKey = hashIntsArray([nodeBorder, backgroundImage, compound, pie], nodeBody); 14816 _p.hasPie = pie != 0; 14817 } 14818 14819 return oldStyleKey !== _p.styleKey; 14820 }; 14821 14822 styfn.clearStyleHints = function (ele) { 14823 var _p = ele._private; 14824 _p.styleKeys = {}; 14825 _p.styleKey = null; 14826 _p.labelKey = null; 14827 _p.labelStyleKey = null; 14828 _p.sourceLabelKey = null; 14829 _p.sourceLabelStyleKey = null; 14830 _p.targetLabelKey = null; 14831 _p.targetLabelStyleKey = null; 14832 _p.nodeKey = null; 14833 _p.hasPie = null; 14834 }; // apply a property to the style (for internal use) 14835 // returns whether application was successful 14836 // 14837 // now, this function flattens the property, and here's how: 14838 // 14839 // for parsedProp:{ bypass: true, deleteBypass: true } 14840 // no property is generated, instead the bypass property in the 14841 // element's style is replaced by what's pointed to by the `bypassed` 14842 // field in the bypass property (i.e. restoring the property the 14843 // bypass was overriding) 14844 // 14845 // for parsedProp:{ mapped: truthy } 14846 // the generated flattenedProp:{ mapping: prop } 14847 // 14848 // for parsedProp:{ bypass: true } 14849 // the generated flattenedProp:{ bypassed: parsedProp } 14850 14851 14852 styfn.applyParsedProperty = function (ele, parsedProp) { 14853 var self = this; 14854 var prop = parsedProp; 14855 var style = ele._private.style; 14856 var flatProp; 14857 var types = self.types; 14858 var type = self.properties[prop.name].type; 14859 var propIsBypass = prop.bypass; 14860 var origProp = style[prop.name]; 14861 var origPropIsBypass = origProp && origProp.bypass; 14862 var _p = ele._private; 14863 var flatPropMapping = 'mapping'; 14864 14865 var getVal = function getVal(p) { 14866 if (p == null) { 14867 return null; 14868 } else if (p.pfValue != null) { 14869 return p.pfValue; 14870 } else { 14871 return p.value; 14872 } 14873 }; 14874 14875 var checkTriggers = function checkTriggers() { 14876 var fromVal = getVal(origProp); 14877 var toVal = getVal(prop); 14878 self.checkTriggers(ele, prop.name, fromVal, toVal); 14879 }; // edge sanity checks to prevent the client from making serious mistakes 14880 14881 14882 if (parsedProp.name === 'curve-style' && ele.isEdge() && ( // loops must be bundled beziers 14883 parsedProp.value !== 'bezier' && ele.isLoop() || // edges connected to compound nodes can not be haystacks 14884 parsedProp.value === 'haystack' && (ele.source().isParent() || ele.target().isParent()))) { 14885 prop = parsedProp = this.parse(parsedProp.name, 'bezier', propIsBypass); 14886 } 14887 14888 if (prop["delete"]) { 14889 // delete the property and use the default value on falsey value 14890 style[prop.name] = undefined; 14891 checkTriggers(); 14892 return true; 14893 } 14894 14895 if (prop.deleteBypassed) { 14896 // delete the property that the 14897 if (!origProp) { 14898 checkTriggers(); 14899 return true; // can't delete if no prop 14900 } else if (origProp.bypass) { 14901 // delete bypassed 14902 origProp.bypassed = undefined; 14903 checkTriggers(); 14904 return true; 14905 } else { 14906 return false; // we're unsuccessful deleting the bypassed 14907 } 14908 } // check if we need to delete the current bypass 14909 14910 14911 if (prop.deleteBypass) { 14912 // then this property is just here to indicate we need to delete 14913 if (!origProp) { 14914 checkTriggers(); 14915 return true; // property is already not defined 14916 } else if (origProp.bypass) { 14917 // then replace the bypass property with the original 14918 // because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary) 14919 style[prop.name] = origProp.bypassed; 14920 checkTriggers(); 14921 return true; 14922 } else { 14923 return false; // we're unsuccessful deleting the bypass 14924 } 14925 } 14926 14927 var printMappingErr = function printMappingErr() { 14928 warn('Do not assign mappings to elements without corresponding data (i.e. ele `' + ele.id() + '` has no mapping for property `' + prop.name + '` with data field `' + prop.field + '`); try a `[' + prop.field + ']` selector to limit scope to elements with `' + prop.field + '` defined'); 14929 }; // put the property in the style objects 14930 14931 14932 switch (prop.mapped) { 14933 // flatten the property if mapped 14934 case types.mapData: 14935 { 14936 // flatten the field (e.g. data.foo.bar) 14937 var fields = prop.field.split('.'); 14938 var fieldVal = _p.data; 14939 14940 for (var i = 0; i < fields.length && fieldVal; i++) { 14941 var field = fields[i]; 14942 fieldVal = fieldVal[field]; 14943 } 14944 14945 if (fieldVal == null) { 14946 printMappingErr(); 14947 return false; 14948 } 14949 14950 var percent; 14951 14952 if (!number(fieldVal)) { 14953 // then don't apply and fall back on the existing style 14954 warn('Do not use continuous mappers without specifying numeric data (i.e. `' + prop.field + ': ' + fieldVal + '` for `' + ele.id() + '` is non-numeric)'); 14955 return false; 14956 } else { 14957 var fieldWidth = prop.fieldMax - prop.fieldMin; 14958 14959 if (fieldWidth === 0) { 14960 // safety check -- not strictly necessary as no props of zero range should be passed here 14961 percent = 0; 14962 } else { 14963 percent = (fieldVal - prop.fieldMin) / fieldWidth; 14964 } 14965 } // make sure to bound percent value 14966 14967 14968 if (percent < 0) { 14969 percent = 0; 14970 } else if (percent > 1) { 14971 percent = 1; 14972 } 14973 14974 if (type.color) { 14975 var r1 = prop.valueMin[0]; 14976 var r2 = prop.valueMax[0]; 14977 var g1 = prop.valueMin[1]; 14978 var g2 = prop.valueMax[1]; 14979 var b1 = prop.valueMin[2]; 14980 var b2 = prop.valueMax[2]; 14981 var a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3]; 14982 var a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3]; 14983 var clr = [Math.round(r1 + (r2 - r1) * percent), Math.round(g1 + (g2 - g1) * percent), Math.round(b1 + (b2 - b1) * percent), Math.round(a1 + (a2 - a1) * percent)]; 14984 flatProp = { 14985 // colours are simple, so just create the flat property instead of expensive string parsing 14986 bypass: prop.bypass, 14987 // we're a bypass if the mapping property is a bypass 14988 name: prop.name, 14989 value: clr, 14990 strValue: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')' 14991 }; 14992 } else if (type.number) { 14993 var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent; 14994 flatProp = this.parse(prop.name, calcValue, prop.bypass, flatPropMapping); 14995 } else { 14996 return false; // can only map to colours and numbers 14997 } 14998 14999 if (!flatProp) { 15000 // if we can't flatten the property, then don't apply the property and fall back on the existing style 15001 printMappingErr(); 15002 return false; 15003 } 15004 15005 flatProp.mapping = prop; // keep a reference to the mapping 15006 15007 prop = flatProp; // the flattened (mapped) property is the one we want 15008 15009 break; 15010 } 15011 // direct mapping 15012 15013 case types.data: 15014 { 15015 // flatten the field (e.g. data.foo.bar) 15016 var _fields = prop.field.split('.'); 15017 15018 var _fieldVal = _p.data; 15019 15020 for (var _i4 = 0; _i4 < _fields.length && _fieldVal; _i4++) { 15021 var _field = _fields[_i4]; 15022 _fieldVal = _fieldVal[_field]; 15023 } 15024 15025 if (_fieldVal != null) { 15026 flatProp = this.parse(prop.name, _fieldVal, prop.bypass, flatPropMapping); 15027 } 15028 15029 if (!flatProp) { 15030 // if we can't flatten the property, then don't apply and fall back on the existing style 15031 printMappingErr(); 15032 return false; 15033 } 15034 15035 flatProp.mapping = prop; // keep a reference to the mapping 15036 15037 prop = flatProp; // the flattened (mapped) property is the one we want 15038 15039 break; 15040 } 15041 15042 case types.fn: 15043 { 15044 var fn = prop.value; 15045 var fnRetVal = prop.fnValue != null ? prop.fnValue : fn(ele); // check for cached value before calling function 15046 15047 prop.prevFnValue = fnRetVal; 15048 15049 if (fnRetVal == null) { 15050 warn('Custom function mappers may not return null (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is null)'); 15051 return false; 15052 } 15053 15054 flatProp = this.parse(prop.name, fnRetVal, prop.bypass, flatPropMapping); 15055 15056 if (!flatProp) { 15057 warn('Custom function mappers may not return invalid values for the property type (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is invalid)'); 15058 return false; 15059 } 15060 15061 flatProp.mapping = copy(prop); // keep a reference to the mapping 15062 15063 prop = flatProp; // the flattened (mapped) property is the one we want 15064 15065 break; 15066 } 15067 15068 case undefined: 15069 break; 15070 // just set the property 15071 15072 default: 15073 return false; 15074 // not a valid mapping 15075 } // if the property is a bypass property, then link the resultant property to the original one 15076 15077 15078 if (propIsBypass) { 15079 if (origPropIsBypass) { 15080 // then this bypass overrides the existing one 15081 prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass 15082 } else { 15083 // then link the orig prop to the new bypass 15084 prop.bypassed = origProp; 15085 } 15086 15087 style[prop.name] = prop; // and set 15088 } else { 15089 // prop is not bypass 15090 if (origPropIsBypass) { 15091 // then keep the orig prop (since it's a bypass) and link to the new prop 15092 origProp.bypassed = prop; 15093 } else { 15094 // then just replace the old prop with the new one 15095 style[prop.name] = prop; 15096 } 15097 } 15098 15099 checkTriggers(); 15100 return true; 15101 }; 15102 15103 styfn.cleanElements = function (eles, keepBypasses) { 15104 for (var i = 0; i < eles.length; i++) { 15105 var ele = eles[i]; 15106 this.clearStyleHints(ele); 15107 ele.dirtyCompoundBoundsCache(); 15108 ele.dirtyBoundingBoxCache(); 15109 15110 if (!keepBypasses) { 15111 ele._private.style = {}; 15112 } else { 15113 var style = ele._private.style; 15114 var propNames = Object.keys(style); 15115 15116 for (var j = 0; j < propNames.length; j++) { 15117 var propName = propNames[j]; 15118 var eleProp = style[propName]; 15119 15120 if (eleProp != null) { 15121 if (eleProp.bypass) { 15122 eleProp.bypassed = null; 15123 } else { 15124 style[propName] = null; 15125 } 15126 } 15127 } 15128 } 15129 } 15130 }; // updates the visual style for all elements (useful for manual style modification after init) 15131 15132 15133 styfn.update = function () { 15134 var cy = this._private.cy; 15135 var eles = cy.mutableElements(); 15136 eles.updateStyle(); 15137 }; // diffProps : { name => { prev, next } } 15138 15139 15140 styfn.updateTransitions = function (ele, diffProps) { 15141 var self = this; 15142 var _p = ele._private; 15143 var props = ele.pstyle('transition-property').value; 15144 var duration = ele.pstyle('transition-duration').pfValue; 15145 var delay = ele.pstyle('transition-delay').pfValue; 15146 15147 if (props.length > 0 && duration > 0) { 15148 var style = {}; // build up the style to animate towards 15149 15150 var anyPrev = false; 15151 15152 for (var i = 0; i < props.length; i++) { 15153 var prop = props[i]; 15154 var styProp = ele.pstyle(prop); 15155 var diffProp = diffProps[prop]; 15156 15157 if (!diffProp) { 15158 continue; 15159 } 15160 15161 var prevProp = diffProp.prev; 15162 var fromProp = prevProp; 15163 var toProp = diffProp.next != null ? diffProp.next : styProp; 15164 var diff = false; 15165 var initVal = void 0; 15166 var initDt = 0.000001; // delta time % value for initVal (allows animating out of init zero opacity) 15167 15168 if (!fromProp) { 15169 continue; 15170 } // consider px values 15171 15172 15173 if (number(fromProp.pfValue) && number(toProp.pfValue)) { 15174 diff = toProp.pfValue - fromProp.pfValue; // nonzero is truthy 15175 15176 initVal = fromProp.pfValue + initDt * diff; // consider numerical values 15177 } else if (number(fromProp.value) && number(toProp.value)) { 15178 diff = toProp.value - fromProp.value; // nonzero is truthy 15179 15180 initVal = fromProp.value + initDt * diff; // consider colour values 15181 } else if (array(fromProp.value) && array(toProp.value)) { 15182 diff = fromProp.value[0] !== toProp.value[0] || fromProp.value[1] !== toProp.value[1] || fromProp.value[2] !== toProp.value[2]; 15183 initVal = fromProp.strValue; 15184 } // the previous value is good for an animation only if it's different 15185 15186 15187 if (diff) { 15188 style[prop] = toProp.strValue; // to val 15189 15190 this.applyBypass(ele, prop, initVal); // from val 15191 15192 anyPrev = true; 15193 } 15194 } // end if props allow ani 15195 // can't transition if there's nothing previous to transition from 15196 15197 15198 if (!anyPrev) { 15199 return; 15200 } 15201 15202 _p.transitioning = true; 15203 new Promise$1(function (resolve) { 15204 if (delay > 0) { 15205 ele.delayAnimation(delay).play().promise().then(resolve); 15206 } else { 15207 resolve(); 15208 } 15209 }).then(function () { 15210 return ele.animation({ 15211 style: style, 15212 duration: duration, 15213 easing: ele.pstyle('transition-timing-function').value, 15214 queue: false 15215 }).play().promise(); 15216 }).then(function () { 15217 // if( !isBypass ){ 15218 self.removeBypasses(ele, props); 15219 ele.emitAndNotify('style'); // } 15220 15221 _p.transitioning = false; 15222 }); 15223 } else if (_p.transitioning) { 15224 this.removeBypasses(ele, props); 15225 ele.emitAndNotify('style'); 15226 _p.transitioning = false; 15227 } 15228 }; 15229 15230 styfn.checkTrigger = function (ele, name, fromValue, toValue, getTrigger, onTrigger) { 15231 var prop = this.properties[name]; 15232 var triggerCheck = getTrigger(prop); 15233 15234 if (triggerCheck != null && triggerCheck(fromValue, toValue)) { 15235 onTrigger(prop); 15236 } 15237 }; 15238 15239 styfn.checkZOrderTrigger = function (ele, name, fromValue, toValue) { 15240 var _this = this; 15241 15242 this.checkTrigger(ele, name, fromValue, toValue, function (prop) { 15243 return prop.triggersZOrder; 15244 }, function () { 15245 _this._private.cy.notify('zorder', ele); 15246 }); 15247 }; 15248 15249 styfn.checkBoundsTrigger = function (ele, name, fromValue, toValue) { 15250 this.checkTrigger(ele, name, fromValue, toValue, function (prop) { 15251 return prop.triggersBounds; 15252 }, function (prop) { 15253 ele.dirtyCompoundBoundsCache(); 15254 ele.dirtyBoundingBoxCache(); // if the prop change makes the bb of pll bezier edges invalid, 15255 // then dirty the pll edge bb cache as well 15256 15257 if ( // only for beziers -- so performance of other edges isn't affected 15258 (ele.pstyle('curve-style').value === 'bezier' // already a bezier 15259 // was just now changed to or from a bezier: 15260 || name === 'curve-style' && (fromValue === 'bezier' || toValue === 'bezier')) && prop.triggersBoundsOfParallelBeziers) { 15261 ele.parallelEdges().forEach(function (pllEdge) { 15262 if (pllEdge.isBundledBezier()) { 15263 pllEdge.dirtyBoundingBoxCache(); 15264 } 15265 }); 15266 } 15267 }); 15268 }; 15269 15270 styfn.checkTriggers = function (ele, name, fromValue, toValue) { 15271 ele.dirtyStyleCache(); 15272 this.checkZOrderTrigger(ele, name, fromValue, toValue); 15273 this.checkBoundsTrigger(ele, name, fromValue, toValue); 15274 }; 15275 15276 var styfn$1 = {}; // bypasses are applied to an existing style on an element, and just tacked on temporarily 15277 // returns true iff application was successful for at least 1 specified property 15278 15279 styfn$1.applyBypass = function (eles, name, value, updateTransitions) { 15280 var self = this; 15281 var props = []; 15282 var isBypass = true; // put all the properties (can specify one or many) in an array after parsing them 15283 15284 if (name === '*' || name === '**') { 15285 // apply to all property names 15286 if (value !== undefined) { 15287 for (var i = 0; i < self.properties.length; i++) { 15288 var prop = self.properties[i]; 15289 var _name = prop.name; 15290 var parsedProp = this.parse(_name, value, true); 15291 15292 if (parsedProp) { 15293 props.push(parsedProp); 15294 } 15295 } 15296 } 15297 } else if (string(name)) { 15298 // then parse the single property 15299 var _parsedProp = this.parse(name, value, true); 15300 15301 if (_parsedProp) { 15302 props.push(_parsedProp); 15303 } 15304 } else if (plainObject(name)) { 15305 // then parse each property 15306 var specifiedProps = name; 15307 updateTransitions = value; 15308 var names = Object.keys(specifiedProps); 15309 15310 for (var _i = 0; _i < names.length; _i++) { 15311 var _name2 = names[_i]; 15312 var _value = specifiedProps[_name2]; 15313 15314 if (_value === undefined) { 15315 // try camel case name too 15316 _value = specifiedProps[dash2camel(_name2)]; 15317 } 15318 15319 if (_value !== undefined) { 15320 var _parsedProp2 = this.parse(_name2, _value, true); 15321 15322 if (_parsedProp2) { 15323 props.push(_parsedProp2); 15324 } 15325 } 15326 } 15327 } else { 15328 // can't do anything without well defined properties 15329 return false; 15330 } // we've failed if there are no valid properties 15331 15332 15333 if (props.length === 0) { 15334 return false; 15335 } // now, apply the bypass properties on the elements 15336 15337 15338 var ret = false; // return true if at least one succesful bypass applied 15339 15340 for (var _i2 = 0; _i2 < eles.length; _i2++) { 15341 // for each ele 15342 var ele = eles[_i2]; 15343 var diffProps = {}; 15344 var diffProp = void 0; 15345 15346 for (var j = 0; j < props.length; j++) { 15347 // for each prop 15348 var _prop = props[j]; 15349 15350 if (updateTransitions) { 15351 var prevProp = ele.pstyle(_prop.name); 15352 diffProp = diffProps[_prop.name] = { 15353 prev: prevProp 15354 }; 15355 } 15356 15357 ret = this.applyParsedProperty(ele, _prop) || ret; 15358 15359 if (updateTransitions) { 15360 diffProp.next = ele.pstyle(_prop.name); 15361 } 15362 } // for props 15363 15364 15365 if (ret) { 15366 this.updateStyleHints(ele); 15367 } 15368 15369 if (updateTransitions) { 15370 this.updateTransitions(ele, diffProps, isBypass); 15371 } 15372 } // for eles 15373 15374 15375 return ret; 15376 }; // only useful in specific cases like animation 15377 15378 15379 styfn$1.overrideBypass = function (eles, name, value) { 15380 name = camel2dash(name); 15381 15382 for (var i = 0; i < eles.length; i++) { 15383 var ele = eles[i]; 15384 var prop = ele._private.style[name]; 15385 var type = this.properties[name].type; 15386 var isColor = type.color; 15387 var isMulti = type.mutiple; 15388 var oldValue = !prop ? null : prop.pfValue != null ? prop.pfValue : prop.value; 15389 15390 if (!prop || !prop.bypass) { 15391 // need a bypass if one doesn't exist 15392 this.applyBypass(ele, name, value); 15393 } else { 15394 prop.value = value; 15395 15396 if (prop.pfValue != null) { 15397 prop.pfValue = value; 15398 } 15399 15400 if (isColor) { 15401 prop.strValue = 'rgb(' + value.join(',') + ')'; 15402 } else if (isMulti) { 15403 prop.strValue = value.join(' '); 15404 } else { 15405 prop.strValue = '' + value; 15406 } 15407 15408 this.updateStyleHints(ele); 15409 } 15410 15411 this.checkTriggers(ele, name, oldValue, value); 15412 } 15413 }; 15414 15415 styfn$1.removeAllBypasses = function (eles, updateTransitions) { 15416 return this.removeBypasses(eles, this.propertyNames, updateTransitions); 15417 }; 15418 15419 styfn$1.removeBypasses = function (eles, props, updateTransitions) { 15420 var isBypass = true; 15421 15422 for (var j = 0; j < eles.length; j++) { 15423 var ele = eles[j]; 15424 var diffProps = {}; 15425 15426 for (var i = 0; i < props.length; i++) { 15427 var name = props[i]; 15428 var prop = this.properties[name]; 15429 var prevProp = ele.pstyle(prop.name); 15430 15431 if (!prevProp || !prevProp.bypass) { 15432 // if a bypass doesn't exist for the prop, nothing needs to be removed 15433 continue; 15434 } 15435 15436 var value = ''; // empty => remove bypass 15437 15438 var parsedProp = this.parse(name, value, true); 15439 var diffProp = diffProps[prop.name] = { 15440 prev: prevProp 15441 }; 15442 this.applyParsedProperty(ele, parsedProp); 15443 diffProp.next = ele.pstyle(prop.name); 15444 } // for props 15445 15446 15447 this.updateStyleHints(ele); 15448 15449 if (updateTransitions) { 15450 this.updateTransitions(ele, diffProps, isBypass); 15451 } 15452 } // for eles 15453 15454 }; 15455 15456 var styfn$2 = {}; // gets what an em size corresponds to in pixels relative to a dom element 15457 15458 styfn$2.getEmSizeInPixels = function () { 15459 var px = this.containerCss('font-size'); 15460 15461 if (px != null) { 15462 return parseFloat(px); 15463 } else { 15464 return 1; // for headless 15465 } 15466 }; // gets css property from the core container 15467 15468 15469 styfn$2.containerCss = function (propName) { 15470 var cy = this._private.cy; 15471 var domElement = cy.container(); 15472 15473 if (window$1 && domElement && window$1.getComputedStyle) { 15474 return window$1.getComputedStyle(domElement).getPropertyValue(propName); 15475 } 15476 }; 15477 15478 var styfn$3 = {}; // gets the rendered style for an element 15479 15480 styfn$3.getRenderedStyle = function (ele, prop) { 15481 if (prop) { 15482 return this.getStylePropertyValue(ele, prop, true); 15483 } else { 15484 return this.getRawStyle(ele, true); 15485 } 15486 }; // gets the raw style for an element 15487 15488 15489 styfn$3.getRawStyle = function (ele, isRenderedVal) { 15490 var self = this; 15491 ele = ele[0]; // insure it's an element 15492 15493 if (ele) { 15494 var rstyle = {}; 15495 15496 for (var i = 0; i < self.properties.length; i++) { 15497 var prop = self.properties[i]; 15498 var val = self.getStylePropertyValue(ele, prop.name, isRenderedVal); 15499 15500 if (val != null) { 15501 rstyle[prop.name] = val; 15502 rstyle[dash2camel(prop.name)] = val; 15503 } 15504 } 15505 15506 return rstyle; 15507 } 15508 }; 15509 15510 styfn$3.getIndexedStyle = function (ele, property, subproperty, index) { 15511 var pstyle = ele.pstyle(property)[subproperty][index]; 15512 return pstyle != null ? pstyle : ele.cy().style().getDefaultProperty(property)[subproperty][0]; 15513 }; 15514 15515 styfn$3.getStylePropertyValue = function (ele, propName, isRenderedVal) { 15516 var self = this; 15517 ele = ele[0]; // insure it's an element 15518 15519 if (ele) { 15520 var prop = self.properties[propName]; 15521 15522 if (prop.alias) { 15523 prop = prop.pointsTo; 15524 } 15525 15526 var type = prop.type; 15527 var styleProp = ele.pstyle(prop.name); 15528 15529 if (styleProp) { 15530 var value = styleProp.value, 15531 units = styleProp.units, 15532 strValue = styleProp.strValue; 15533 15534 if (isRenderedVal && type.number && value != null && number(value)) { 15535 var zoom = ele.cy().zoom(); 15536 15537 var getRenderedValue = function getRenderedValue(val) { 15538 return val * zoom; 15539 }; 15540 15541 var getValueStringWithUnits = function getValueStringWithUnits(val, units) { 15542 return getRenderedValue(val) + units; 15543 }; 15544 15545 var isArrayValue = array(value); 15546 var haveUnits = isArrayValue ? units.every(function (u) { 15547 return u != null; 15548 }) : units != null; 15549 15550 if (haveUnits) { 15551 if (isArrayValue) { 15552 return value.map(function (v, i) { 15553 return getValueStringWithUnits(v, units[i]); 15554 }).join(' '); 15555 } else { 15556 return getValueStringWithUnits(value, units); 15557 } 15558 } else { 15559 if (isArrayValue) { 15560 return value.map(function (v) { 15561 return string(v) ? v : '' + getRenderedValue(v); 15562 }).join(' '); 15563 } else { 15564 return '' + getRenderedValue(value); 15565 } 15566 } 15567 } else if (strValue != null) { 15568 return strValue; 15569 } 15570 } 15571 15572 return null; 15573 } 15574 }; 15575 15576 styfn$3.getAnimationStartStyle = function (ele, aniProps) { 15577 var rstyle = {}; 15578 15579 for (var i = 0; i < aniProps.length; i++) { 15580 var aniProp = aniProps[i]; 15581 var name = aniProp.name; 15582 var styleProp = ele.pstyle(name); 15583 15584 if (styleProp !== undefined) { 15585 // then make a prop of it 15586 if (plainObject(styleProp)) { 15587 styleProp = this.parse(name, styleProp.strValue); 15588 } else { 15589 styleProp = this.parse(name, styleProp); 15590 } 15591 } 15592 15593 if (styleProp) { 15594 rstyle[name] = styleProp; 15595 } 15596 } 15597 15598 return rstyle; 15599 }; 15600 15601 styfn$3.getPropsList = function (propsObj) { 15602 var self = this; 15603 var rstyle = []; 15604 var style = propsObj; 15605 var props = self.properties; 15606 15607 if (style) { 15608 var names = Object.keys(style); 15609 15610 for (var i = 0; i < names.length; i++) { 15611 var name = names[i]; 15612 var val = style[name]; 15613 var prop = props[name] || props[camel2dash(name)]; 15614 var styleProp = this.parse(prop.name, val); 15615 15616 if (styleProp) { 15617 rstyle.push(styleProp); 15618 } 15619 } 15620 } 15621 15622 return rstyle; 15623 }; 15624 15625 styfn$3.getNonDefaultPropertiesHash = function (ele, propNames, seed) { 15626 var hash = seed; 15627 var name, val, strVal, chVal; 15628 var i, j; 15629 15630 for (i = 0; i < propNames.length; i++) { 15631 name = propNames[i]; 15632 val = ele.pstyle(name, false); 15633 15634 if (val == null) { 15635 continue; 15636 } else if (val.pfValue != null) { 15637 hash = hashInt(chVal, hash); 15638 } else { 15639 strVal = val.strValue; 15640 15641 for (j = 0; j < strVal.length; j++) { 15642 chVal = strVal.charCodeAt(j); 15643 hash = hashInt(chVal, hash); 15644 } 15645 } 15646 } 15647 15648 return hash; 15649 }; 15650 15651 styfn$3.getPropertiesHash = styfn$3.getNonDefaultPropertiesHash; 15652 15653 var styfn$4 = {}; 15654 15655 styfn$4.appendFromJson = function (json) { 15656 var style = this; 15657 15658 for (var i = 0; i < json.length; i++) { 15659 var context = json[i]; 15660 var selector = context.selector; 15661 var props = context.style || context.css; 15662 var names = Object.keys(props); 15663 style.selector(selector); // apply selector 15664 15665 for (var j = 0; j < names.length; j++) { 15666 var name = names[j]; 15667 var value = props[name]; 15668 style.css(name, value); // apply property 15669 } 15670 } 15671 15672 return style; 15673 }; // accessible cy.style() function 15674 15675 15676 styfn$4.fromJson = function (json) { 15677 var style = this; 15678 style.resetToDefault(); 15679 style.appendFromJson(json); 15680 return style; 15681 }; // get json from cy.style() api 15682 15683 15684 styfn$4.json = function () { 15685 var json = []; 15686 15687 for (var i = this.defaultLength; i < this.length; i++) { 15688 var cxt = this[i]; 15689 var selector = cxt.selector; 15690 var props = cxt.properties; 15691 var css = {}; 15692 15693 for (var j = 0; j < props.length; j++) { 15694 var prop = props[j]; 15695 css[prop.name] = prop.strValue; 15696 } 15697 15698 json.push({ 15699 selector: !selector ? 'core' : selector.toString(), 15700 style: css 15701 }); 15702 } 15703 15704 return json; 15705 }; 15706 15707 var styfn$5 = {}; 15708 15709 styfn$5.appendFromString = function (string) { 15710 var self = this; 15711 var style = this; 15712 var remaining = '' + string; 15713 var selAndBlockStr; 15714 var blockRem; 15715 var propAndValStr; // remove comments from the style string 15716 15717 remaining = remaining.replace(/[/][*](\s|.)+?[*][/]/g, ''); 15718 15719 function removeSelAndBlockFromRemaining() { 15720 // remove the parsed selector and block from the remaining text to parse 15721 if (remaining.length > selAndBlockStr.length) { 15722 remaining = remaining.substr(selAndBlockStr.length); 15723 } else { 15724 remaining = ''; 15725 } 15726 } 15727 15728 function removePropAndValFromRem() { 15729 // remove the parsed property and value from the remaining block text to parse 15730 if (blockRem.length > propAndValStr.length) { 15731 blockRem = blockRem.substr(propAndValStr.length); 15732 } else { 15733 blockRem = ''; 15734 } 15735 } 15736 15737 for (;;) { 15738 var nothingLeftToParse = remaining.match(/^\s*$/); 15739 15740 if (nothingLeftToParse) { 15741 break; 15742 } 15743 15744 var selAndBlock = remaining.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/); 15745 15746 if (!selAndBlock) { 15747 warn('Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: ' + remaining); 15748 break; 15749 } 15750 15751 selAndBlockStr = selAndBlock[0]; // parse the selector 15752 15753 var selectorStr = selAndBlock[1]; 15754 15755 if (selectorStr !== 'core') { 15756 var selector = new Selector(selectorStr); 15757 15758 if (selector.invalid) { 15759 warn('Skipping parsing of block: Invalid selector found in string stylesheet: ' + selectorStr); // skip this selector and block 15760 15761 removeSelAndBlockFromRemaining(); 15762 continue; 15763 } 15764 } // parse the block of properties and values 15765 15766 15767 var blockStr = selAndBlock[2]; 15768 var invalidBlock = false; 15769 blockRem = blockStr; 15770 var props = []; 15771 15772 for (;;) { 15773 var _nothingLeftToParse = blockRem.match(/^\s*$/); 15774 15775 if (_nothingLeftToParse) { 15776 break; 15777 } 15778 15779 var propAndVal = blockRem.match(/^\s*(.+?)\s*:\s*(.+?)\s*;/); 15780 15781 if (!propAndVal) { 15782 warn('Skipping parsing of block: Invalid formatting of style property and value definitions found in:' + blockStr); 15783 invalidBlock = true; 15784 break; 15785 } 15786 15787 propAndValStr = propAndVal[0]; 15788 var propStr = propAndVal[1]; 15789 var valStr = propAndVal[2]; 15790 var prop = self.properties[propStr]; 15791 15792 if (!prop) { 15793 warn('Skipping property: Invalid property name in: ' + propAndValStr); // skip this property in the block 15794 15795 removePropAndValFromRem(); 15796 continue; 15797 } 15798 15799 var parsedProp = style.parse(propStr, valStr); 15800 15801 if (!parsedProp) { 15802 warn('Skipping property: Invalid property definition in: ' + propAndValStr); // skip this property in the block 15803 15804 removePropAndValFromRem(); 15805 continue; 15806 } 15807 15808 props.push({ 15809 name: propStr, 15810 val: valStr 15811 }); 15812 removePropAndValFromRem(); 15813 } 15814 15815 if (invalidBlock) { 15816 removeSelAndBlockFromRemaining(); 15817 break; 15818 } // put the parsed block in the style 15819 15820 15821 style.selector(selectorStr); 15822 15823 for (var i = 0; i < props.length; i++) { 15824 var _prop = props[i]; 15825 style.css(_prop.name, _prop.val); 15826 } 15827 15828 removeSelAndBlockFromRemaining(); 15829 } 15830 15831 return style; 15832 }; 15833 15834 styfn$5.fromString = function (string) { 15835 var style = this; 15836 style.resetToDefault(); 15837 style.appendFromString(string); 15838 return style; 15839 }; 15840 15841 var styfn$6 = {}; 15842 15843 (function () { 15844 var number = number$1; 15845 var rgba = rgbaNoBackRefs; 15846 var hsla = hslaNoBackRefs; 15847 var hex3$1 = hex3; 15848 var hex6$1 = hex6; 15849 15850 var data = function data(prefix) { 15851 return '^' + prefix + '\\s*\\(\\s*([\\w\\.]+)\\s*\\)$'; 15852 }; 15853 15854 var mapData = function mapData(prefix) { 15855 var mapArg = number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3$1 + '|' + hex6$1; 15856 return '^' + prefix + '\\s*\\(([\\w\\.]+)\\s*\\,\\s*(' + number + ')\\s*\\,\\s*(' + number + ')\\s*,\\s*(' + mapArg + ')\\s*\\,\\s*(' + mapArg + ')\\)$'; 15857 }; 15858 15859 var urlRegexes = ['^url\\s*\\(\\s*[\'"]?(.+?)[\'"]?\\s*\\)$', '^(none)$', '^(.+)$']; // each visual style property has a type and needs to be validated according to it 15860 15861 styfn$6.types = { 15862 time: { 15863 number: true, 15864 min: 0, 15865 units: 's|ms', 15866 implicitUnits: 'ms' 15867 }, 15868 percent: { 15869 number: true, 15870 min: 0, 15871 max: 100, 15872 units: '%', 15873 implicitUnits: '%' 15874 }, 15875 percentages: { 15876 number: true, 15877 min: 0, 15878 max: 100, 15879 units: '%', 15880 implicitUnits: '%', 15881 multiple: true 15882 }, 15883 zeroOneNumber: { 15884 number: true, 15885 min: 0, 15886 max: 1, 15887 unitless: true 15888 }, 15889 zeroOneNumbers: { 15890 number: true, 15891 min: 0, 15892 max: 1, 15893 unitless: true, 15894 multiple: true 15895 }, 15896 nOneOneNumber: { 15897 number: true, 15898 min: -1, 15899 max: 1, 15900 unitless: true 15901 }, 15902 nonNegativeInt: { 15903 number: true, 15904 min: 0, 15905 integer: true, 15906 unitless: true 15907 }, 15908 position: { 15909 enums: ['parent', 'origin'] 15910 }, 15911 nodeSize: { 15912 number: true, 15913 min: 0, 15914 enums: ['label'] 15915 }, 15916 number: { 15917 number: true, 15918 unitless: true 15919 }, 15920 numbers: { 15921 number: true, 15922 unitless: true, 15923 multiple: true 15924 }, 15925 positiveNumber: { 15926 number: true, 15927 unitless: true, 15928 min: 0, 15929 strictMin: true 15930 }, 15931 size: { 15932 number: true, 15933 min: 0 15934 }, 15935 bidirectionalSize: { 15936 number: true 15937 }, 15938 // allows negative 15939 bidirectionalSizes: { 15940 number: true, 15941 multiple: true 15942 }, 15943 // allows negative 15944 sizeMaybePercent: { 15945 number: true, 15946 min: 0, 15947 allowPercent: true 15948 }, 15949 axisDirection: { 15950 enums: ['horizontal', 'leftward', 'rightward', 'vertical', 'upward', 'downward', 'auto'] 15951 }, 15952 paddingRelativeTo: { 15953 enums: ['width', 'height', 'average', 'min', 'max'] 15954 }, 15955 bgWH: { 15956 number: true, 15957 min: 0, 15958 allowPercent: true, 15959 enums: ['auto'], 15960 multiple: true 15961 }, 15962 bgPos: { 15963 number: true, 15964 allowPercent: true, 15965 multiple: true 15966 }, 15967 bgRelativeTo: { 15968 enums: ['inner', 'include-padding'], 15969 multiple: true 15970 }, 15971 bgRepeat: { 15972 enums: ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'], 15973 multiple: true 15974 }, 15975 bgFit: { 15976 enums: ['none', 'contain', 'cover'], 15977 multiple: true 15978 }, 15979 bgCrossOrigin: { 15980 enums: ['anonymous', 'use-credentials'], 15981 multiple: true 15982 }, 15983 bgClip: { 15984 enums: ['none', 'node'], 15985 multiple: true 15986 }, 15987 color: { 15988 color: true 15989 }, 15990 colors: { 15991 color: true, 15992 multiple: true 15993 }, 15994 fill: { 15995 enums: ['solid', 'linear-gradient', 'radial-gradient'] 15996 }, 15997 bool: { 15998 enums: ['yes', 'no'] 15999 }, 16000 lineStyle: { 16001 enums: ['solid', 'dotted', 'dashed'] 16002 }, 16003 lineCap: { 16004 enums: ['butt', 'round', 'square'] 16005 }, 16006 borderStyle: { 16007 enums: ['solid', 'dotted', 'dashed', 'double'] 16008 }, 16009 curveStyle: { 16010 enums: ['bezier', 'unbundled-bezier', 'haystack', 'segments', 'straight', 'taxi'] 16011 }, 16012 fontFamily: { 16013 regex: '^([\\w- \\"]+(?:\\s*,\\s*[\\w- \\"]+)*)$' 16014 }, 16015 fontStyle: { 16016 enums: ['italic', 'normal', 'oblique'] 16017 }, 16018 fontWeight: { 16019 enums: ['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '800', '900', 100, 200, 300, 400, 500, 600, 700, 800, 900] 16020 }, 16021 textDecoration: { 16022 enums: ['none', 'underline', 'overline', 'line-through'] 16023 }, 16024 textTransform: { 16025 enums: ['none', 'uppercase', 'lowercase'] 16026 }, 16027 textWrap: { 16028 enums: ['none', 'wrap', 'ellipsis'] 16029 }, 16030 textOverflowWrap: { 16031 enums: ['whitespace', 'anywhere'] 16032 }, 16033 textBackgroundShape: { 16034 enums: ['rectangle', 'roundrectangle', 'round-rectangle'] 16035 }, 16036 nodeShape: { 16037 enums: ['rectangle', 'roundrectangle', 'round-rectangle', 'cutrectangle', 'cut-rectangle', 'bottomroundrectangle', 'bottom-round-rectangle', 'barrel', 'ellipse', 'triangle', 'round-triangle', 'square', 'pentagon', 'round-pentagon', 'hexagon', 'round-hexagon', 'concavehexagon', 'concave-hexagon', 'heptagon', 'round-heptagon', 'octagon', 'round-octagon', 'tag', 'round-tag', 'star', 'diamond', 'round-diamond', 'vee', 'rhomboid', 'polygon'] 16038 }, 16039 compoundIncludeLabels: { 16040 enums: ['include', 'exclude'] 16041 }, 16042 arrowShape: { 16043 enums: ['tee', 'triangle', 'triangle-tee', 'triangle-cross', 'triangle-backcurve', 'vee', 'square', 'circle', 'diamond', 'chevron', 'none'] 16044 }, 16045 arrowFill: { 16046 enums: ['filled', 'hollow'] 16047 }, 16048 display: { 16049 enums: ['element', 'none'] 16050 }, 16051 visibility: { 16052 enums: ['hidden', 'visible'] 16053 }, 16054 zCompoundDepth: { 16055 enums: ['bottom', 'orphan', 'auto', 'top'] 16056 }, 16057 zIndexCompare: { 16058 enums: ['auto', 'manual'] 16059 }, 16060 valign: { 16061 enums: ['top', 'center', 'bottom'] 16062 }, 16063 halign: { 16064 enums: ['left', 'center', 'right'] 16065 }, 16066 justification: { 16067 enums: ['left', 'center', 'right', 'auto'] 16068 }, 16069 text: { 16070 string: true 16071 }, 16072 data: { 16073 mapping: true, 16074 regex: data('data') 16075 }, 16076 layoutData: { 16077 mapping: true, 16078 regex: data('layoutData') 16079 }, 16080 scratch: { 16081 mapping: true, 16082 regex: data('scratch') 16083 }, 16084 mapData: { 16085 mapping: true, 16086 regex: mapData('mapData') 16087 }, 16088 mapLayoutData: { 16089 mapping: true, 16090 regex: mapData('mapLayoutData') 16091 }, 16092 mapScratch: { 16093 mapping: true, 16094 regex: mapData('mapScratch') 16095 }, 16096 fn: { 16097 mapping: true, 16098 fn: true 16099 }, 16100 url: { 16101 regexes: urlRegexes, 16102 singleRegexMatchValue: true 16103 }, 16104 urls: { 16105 regexes: urlRegexes, 16106 singleRegexMatchValue: true, 16107 multiple: true 16108 }, 16109 propList: { 16110 propList: true 16111 }, 16112 angle: { 16113 number: true, 16114 units: 'deg|rad', 16115 implicitUnits: 'rad' 16116 }, 16117 textRotation: { 16118 number: true, 16119 units: 'deg|rad', 16120 implicitUnits: 'rad', 16121 enums: ['none', 'autorotate'] 16122 }, 16123 polygonPointList: { 16124 number: true, 16125 multiple: true, 16126 evenMultiple: true, 16127 min: -1, 16128 max: 1, 16129 unitless: true 16130 }, 16131 edgeDistances: { 16132 enums: ['intersection', 'node-position'] 16133 }, 16134 edgeEndpoint: { 16135 number: true, 16136 multiple: true, 16137 units: '%|px|em|deg|rad', 16138 implicitUnits: 'px', 16139 enums: ['inside-to-node', 'outside-to-node', 'outside-to-node-or-label', 'outside-to-line', 'outside-to-line-or-label'], 16140 singleEnum: true, 16141 validate: function validate(valArr, unitsArr) { 16142 switch (valArr.length) { 16143 case 2: 16144 // can be % or px only 16145 return unitsArr[0] !== 'deg' && unitsArr[0] !== 'rad' && unitsArr[1] !== 'deg' && unitsArr[1] !== 'rad'; 16146 16147 case 1: 16148 // can be enum, deg, or rad only 16149 return string(valArr[0]) || unitsArr[0] === 'deg' || unitsArr[0] === 'rad'; 16150 16151 default: 16152 return false; 16153 } 16154 } 16155 }, 16156 easing: { 16157 regexes: ['^(spring)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$', '^(cubic-bezier)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$'], 16158 enums: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'ease-in-sine', 'ease-out-sine', 'ease-in-out-sine', 'ease-in-quad', 'ease-out-quad', 'ease-in-out-quad', 'ease-in-cubic', 'ease-out-cubic', 'ease-in-out-cubic', 'ease-in-quart', 'ease-out-quart', 'ease-in-out-quart', 'ease-in-quint', 'ease-out-quint', 'ease-in-out-quint', 'ease-in-expo', 'ease-out-expo', 'ease-in-out-expo', 'ease-in-circ', 'ease-out-circ', 'ease-in-out-circ'] 16159 }, 16160 gradientDirection: { 16161 enums: ['to-bottom', 'to-top', 'to-left', 'to-right', 'to-bottom-right', 'to-bottom-left', 'to-top-right', 'to-top-left', 'to-right-bottom', 'to-left-bottom', 'to-right-top', 'to-left-top'] 16162 }, 16163 boundsExpansion: { 16164 number: true, 16165 multiple: true, 16166 min: 0, 16167 validate: function validate(valArr) { 16168 var length = valArr.length; 16169 return length === 1 || length === 2 || length === 4; 16170 } 16171 } 16172 }; 16173 var diff = { 16174 zeroNonZero: function zeroNonZero(val1, val2) { 16175 if ((val1 == null || val2 == null) && val1 !== val2) { 16176 return true; // null cases could represent any value 16177 } 16178 16179 if (val1 == 0 && val2 != 0) { 16180 return true; 16181 } else if (val1 != 0 && val2 == 0) { 16182 return true; 16183 } else { 16184 return false; 16185 } 16186 }, 16187 any: function any(val1, val2) { 16188 return val1 != val2; 16189 } 16190 }; // define visual style properties 16191 // 16192 // - n.b. adding a new group of props may require updates to updateStyleHints() 16193 // - adding new props to an existing group gets handled automatically 16194 16195 var t = styfn$6.types; 16196 var mainLabel = [{ 16197 name: 'label', 16198 type: t.text, 16199 triggersBounds: diff.any 16200 }, { 16201 name: 'text-rotation', 16202 type: t.textRotation, 16203 triggersBounds: diff.any 16204 }, { 16205 name: 'text-margin-x', 16206 type: t.bidirectionalSize, 16207 triggersBounds: diff.any 16208 }, { 16209 name: 'text-margin-y', 16210 type: t.bidirectionalSize, 16211 triggersBounds: diff.any 16212 }]; 16213 var sourceLabel = [{ 16214 name: 'source-label', 16215 type: t.text, 16216 triggersBounds: diff.any 16217 }, { 16218 name: 'source-text-rotation', 16219 type: t.textRotation, 16220 triggersBounds: diff.any 16221 }, { 16222 name: 'source-text-margin-x', 16223 type: t.bidirectionalSize, 16224 triggersBounds: diff.any 16225 }, { 16226 name: 'source-text-margin-y', 16227 type: t.bidirectionalSize, 16228 triggersBounds: diff.any 16229 }, { 16230 name: 'source-text-offset', 16231 type: t.size, 16232 triggersBounds: diff.any 16233 }]; 16234 var targetLabel = [{ 16235 name: 'target-label', 16236 type: t.text, 16237 triggersBounds: diff.any 16238 }, { 16239 name: 'target-text-rotation', 16240 type: t.textRotation, 16241 triggersBounds: diff.any 16242 }, { 16243 name: 'target-text-margin-x', 16244 type: t.bidirectionalSize, 16245 triggersBounds: diff.any 16246 }, { 16247 name: 'target-text-margin-y', 16248 type: t.bidirectionalSize, 16249 triggersBounds: diff.any 16250 }, { 16251 name: 'target-text-offset', 16252 type: t.size, 16253 triggersBounds: diff.any 16254 }]; 16255 var labelDimensions = [{ 16256 name: 'font-family', 16257 type: t.fontFamily, 16258 triggersBounds: diff.any 16259 }, { 16260 name: 'font-style', 16261 type: t.fontStyle, 16262 triggersBounds: diff.any 16263 }, { 16264 name: 'font-weight', 16265 type: t.fontWeight, 16266 triggersBounds: diff.any 16267 }, { 16268 name: 'font-size', 16269 type: t.size, 16270 triggersBounds: diff.any 16271 }, { 16272 name: 'text-transform', 16273 type: t.textTransform, 16274 triggersBounds: diff.any 16275 }, { 16276 name: 'text-wrap', 16277 type: t.textWrap, 16278 triggersBounds: diff.any 16279 }, { 16280 name: 'text-overflow-wrap', 16281 type: t.textOverflowWrap, 16282 triggersBounds: diff.any 16283 }, { 16284 name: 'text-max-width', 16285 type: t.size, 16286 triggersBounds: diff.any 16287 }, { 16288 name: 'text-outline-width', 16289 type: t.size, 16290 triggersBounds: diff.any 16291 }, { 16292 name: 'line-height', 16293 type: t.positiveNumber, 16294 triggersBounds: diff.any 16295 }]; 16296 var commonLabel = [{ 16297 name: 'text-valign', 16298 type: t.valign, 16299 triggersBounds: diff.any 16300 }, { 16301 name: 'text-halign', 16302 type: t.halign, 16303 triggersBounds: diff.any 16304 }, { 16305 name: 'color', 16306 type: t.color 16307 }, { 16308 name: 'text-outline-color', 16309 type: t.color 16310 }, { 16311 name: 'text-outline-opacity', 16312 type: t.zeroOneNumber 16313 }, { 16314 name: 'text-background-color', 16315 type: t.color 16316 }, { 16317 name: 'text-background-opacity', 16318 type: t.zeroOneNumber 16319 }, { 16320 name: 'text-background-padding', 16321 type: t.size, 16322 triggersBounds: diff.any 16323 }, { 16324 name: 'text-border-opacity', 16325 type: t.zeroOneNumber 16326 }, { 16327 name: 'text-border-color', 16328 type: t.color 16329 }, { 16330 name: 'text-border-width', 16331 type: t.size, 16332 triggersBounds: diff.any 16333 }, { 16334 name: 'text-border-style', 16335 type: t.borderStyle, 16336 triggersBounds: diff.any 16337 }, { 16338 name: 'text-background-shape', 16339 type: t.textBackgroundShape, 16340 triggersBounds: diff.any 16341 }, { 16342 name: 'text-justification', 16343 type: t.justification 16344 }]; 16345 var behavior = [{ 16346 name: 'events', 16347 type: t.bool 16348 }, { 16349 name: 'text-events', 16350 type: t.bool 16351 }]; 16352 var visibility = [{ 16353 name: 'display', 16354 type: t.display, 16355 triggersZOrder: diff.any, 16356 triggersBounds: diff.any, 16357 triggersBoundsOfParallelBeziers: true 16358 }, { 16359 name: 'visibility', 16360 type: t.visibility, 16361 triggersZOrder: diff.any 16362 }, { 16363 name: 'opacity', 16364 type: t.zeroOneNumber, 16365 triggersZOrder: diff.zeroNonZero 16366 }, { 16367 name: 'text-opacity', 16368 type: t.zeroOneNumber 16369 }, { 16370 name: 'min-zoomed-font-size', 16371 type: t.size 16372 }, { 16373 name: 'z-compound-depth', 16374 type: t.zCompoundDepth, 16375 triggersZOrder: diff.any 16376 }, { 16377 name: 'z-index-compare', 16378 type: t.zIndexCompare, 16379 triggersZOrder: diff.any 16380 }, { 16381 name: 'z-index', 16382 type: t.nonNegativeInt, 16383 triggersZOrder: diff.any 16384 }]; 16385 var overlay = [{ 16386 name: 'overlay-padding', 16387 type: t.size, 16388 triggersBounds: diff.any 16389 }, { 16390 name: 'overlay-color', 16391 type: t.color 16392 }, { 16393 name: 'overlay-opacity', 16394 type: t.zeroOneNumber, 16395 triggersBounds: diff.zeroNonZero 16396 }]; 16397 var transition = [{ 16398 name: 'transition-property', 16399 type: t.propList 16400 }, { 16401 name: 'transition-duration', 16402 type: t.time 16403 }, { 16404 name: 'transition-delay', 16405 type: t.time 16406 }, { 16407 name: 'transition-timing-function', 16408 type: t.easing 16409 }]; 16410 16411 var nodeSizeHashOverride = function nodeSizeHashOverride(ele, parsedProp) { 16412 if (parsedProp.value === 'label') { 16413 return -ele.poolIndex(); // no hash key hits is using label size (hitrate for perf probably low anyway) 16414 } else { 16415 return parsedProp.pfValue; 16416 } 16417 }; 16418 16419 var nodeBody = [{ 16420 name: 'height', 16421 type: t.nodeSize, 16422 triggersBounds: diff.any, 16423 hashOverride: nodeSizeHashOverride 16424 }, { 16425 name: 'width', 16426 type: t.nodeSize, 16427 triggersBounds: diff.any, 16428 hashOverride: nodeSizeHashOverride 16429 }, { 16430 name: 'shape', 16431 type: t.nodeShape, 16432 triggersBounds: diff.any 16433 }, { 16434 name: 'shape-polygon-points', 16435 type: t.polygonPointList, 16436 triggersBounds: diff.any 16437 }, { 16438 name: 'background-color', 16439 type: t.color 16440 }, { 16441 name: 'background-fill', 16442 type: t.fill 16443 }, { 16444 name: 'background-opacity', 16445 type: t.zeroOneNumber 16446 }, { 16447 name: 'background-blacken', 16448 type: t.nOneOneNumber 16449 }, { 16450 name: 'background-gradient-stop-colors', 16451 type: t.colors 16452 }, { 16453 name: 'background-gradient-stop-positions', 16454 type: t.percentages 16455 }, { 16456 name: 'background-gradient-direction', 16457 type: t.gradientDirection 16458 }, { 16459 name: 'padding', 16460 type: t.sizeMaybePercent, 16461 triggersBounds: diff.any 16462 }, { 16463 name: 'padding-relative-to', 16464 type: t.paddingRelativeTo, 16465 triggersBounds: diff.any 16466 }, { 16467 name: 'bounds-expansion', 16468 type: t.boundsExpansion, 16469 triggersBounds: diff.any 16470 }]; 16471 var nodeBorder = [{ 16472 name: 'border-color', 16473 type: t.color 16474 }, { 16475 name: 'border-opacity', 16476 type: t.zeroOneNumber 16477 }, { 16478 name: 'border-width', 16479 type: t.size, 16480 triggersBounds: diff.any 16481 }, { 16482 name: 'border-style', 16483 type: t.borderStyle 16484 }]; 16485 var backgroundImage = [{ 16486 name: 'background-image', 16487 type: t.urls 16488 }, { 16489 name: 'background-image-crossorigin', 16490 type: t.bgCrossOrigin 16491 }, { 16492 name: 'background-image-opacity', 16493 type: t.zeroOneNumbers 16494 }, { 16495 name: 'background-position-x', 16496 type: t.bgPos 16497 }, { 16498 name: 'background-position-y', 16499 type: t.bgPos 16500 }, { 16501 name: 'background-width-relative-to', 16502 type: t.bgRelativeTo 16503 }, { 16504 name: 'background-height-relative-to', 16505 type: t.bgRelativeTo 16506 }, { 16507 name: 'background-repeat', 16508 type: t.bgRepeat 16509 }, { 16510 name: 'background-fit', 16511 type: t.bgFit 16512 }, { 16513 name: 'background-clip', 16514 type: t.bgClip 16515 }, { 16516 name: 'background-width', 16517 type: t.bgWH 16518 }, { 16519 name: 'background-height', 16520 type: t.bgWH 16521 }, { 16522 name: 'background-offset-x', 16523 type: t.bgPos 16524 }, { 16525 name: 'background-offset-y', 16526 type: t.bgPos 16527 }]; 16528 var compound = [{ 16529 name: 'position', 16530 type: t.position, 16531 triggersBounds: diff.any 16532 }, { 16533 name: 'compound-sizing-wrt-labels', 16534 type: t.compoundIncludeLabels, 16535 triggersBounds: diff.any 16536 }, { 16537 name: 'min-width', 16538 type: t.size, 16539 triggersBounds: diff.any 16540 }, { 16541 name: 'min-width-bias-left', 16542 type: t.sizeMaybePercent, 16543 triggersBounds: diff.any 16544 }, { 16545 name: 'min-width-bias-right', 16546 type: t.sizeMaybePercent, 16547 triggersBounds: diff.any 16548 }, { 16549 name: 'min-height', 16550 type: t.size, 16551 triggersBounds: diff.any 16552 }, { 16553 name: 'min-height-bias-top', 16554 type: t.sizeMaybePercent, 16555 triggersBounds: diff.any 16556 }, { 16557 name: 'min-height-bias-bottom', 16558 type: t.sizeMaybePercent, 16559 triggersBounds: diff.any 16560 }]; 16561 var edgeLine = [{ 16562 name: 'line-style', 16563 type: t.lineStyle 16564 }, { 16565 name: 'line-color', 16566 type: t.color 16567 }, { 16568 name: 'line-fill', 16569 type: t.fill 16570 }, { 16571 name: 'line-cap', 16572 type: t.lineCap 16573 }, { 16574 name: 'line-dash-pattern', 16575 type: t.numbers 16576 }, { 16577 name: 'line-dash-offset', 16578 type: t.number 16579 }, { 16580 name: 'line-gradient-stop-colors', 16581 type: t.colors 16582 }, { 16583 name: 'line-gradient-stop-positions', 16584 type: t.percentages 16585 }, { 16586 name: 'curve-style', 16587 type: t.curveStyle, 16588 triggersBounds: diff.any, 16589 triggersBoundsOfParallelBeziers: true 16590 }, { 16591 name: 'haystack-radius', 16592 type: t.zeroOneNumber, 16593 triggersBounds: diff.any 16594 }, { 16595 name: 'source-endpoint', 16596 type: t.edgeEndpoint, 16597 triggersBounds: diff.any 16598 }, { 16599 name: 'target-endpoint', 16600 type: t.edgeEndpoint, 16601 triggersBounds: diff.any 16602 }, { 16603 name: 'control-point-step-size', 16604 type: t.size, 16605 triggersBounds: diff.any 16606 }, { 16607 name: 'control-point-distances', 16608 type: t.bidirectionalSizes, 16609 triggersBounds: diff.any 16610 }, { 16611 name: 'control-point-weights', 16612 type: t.numbers, 16613 triggersBounds: diff.any 16614 }, { 16615 name: 'segment-distances', 16616 type: t.bidirectionalSizes, 16617 triggersBounds: diff.any 16618 }, { 16619 name: 'segment-weights', 16620 type: t.numbers, 16621 triggersBounds: diff.any 16622 }, { 16623 name: 'taxi-turn', 16624 type: t.sizeMaybePercent, 16625 triggersBounds: diff.any 16626 }, { 16627 name: 'taxi-turn-min-distance', 16628 type: t.size, 16629 triggersBounds: diff.any 16630 }, { 16631 name: 'taxi-direction', 16632 type: t.axisDirection, 16633 triggersBounds: diff.any 16634 }, { 16635 name: 'edge-distances', 16636 type: t.edgeDistances, 16637 triggersBounds: diff.any 16638 }, { 16639 name: 'arrow-scale', 16640 type: t.positiveNumber, 16641 triggersBounds: diff.any 16642 }, { 16643 name: 'loop-direction', 16644 type: t.angle, 16645 triggersBounds: diff.any 16646 }, { 16647 name: 'loop-sweep', 16648 type: t.angle, 16649 triggersBounds: diff.any 16650 }, { 16651 name: 'source-distance-from-node', 16652 type: t.size, 16653 triggersBounds: diff.any 16654 }, { 16655 name: 'target-distance-from-node', 16656 type: t.size, 16657 triggersBounds: diff.any 16658 }]; 16659 var ghost = [{ 16660 name: 'ghost', 16661 type: t.bool, 16662 triggersBounds: diff.any 16663 }, { 16664 name: 'ghost-offset-x', 16665 type: t.bidirectionalSize, 16666 triggersBounds: diff.any 16667 }, { 16668 name: 'ghost-offset-y', 16669 type: t.bidirectionalSize, 16670 triggersBounds: diff.any 16671 }, { 16672 name: 'ghost-opacity', 16673 type: t.zeroOneNumber 16674 }]; 16675 var core = [{ 16676 name: 'selection-box-color', 16677 type: t.color 16678 }, { 16679 name: 'selection-box-opacity', 16680 type: t.zeroOneNumber 16681 }, { 16682 name: 'selection-box-border-color', 16683 type: t.color 16684 }, { 16685 name: 'selection-box-border-width', 16686 type: t.size 16687 }, { 16688 name: 'active-bg-color', 16689 type: t.color 16690 }, { 16691 name: 'active-bg-opacity', 16692 type: t.zeroOneNumber 16693 }, { 16694 name: 'active-bg-size', 16695 type: t.size 16696 }, { 16697 name: 'outside-texture-bg-color', 16698 type: t.color 16699 }, { 16700 name: 'outside-texture-bg-opacity', 16701 type: t.zeroOneNumber 16702 }]; // pie backgrounds for nodes 16703 16704 var pie = []; 16705 styfn$6.pieBackgroundN = 16; // because the pie properties are numbered, give access to a constant N (for renderer use) 16706 16707 pie.push({ 16708 name: 'pie-size', 16709 type: t.sizeMaybePercent 16710 }); 16711 16712 for (var i = 1; i <= styfn$6.pieBackgroundN; i++) { 16713 pie.push({ 16714 name: 'pie-' + i + '-background-color', 16715 type: t.color 16716 }); 16717 pie.push({ 16718 name: 'pie-' + i + '-background-size', 16719 type: t.percent 16720 }); 16721 pie.push({ 16722 name: 'pie-' + i + '-background-opacity', 16723 type: t.zeroOneNumber 16724 }); 16725 } // edge arrows 16726 16727 16728 var edgeArrow = []; 16729 var arrowPrefixes = styfn$6.arrowPrefixes = ['source', 'mid-source', 'target', 'mid-target']; 16730 [{ 16731 name: 'arrow-shape', 16732 type: t.arrowShape, 16733 triggersBounds: diff.any 16734 }, { 16735 name: 'arrow-color', 16736 type: t.color 16737 }, { 16738 name: 'arrow-fill', 16739 type: t.arrowFill 16740 }].forEach(function (prop) { 16741 arrowPrefixes.forEach(function (prefix) { 16742 var name = prefix + '-' + prop.name; 16743 var type = prop.type, 16744 triggersBounds = prop.triggersBounds; 16745 edgeArrow.push({ 16746 name: name, 16747 type: type, 16748 triggersBounds: triggersBounds 16749 }); 16750 }); 16751 }, {}); 16752 var props = styfn$6.properties = [].concat(behavior, transition, visibility, overlay, ghost, commonLabel, labelDimensions, mainLabel, sourceLabel, targetLabel, nodeBody, nodeBorder, backgroundImage, pie, compound, edgeLine, edgeArrow, core); 16753 var propGroups = styfn$6.propertyGroups = { 16754 // common to all eles 16755 behavior: behavior, 16756 transition: transition, 16757 visibility: visibility, 16758 overlay: overlay, 16759 ghost: ghost, 16760 // labels 16761 commonLabel: commonLabel, 16762 labelDimensions: labelDimensions, 16763 mainLabel: mainLabel, 16764 sourceLabel: sourceLabel, 16765 targetLabel: targetLabel, 16766 // node props 16767 nodeBody: nodeBody, 16768 nodeBorder: nodeBorder, 16769 backgroundImage: backgroundImage, 16770 pie: pie, 16771 compound: compound, 16772 // edge props 16773 edgeLine: edgeLine, 16774 edgeArrow: edgeArrow, 16775 core: core 16776 }; 16777 var propGroupNames = styfn$6.propertyGroupNames = {}; 16778 var propGroupKeys = styfn$6.propertyGroupKeys = Object.keys(propGroups); 16779 propGroupKeys.forEach(function (key) { 16780 propGroupNames[key] = propGroups[key].map(function (prop) { 16781 return prop.name; 16782 }); 16783 propGroups[key].forEach(function (prop) { 16784 return prop.groupKey = key; 16785 }); 16786 }); // define aliases 16787 16788 var aliases = styfn$6.aliases = [{ 16789 name: 'content', 16790 pointsTo: 'label' 16791 }, { 16792 name: 'control-point-distance', 16793 pointsTo: 'control-point-distances' 16794 }, { 16795 name: 'control-point-weight', 16796 pointsTo: 'control-point-weights' 16797 }, { 16798 name: 'edge-text-rotation', 16799 pointsTo: 'text-rotation' 16800 }, { 16801 name: 'padding-left', 16802 pointsTo: 'padding' 16803 }, { 16804 name: 'padding-right', 16805 pointsTo: 'padding' 16806 }, { 16807 name: 'padding-top', 16808 pointsTo: 'padding' 16809 }, { 16810 name: 'padding-bottom', 16811 pointsTo: 'padding' 16812 }]; // list of property names 16813 16814 styfn$6.propertyNames = props.map(function (p) { 16815 return p.name; 16816 }); // allow access of properties by name ( e.g. style.properties.height ) 16817 16818 for (var _i = 0; _i < props.length; _i++) { 16819 var prop = props[_i]; 16820 props[prop.name] = prop; // allow lookup by name 16821 } // map aliases 16822 16823 16824 for (var _i2 = 0; _i2 < aliases.length; _i2++) { 16825 var alias = aliases[_i2]; 16826 var pointsToProp = props[alias.pointsTo]; 16827 var aliasProp = { 16828 name: alias.name, 16829 alias: true, 16830 pointsTo: pointsToProp 16831 }; // add alias prop for parsing 16832 16833 props.push(aliasProp); 16834 props[alias.name] = aliasProp; // allow lookup by name 16835 } 16836 })(); 16837 16838 styfn$6.getDefaultProperty = function (name) { 16839 return this.getDefaultProperties()[name]; 16840 }; 16841 16842 styfn$6.getDefaultProperties = function () { 16843 var _p = this._private; 16844 16845 if (_p.defaultProperties != null) { 16846 return _p.defaultProperties; 16847 } 16848 16849 var rawProps = extend({ 16850 // core props 16851 'selection-box-color': '#ddd', 16852 'selection-box-opacity': 0.65, 16853 'selection-box-border-color': '#aaa', 16854 'selection-box-border-width': 1, 16855 'active-bg-color': 'black', 16856 'active-bg-opacity': 0.15, 16857 'active-bg-size': 30, 16858 'outside-texture-bg-color': '#000', 16859 'outside-texture-bg-opacity': 0.125, 16860 // common node/edge props 16861 'events': 'yes', 16862 'text-events': 'no', 16863 'text-valign': 'top', 16864 'text-halign': 'center', 16865 'text-justification': 'auto', 16866 'line-height': 1, 16867 'color': '#000', 16868 'text-outline-color': '#000', 16869 'text-outline-width': 0, 16870 'text-outline-opacity': 1, 16871 'text-opacity': 1, 16872 'text-decoration': 'none', 16873 'text-transform': 'none', 16874 'text-wrap': 'none', 16875 'text-overflow-wrap': 'whitespace', 16876 'text-max-width': 9999, 16877 'text-background-color': '#000', 16878 'text-background-opacity': 0, 16879 'text-background-shape': 'rectangle', 16880 'text-background-padding': 0, 16881 'text-border-opacity': 0, 16882 'text-border-width': 0, 16883 'text-border-style': 'solid', 16884 'text-border-color': '#000', 16885 'font-family': 'Helvetica Neue, Helvetica, sans-serif', 16886 'font-style': 'normal', 16887 'font-weight': 'normal', 16888 'font-size': 16, 16889 'min-zoomed-font-size': 0, 16890 'text-rotation': 'none', 16891 'source-text-rotation': 'none', 16892 'target-text-rotation': 'none', 16893 'visibility': 'visible', 16894 'display': 'element', 16895 'opacity': 1, 16896 'z-compound-depth': 'auto', 16897 'z-index-compare': 'auto', 16898 'z-index': 0, 16899 'label': '', 16900 'text-margin-x': 0, 16901 'text-margin-y': 0, 16902 'source-label': '', 16903 'source-text-offset': 0, 16904 'source-text-margin-x': 0, 16905 'source-text-margin-y': 0, 16906 'target-label': '', 16907 'target-text-offset': 0, 16908 'target-text-margin-x': 0, 16909 'target-text-margin-y': 0, 16910 'overlay-opacity': 0, 16911 'overlay-color': '#000', 16912 'overlay-padding': 10, 16913 'transition-property': 'none', 16914 'transition-duration': 0, 16915 'transition-delay': 0, 16916 'transition-timing-function': 'linear', 16917 // node props 16918 'background-blacken': 0, 16919 'background-color': '#999', 16920 'background-fill': 'solid', 16921 'background-opacity': 1, 16922 'background-image': 'none', 16923 'background-image-crossorigin': 'anonymous', 16924 'background-image-opacity': 1, 16925 'background-position-x': '50%', 16926 'background-position-y': '50%', 16927 'background-offset-x': 0, 16928 'background-offset-y': 0, 16929 'background-width-relative-to': 'include-padding', 16930 'background-height-relative-to': 'include-padding', 16931 'background-repeat': 'no-repeat', 16932 'background-fit': 'none', 16933 'background-clip': 'node', 16934 'background-width': 'auto', 16935 'background-height': 'auto', 16936 'border-color': '#000', 16937 'border-opacity': 1, 16938 'border-width': 0, 16939 'border-style': 'solid', 16940 'height': 30, 16941 'width': 30, 16942 'shape': 'ellipse', 16943 'shape-polygon-points': '-1, -1, 1, -1, 1, 1, -1, 1', 16944 'bounds-expansion': 0, 16945 // node gradient 16946 'background-gradient-direction': 'to-bottom', 16947 'background-gradient-stop-colors': '#999', 16948 'background-gradient-stop-positions': '0%', 16949 // ghost props 16950 'ghost': 'no', 16951 'ghost-offset-y': 0, 16952 'ghost-offset-x': 0, 16953 'ghost-opacity': 0, 16954 // compound props 16955 'padding': 0, 16956 'padding-relative-to': 'width', 16957 'position': 'origin', 16958 'compound-sizing-wrt-labels': 'include', 16959 'min-width': 0, 16960 'min-width-bias-left': 0, 16961 'min-width-bias-right': 0, 16962 'min-height': 0, 16963 'min-height-bias-top': 0, 16964 'min-height-bias-bottom': 0 16965 }, { 16966 // node pie bg 16967 'pie-size': '100%' 16968 }, [{ 16969 name: 'pie-{{i}}-background-color', 16970 value: 'black' 16971 }, { 16972 name: 'pie-{{i}}-background-size', 16973 value: '0%' 16974 }, { 16975 name: 'pie-{{i}}-background-opacity', 16976 value: 1 16977 }].reduce(function (css, prop) { 16978 for (var i = 1; i <= styfn$6.pieBackgroundN; i++) { 16979 var name = prop.name.replace('{{i}}', i); 16980 var val = prop.value; 16981 css[name] = val; 16982 } 16983 16984 return css; 16985 }, {}), { 16986 // edge props 16987 'line-style': 'solid', 16988 'line-color': '#999', 16989 'line-fill': 'solid', 16990 'line-cap': 'butt', 16991 'line-gradient-stop-colors': '#999', 16992 'line-gradient-stop-positions': '0%', 16993 'control-point-step-size': 40, 16994 'control-point-weights': 0.5, 16995 'segment-weights': 0.5, 16996 'segment-distances': 20, 16997 'taxi-turn': '50%', 16998 'taxi-turn-min-distance': 10, 16999 'taxi-direction': 'auto', 17000 'edge-distances': 'intersection', 17001 'curve-style': 'haystack', 17002 'haystack-radius': 0, 17003 'arrow-scale': 1, 17004 'loop-direction': '-45deg', 17005 'loop-sweep': '-90deg', 17006 'source-distance-from-node': 0, 17007 'target-distance-from-node': 0, 17008 'source-endpoint': 'outside-to-node', 17009 'target-endpoint': 'outside-to-node', 17010 'line-dash-pattern': [6, 3], 17011 'line-dash-offset': 0 17012 }, [{ 17013 name: 'arrow-shape', 17014 value: 'none' 17015 }, { 17016 name: 'arrow-color', 17017 value: '#999' 17018 }, { 17019 name: 'arrow-fill', 17020 value: 'filled' 17021 }].reduce(function (css, prop) { 17022 styfn$6.arrowPrefixes.forEach(function (prefix) { 17023 var name = prefix + '-' + prop.name; 17024 var val = prop.value; 17025 css[name] = val; 17026 }); 17027 return css; 17028 }, {})); 17029 var parsedProps = {}; 17030 17031 for (var i = 0; i < this.properties.length; i++) { 17032 var prop = this.properties[i]; 17033 17034 if (prop.pointsTo) { 17035 continue; 17036 } 17037 17038 var name = prop.name; 17039 var val = rawProps[name]; 17040 var parsedProp = this.parse(name, val); 17041 parsedProps[name] = parsedProp; 17042 } 17043 17044 _p.defaultProperties = parsedProps; 17045 return _p.defaultProperties; 17046 }; 17047 17048 styfn$6.addDefaultStylesheet = function () { 17049 this.selector(':parent').css({ 17050 'shape': 'rectangle', 17051 'padding': 10, 17052 'background-color': '#eee', 17053 'border-color': '#ccc', 17054 'border-width': 1 17055 }).selector('edge').css({ 17056 'width': 3 17057 }).selector(':loop').css({ 17058 'curve-style': 'bezier' 17059 }).selector('edge:compound').css({ 17060 'curve-style': 'bezier', 17061 'source-endpoint': 'outside-to-line', 17062 'target-endpoint': 'outside-to-line' 17063 }).selector(':selected').css({ 17064 'background-color': '#0169D9', 17065 'line-color': '#0169D9', 17066 'source-arrow-color': '#0169D9', 17067 'target-arrow-color': '#0169D9', 17068 'mid-source-arrow-color': '#0169D9', 17069 'mid-target-arrow-color': '#0169D9' 17070 }).selector(':parent:selected').css({ 17071 'background-color': '#CCE1F9', 17072 'border-color': '#aec8e5' 17073 }).selector(':active').css({ 17074 'overlay-color': 'black', 17075 'overlay-padding': 10, 17076 'overlay-opacity': 0.25 17077 }); 17078 this.defaultLength = this.length; 17079 }; 17080 17081 var styfn$7 = {}; // a caching layer for property parsing 17082 17083 styfn$7.parse = function (name, value, propIsBypass, propIsFlat) { 17084 var self = this; // function values can't be cached in all cases, and there isn't much benefit of caching them anyway 17085 17086 if (fn(value)) { 17087 return self.parseImplWarn(name, value, propIsBypass, propIsFlat); 17088 } 17089 17090 var flatKey = propIsFlat === 'mapping' || propIsFlat === true || propIsFlat === false || propIsFlat == null ? 'dontcare' : propIsFlat; 17091 var bypassKey = propIsBypass ? 't' : 'f'; 17092 var valueKey = '' + value; 17093 var argHash = hashStrings(name, valueKey, bypassKey, flatKey); 17094 var propCache = self.propCache = self.propCache || []; 17095 var ret; 17096 17097 if (!(ret = propCache[argHash])) { 17098 ret = propCache[argHash] = self.parseImplWarn(name, value, propIsBypass, propIsFlat); 17099 } // - bypasses can't be shared b/c the value can be changed by animations or otherwise overridden 17100 // - mappings can't be shared b/c mappings are per-element 17101 17102 17103 if (propIsBypass || propIsFlat === 'mapping') { 17104 // need a copy since props are mutated later in their lifecycles 17105 ret = copy(ret); 17106 17107 if (ret) { 17108 ret.value = copy(ret.value); // because it could be an array, e.g. colour 17109 } 17110 } 17111 17112 return ret; 17113 }; 17114 17115 styfn$7.parseImplWarn = function (name, value, propIsBypass, propIsFlat) { 17116 var prop = this.parseImpl(name, value, propIsBypass, propIsFlat); 17117 17118 if (!prop && value != null) { 17119 warn("The style property `".concat(name, ": ").concat(value, "` is invalid")); 17120 } 17121 17122 return prop; 17123 }; // parse a property; return null on invalid; return parsed property otherwise 17124 // fields : 17125 // - name : the name of the property 17126 // - value : the parsed, native-typed value of the property 17127 // - strValue : a string value that represents the property value in valid css 17128 // - bypass : true iff the property is a bypass property 17129 17130 17131 styfn$7.parseImpl = function (name, value, propIsBypass, propIsFlat) { 17132 var self = this; 17133 name = camel2dash(name); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName') 17134 17135 var property = self.properties[name]; 17136 var passedValue = value; 17137 var types = self.types; 17138 17139 if (!property) { 17140 return null; 17141 } // return null on property of unknown name 17142 17143 17144 if (value === undefined) { 17145 return null; 17146 } // can't assign undefined 17147 // the property may be an alias 17148 17149 17150 if (property.alias) { 17151 property = property.pointsTo; 17152 name = property.name; 17153 } 17154 17155 var valueIsString = string(value); 17156 17157 if (valueIsString) { 17158 // trim the value to make parsing easier 17159 value = value.trim(); 17160 } 17161 17162 var type = property.type; 17163 17164 if (!type) { 17165 return null; 17166 } // no type, no luck 17167 // check if bypass is null or empty string (i.e. indication to delete bypass property) 17168 17169 17170 if (propIsBypass && (value === '' || value === null)) { 17171 return { 17172 name: name, 17173 value: value, 17174 bypass: true, 17175 deleteBypass: true 17176 }; 17177 } // check if value is a function used as a mapper 17178 17179 17180 if (fn(value)) { 17181 return { 17182 name: name, 17183 value: value, 17184 strValue: 'fn', 17185 mapped: types.fn, 17186 bypass: propIsBypass 17187 }; 17188 } // check if value is mapped 17189 17190 17191 var data, mapData; 17192 17193 if (!valueIsString || propIsFlat || value.length < 7 || value[1] !== 'a') ; else if (value.length >= 7 && value[0] === 'd' && (data = new RegExp(types.data.regex).exec(value))) { 17194 if (propIsBypass) { 17195 return false; 17196 } // mappers not allowed in bypass 17197 17198 17199 var mapped = types.data; 17200 return { 17201 name: name, 17202 value: data, 17203 strValue: '' + value, 17204 mapped: mapped, 17205 field: data[1], 17206 bypass: propIsBypass 17207 }; 17208 } else if (value.length >= 10 && value[0] === 'm' && (mapData = new RegExp(types.mapData.regex).exec(value))) { 17209 if (propIsBypass) { 17210 return false; 17211 } // mappers not allowed in bypass 17212 17213 17214 if (type.multiple) { 17215 return false; 17216 } // impossible to map to num 17217 17218 17219 var _mapped = types.mapData; // we can map only if the type is a colour or a number 17220 17221 if (!(type.color || type.number)) { 17222 return false; 17223 } 17224 17225 var valueMin = this.parse(name, mapData[4]); // parse to validate 17226 17227 if (!valueMin || valueMin.mapped) { 17228 return false; 17229 } // can't be invalid or mapped 17230 17231 17232 var valueMax = this.parse(name, mapData[5]); // parse to validate 17233 17234 if (!valueMax || valueMax.mapped) { 17235 return false; 17236 } // can't be invalid or mapped 17237 // check if valueMin and valueMax are the same 17238 17239 17240 if (valueMin.pfValue === valueMax.pfValue || valueMin.strValue === valueMax.strValue) { 17241 warn('`' + name + ': ' + value + '` is not a valid mapper because the output range is zero; converting to `' + name + ': ' + valueMin.strValue + '`'); 17242 return this.parse(name, valueMin.strValue); // can't make much of a mapper without a range 17243 } else if (type.color) { 17244 var c1 = valueMin.value; 17245 var c2 = valueMax.value; 17246 var same = c1[0] === c2[0] // red 17247 && c1[1] === c2[1] // green 17248 && c1[2] === c2[2] // blue 17249 && ( // optional alpha 17250 c1[3] === c2[3] // same alpha outright 17251 || (c1[3] == null || c1[3] === 1) && ( // full opacity for colour 1? 17252 c2[3] == null || c2[3] === 1) // full opacity for colour 2? 17253 ); 17254 17255 if (same) { 17256 return false; 17257 } // can't make a mapper without a range 17258 17259 } 17260 17261 return { 17262 name: name, 17263 value: mapData, 17264 strValue: '' + value, 17265 mapped: _mapped, 17266 field: mapData[1], 17267 fieldMin: parseFloat(mapData[2]), 17268 // min & max are numeric 17269 fieldMax: parseFloat(mapData[3]), 17270 valueMin: valueMin.value, 17271 valueMax: valueMax.value, 17272 bypass: propIsBypass 17273 }; 17274 } 17275 17276 if (type.multiple && propIsFlat !== 'multiple') { 17277 var vals; 17278 17279 if (valueIsString) { 17280 vals = value.split(/\s+/); 17281 } else if (array(value)) { 17282 vals = value; 17283 } else { 17284 vals = [value]; 17285 } 17286 17287 if (type.evenMultiple && vals.length % 2 !== 0) { 17288 return null; 17289 } 17290 17291 var valArr = []; 17292 var unitsArr = []; 17293 var pfValArr = []; 17294 var strVal = ''; 17295 var hasEnum = false; 17296 17297 for (var i = 0; i < vals.length; i++) { 17298 var p = self.parse(name, vals[i], propIsBypass, 'multiple'); 17299 hasEnum = hasEnum || string(p.value); 17300 valArr.push(p.value); 17301 pfValArr.push(p.pfValue != null ? p.pfValue : p.value); 17302 unitsArr.push(p.units); 17303 strVal += (i > 0 ? ' ' : '') + p.strValue; 17304 } 17305 17306 if (type.validate && !type.validate(valArr, unitsArr)) { 17307 return null; 17308 } 17309 17310 if (type.singleEnum && hasEnum) { 17311 if (valArr.length === 1 && string(valArr[0])) { 17312 return { 17313 name: name, 17314 value: valArr[0], 17315 strValue: valArr[0], 17316 bypass: propIsBypass 17317 }; 17318 } else { 17319 return null; 17320 } 17321 } 17322 17323 return { 17324 name: name, 17325 value: valArr, 17326 pfValue: pfValArr, 17327 strValue: strVal, 17328 bypass: propIsBypass, 17329 units: unitsArr 17330 }; 17331 } // several types also allow enums 17332 17333 17334 var checkEnums = function checkEnums() { 17335 for (var _i = 0; _i < type.enums.length; _i++) { 17336 var en = type.enums[_i]; 17337 17338 if (en === value) { 17339 return { 17340 name: name, 17341 value: value, 17342 strValue: '' + value, 17343 bypass: propIsBypass 17344 }; 17345 } 17346 } 17347 17348 return null; 17349 }; // check the type and return the appropriate object 17350 17351 17352 if (type.number) { 17353 var units; 17354 var implicitUnits = 'px'; // not set => px 17355 17356 if (type.units) { 17357 // use specified units if set 17358 units = type.units; 17359 } 17360 17361 if (type.implicitUnits) { 17362 implicitUnits = type.implicitUnits; 17363 } 17364 17365 if (!type.unitless) { 17366 if (valueIsString) { 17367 var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : ''); 17368 17369 if (units) { 17370 unitsRegex = units; 17371 } // only allow explicit units if so set 17372 17373 17374 var match = value.match('^(' + number$1 + ')(' + unitsRegex + ')?' + '$'); 17375 17376 if (match) { 17377 value = match[1]; 17378 units = match[2] || implicitUnits; 17379 } 17380 } else if (!units || type.implicitUnits) { 17381 units = implicitUnits; // implicitly px if unspecified 17382 } 17383 } 17384 17385 value = parseFloat(value); // if not a number and enums not allowed, then the value is invalid 17386 17387 if (isNaN(value) && type.enums === undefined) { 17388 return null; 17389 } // check if this number type also accepts special keywords in place of numbers 17390 // (i.e. `left`, `auto`, etc) 17391 17392 17393 if (isNaN(value) && type.enums !== undefined) { 17394 value = passedValue; 17395 return checkEnums(); 17396 } // check if value must be an integer 17397 17398 17399 if (type.integer && !integer(value)) { 17400 return null; 17401 } // check value is within range 17402 17403 17404 if (type.min !== undefined && (value < type.min || type.strictMin && value === type.min) || type.max !== undefined && (value > type.max || type.strictMax && value === type.max)) { 17405 return null; 17406 } 17407 17408 var ret = { 17409 name: name, 17410 value: value, 17411 strValue: '' + value + (units ? units : ''), 17412 units: units, 17413 bypass: propIsBypass 17414 }; // normalise value in pixels 17415 17416 if (type.unitless || units !== 'px' && units !== 'em') { 17417 ret.pfValue = value; 17418 } else { 17419 ret.pfValue = units === 'px' || !units ? value : this.getEmSizeInPixels() * value; 17420 } // normalise value in ms 17421 17422 17423 if (units === 'ms' || units === 's') { 17424 ret.pfValue = units === 'ms' ? value : 1000 * value; 17425 } // normalise value in rad 17426 17427 17428 if (units === 'deg' || units === 'rad') { 17429 ret.pfValue = units === 'rad' ? value : deg2rad(value); 17430 } // normalize value in % 17431 17432 17433 if (units === '%') { 17434 ret.pfValue = value / 100; 17435 } 17436 17437 return ret; 17438 } else if (type.propList) { 17439 var props = []; 17440 var propsStr = '' + value; 17441 17442 if (propsStr === 'none') ; else { 17443 // go over each prop 17444 var propsSplit = propsStr.split(/\s*,\s*|\s+/); 17445 17446 for (var _i2 = 0; _i2 < propsSplit.length; _i2++) { 17447 var propName = propsSplit[_i2].trim(); 17448 17449 if (self.properties[propName]) { 17450 props.push(propName); 17451 } else { 17452 warn('`' + propName + '` is not a valid property name'); 17453 } 17454 } 17455 17456 if (props.length === 0) { 17457 return null; 17458 } 17459 } 17460 17461 return { 17462 name: name, 17463 value: props, 17464 strValue: props.length === 0 ? 'none' : props.join(' '), 17465 bypass: propIsBypass 17466 }; 17467 } else if (type.color) { 17468 var tuple = color2tuple(value); 17469 17470 if (!tuple) { 17471 return null; 17472 } 17473 17474 return { 17475 name: name, 17476 value: tuple, 17477 pfValue: tuple, 17478 strValue: 'rgb(' + tuple[0] + ',' + tuple[1] + ',' + tuple[2] + ')', 17479 // n.b. no spaces b/c of multiple support 17480 bypass: propIsBypass 17481 }; 17482 } else if (type.regex || type.regexes) { 17483 // first check enums 17484 if (type.enums) { 17485 var enumProp = checkEnums(); 17486 17487 if (enumProp) { 17488 return enumProp; 17489 } 17490 } 17491 17492 var regexes = type.regexes ? type.regexes : [type.regex]; 17493 17494 for (var _i3 = 0; _i3 < regexes.length; _i3++) { 17495 var regex = new RegExp(regexes[_i3]); // make a regex from the type string 17496 17497 var m = regex.exec(value); 17498 17499 if (m) { 17500 // regex matches 17501 return { 17502 name: name, 17503 value: type.singleRegexMatchValue ? m[1] : m, 17504 strValue: '' + value, 17505 bypass: propIsBypass 17506 }; 17507 } 17508 } 17509 17510 return null; // didn't match any 17511 } else if (type.string) { 17512 // just return 17513 return { 17514 name: name, 17515 value: '' + value, 17516 strValue: '' + value, 17517 bypass: propIsBypass 17518 }; 17519 } else if (type.enums) { 17520 // check enums last because it's a combo type in others 17521 return checkEnums(); 17522 } else { 17523 return null; // not a type we can handle 17524 } 17525 }; 17526 17527 var Style = function Style(cy) { 17528 if (!(this instanceof Style)) { 17529 return new Style(cy); 17530 } 17531 17532 if (!core(cy)) { 17533 error('A style must have a core reference'); 17534 return; 17535 } 17536 17537 this._private = { 17538 cy: cy, 17539 coreStyle: {} 17540 }; 17541 this.length = 0; 17542 this.resetToDefault(); 17543 }; 17544 17545 var styfn$8 = Style.prototype; 17546 17547 styfn$8.instanceString = function () { 17548 return 'style'; 17549 }; // remove all contexts 17550 17551 17552 styfn$8.clear = function () { 17553 for (var i = 0; i < this.length; i++) { 17554 this[i] = undefined; 17555 } 17556 17557 this.length = 0; 17558 var _p = this._private; 17559 _p.newStyle = true; 17560 return this; // chaining 17561 }; 17562 17563 styfn$8.resetToDefault = function () { 17564 this.clear(); 17565 this.addDefaultStylesheet(); 17566 return this; 17567 }; // builds a style object for the 'core' selector 17568 17569 17570 styfn$8.core = function (propName) { 17571 return this._private.coreStyle[propName] || this.getDefaultProperty(propName); 17572 }; // create a new context from the specified selector string and switch to that context 17573 17574 17575 styfn$8.selector = function (selectorStr) { 17576 // 'core' is a special case and does not need a selector 17577 var selector = selectorStr === 'core' ? null : new Selector(selectorStr); 17578 var i = this.length++; // new context means new index 17579 17580 this[i] = { 17581 selector: selector, 17582 properties: [], 17583 mappedProperties: [], 17584 index: i 17585 }; 17586 return this; // chaining 17587 }; // add one or many css rules to the current context 17588 17589 17590 styfn$8.css = function () { 17591 var self = this; 17592 var args = arguments; 17593 17594 if (args.length === 1) { 17595 var map = args[0]; 17596 17597 for (var i = 0; i < self.properties.length; i++) { 17598 var prop = self.properties[i]; 17599 var mapVal = map[prop.name]; 17600 17601 if (mapVal === undefined) { 17602 mapVal = map[dash2camel(prop.name)]; 17603 } 17604 17605 if (mapVal !== undefined) { 17606 this.cssRule(prop.name, mapVal); 17607 } 17608 } 17609 } else if (args.length === 2) { 17610 this.cssRule(args[0], args[1]); 17611 } // do nothing if args are invalid 17612 17613 17614 return this; // chaining 17615 }; 17616 17617 styfn$8.style = styfn$8.css; // add a single css rule to the current context 17618 17619 styfn$8.cssRule = function (name, value) { 17620 // name-value pair 17621 var property = this.parse(name, value); // add property to current context if valid 17622 17623 if (property) { 17624 var i = this.length - 1; 17625 this[i].properties.push(property); 17626 this[i].properties[property.name] = property; // allow access by name as well 17627 17628 if (property.name.match(/pie-(\d+)-background-size/) && property.value) { 17629 this._private.hasPie = true; 17630 } 17631 17632 if (property.mapped) { 17633 this[i].mappedProperties.push(property); 17634 } // add to core style if necessary 17635 17636 17637 var currentSelectorIsCore = !this[i].selector; 17638 17639 if (currentSelectorIsCore) { 17640 this._private.coreStyle[property.name] = property; 17641 } 17642 } 17643 17644 return this; // chaining 17645 }; 17646 17647 styfn$8.append = function (style) { 17648 if (stylesheet(style)) { 17649 style.appendToStyle(this); 17650 } else if (array(style)) { 17651 this.appendFromJson(style); 17652 } else if (string(style)) { 17653 this.appendFromString(style); 17654 } // you probably wouldn't want to append a Style, since you'd duplicate the default parts 17655 17656 17657 return this; 17658 }; // static function 17659 17660 17661 Style.fromJson = function (cy, json) { 17662 var style = new Style(cy); 17663 style.fromJson(json); 17664 return style; 17665 }; 17666 17667 Style.fromString = function (cy, string) { 17668 return new Style(cy).fromString(string); 17669 }; 17670 17671 [styfn, styfn$1, styfn$2, styfn$3, styfn$4, styfn$5, styfn$6, styfn$7].forEach(function (props) { 17672 extend(styfn$8, props); 17673 }); 17674 Style.types = styfn$8.types; 17675 Style.properties = styfn$8.properties; 17676 Style.propertyGroups = styfn$8.propertyGroups; 17677 Style.propertyGroupNames = styfn$8.propertyGroupNames; 17678 Style.propertyGroupKeys = styfn$8.propertyGroupKeys; 17679 17680 var corefn$7 = { 17681 style: function style(newStyle) { 17682 if (newStyle) { 17683 var s = this.setStyle(newStyle); 17684 s.update(); 17685 } 17686 17687 return this._private.style; 17688 }, 17689 setStyle: function setStyle(style) { 17690 var _p = this._private; 17691 17692 if (stylesheet(style)) { 17693 _p.style = style.generateStyle(this); 17694 } else if (array(style)) { 17695 _p.style = Style.fromJson(this, style); 17696 } else if (string(style)) { 17697 _p.style = Style.fromString(this, style); 17698 } else { 17699 _p.style = Style(this); 17700 } 17701 17702 return _p.style; 17703 } 17704 }; 17705 17706 var defaultSelectionType = 'single'; 17707 var corefn$8 = { 17708 autolock: function autolock(bool) { 17709 if (bool !== undefined) { 17710 this._private.autolock = bool ? true : false; 17711 } else { 17712 return this._private.autolock; 17713 } 17714 17715 return this; // chaining 17716 }, 17717 autoungrabify: function autoungrabify(bool) { 17718 if (bool !== undefined) { 17719 this._private.autoungrabify = bool ? true : false; 17720 } else { 17721 return this._private.autoungrabify; 17722 } 17723 17724 return this; // chaining 17725 }, 17726 autounselectify: function autounselectify(bool) { 17727 if (bool !== undefined) { 17728 this._private.autounselectify = bool ? true : false; 17729 } else { 17730 return this._private.autounselectify; 17731 } 17732 17733 return this; // chaining 17734 }, 17735 selectionType: function selectionType(selType) { 17736 var _p = this._private; 17737 17738 if (_p.selectionType == null) { 17739 _p.selectionType = defaultSelectionType; 17740 } 17741 17742 if (selType !== undefined) { 17743 if (selType === 'additive' || selType === 'single') { 17744 _p.selectionType = selType; 17745 } 17746 } else { 17747 return _p.selectionType; 17748 } 17749 17750 return this; 17751 }, 17752 panningEnabled: function panningEnabled(bool) { 17753 if (bool !== undefined) { 17754 this._private.panningEnabled = bool ? true : false; 17755 } else { 17756 return this._private.panningEnabled; 17757 } 17758 17759 return this; // chaining 17760 }, 17761 userPanningEnabled: function userPanningEnabled(bool) { 17762 if (bool !== undefined) { 17763 this._private.userPanningEnabled = bool ? true : false; 17764 } else { 17765 return this._private.userPanningEnabled; 17766 } 17767 17768 return this; // chaining 17769 }, 17770 zoomingEnabled: function zoomingEnabled(bool) { 17771 if (bool !== undefined) { 17772 this._private.zoomingEnabled = bool ? true : false; 17773 } else { 17774 return this._private.zoomingEnabled; 17775 } 17776 17777 return this; // chaining 17778 }, 17779 userZoomingEnabled: function userZoomingEnabled(bool) { 17780 if (bool !== undefined) { 17781 this._private.userZoomingEnabled = bool ? true : false; 17782 } else { 17783 return this._private.userZoomingEnabled; 17784 } 17785 17786 return this; // chaining 17787 }, 17788 boxSelectionEnabled: function boxSelectionEnabled(bool) { 17789 if (bool !== undefined) { 17790 this._private.boxSelectionEnabled = bool ? true : false; 17791 } else { 17792 return this._private.boxSelectionEnabled; 17793 } 17794 17795 return this; // chaining 17796 }, 17797 pan: function pan() { 17798 var args = arguments; 17799 var pan = this._private.pan; 17800 var dim, val, dims, x, y; 17801 17802 switch (args.length) { 17803 case 0: 17804 // .pan() 17805 return pan; 17806 17807 case 1: 17808 if (string(args[0])) { 17809 // .pan('x') 17810 dim = args[0]; 17811 return pan[dim]; 17812 } else if (plainObject(args[0])) { 17813 // .pan({ x: 0, y: 100 }) 17814 if (!this._private.panningEnabled) { 17815 return this; 17816 } 17817 17818 dims = args[0]; 17819 x = dims.x; 17820 y = dims.y; 17821 17822 if (number(x)) { 17823 pan.x = x; 17824 } 17825 17826 if (number(y)) { 17827 pan.y = y; 17828 } 17829 17830 this.emit('pan viewport'); 17831 } 17832 17833 break; 17834 17835 case 2: 17836 // .pan('x', 100) 17837 if (!this._private.panningEnabled) { 17838 return this; 17839 } 17840 17841 dim = args[0]; 17842 val = args[1]; 17843 17844 if ((dim === 'x' || dim === 'y') && number(val)) { 17845 pan[dim] = val; 17846 } 17847 17848 this.emit('pan viewport'); 17849 break; 17850 // invalid 17851 } 17852 17853 this.notify('viewport'); 17854 return this; // chaining 17855 }, 17856 panBy: function panBy(arg0, arg1) { 17857 var args = arguments; 17858 var pan = this._private.pan; 17859 var dim, val, dims, x, y; 17860 17861 if (!this._private.panningEnabled) { 17862 return this; 17863 } 17864 17865 switch (args.length) { 17866 case 1: 17867 if (plainObject(arg0)) { 17868 // .panBy({ x: 0, y: 100 }) 17869 dims = args[0]; 17870 x = dims.x; 17871 y = dims.y; 17872 17873 if (number(x)) { 17874 pan.x += x; 17875 } 17876 17877 if (number(y)) { 17878 pan.y += y; 17879 } 17880 17881 this.emit('pan viewport'); 17882 } 17883 17884 break; 17885 17886 case 2: 17887 // .panBy('x', 100) 17888 dim = arg0; 17889 val = arg1; 17890 17891 if ((dim === 'x' || dim === 'y') && number(val)) { 17892 pan[dim] += val; 17893 } 17894 17895 this.emit('pan viewport'); 17896 break; 17897 // invalid 17898 } 17899 17900 this.notify('viewport'); 17901 return this; // chaining 17902 }, 17903 fit: function fit(elements, padding) { 17904 var viewportState = this.getFitViewport(elements, padding); 17905 17906 if (viewportState) { 17907 var _p = this._private; 17908 _p.zoom = viewportState.zoom; 17909 _p.pan = viewportState.pan; 17910 this.emit('pan zoom viewport'); 17911 this.notify('viewport'); 17912 } 17913 17914 return this; // chaining 17915 }, 17916 getFitViewport: function getFitViewport(elements, padding) { 17917 if (number(elements) && padding === undefined) { 17918 // elements is optional 17919 padding = elements; 17920 elements = undefined; 17921 } 17922 17923 if (!this._private.panningEnabled || !this._private.zoomingEnabled) { 17924 return; 17925 } 17926 17927 var bb; 17928 17929 if (string(elements)) { 17930 var sel = elements; 17931 elements = this.$(sel); 17932 } else if (boundingBox(elements)) { 17933 // assume bb 17934 var bbe = elements; 17935 bb = { 17936 x1: bbe.x1, 17937 y1: bbe.y1, 17938 x2: bbe.x2, 17939 y2: bbe.y2 17940 }; 17941 bb.w = bb.x2 - bb.x1; 17942 bb.h = bb.y2 - bb.y1; 17943 } else if (!elementOrCollection(elements)) { 17944 elements = this.mutableElements(); 17945 } 17946 17947 if (elementOrCollection(elements) && elements.empty()) { 17948 return; 17949 } // can't fit to nothing 17950 17951 17952 bb = bb || elements.boundingBox(); 17953 var w = this.width(); 17954 var h = this.height(); 17955 var zoom; 17956 padding = number(padding) ? padding : 0; 17957 17958 if (!isNaN(w) && !isNaN(h) && w > 0 && h > 0 && !isNaN(bb.w) && !isNaN(bb.h) && bb.w > 0 && bb.h > 0) { 17959 zoom = Math.min((w - 2 * padding) / bb.w, (h - 2 * padding) / bb.h); // crop zoom 17960 17961 zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; 17962 zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; 17963 var pan = { 17964 // now pan to middle 17965 x: (w - zoom * (bb.x1 + bb.x2)) / 2, 17966 y: (h - zoom * (bb.y1 + bb.y2)) / 2 17967 }; 17968 return { 17969 zoom: zoom, 17970 pan: pan 17971 }; 17972 } 17973 17974 return; 17975 }, 17976 zoomRange: function zoomRange(min, max) { 17977 var _p = this._private; 17978 17979 if (max == null) { 17980 var opts = min; 17981 min = opts.min; 17982 max = opts.max; 17983 } 17984 17985 if (number(min) && number(max) && min <= max) { 17986 _p.minZoom = min; 17987 _p.maxZoom = max; 17988 } else if (number(min) && max === undefined && min <= _p.maxZoom) { 17989 _p.minZoom = min; 17990 } else if (number(max) && min === undefined && max >= _p.minZoom) { 17991 _p.maxZoom = max; 17992 } 17993 17994 return this; 17995 }, 17996 minZoom: function minZoom(zoom) { 17997 if (zoom === undefined) { 17998 return this._private.minZoom; 17999 } else { 18000 return this.zoomRange({ 18001 min: zoom 18002 }); 18003 } 18004 }, 18005 maxZoom: function maxZoom(zoom) { 18006 if (zoom === undefined) { 18007 return this._private.maxZoom; 18008 } else { 18009 return this.zoomRange({ 18010 max: zoom 18011 }); 18012 } 18013 }, 18014 getZoomedViewport: function getZoomedViewport(params) { 18015 var _p = this._private; 18016 var currentPan = _p.pan; 18017 var currentZoom = _p.zoom; 18018 var pos; // in rendered px 18019 18020 var zoom; 18021 var bail = false; 18022 18023 if (!_p.zoomingEnabled) { 18024 // zooming disabled 18025 bail = true; 18026 } 18027 18028 if (number(params)) { 18029 // then set the zoom 18030 zoom = params; 18031 } else if (plainObject(params)) { 18032 // then zoom about a point 18033 zoom = params.level; 18034 18035 if (params.position != null) { 18036 pos = modelToRenderedPosition(params.position, currentZoom, currentPan); 18037 } else if (params.renderedPosition != null) { 18038 pos = params.renderedPosition; 18039 } 18040 18041 if (pos != null && !_p.panningEnabled) { 18042 // panning disabled 18043 bail = true; 18044 } 18045 } // crop zoom 18046 18047 18048 zoom = zoom > _p.maxZoom ? _p.maxZoom : zoom; 18049 zoom = zoom < _p.minZoom ? _p.minZoom : zoom; // can't zoom with invalid params 18050 18051 if (bail || !number(zoom) || zoom === currentZoom || pos != null && (!number(pos.x) || !number(pos.y))) { 18052 return null; 18053 } 18054 18055 if (pos != null) { 18056 // set zoom about position 18057 var pan1 = currentPan; 18058 var zoom1 = currentZoom; 18059 var zoom2 = zoom; 18060 var pan2 = { 18061 x: -zoom2 / zoom1 * (pos.x - pan1.x) + pos.x, 18062 y: -zoom2 / zoom1 * (pos.y - pan1.y) + pos.y 18063 }; 18064 return { 18065 zoomed: true, 18066 panned: true, 18067 zoom: zoom2, 18068 pan: pan2 18069 }; 18070 } else { 18071 // just set the zoom 18072 return { 18073 zoomed: true, 18074 panned: false, 18075 zoom: zoom, 18076 pan: currentPan 18077 }; 18078 } 18079 }, 18080 zoom: function zoom(params) { 18081 if (params === undefined) { 18082 // get 18083 return this._private.zoom; 18084 } else { 18085 // set 18086 var vp = this.getZoomedViewport(params); 18087 var _p = this._private; 18088 18089 if (vp == null || !vp.zoomed) { 18090 return this; 18091 } 18092 18093 _p.zoom = vp.zoom; 18094 18095 if (vp.panned) { 18096 _p.pan.x = vp.pan.x; 18097 _p.pan.y = vp.pan.y; 18098 } 18099 18100 this.emit('zoom' + (vp.panned ? ' pan' : '') + ' viewport'); 18101 this.notify('viewport'); 18102 return this; // chaining 18103 } 18104 }, 18105 viewport: function viewport(opts) { 18106 var _p = this._private; 18107 var zoomDefd = true; 18108 var panDefd = true; 18109 var events = []; // to trigger 18110 18111 var zoomFailed = false; 18112 var panFailed = false; 18113 18114 if (!opts) { 18115 return this; 18116 } 18117 18118 if (!number(opts.zoom)) { 18119 zoomDefd = false; 18120 } 18121 18122 if (!plainObject(opts.pan)) { 18123 panDefd = false; 18124 } 18125 18126 if (!zoomDefd && !panDefd) { 18127 return this; 18128 } 18129 18130 if (zoomDefd) { 18131 var z = opts.zoom; 18132 18133 if (z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled) { 18134 zoomFailed = true; 18135 } else { 18136 _p.zoom = z; 18137 events.push('zoom'); 18138 } 18139 } 18140 18141 if (panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled) { 18142 var p = opts.pan; 18143 18144 if (number(p.x)) { 18145 _p.pan.x = p.x; 18146 panFailed = false; 18147 } 18148 18149 if (number(p.y)) { 18150 _p.pan.y = p.y; 18151 panFailed = false; 18152 } 18153 18154 if (!panFailed) { 18155 events.push('pan'); 18156 } 18157 } 18158 18159 if (events.length > 0) { 18160 events.push('viewport'); 18161 this.emit(events.join(' ')); 18162 this.notify('viewport'); 18163 } 18164 18165 return this; // chaining 18166 }, 18167 center: function center(elements) { 18168 var pan = this.getCenterPan(elements); 18169 18170 if (pan) { 18171 this._private.pan = pan; 18172 this.emit('pan viewport'); 18173 this.notify('viewport'); 18174 } 18175 18176 return this; // chaining 18177 }, 18178 getCenterPan: function getCenterPan(elements, zoom) { 18179 if (!this._private.panningEnabled) { 18180 return; 18181 } 18182 18183 if (string(elements)) { 18184 var selector = elements; 18185 elements = this.mutableElements().filter(selector); 18186 } else if (!elementOrCollection(elements)) { 18187 elements = this.mutableElements(); 18188 } 18189 18190 if (elements.length === 0) { 18191 return; 18192 } // can't centre pan to nothing 18193 18194 18195 var bb = elements.boundingBox(); 18196 var w = this.width(); 18197 var h = this.height(); 18198 zoom = zoom === undefined ? this._private.zoom : zoom; 18199 var pan = { 18200 // middle 18201 x: (w - zoom * (bb.x1 + bb.x2)) / 2, 18202 y: (h - zoom * (bb.y1 + bb.y2)) / 2 18203 }; 18204 return pan; 18205 }, 18206 reset: function reset() { 18207 if (!this._private.panningEnabled || !this._private.zoomingEnabled) { 18208 return this; 18209 } 18210 18211 this.viewport({ 18212 pan: { 18213 x: 0, 18214 y: 0 18215 }, 18216 zoom: 1 18217 }); 18218 return this; // chaining 18219 }, 18220 invalidateSize: function invalidateSize() { 18221 this._private.sizeCache = null; 18222 }, 18223 size: function size() { 18224 var _p = this._private; 18225 var container = _p.container; 18226 return _p.sizeCache = _p.sizeCache || (container ? function () { 18227 var style = window$1.getComputedStyle(container); 18228 18229 var val = function val(name) { 18230 return parseFloat(style.getPropertyValue(name)); 18231 }; 18232 18233 return { 18234 width: container.clientWidth - val('padding-left') - val('padding-right'), 18235 height: container.clientHeight - val('padding-top') - val('padding-bottom') 18236 }; 18237 }() : { 18238 // fallback if no container (not 0 b/c can be used for dividing etc) 18239 width: 1, 18240 height: 1 18241 }); 18242 }, 18243 width: function width() { 18244 return this.size().width; 18245 }, 18246 height: function height() { 18247 return this.size().height; 18248 }, 18249 extent: function extent() { 18250 var pan = this._private.pan; 18251 var zoom = this._private.zoom; 18252 var rb = this.renderedExtent(); 18253 var b = { 18254 x1: (rb.x1 - pan.x) / zoom, 18255 x2: (rb.x2 - pan.x) / zoom, 18256 y1: (rb.y1 - pan.y) / zoom, 18257 y2: (rb.y2 - pan.y) / zoom 18258 }; 18259 b.w = b.x2 - b.x1; 18260 b.h = b.y2 - b.y1; 18261 return b; 18262 }, 18263 renderedExtent: function renderedExtent() { 18264 var width = this.width(); 18265 var height = this.height(); 18266 return { 18267 x1: 0, 18268 y1: 0, 18269 x2: width, 18270 y2: height, 18271 w: width, 18272 h: height 18273 }; 18274 } 18275 }; // aliases 18276 18277 corefn$8.centre = corefn$8.center; // backwards compatibility 18278 18279 corefn$8.autolockNodes = corefn$8.autolock; 18280 corefn$8.autoungrabifyNodes = corefn$8.autoungrabify; 18281 18282 var fn$6 = { 18283 data: define$3.data({ 18284 field: 'data', 18285 bindingEvent: 'data', 18286 allowBinding: true, 18287 allowSetting: true, 18288 settingEvent: 'data', 18289 settingTriggersEvent: true, 18290 triggerFnName: 'trigger', 18291 allowGetting: true 18292 }), 18293 removeData: define$3.removeData({ 18294 field: 'data', 18295 event: 'data', 18296 triggerFnName: 'trigger', 18297 triggerEvent: true 18298 }), 18299 scratch: define$3.data({ 18300 field: 'scratch', 18301 bindingEvent: 'scratch', 18302 allowBinding: true, 18303 allowSetting: true, 18304 settingEvent: 'scratch', 18305 settingTriggersEvent: true, 18306 triggerFnName: 'trigger', 18307 allowGetting: true 18308 }), 18309 removeScratch: define$3.removeData({ 18310 field: 'scratch', 18311 event: 'scratch', 18312 triggerFnName: 'trigger', 18313 triggerEvent: true 18314 }) 18315 }; // aliases 18316 18317 fn$6.attr = fn$6.data; 18318 fn$6.removeAttr = fn$6.removeData; 18319 18320 var Core = function Core(opts) { 18321 var cy = this; 18322 opts = extend({}, opts); 18323 var container = opts.container; // allow for passing a wrapped jquery object 18324 // e.g. cytoscape({ container: $('#cy') }) 18325 18326 if (container && !htmlElement(container) && htmlElement(container[0])) { 18327 container = container[0]; 18328 } 18329 18330 var reg = container ? container._cyreg : null; // e.g. already registered some info (e.g. readies) via jquery 18331 18332 reg = reg || {}; 18333 18334 if (reg && reg.cy) { 18335 reg.cy.destroy(); 18336 reg = {}; // old instance => replace reg completely 18337 } 18338 18339 var readies = reg.readies = reg.readies || []; 18340 18341 if (container) { 18342 container._cyreg = reg; 18343 } // make sure container assoc'd reg points to this cy 18344 18345 18346 reg.cy = cy; 18347 var head = window$1 !== undefined && container !== undefined && !opts.headless; 18348 var options = opts; 18349 options.layout = extend({ 18350 name: head ? 'grid' : 'null' 18351 }, options.layout); 18352 options.renderer = extend({ 18353 name: head ? 'canvas' : 'null' 18354 }, options.renderer); 18355 18356 var defVal = function defVal(def, val, altVal) { 18357 if (val !== undefined) { 18358 return val; 18359 } else if (altVal !== undefined) { 18360 return altVal; 18361 } else { 18362 return def; 18363 } 18364 }; 18365 18366 var _p = this._private = { 18367 container: container, 18368 // html dom ele container 18369 ready: false, 18370 // whether ready has been triggered 18371 options: options, 18372 // cached options 18373 elements: new Collection(this), 18374 // elements in the graph 18375 listeners: [], 18376 // list of listeners 18377 aniEles: new Collection(this), 18378 // elements being animated 18379 data: {}, 18380 // data for the core 18381 scratch: {}, 18382 // scratch object for core 18383 layout: null, 18384 renderer: null, 18385 destroyed: false, 18386 // whether destroy was called 18387 notificationsEnabled: true, 18388 // whether notifications are sent to the renderer 18389 minZoom: 1e-50, 18390 maxZoom: 1e50, 18391 zoomingEnabled: defVal(true, options.zoomingEnabled), 18392 userZoomingEnabled: defVal(true, options.userZoomingEnabled), 18393 panningEnabled: defVal(true, options.panningEnabled), 18394 userPanningEnabled: defVal(true, options.userPanningEnabled), 18395 boxSelectionEnabled: defVal(true, options.boxSelectionEnabled), 18396 autolock: defVal(false, options.autolock, options.autolockNodes), 18397 autoungrabify: defVal(false, options.autoungrabify, options.autoungrabifyNodes), 18398 autounselectify: defVal(false, options.autounselectify), 18399 styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled, 18400 zoom: number(options.zoom) ? options.zoom : 1, 18401 pan: { 18402 x: plainObject(options.pan) && number(options.pan.x) ? options.pan.x : 0, 18403 y: plainObject(options.pan) && number(options.pan.y) ? options.pan.y : 0 18404 }, 18405 animation: { 18406 // object for currently-running animations 18407 current: [], 18408 queue: [] 18409 }, 18410 hasCompoundNodes: false 18411 }; 18412 18413 this.createEmitter(); // set selection type 18414 18415 this.selectionType(options.selectionType); // init zoom bounds 18416 18417 this.zoomRange({ 18418 min: options.minZoom, 18419 max: options.maxZoom 18420 }); 18421 18422 var loadExtData = function loadExtData(extData, next) { 18423 var anyIsPromise = extData.some(promise); 18424 18425 if (anyIsPromise) { 18426 return Promise$1.all(extData).then(next); // load all data asynchronously, then exec rest of init 18427 } else { 18428 next(extData); // exec synchronously for convenience 18429 } 18430 }; // start with the default stylesheet so we have something before loading an external stylesheet 18431 18432 18433 if (_p.styleEnabled) { 18434 cy.setStyle([]); 18435 } // create the renderer 18436 18437 18438 var rendererOptions = extend({}, options, options.renderer); // allow rendering hints in top level options 18439 18440 cy.initRenderer(rendererOptions); 18441 18442 var setElesAndLayout = function setElesAndLayout(elements, onload, ondone) { 18443 cy.notifications(false); // remove old elements 18444 18445 var oldEles = cy.mutableElements(); 18446 18447 if (oldEles.length > 0) { 18448 oldEles.remove(); 18449 } 18450 18451 if (elements != null) { 18452 if (plainObject(elements) || array(elements)) { 18453 cy.add(elements); 18454 } 18455 } 18456 18457 cy.one('layoutready', function (e) { 18458 cy.notifications(true); 18459 cy.emit(e); // we missed this event by turning notifications off, so pass it on 18460 18461 cy.one('load', onload); 18462 cy.emitAndNotify('load'); 18463 }).one('layoutstop', function () { 18464 cy.one('done', ondone); 18465 cy.emit('done'); 18466 }); 18467 var layoutOpts = extend({}, cy._private.options.layout); 18468 layoutOpts.eles = cy.elements(); 18469 cy.layout(layoutOpts).run(); 18470 }; 18471 18472 loadExtData([options.style, options.elements], function (thens) { 18473 var initStyle = thens[0]; 18474 var initEles = thens[1]; // init style 18475 18476 if (_p.styleEnabled) { 18477 cy.style().append(initStyle); 18478 } // initial load 18479 18480 18481 setElesAndLayout(initEles, function () { 18482 // onready 18483 cy.startAnimationLoop(); 18484 _p.ready = true; // if a ready callback is specified as an option, the bind it 18485 18486 if (fn(options.ready)) { 18487 cy.on('ready', options.ready); 18488 } // bind all the ready handlers registered before creating this instance 18489 18490 18491 for (var i = 0; i < readies.length; i++) { 18492 var fn$1 = readies[i]; 18493 cy.on('ready', fn$1); 18494 } 18495 18496 if (reg) { 18497 reg.readies = []; 18498 } // clear b/c we've bound them all and don't want to keep it around in case a new core uses the same div etc 18499 18500 18501 cy.emit('ready'); 18502 }, options.done); 18503 }); 18504 }; 18505 18506 var corefn$9 = Core.prototype; // short alias 18507 18508 extend(corefn$9, { 18509 instanceString: function instanceString() { 18510 return 'core'; 18511 }, 18512 isReady: function isReady() { 18513 return this._private.ready; 18514 }, 18515 destroyed: function destroyed() { 18516 return this._private.destroyed; 18517 }, 18518 ready: function ready(fn) { 18519 if (this.isReady()) { 18520 this.emitter().emit('ready', [], fn); // just calls fn as though triggered via ready event 18521 } else { 18522 this.on('ready', fn); 18523 } 18524 18525 return this; 18526 }, 18527 destroy: function destroy() { 18528 var cy = this; 18529 if (cy.destroyed()) return; 18530 cy.stopAnimationLoop(); 18531 cy.destroyRenderer(); 18532 this.emit('destroy'); 18533 cy._private.destroyed = true; 18534 return cy; 18535 }, 18536 hasElementWithId: function hasElementWithId(id) { 18537 return this._private.elements.hasElementWithId(id); 18538 }, 18539 getElementById: function getElementById(id) { 18540 return this._private.elements.getElementById(id); 18541 }, 18542 hasCompoundNodes: function hasCompoundNodes() { 18543 return this._private.hasCompoundNodes; 18544 }, 18545 headless: function headless() { 18546 return this._private.renderer.isHeadless(); 18547 }, 18548 styleEnabled: function styleEnabled() { 18549 return this._private.styleEnabled; 18550 }, 18551 addToPool: function addToPool(eles) { 18552 this._private.elements.merge(eles); 18553 18554 return this; // chaining 18555 }, 18556 removeFromPool: function removeFromPool(eles) { 18557 this._private.elements.unmerge(eles); 18558 18559 return this; 18560 }, 18561 container: function container() { 18562 return this._private.container || null; 18563 }, 18564 mount: function mount(container) { 18565 if (container == null) { 18566 return; 18567 } 18568 18569 var cy = this; 18570 var _p = cy._private; 18571 var options = _p.options; 18572 18573 if (!htmlElement(container) && htmlElement(container[0])) { 18574 container = container[0]; 18575 } 18576 18577 cy.stopAnimationLoop(); 18578 cy.destroyRenderer(); 18579 _p.container = container; 18580 _p.styleEnabled = true; 18581 cy.invalidateSize(); 18582 cy.initRenderer(extend({}, options, options.renderer, { 18583 // allow custom renderer name to be re-used, otherwise use canvas 18584 name: options.renderer.name === 'null' ? 'canvas' : options.renderer.name 18585 })); 18586 cy.startAnimationLoop(); 18587 cy.style(options.style); 18588 cy.emit('mount'); 18589 return cy; 18590 }, 18591 unmount: function unmount() { 18592 var cy = this; 18593 cy.stopAnimationLoop(); 18594 cy.destroyRenderer(); 18595 cy.initRenderer({ 18596 name: 'null' 18597 }); 18598 cy.emit('unmount'); 18599 return cy; 18600 }, 18601 options: function options() { 18602 return copy(this._private.options); 18603 }, 18604 json: function json(obj) { 18605 var cy = this; 18606 var _p = cy._private; 18607 var eles = cy.mutableElements(); 18608 18609 var getFreshRef = function getFreshRef(ele) { 18610 return cy.getElementById(ele.id()); 18611 }; 18612 18613 if (plainObject(obj)) { 18614 // set 18615 cy.startBatch(); 18616 18617 if (obj.elements) { 18618 var idInJson = {}; 18619 18620 var updateEles = function updateEles(jsons, gr) { 18621 var toAdd = []; 18622 var toMod = []; 18623 18624 for (var i = 0; i < jsons.length; i++) { 18625 var json = jsons[i]; 18626 var id = '' + json.data.id; // id must be string 18627 18628 var ele = cy.getElementById(id); 18629 idInJson[id] = true; 18630 18631 if (ele.length !== 0) { 18632 // existing element should be updated 18633 toMod.push({ 18634 ele: ele, 18635 json: json 18636 }); 18637 } else { 18638 // otherwise should be added 18639 if (gr) { 18640 json.group = gr; 18641 toAdd.push(json); 18642 } else { 18643 toAdd.push(json); 18644 } 18645 } 18646 } 18647 18648 cy.add(toAdd); 18649 18650 for (var _i = 0; _i < toMod.length; _i++) { 18651 var _toMod$_i = toMod[_i], 18652 _ele = _toMod$_i.ele, 18653 _json = _toMod$_i.json; 18654 18655 _ele.json(_json); 18656 } 18657 }; 18658 18659 if (array(obj.elements)) { 18660 // elements: [] 18661 updateEles(obj.elements); 18662 } else { 18663 // elements: { nodes: [], edges: [] } 18664 var grs = ['nodes', 'edges']; 18665 18666 for (var i = 0; i < grs.length; i++) { 18667 var gr = grs[i]; 18668 var elements = obj.elements[gr]; 18669 18670 if (array(elements)) { 18671 updateEles(elements, gr); 18672 } 18673 } 18674 } 18675 18676 var parentsToRemove = cy.collection(); 18677 eles.filter(function (ele) { 18678 return !idInJson[ele.id()]; 18679 }).forEach(function (ele) { 18680 if (ele.isParent()) { 18681 parentsToRemove.merge(ele); 18682 } else { 18683 ele.remove(); 18684 } 18685 }); // so that children are not removed w/parent 18686 18687 parentsToRemove.forEach(function (ele) { 18688 return ele.children().move({ 18689 parent: null 18690 }); 18691 }); // intermediate parents may be moved by prior line, so make sure we remove by fresh refs 18692 18693 parentsToRemove.forEach(function (ele) { 18694 return getFreshRef(ele).remove(); 18695 }); 18696 } 18697 18698 if (obj.style) { 18699 cy.style(obj.style); 18700 } 18701 18702 if (obj.zoom != null && obj.zoom !== _p.zoom) { 18703 cy.zoom(obj.zoom); 18704 } 18705 18706 if (obj.pan) { 18707 if (obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y) { 18708 cy.pan(obj.pan); 18709 } 18710 } 18711 18712 if (obj.data) { 18713 cy.data(obj.data); 18714 } 18715 18716 var fields = ['minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled', 'panningEnabled', 'userPanningEnabled', 'boxSelectionEnabled', 'autolock', 'autoungrabify', 'autounselectify']; 18717 18718 for (var _i2 = 0; _i2 < fields.length; _i2++) { 18719 var f = fields[_i2]; 18720 18721 if (obj[f] != null) { 18722 cy[f](obj[f]); 18723 } 18724 } 18725 18726 cy.endBatch(); 18727 return this; // chaining 18728 } else { 18729 // get 18730 var flat = !!obj; 18731 var json = {}; 18732 18733 if (flat) { 18734 json.elements = this.elements().map(function (ele) { 18735 return ele.json(); 18736 }); 18737 } else { 18738 json.elements = {}; 18739 eles.forEach(function (ele) { 18740 var group = ele.group(); 18741 18742 if (!json.elements[group]) { 18743 json.elements[group] = []; 18744 } 18745 18746 json.elements[group].push(ele.json()); 18747 }); 18748 } 18749 18750 if (this._private.styleEnabled) { 18751 json.style = cy.style().json(); 18752 } 18753 18754 json.data = copy(cy.data()); 18755 var options = _p.options; 18756 json.zoomingEnabled = _p.zoomingEnabled; 18757 json.userZoomingEnabled = _p.userZoomingEnabled; 18758 json.zoom = _p.zoom; 18759 json.minZoom = _p.minZoom; 18760 json.maxZoom = _p.maxZoom; 18761 json.panningEnabled = _p.panningEnabled; 18762 json.userPanningEnabled = _p.userPanningEnabled; 18763 json.pan = copy(_p.pan); 18764 json.boxSelectionEnabled = _p.boxSelectionEnabled; 18765 json.renderer = copy(options.renderer); 18766 json.hideEdgesOnViewport = options.hideEdgesOnViewport; 18767 json.textureOnViewport = options.textureOnViewport; 18768 json.wheelSensitivity = options.wheelSensitivity; 18769 json.motionBlur = options.motionBlur; 18770 return json; 18771 } 18772 } 18773 }); 18774 corefn$9.$id = corefn$9.getElementById; 18775 [corefn, corefn$1, elesfn$v, corefn$2, corefn$3, corefn$4, corefn$5, corefn$6, corefn$7, corefn$8, fn$6].forEach(function (props) { 18776 extend(corefn$9, props); 18777 }); 18778 18779 /* eslint-disable no-unused-vars */ 18780 18781 var defaults$9 = { 18782 fit: true, 18783 // whether to fit the viewport to the graph 18784 directed: false, 18785 // whether the tree is directed downwards (or edges can point in any direction if false) 18786 padding: 30, 18787 // padding on fit 18788 circle: false, 18789 // put depths in concentric circles if true, put depths top down if false 18790 grid: false, 18791 // whether to create an even grid into which the DAG is placed (circle:false only) 18792 spacingFactor: 1.75, 18793 // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap) 18794 boundingBox: undefined, 18795 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 18796 avoidOverlap: true, 18797 // prevents node overlap, may overflow boundingBox if not enough space 18798 nodeDimensionsIncludeLabels: false, 18799 // Excludes the label when calculating node bounding boxes for the layout algorithm 18800 roots: undefined, 18801 // the roots of the trees 18802 maximal: false, 18803 // whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only) 18804 animate: false, 18805 // whether to transition the node positions 18806 animationDuration: 500, 18807 // duration of animation in ms if enabled 18808 animationEasing: undefined, 18809 // easing of animation if enabled, 18810 animateFilter: function animateFilter(node, i) { 18811 return true; 18812 }, 18813 // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts 18814 ready: undefined, 18815 // callback on layoutready 18816 stop: undefined, 18817 // callback on layoutstop 18818 transform: function transform(node, position) { 18819 return position; 18820 } // transform a given node position. Useful for changing flow direction in discrete layouts 18821 18822 }; 18823 /* eslint-enable */ 18824 18825 var getInfo = function getInfo(ele) { 18826 return ele.scratch('breadthfirst'); 18827 }; 18828 18829 var setInfo = function setInfo(ele, obj) { 18830 return ele.scratch('breadthfirst', obj); 18831 }; 18832 18833 function BreadthFirstLayout(options) { 18834 this.options = extend({}, defaults$9, options); 18835 } 18836 18837 BreadthFirstLayout.prototype.run = function () { 18838 var params = this.options; 18839 var options = params; 18840 var cy = params.cy; 18841 var eles = options.eles; 18842 var nodes = eles.nodes().filter(function (n) { 18843 return !n.isParent(); 18844 }); 18845 var graph = eles; 18846 var directed = options.directed; 18847 var maximal = options.maximal || options.maximalAdjustments > 0; // maximalAdjustments for compat. w/ old code 18848 18849 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 18850 x1: 0, 18851 y1: 0, 18852 w: cy.width(), 18853 h: cy.height() 18854 }); 18855 var roots; 18856 18857 if (elementOrCollection(options.roots)) { 18858 roots = options.roots; 18859 } else if (array(options.roots)) { 18860 var rootsArray = []; 18861 18862 for (var i = 0; i < options.roots.length; i++) { 18863 var id = options.roots[i]; 18864 var ele = cy.getElementById(id); 18865 rootsArray.push(ele); 18866 } 18867 18868 roots = cy.collection(rootsArray); 18869 } else if (string(options.roots)) { 18870 roots = cy.$(options.roots); 18871 } else { 18872 if (directed) { 18873 roots = nodes.roots(); 18874 } else { 18875 var components = eles.components(); 18876 roots = cy.collection(); 18877 18878 var _loop = function _loop(_i) { 18879 var comp = components[_i]; 18880 var maxDegree = comp.maxDegree(false); 18881 var compRoots = comp.filter(function (ele) { 18882 return ele.degree(false) === maxDegree; 18883 }); 18884 roots = roots.add(compRoots); 18885 }; 18886 18887 for (var _i = 0; _i < components.length; _i++) { 18888 _loop(_i); 18889 } 18890 } 18891 } 18892 18893 var depths = []; 18894 var foundByBfs = {}; 18895 18896 var addToDepth = function addToDepth(ele, d) { 18897 if (depths[d] == null) { 18898 depths[d] = []; 18899 } 18900 18901 var i = depths[d].length; 18902 depths[d].push(ele); 18903 setInfo(ele, { 18904 index: i, 18905 depth: d 18906 }); 18907 }; 18908 18909 var changeDepth = function changeDepth(ele, newDepth) { 18910 var _getInfo = getInfo(ele), 18911 depth = _getInfo.depth, 18912 index = _getInfo.index; 18913 18914 depths[depth][index] = null; 18915 addToDepth(ele, newDepth); 18916 }; // find the depths of the nodes 18917 18918 18919 graph.bfs({ 18920 roots: roots, 18921 directed: options.directed, 18922 visit: function visit(node, edge, pNode, i, depth) { 18923 var ele = node[0]; 18924 var id = ele.id(); 18925 addToDepth(ele, depth); 18926 foundByBfs[id] = true; 18927 } 18928 }); // check for nodes not found by bfs 18929 18930 var orphanNodes = []; 18931 18932 for (var _i2 = 0; _i2 < nodes.length; _i2++) { 18933 var _ele = nodes[_i2]; 18934 18935 if (foundByBfs[_ele.id()]) { 18936 continue; 18937 } else { 18938 orphanNodes.push(_ele); 18939 } 18940 } // assign the nodes a depth and index 18941 18942 18943 var assignDepthsAt = function assignDepthsAt(i) { 18944 var eles = depths[i]; 18945 18946 for (var j = 0; j < eles.length; j++) { 18947 var _ele2 = eles[j]; 18948 18949 if (_ele2 == null) { 18950 eles.splice(j, 1); 18951 j--; 18952 continue; 18953 } 18954 18955 setInfo(_ele2, { 18956 depth: i, 18957 index: j 18958 }); 18959 } 18960 }; 18961 18962 var assignDepths = function assignDepths() { 18963 for (var _i3 = 0; _i3 < depths.length; _i3++) { 18964 assignDepthsAt(_i3); 18965 } 18966 }; 18967 18968 var adjustMaximally = function adjustMaximally(ele, shifted) { 18969 var eInfo = getInfo(ele); 18970 var incomers = ele.incomers().filter(function (el) { 18971 return el.isNode() && eles.has(el); 18972 }); 18973 var maxDepth = -1; 18974 var id = ele.id(); 18975 18976 for (var k = 0; k < incomers.length; k++) { 18977 var incmr = incomers[k]; 18978 var iInfo = getInfo(incmr); 18979 maxDepth = Math.max(maxDepth, iInfo.depth); 18980 } 18981 18982 if (eInfo.depth <= maxDepth) { 18983 if (shifted[id]) { 18984 return null; 18985 } 18986 18987 changeDepth(ele, maxDepth + 1); 18988 shifted[id] = true; 18989 return true; 18990 } 18991 18992 return false; 18993 }; // for the directed case, try to make the edges all go down (i.e. depth i => depth i + 1) 18994 18995 18996 if (directed && maximal) { 18997 var Q = []; 18998 var shifted = {}; 18999 19000 var enqueue = function enqueue(n) { 19001 return Q.push(n); 19002 }; 19003 19004 var dequeue = function dequeue() { 19005 return Q.shift(); 19006 }; 19007 19008 nodes.forEach(function (n) { 19009 return Q.push(n); 19010 }); 19011 19012 while (Q.length > 0) { 19013 var _ele3 = dequeue(); 19014 19015 var didShift = adjustMaximally(_ele3, shifted); 19016 19017 if (didShift) { 19018 _ele3.outgoers().filter(function (el) { 19019 return el.isNode() && eles.has(el); 19020 }).forEach(enqueue); 19021 } else if (didShift === null) { 19022 warn('Detected double maximal shift for node `' + _ele3.id() + '`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.'); 19023 break; // exit on failure 19024 } 19025 } 19026 } 19027 19028 assignDepths(); // clear holes 19029 // find min distance we need to leave between nodes 19030 19031 var minDistance = 0; 19032 19033 if (options.avoidOverlap) { 19034 for (var _i4 = 0; _i4 < nodes.length; _i4++) { 19035 var n = nodes[_i4]; 19036 var nbb = n.layoutDimensions(options); 19037 var w = nbb.w; 19038 var h = nbb.h; 19039 minDistance = Math.max(minDistance, w, h); 19040 } 19041 } // get the weighted percent for an element based on its connectivity to other levels 19042 19043 19044 var cachedWeightedPercent = {}; 19045 19046 var getWeightedPercent = function getWeightedPercent(ele) { 19047 if (cachedWeightedPercent[ele.id()]) { 19048 return cachedWeightedPercent[ele.id()]; 19049 } 19050 19051 var eleDepth = getInfo(ele).depth; 19052 var neighbors = ele.neighborhood(); 19053 var percent = 0; 19054 var samples = 0; 19055 19056 for (var _i5 = 0; _i5 < neighbors.length; _i5++) { 19057 var neighbor = neighbors[_i5]; 19058 19059 if (neighbor.isEdge() || neighbor.isParent() || !nodes.has(neighbor)) { 19060 continue; 19061 } 19062 19063 var bf = getInfo(neighbor); 19064 var index = bf.index; 19065 var depth = bf.depth; // unassigned neighbours shouldn't affect the ordering 19066 19067 if (index == null || depth == null) { 19068 continue; 19069 } 19070 19071 var nDepth = depths[depth].length; 19072 19073 if (depth < eleDepth) { 19074 // only get influenced by elements above 19075 percent += index / nDepth; 19076 samples++; 19077 } 19078 } 19079 19080 samples = Math.max(1, samples); 19081 percent = percent / samples; 19082 19083 if (samples === 0) { 19084 // put lone nodes at the start 19085 percent = 0; 19086 } 19087 19088 cachedWeightedPercent[ele.id()] = percent; 19089 return percent; 19090 }; // rearrange the indices in each depth level based on connectivity 19091 19092 19093 var sortFn = function sortFn(a, b) { 19094 var apct = getWeightedPercent(a); 19095 var bpct = getWeightedPercent(b); 19096 var diff = apct - bpct; 19097 19098 if (diff === 0) { 19099 return ascending(a.id(), b.id()); // make sure sort doesn't have don't-care comparisons 19100 } else { 19101 return diff; 19102 } 19103 }; // sort each level to make connected nodes closer 19104 19105 19106 for (var _i6 = 0; _i6 < depths.length; _i6++) { 19107 depths[_i6].sort(sortFn); 19108 19109 assignDepthsAt(_i6); 19110 } // assign orphan nodes to a new top-level depth 19111 19112 19113 var orphanDepth = []; 19114 19115 for (var _i7 = 0; _i7 < orphanNodes.length; _i7++) { 19116 orphanDepth.push(orphanNodes[_i7]); 19117 } 19118 19119 depths.unshift(orphanDepth); 19120 assignDepths(); 19121 var biggestDepthSize = 0; 19122 19123 for (var _i8 = 0; _i8 < depths.length; _i8++) { 19124 biggestDepthSize = Math.max(depths[_i8].length, biggestDepthSize); 19125 } 19126 19127 var center = { 19128 x: bb.x1 + bb.w / 2, 19129 y: bb.x1 + bb.h / 2 19130 }; 19131 var maxDepthSize = depths.reduce(function (max, eles) { 19132 return Math.max(max, eles.length); 19133 }, 0); 19134 19135 var getPosition = function getPosition(ele) { 19136 var _getInfo2 = getInfo(ele), 19137 depth = _getInfo2.depth, 19138 index = _getInfo2.index; 19139 19140 var depthSize = depths[depth].length; 19141 var distanceX = Math.max(bb.w / ((options.grid ? maxDepthSize : depthSize) + 1), minDistance); 19142 var distanceY = Math.max(bb.h / (depths.length + 1), minDistance); 19143 var radiusStepSize = Math.min(bb.w / 2 / depths.length, bb.h / 2 / depths.length); 19144 radiusStepSize = Math.max(radiusStepSize, minDistance); 19145 19146 if (!options.circle) { 19147 var epos = { 19148 x: center.x + (index + 1 - (depthSize + 1) / 2) * distanceX, 19149 y: (depth + 1) * distanceY 19150 }; 19151 return epos; 19152 } else { 19153 var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize / 2 : 0); 19154 var theta = 2 * Math.PI / depths[depth].length * index; 19155 19156 if (depth === 0 && depths[0].length === 1) { 19157 radius = 1; 19158 } 19159 19160 return { 19161 x: center.x + radius * Math.cos(theta), 19162 y: center.y + radius * Math.sin(theta) 19163 }; 19164 } 19165 }; 19166 19167 nodes.layoutPositions(this, options, getPosition); 19168 return this; // chaining 19169 }; 19170 19171 var defaults$a = { 19172 fit: true, 19173 // whether to fit the viewport to the graph 19174 padding: 30, 19175 // the padding on fit 19176 boundingBox: undefined, 19177 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19178 avoidOverlap: true, 19179 // prevents node overlap, may overflow boundingBox and radius if not enough space 19180 nodeDimensionsIncludeLabels: false, 19181 // Excludes the label when calculating node bounding boxes for the layout algorithm 19182 spacingFactor: undefined, 19183 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 19184 radius: undefined, 19185 // the radius of the circle 19186 startAngle: 3 / 2 * Math.PI, 19187 // where nodes start in radians 19188 sweep: undefined, 19189 // how many radians should be between the first and last node (defaults to full circle) 19190 clockwise: true, 19191 // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 19192 sort: undefined, 19193 // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 19194 animate: false, 19195 // whether to transition the node positions 19196 animationDuration: 500, 19197 // duration of animation in ms if enabled 19198 animationEasing: undefined, 19199 // easing of animation if enabled 19200 animateFilter: function animateFilter(node, i) { 19201 return true; 19202 }, 19203 // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts 19204 ready: undefined, 19205 // callback on layoutready 19206 stop: undefined, 19207 // callback on layoutstop 19208 transform: function transform(node, position) { 19209 return position; 19210 } // transform a given node position. Useful for changing flow direction in discrete layouts 19211 19212 }; 19213 19214 function CircleLayout(options) { 19215 this.options = extend({}, defaults$a, options); 19216 } 19217 19218 CircleLayout.prototype.run = function () { 19219 var params = this.options; 19220 var options = params; 19221 var cy = params.cy; 19222 var eles = options.eles; 19223 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 19224 var nodes = eles.nodes().not(':parent'); 19225 19226 if (options.sort) { 19227 nodes = nodes.sort(options.sort); 19228 } 19229 19230 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 19231 x1: 0, 19232 y1: 0, 19233 w: cy.width(), 19234 h: cy.height() 19235 }); 19236 var center = { 19237 x: bb.x1 + bb.w / 2, 19238 y: bb.y1 + bb.h / 2 19239 }; 19240 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / nodes.length : options.sweep; 19241 var dTheta = sweep / Math.max(1, nodes.length - 1); 19242 var r; 19243 var minDistance = 0; 19244 19245 for (var i = 0; i < nodes.length; i++) { 19246 var n = nodes[i]; 19247 var nbb = n.layoutDimensions(options); 19248 var w = nbb.w; 19249 var h = nbb.h; 19250 minDistance = Math.max(minDistance, w, h); 19251 } 19252 19253 if (number(options.radius)) { 19254 r = options.radius; 19255 } else if (nodes.length <= 1) { 19256 r = 0; 19257 } else { 19258 r = Math.min(bb.h, bb.w) / 2 - minDistance; 19259 } // calculate the radius 19260 19261 19262 if (nodes.length > 1 && options.avoidOverlap) { 19263 // but only if more than one node (can't overlap) 19264 minDistance *= 1.75; // just to have some nice spacing 19265 19266 var dcos = Math.cos(dTheta) - Math.cos(0); 19267 var dsin = Math.sin(dTheta) - Math.sin(0); 19268 var rMin = Math.sqrt(minDistance * minDistance / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping 19269 19270 r = Math.max(rMin, r); 19271 } 19272 19273 var getPos = function getPos(ele, i) { 19274 var theta = options.startAngle + i * dTheta * (clockwise ? 1 : -1); 19275 var rx = r * Math.cos(theta); 19276 var ry = r * Math.sin(theta); 19277 var pos = { 19278 x: center.x + rx, 19279 y: center.y + ry 19280 }; 19281 return pos; 19282 }; 19283 19284 nodes.layoutPositions(this, options, getPos); 19285 return this; // chaining 19286 }; 19287 19288 var defaults$b = { 19289 fit: true, 19290 // whether to fit the viewport to the graph 19291 padding: 30, 19292 // the padding on fit 19293 startAngle: 3 / 2 * Math.PI, 19294 // where nodes start in radians 19295 sweep: undefined, 19296 // how many radians should be between the first and last node (defaults to full circle) 19297 clockwise: true, 19298 // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 19299 equidistant: false, 19300 // whether levels have an equal radial distance betwen them, may cause bounding box overflow 19301 minNodeSpacing: 10, 19302 // min spacing between outside of nodes (used for radius adjustment) 19303 boundingBox: undefined, 19304 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19305 avoidOverlap: true, 19306 // prevents node overlap, may overflow boundingBox if not enough space 19307 nodeDimensionsIncludeLabels: false, 19308 // Excludes the label when calculating node bounding boxes for the layout algorithm 19309 height: undefined, 19310 // height of layout area (overrides container height) 19311 width: undefined, 19312 // width of layout area (overrides container width) 19313 spacingFactor: undefined, 19314 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 19315 concentric: function concentric(node) { 19316 // returns numeric value for each node, placing higher nodes in levels towards the centre 19317 return node.degree(); 19318 }, 19319 levelWidth: function levelWidth(nodes) { 19320 // the letiation of concentric values in each level 19321 return nodes.maxDegree() / 4; 19322 }, 19323 animate: false, 19324 // whether to transition the node positions 19325 animationDuration: 500, 19326 // duration of animation in ms if enabled 19327 animationEasing: undefined, 19328 // easing of animation if enabled 19329 animateFilter: function animateFilter(node, i) { 19330 return true; 19331 }, 19332 // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts 19333 ready: undefined, 19334 // callback on layoutready 19335 stop: undefined, 19336 // callback on layoutstop 19337 transform: function transform(node, position) { 19338 return position; 19339 } // transform a given node position. Useful for changing flow direction in discrete layouts 19340 19341 }; 19342 19343 function ConcentricLayout(options) { 19344 this.options = extend({}, defaults$b, options); 19345 } 19346 19347 ConcentricLayout.prototype.run = function () { 19348 var params = this.options; 19349 var options = params; 19350 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 19351 var cy = params.cy; 19352 var eles = options.eles; 19353 var nodes = eles.nodes().not(':parent'); 19354 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 19355 x1: 0, 19356 y1: 0, 19357 w: cy.width(), 19358 h: cy.height() 19359 }); 19360 var center = { 19361 x: bb.x1 + bb.w / 2, 19362 y: bb.y1 + bb.h / 2 19363 }; 19364 var nodeValues = []; // { node, value } 19365 19366 var maxNodeSize = 0; 19367 19368 for (var i = 0; i < nodes.length; i++) { 19369 var node = nodes[i]; 19370 var value = void 0; // calculate the node value 19371 19372 value = options.concentric(node); 19373 nodeValues.push({ 19374 value: value, 19375 node: node 19376 }); // for style mapping 19377 19378 node._private.scratch.concentric = value; 19379 } // in case we used the `concentric` in style 19380 19381 19382 nodes.updateStyle(); // calculate max size now based on potentially updated mappers 19383 19384 for (var _i = 0; _i < nodes.length; _i++) { 19385 var _node = nodes[_i]; 19386 19387 var nbb = _node.layoutDimensions(options); 19388 19389 maxNodeSize = Math.max(maxNodeSize, nbb.w, nbb.h); 19390 } // sort node values in descreasing order 19391 19392 19393 nodeValues.sort(function (a, b) { 19394 return b.value - a.value; 19395 }); 19396 var levelWidth = options.levelWidth(nodes); // put the values into levels 19397 19398 var levels = [[]]; 19399 var currentLevel = levels[0]; 19400 19401 for (var _i2 = 0; _i2 < nodeValues.length; _i2++) { 19402 var val = nodeValues[_i2]; 19403 19404 if (currentLevel.length > 0) { 19405 var diff = Math.abs(currentLevel[0].value - val.value); 19406 19407 if (diff >= levelWidth) { 19408 currentLevel = []; 19409 levels.push(currentLevel); 19410 } 19411 } 19412 19413 currentLevel.push(val); 19414 } // create positions from levels 19415 19416 19417 var minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes 19418 19419 if (!options.avoidOverlap) { 19420 // then strictly constrain to bb 19421 var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; 19422 var maxR = Math.min(bb.w, bb.h) / 2 - minDist; 19423 var rStep = maxR / (levels.length + firstLvlHasMulti ? 1 : 0); 19424 minDist = Math.min(minDist, rStep); 19425 } // find the metrics for each level 19426 19427 19428 var r = 0; 19429 19430 for (var _i3 = 0; _i3 < levels.length; _i3++) { 19431 var level = levels[_i3]; 19432 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / level.length : options.sweep; 19433 var dTheta = level.dTheta = sweep / Math.max(1, level.length - 1); // calculate the radius 19434 19435 if (level.length > 1 && options.avoidOverlap) { 19436 // but only if more than one node (can't overlap) 19437 var dcos = Math.cos(dTheta) - Math.cos(0); 19438 var dsin = Math.sin(dTheta) - Math.sin(0); 19439 var rMin = Math.sqrt(minDist * minDist / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping 19440 19441 r = Math.max(rMin, r); 19442 } 19443 19444 level.r = r; 19445 r += minDist; 19446 } 19447 19448 if (options.equidistant) { 19449 var rDeltaMax = 0; 19450 var _r = 0; 19451 19452 for (var _i4 = 0; _i4 < levels.length; _i4++) { 19453 var _level = levels[_i4]; 19454 var rDelta = _level.r - _r; 19455 rDeltaMax = Math.max(rDeltaMax, rDelta); 19456 } 19457 19458 _r = 0; 19459 19460 for (var _i5 = 0; _i5 < levels.length; _i5++) { 19461 var _level2 = levels[_i5]; 19462 19463 if (_i5 === 0) { 19464 _r = _level2.r; 19465 } 19466 19467 _level2.r = _r; 19468 _r += rDeltaMax; 19469 } 19470 } // calculate the node positions 19471 19472 19473 var pos = {}; // id => position 19474 19475 for (var _i6 = 0; _i6 < levels.length; _i6++) { 19476 var _level3 = levels[_i6]; 19477 var _dTheta = _level3.dTheta; 19478 var _r2 = _level3.r; 19479 19480 for (var j = 0; j < _level3.length; j++) { 19481 var _val = _level3[j]; 19482 var theta = options.startAngle + (clockwise ? 1 : -1) * _dTheta * j; 19483 var p = { 19484 x: center.x + _r2 * Math.cos(theta), 19485 y: center.y + _r2 * Math.sin(theta) 19486 }; 19487 pos[_val.node.id()] = p; 19488 } 19489 } // position the nodes 19490 19491 19492 nodes.layoutPositions(this, options, function (ele) { 19493 var id = ele.id(); 19494 return pos[id]; 19495 }); 19496 return this; // chaining 19497 }; 19498 19499 /* 19500 The CoSE layout was written by Gerardo Huck. 19501 https://www.linkedin.com/in/gerardohuck/ 19502 19503 Based on the following article: 19504 http://dl.acm.org/citation.cfm?id=1498047 19505 19506 Modifications tracked on Github. 19507 */ 19508 var DEBUG; 19509 /** 19510 * @brief : default layout options 19511 */ 19512 19513 var defaults$c = { 19514 // Called on `layoutready` 19515 ready: function ready() {}, 19516 // Called on `layoutstop` 19517 stop: function stop() {}, 19518 // Whether to animate while running the layout 19519 // true : Animate continuously as the layout is running 19520 // false : Just show the end result 19521 // 'end' : Animate with the end result, from the initial positions to the end positions 19522 animate: true, 19523 // Easing of the animation for animate:'end' 19524 animationEasing: undefined, 19525 // The duration of the animation for animate:'end' 19526 animationDuration: undefined, 19527 // A function that determines whether the node should be animated 19528 // All nodes animated by default on animate enabled 19529 // Non-animated nodes are positioned immediately when the layout starts 19530 animateFilter: function animateFilter(node, i) { 19531 return true; 19532 }, 19533 // The layout animates only after this many milliseconds for animate:true 19534 // (prevents flashing on fast runs) 19535 animationThreshold: 250, 19536 // Number of iterations between consecutive screen positions update 19537 refresh: 20, 19538 // Whether to fit the network view after when done 19539 fit: true, 19540 // Padding on fit 19541 padding: 30, 19542 // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19543 boundingBox: undefined, 19544 // Excludes the label when calculating node bounding boxes for the layout algorithm 19545 nodeDimensionsIncludeLabels: false, 19546 // Randomize the initial positions of the nodes (true) or use existing positions (false) 19547 randomize: false, 19548 // Extra spacing between components in non-compound graphs 19549 componentSpacing: 40, 19550 // Node repulsion (non overlapping) multiplier 19551 nodeRepulsion: function nodeRepulsion(node) { 19552 return 2048; 19553 }, 19554 // Node repulsion (overlapping) multiplier 19555 nodeOverlap: 4, 19556 // Ideal edge (non nested) length 19557 idealEdgeLength: function idealEdgeLength(edge) { 19558 return 32; 19559 }, 19560 // Divisor to compute edge forces 19561 edgeElasticity: function edgeElasticity(edge) { 19562 return 32; 19563 }, 19564 // Nesting factor (multiplier) to compute ideal edge length for nested edges 19565 nestingFactor: 1.2, 19566 // Gravity force (constant) 19567 gravity: 1, 19568 // Maximum number of iterations to perform 19569 numIter: 1000, 19570 // Initial temperature (maximum node displacement) 19571 initialTemp: 1000, 19572 // Cooling factor (how the temperature is reduced between consecutive iterations 19573 coolingFactor: 0.99, 19574 // Lower temperature threshold (below this point the layout will end) 19575 minTemp: 1.0 19576 }; 19577 /** 19578 * @brief : constructor 19579 * @arg options : object containing layout options 19580 */ 19581 19582 function CoseLayout(options) { 19583 this.options = extend({}, defaults$c, options); 19584 this.options.layout = this; 19585 } 19586 /** 19587 * @brief : runs the layout 19588 */ 19589 19590 19591 CoseLayout.prototype.run = function () { 19592 var options = this.options; 19593 var cy = options.cy; 19594 var layout = this; 19595 layout.stopped = false; 19596 19597 if (options.animate === true || options.animate === false) { 19598 layout.emit({ 19599 type: 'layoutstart', 19600 layout: layout 19601 }); 19602 } // Set DEBUG - Global variable 19603 19604 19605 if (true === options.debug) { 19606 DEBUG = true; 19607 } else { 19608 DEBUG = false; 19609 } // Initialize layout info 19610 19611 19612 var layoutInfo = createLayoutInfo(cy, layout, options); // Show LayoutInfo contents if debugging 19613 19614 if (DEBUG) { 19615 printLayoutInfo(layoutInfo); 19616 } // If required, randomize node positions 19617 19618 19619 if (options.randomize) { 19620 randomizePositions(layoutInfo); 19621 } 19622 19623 var startTime = performanceNow(); 19624 19625 var refresh = function refresh() { 19626 refreshPositions(layoutInfo, cy, options); // Fit the graph if necessary 19627 19628 if (true === options.fit) { 19629 cy.fit(options.padding); 19630 } 19631 }; 19632 19633 var mainLoop = function mainLoop(i) { 19634 if (layout.stopped || i >= options.numIter) { 19635 // logDebug("Layout manually stopped. Stopping computation in step " + i); 19636 return false; 19637 } // Do one step in the phisical simulation 19638 19639 19640 step$1(layoutInfo, options); // Update temperature 19641 19642 layoutInfo.temperature = layoutInfo.temperature * options.coolingFactor; // logDebug("New temperature: " + layoutInfo.temperature); 19643 19644 if (layoutInfo.temperature < options.minTemp) { 19645 // logDebug("Temperature drop below minimum threshold. Stopping computation in step " + i); 19646 return false; 19647 } 19648 19649 return true; 19650 }; 19651 19652 var done = function done() { 19653 if (options.animate === true || options.animate === false) { 19654 refresh(); // Layout has finished 19655 19656 layout.one('layoutstop', options.stop); 19657 layout.emit({ 19658 type: 'layoutstop', 19659 layout: layout 19660 }); 19661 } else { 19662 var nodes = options.eles.nodes(); 19663 var getScaledPos = getScaleInBoundsFn(layoutInfo, options, nodes); 19664 nodes.layoutPositions(layout, options, getScaledPos); 19665 } 19666 }; 19667 19668 var i = 0; 19669 var loopRet = true; 19670 19671 if (options.animate === true) { 19672 var frame = function frame() { 19673 var f = 0; 19674 19675 while (loopRet && f < options.refresh) { 19676 loopRet = mainLoop(i); 19677 i++; 19678 f++; 19679 } 19680 19681 if (!loopRet) { 19682 // it's done 19683 separateComponents(layoutInfo, options); 19684 done(); 19685 } else { 19686 var now = performanceNow(); 19687 19688 if (now - startTime >= options.animationThreshold) { 19689 refresh(); 19690 } 19691 19692 requestAnimationFrame(frame); 19693 } 19694 }; 19695 19696 frame(); 19697 } else { 19698 while (loopRet) { 19699 loopRet = mainLoop(i); 19700 i++; 19701 } 19702 19703 separateComponents(layoutInfo, options); 19704 done(); 19705 } 19706 19707 return this; // chaining 19708 }; 19709 /** 19710 * @brief : called on continuous layouts to stop them before they finish 19711 */ 19712 19713 19714 CoseLayout.prototype.stop = function () { 19715 this.stopped = true; 19716 19717 if (this.thread) { 19718 this.thread.stop(); 19719 } 19720 19721 this.emit('layoutstop'); 19722 return this; // chaining 19723 }; 19724 19725 CoseLayout.prototype.destroy = function () { 19726 if (this.thread) { 19727 this.thread.stop(); 19728 } 19729 19730 return this; // chaining 19731 }; 19732 /** 19733 * @brief : Creates an object which is contains all the data 19734 * used in the layout process 19735 * @arg cy : cytoscape.js object 19736 * @return : layoutInfo object initialized 19737 */ 19738 19739 19740 var createLayoutInfo = function createLayoutInfo(cy, layout, options) { 19741 // Shortcut 19742 var edges = options.eles.edges(); 19743 var nodes = options.eles.nodes(); 19744 var layoutInfo = { 19745 isCompound: cy.hasCompoundNodes(), 19746 layoutNodes: [], 19747 idToIndex: {}, 19748 nodeSize: nodes.size(), 19749 graphSet: [], 19750 indexToGraph: [], 19751 layoutEdges: [], 19752 edgeSize: edges.size(), 19753 temperature: options.initialTemp, 19754 clientWidth: cy.width(), 19755 clientHeight: cy.width(), 19756 boundingBox: makeBoundingBox(options.boundingBox ? options.boundingBox : { 19757 x1: 0, 19758 y1: 0, 19759 w: cy.width(), 19760 h: cy.height() 19761 }) 19762 }; 19763 var components = options.eles.components(); 19764 var id2cmptId = {}; 19765 19766 for (var i = 0; i < components.length; i++) { 19767 var component = components[i]; 19768 19769 for (var j = 0; j < component.length; j++) { 19770 var node = component[j]; 19771 id2cmptId[node.id()] = i; 19772 } 19773 } // Iterate over all nodes, creating layout nodes 19774 19775 19776 for (var i = 0; i < layoutInfo.nodeSize; i++) { 19777 var n = nodes[i]; 19778 var nbb = n.layoutDimensions(options); 19779 var tempNode = {}; 19780 tempNode.isLocked = n.locked(); 19781 tempNode.id = n.data('id'); 19782 tempNode.parentId = n.data('parent'); 19783 tempNode.cmptId = id2cmptId[n.id()]; 19784 tempNode.children = []; 19785 tempNode.positionX = n.position('x'); 19786 tempNode.positionY = n.position('y'); 19787 tempNode.offsetX = 0; 19788 tempNode.offsetY = 0; 19789 tempNode.height = nbb.w; 19790 tempNode.width = nbb.h; 19791 tempNode.maxX = tempNode.positionX + tempNode.width / 2; 19792 tempNode.minX = tempNode.positionX - tempNode.width / 2; 19793 tempNode.maxY = tempNode.positionY + tempNode.height / 2; 19794 tempNode.minY = tempNode.positionY - tempNode.height / 2; 19795 tempNode.padLeft = parseFloat(n.style('padding')); 19796 tempNode.padRight = parseFloat(n.style('padding')); 19797 tempNode.padTop = parseFloat(n.style('padding')); 19798 tempNode.padBottom = parseFloat(n.style('padding')); // forces 19799 19800 tempNode.nodeRepulsion = fn(options.nodeRepulsion) ? options.nodeRepulsion(n) : options.nodeRepulsion; // Add new node 19801 19802 layoutInfo.layoutNodes.push(tempNode); // Add entry to id-index map 19803 19804 layoutInfo.idToIndex[tempNode.id] = i; 19805 } // Inline implementation of a queue, used for traversing the graph in BFS order 19806 19807 19808 var queue = []; 19809 var start = 0; // Points to the start the queue 19810 19811 var end = -1; // Points to the end of the queue 19812 19813 var tempGraph = []; // Second pass to add child information and 19814 // initialize queue for hierarchical traversal 19815 19816 for (var i = 0; i < layoutInfo.nodeSize; i++) { 19817 var n = layoutInfo.layoutNodes[i]; 19818 var p_id = n.parentId; // Check if node n has a parent node 19819 19820 if (null != p_id) { 19821 // Add node Id to parent's list of children 19822 layoutInfo.layoutNodes[layoutInfo.idToIndex[p_id]].children.push(n.id); 19823 } else { 19824 // If a node doesn't have a parent, then it's in the root graph 19825 queue[++end] = n.id; 19826 tempGraph.push(n.id); 19827 } 19828 } // Add root graph to graphSet 19829 19830 19831 layoutInfo.graphSet.push(tempGraph); // Traverse the graph, level by level, 19832 19833 while (start <= end) { 19834 // Get the node to visit and remove it from queue 19835 var node_id = queue[start++]; 19836 var node_ix = layoutInfo.idToIndex[node_id]; 19837 var node = layoutInfo.layoutNodes[node_ix]; 19838 var children = node.children; 19839 19840 if (children.length > 0) { 19841 // Add children nodes as a new graph to graph set 19842 layoutInfo.graphSet.push(children); // Add children to que queue to be visited 19843 19844 for (var i = 0; i < children.length; i++) { 19845 queue[++end] = children[i]; 19846 } 19847 } 19848 } // Create indexToGraph map 19849 19850 19851 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 19852 var graph = layoutInfo.graphSet[i]; 19853 19854 for (var j = 0; j < graph.length; j++) { 19855 var index = layoutInfo.idToIndex[graph[j]]; 19856 layoutInfo.indexToGraph[index] = i; 19857 } 19858 } // Iterate over all edges, creating Layout Edges 19859 19860 19861 for (var i = 0; i < layoutInfo.edgeSize; i++) { 19862 var e = edges[i]; 19863 var tempEdge = {}; 19864 tempEdge.id = e.data('id'); 19865 tempEdge.sourceId = e.data('source'); 19866 tempEdge.targetId = e.data('target'); // Compute ideal length 19867 19868 var idealLength = fn(options.idealEdgeLength) ? options.idealEdgeLength(e) : options.idealEdgeLength; 19869 var elasticity = fn(options.edgeElasticity) ? options.edgeElasticity(e) : options.edgeElasticity; // Check if it's an inter graph edge 19870 19871 var sourceIx = layoutInfo.idToIndex[tempEdge.sourceId]; 19872 var targetIx = layoutInfo.idToIndex[tempEdge.targetId]; 19873 var sourceGraph = layoutInfo.indexToGraph[sourceIx]; 19874 var targetGraph = layoutInfo.indexToGraph[targetIx]; 19875 19876 if (sourceGraph != targetGraph) { 19877 // Find lowest common graph ancestor 19878 var lca = findLCA(tempEdge.sourceId, tempEdge.targetId, layoutInfo); // Compute sum of node depths, relative to lca graph 19879 19880 var lcaGraph = layoutInfo.graphSet[lca]; 19881 var depth = 0; // Source depth 19882 19883 var tempNode = layoutInfo.layoutNodes[sourceIx]; 19884 19885 while (-1 === lcaGraph.indexOf(tempNode.id)) { 19886 tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]]; 19887 depth++; 19888 } // Target depth 19889 19890 19891 tempNode = layoutInfo.layoutNodes[targetIx]; 19892 19893 while (-1 === lcaGraph.indexOf(tempNode.id)) { 19894 tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]]; 19895 depth++; 19896 } // logDebug('LCA of nodes ' + tempEdge.sourceId + ' and ' + tempEdge.targetId + 19897 // ". Index: " + lca + " Contents: " + lcaGraph.toString() + 19898 // ". Depth: " + depth); 19899 // Update idealLength 19900 19901 19902 idealLength *= depth * options.nestingFactor; 19903 } 19904 19905 tempEdge.idealLength = idealLength; 19906 tempEdge.elasticity = elasticity; 19907 layoutInfo.layoutEdges.push(tempEdge); 19908 } // Finally, return layoutInfo object 19909 19910 19911 return layoutInfo; 19912 }; 19913 /** 19914 * @brief : This function finds the index of the lowest common 19915 * graph ancestor between 2 nodes in the subtree 19916 * (from the graph hierarchy induced tree) whose 19917 * root is graphIx 19918 * 19919 * @arg node1: node1's ID 19920 * @arg node2: node2's ID 19921 * @arg layoutInfo: layoutInfo object 19922 * 19923 */ 19924 19925 19926 var findLCA = function findLCA(node1, node2, layoutInfo) { 19927 // Find their common ancester, starting from the root graph 19928 var res = findLCA_aux(node1, node2, 0, layoutInfo); 19929 19930 if (2 > res.count) { 19931 // If aux function couldn't find the common ancester, 19932 // then it is the root graph 19933 return 0; 19934 } else { 19935 return res.graph; 19936 } 19937 }; 19938 /** 19939 * @brief : Auxiliary function used for LCA computation 19940 * 19941 * @arg node1 : node1's ID 19942 * @arg node2 : node2's ID 19943 * @arg graphIx : subgraph index 19944 * @arg layoutInfo : layoutInfo object 19945 * 19946 * @return : object of the form {count: X, graph: Y}, where: 19947 * X is the number of ancesters (max: 2) found in 19948 * graphIx (and it's subgraphs), 19949 * Y is the graph index of the lowest graph containing 19950 * all X nodes 19951 */ 19952 19953 19954 var findLCA_aux = function findLCA_aux(node1, node2, graphIx, layoutInfo) { 19955 var graph = layoutInfo.graphSet[graphIx]; // If both nodes belongs to graphIx 19956 19957 if (-1 < graph.indexOf(node1) && -1 < graph.indexOf(node2)) { 19958 return { 19959 count: 2, 19960 graph: graphIx 19961 }; 19962 } // Make recursive calls for all subgraphs 19963 19964 19965 var c = 0; 19966 19967 for (var i = 0; i < graph.length; i++) { 19968 var nodeId = graph[i]; 19969 var nodeIx = layoutInfo.idToIndex[nodeId]; 19970 var children = layoutInfo.layoutNodes[nodeIx].children; // If the node has no child, skip it 19971 19972 if (0 === children.length) { 19973 continue; 19974 } 19975 19976 var childGraphIx = layoutInfo.indexToGraph[layoutInfo.idToIndex[children[0]]]; 19977 var result = findLCA_aux(node1, node2, childGraphIx, layoutInfo); 19978 19979 if (0 === result.count) { 19980 // Neither node1 nor node2 are present in this subgraph 19981 continue; 19982 } else if (1 === result.count) { 19983 // One of (node1, node2) is present in this subgraph 19984 c++; 19985 19986 if (2 === c) { 19987 // We've already found both nodes, no need to keep searching 19988 break; 19989 } 19990 } else { 19991 // Both nodes are present in this subgraph 19992 return result; 19993 } 19994 } 19995 19996 return { 19997 count: c, 19998 graph: graphIx 19999 }; 20000 }; 20001 /** 20002 * @brief: printsLayoutInfo into js console 20003 * Only used for debbuging 20004 */ 20005 20006 20007 if (false) { 20008 var printLayoutInfo; 20009 } 20010 /** 20011 * @brief : Randomizes the position of all nodes 20012 */ 20013 20014 20015 var randomizePositions = function randomizePositions(layoutInfo, cy) { 20016 var width = layoutInfo.clientWidth; 20017 var height = layoutInfo.clientHeight; 20018 20019 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20020 var n = layoutInfo.layoutNodes[i]; // No need to randomize compound nodes or locked nodes 20021 20022 if (0 === n.children.length && !n.isLocked) { 20023 n.positionX = Math.random() * width; 20024 n.positionY = Math.random() * height; 20025 } 20026 } 20027 }; 20028 20029 var getScaleInBoundsFn = function getScaleInBoundsFn(layoutInfo, options, nodes) { 20030 var bb = layoutInfo.boundingBox; 20031 var coseBB = { 20032 x1: Infinity, 20033 x2: -Infinity, 20034 y1: Infinity, 20035 y2: -Infinity 20036 }; 20037 20038 if (options.boundingBox) { 20039 nodes.forEach(function (node) { 20040 var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[node.data('id')]]; 20041 coseBB.x1 = Math.min(coseBB.x1, lnode.positionX); 20042 coseBB.x2 = Math.max(coseBB.x2, lnode.positionX); 20043 coseBB.y1 = Math.min(coseBB.y1, lnode.positionY); 20044 coseBB.y2 = Math.max(coseBB.y2, lnode.positionY); 20045 }); 20046 coseBB.w = coseBB.x2 - coseBB.x1; 20047 coseBB.h = coseBB.y2 - coseBB.y1; 20048 } 20049 20050 return function (ele, i) { 20051 var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[ele.data('id')]]; 20052 20053 if (options.boundingBox) { 20054 // then add extra bounding box constraint 20055 var pctX = (lnode.positionX - coseBB.x1) / coseBB.w; 20056 var pctY = (lnode.positionY - coseBB.y1) / coseBB.h; 20057 return { 20058 x: bb.x1 + pctX * bb.w, 20059 y: bb.y1 + pctY * bb.h 20060 }; 20061 } else { 20062 return { 20063 x: lnode.positionX, 20064 y: lnode.positionY 20065 }; 20066 } 20067 }; 20068 }; 20069 /** 20070 * @brief : Updates the positions of nodes in the network 20071 * @arg layoutInfo : LayoutInfo object 20072 * @arg cy : Cytoscape object 20073 * @arg options : Layout options 20074 */ 20075 20076 20077 var refreshPositions = function refreshPositions(layoutInfo, cy, options) { 20078 // var s = 'Refreshing positions'; 20079 // logDebug(s); 20080 var layout = options.layout; 20081 var nodes = options.eles.nodes(); 20082 var getScaledPos = getScaleInBoundsFn(layoutInfo, options, nodes); 20083 nodes.positions(getScaledPos); // Trigger layoutReady only on first call 20084 20085 if (true !== layoutInfo.ready) { 20086 // s = 'Triggering layoutready'; 20087 // logDebug(s); 20088 layoutInfo.ready = true; 20089 layout.one('layoutready', options.ready); 20090 layout.emit({ 20091 type: 'layoutready', 20092 layout: this 20093 }); 20094 } 20095 }; 20096 /** 20097 * @brief : Logs a debug message in JS console, if DEBUG is ON 20098 */ 20099 // var logDebug = function(text) { 20100 // if (DEBUG) { 20101 // console.debug(text); 20102 // } 20103 // }; 20104 20105 /** 20106 * @brief : Performs one iteration of the physical simulation 20107 * @arg layoutInfo : LayoutInfo object already initialized 20108 * @arg cy : Cytoscape object 20109 * @arg options : Layout options 20110 */ 20111 20112 20113 var step$1 = function step(layoutInfo, options, _step) { 20114 // var s = "\n\n###############################"; 20115 // s += "\nSTEP: " + step; 20116 // s += "\n###############################\n"; 20117 // logDebug(s); 20118 // Calculate node repulsions 20119 calculateNodeForces(layoutInfo, options); // Calculate edge forces 20120 20121 calculateEdgeForces(layoutInfo); // Calculate gravity forces 20122 20123 calculateGravityForces(layoutInfo, options); // Propagate forces from parent to child 20124 20125 propagateForces(layoutInfo); // Update positions based on calculated forces 20126 20127 updatePositions(layoutInfo); 20128 }; 20129 /** 20130 * @brief : Computes the node repulsion forces 20131 */ 20132 20133 20134 var calculateNodeForces = function calculateNodeForces(layoutInfo, options) { 20135 // Go through each of the graphs in graphSet 20136 // Nodes only repel each other if they belong to the same graph 20137 // var s = 'calculateNodeForces'; 20138 // logDebug(s); 20139 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 20140 var graph = layoutInfo.graphSet[i]; 20141 var numNodes = graph.length; // s = "Set: " + graph.toString(); 20142 // logDebug(s); 20143 // Now get all the pairs of nodes 20144 // Only get each pair once, (A, B) = (B, A) 20145 20146 for (var j = 0; j < numNodes; j++) { 20147 var node1 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]]; 20148 20149 for (var k = j + 1; k < numNodes; k++) { 20150 var node2 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[k]]]; 20151 nodeRepulsion(node1, node2, layoutInfo, options); 20152 } 20153 } 20154 } 20155 }; 20156 20157 var randomDistance = function randomDistance(max) { 20158 return -max + 2 * max * Math.random(); 20159 }; 20160 /** 20161 * @brief : Compute the node repulsion forces between a pair of nodes 20162 */ 20163 20164 20165 var nodeRepulsion = function nodeRepulsion(node1, node2, layoutInfo, options) { 20166 // var s = "Node repulsion. Node1: " + node1.id + " Node2: " + node2.id; 20167 var cmptId1 = node1.cmptId; 20168 var cmptId2 = node2.cmptId; 20169 20170 if (cmptId1 !== cmptId2 && !layoutInfo.isCompound) { 20171 return; 20172 } // Get direction of line connecting both node centers 20173 20174 20175 var directionX = node2.positionX - node1.positionX; 20176 var directionY = node2.positionY - node1.positionY; 20177 var maxRandDist = 1; // s += "\ndirectionX: " + directionX + ", directionY: " + directionY; 20178 // If both centers are the same, apply a random force 20179 20180 if (0 === directionX && 0 === directionY) { 20181 directionX = randomDistance(maxRandDist); 20182 directionY = randomDistance(maxRandDist); 20183 } 20184 20185 var overlap = nodesOverlap(node1, node2, directionX, directionY); 20186 20187 if (overlap > 0) { 20188 // s += "\nNodes DO overlap."; 20189 // s += "\nOverlap: " + overlap; 20190 // If nodes overlap, repulsion force is proportional 20191 // to the overlap 20192 var force = options.nodeOverlap * overlap; // Compute the module and components of the force vector 20193 20194 var distance = Math.sqrt(directionX * directionX + directionY * directionY); // s += "\nDistance: " + distance; 20195 20196 var forceX = force * directionX / distance; 20197 var forceY = force * directionY / distance; 20198 } else { 20199 // s += "\nNodes do NOT overlap."; 20200 // If there's no overlap, force is inversely proportional 20201 // to squared distance 20202 // Get clipping points for both nodes 20203 var point1 = findClippingPoint(node1, directionX, directionY); 20204 var point2 = findClippingPoint(node2, -1 * directionX, -1 * directionY); // Use clipping points to compute distance 20205 20206 var distanceX = point2.x - point1.x; 20207 var distanceY = point2.y - point1.y; 20208 var distanceSqr = distanceX * distanceX + distanceY * distanceY; 20209 var distance = Math.sqrt(distanceSqr); // s += "\nDistance: " + distance; 20210 // Compute the module and components of the force vector 20211 20212 var force = (node1.nodeRepulsion + node2.nodeRepulsion) / distanceSqr; 20213 var forceX = force * distanceX / distance; 20214 var forceY = force * distanceY / distance; 20215 } // Apply force 20216 20217 20218 if (!node1.isLocked) { 20219 node1.offsetX -= forceX; 20220 node1.offsetY -= forceY; 20221 } 20222 20223 if (!node2.isLocked) { 20224 node2.offsetX += forceX; 20225 node2.offsetY += forceY; 20226 } // s += "\nForceX: " + forceX + " ForceY: " + forceY; 20227 // logDebug(s); 20228 20229 20230 return; 20231 }; 20232 /** 20233 * @brief : Determines whether two nodes overlap or not 20234 * @return : Amount of overlapping (0 => no overlap) 20235 */ 20236 20237 20238 var nodesOverlap = function nodesOverlap(node1, node2, dX, dY) { 20239 if (dX > 0) { 20240 var overlapX = node1.maxX - node2.minX; 20241 } else { 20242 var overlapX = node2.maxX - node1.minX; 20243 } 20244 20245 if (dY > 0) { 20246 var overlapY = node1.maxY - node2.minY; 20247 } else { 20248 var overlapY = node2.maxY - node1.minY; 20249 } 20250 20251 if (overlapX >= 0 && overlapY >= 0) { 20252 return Math.sqrt(overlapX * overlapX + overlapY * overlapY); 20253 } else { 20254 return 0; 20255 } 20256 }; 20257 /** 20258 * @brief : Finds the point in which an edge (direction dX, dY) intersects 20259 * the rectangular bounding box of it's source/target node 20260 */ 20261 20262 20263 var findClippingPoint = function findClippingPoint(node, dX, dY) { 20264 // Shorcuts 20265 var X = node.positionX; 20266 var Y = node.positionY; 20267 var H = node.height || 1; 20268 var W = node.width || 1; 20269 var dirSlope = dY / dX; 20270 var nodeSlope = H / W; // var s = 'Computing clipping point of node ' + node.id + 20271 // " . Height: " + H + ", Width: " + W + 20272 // "\nDirection " + dX + ", " + dY; 20273 // 20274 // Compute intersection 20275 20276 var res = {}; // Case: Vertical direction (up) 20277 20278 if (0 === dX && 0 < dY) { 20279 res.x = X; // s += "\nUp direction"; 20280 20281 res.y = Y + H / 2; 20282 return res; 20283 } // Case: Vertical direction (down) 20284 20285 20286 if (0 === dX && 0 > dY) { 20287 res.x = X; 20288 res.y = Y + H / 2; // s += "\nDown direction"; 20289 20290 return res; 20291 } // Case: Intersects the right border 20292 20293 20294 if (0 < dX && -1 * nodeSlope <= dirSlope && dirSlope <= nodeSlope) { 20295 res.x = X + W / 2; 20296 res.y = Y + W * dY / 2 / dX; // s += "\nRightborder"; 20297 20298 return res; 20299 } // Case: Intersects the left border 20300 20301 20302 if (0 > dX && -1 * nodeSlope <= dirSlope && dirSlope <= nodeSlope) { 20303 res.x = X - W / 2; 20304 res.y = Y - W * dY / 2 / dX; // s += "\nLeftborder"; 20305 20306 return res; 20307 } // Case: Intersects the top border 20308 20309 20310 if (0 < dY && (dirSlope <= -1 * nodeSlope || dirSlope >= nodeSlope)) { 20311 res.x = X + H * dX / 2 / dY; 20312 res.y = Y + H / 2; // s += "\nTop border"; 20313 20314 return res; 20315 } // Case: Intersects the bottom border 20316 20317 20318 if (0 > dY && (dirSlope <= -1 * nodeSlope || dirSlope >= nodeSlope)) { 20319 res.x = X - H * dX / 2 / dY; 20320 res.y = Y - H / 2; // s += "\nBottom border"; 20321 20322 return res; 20323 } // s += "\nClipping point found at " + res.x + ", " + res.y; 20324 // logDebug(s); 20325 20326 20327 return res; 20328 }; 20329 /** 20330 * @brief : Calculates all edge forces 20331 */ 20332 20333 20334 var calculateEdgeForces = function calculateEdgeForces(layoutInfo, options) { 20335 // Iterate over all edges 20336 for (var i = 0; i < layoutInfo.edgeSize; i++) { 20337 // Get edge, source & target nodes 20338 var edge = layoutInfo.layoutEdges[i]; 20339 var sourceIx = layoutInfo.idToIndex[edge.sourceId]; 20340 var source = layoutInfo.layoutNodes[sourceIx]; 20341 var targetIx = layoutInfo.idToIndex[edge.targetId]; 20342 var target = layoutInfo.layoutNodes[targetIx]; // Get direction of line connecting both node centers 20343 20344 var directionX = target.positionX - source.positionX; 20345 var directionY = target.positionY - source.positionY; // If both centers are the same, do nothing. 20346 // A random force has already been applied as node repulsion 20347 20348 if (0 === directionX && 0 === directionY) { 20349 continue; 20350 } // Get clipping points for both nodes 20351 20352 20353 var point1 = findClippingPoint(source, directionX, directionY); 20354 var point2 = findClippingPoint(target, -1 * directionX, -1 * directionY); 20355 var lx = point2.x - point1.x; 20356 var ly = point2.y - point1.y; 20357 var l = Math.sqrt(lx * lx + ly * ly); 20358 var force = Math.pow(edge.idealLength - l, 2) / edge.elasticity; 20359 20360 if (0 !== l) { 20361 var forceX = force * lx / l; 20362 var forceY = force * ly / l; 20363 } else { 20364 var forceX = 0; 20365 var forceY = 0; 20366 } // Add this force to target and source nodes 20367 20368 20369 if (!source.isLocked) { 20370 source.offsetX += forceX; 20371 source.offsetY += forceY; 20372 } 20373 20374 if (!target.isLocked) { 20375 target.offsetX -= forceX; 20376 target.offsetY -= forceY; 20377 } // var s = 'Edge force between nodes ' + source.id + ' and ' + target.id; 20378 // s += "\nDistance: " + l + " Force: (" + forceX + ", " + forceY + ")"; 20379 // logDebug(s); 20380 20381 } 20382 }; 20383 /** 20384 * @brief : Computes gravity forces for all nodes 20385 */ 20386 20387 20388 var calculateGravityForces = function calculateGravityForces(layoutInfo, options) { 20389 var distThreshold = 1; // var s = 'calculateGravityForces'; 20390 // logDebug(s); 20391 20392 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 20393 var graph = layoutInfo.graphSet[i]; 20394 var numNodes = graph.length; // s = "Set: " + graph.toString(); 20395 // logDebug(s); 20396 // Compute graph center 20397 20398 if (0 === i) { 20399 var centerX = layoutInfo.clientHeight / 2; 20400 var centerY = layoutInfo.clientWidth / 2; 20401 } else { 20402 // Get Parent node for this graph, and use its position as center 20403 var temp = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[0]]]; 20404 var parent = layoutInfo.layoutNodes[layoutInfo.idToIndex[temp.parentId]]; 20405 var centerX = parent.positionX; 20406 var centerY = parent.positionY; 20407 } // s = "Center found at: " + centerX + ", " + centerY; 20408 // logDebug(s); 20409 // Apply force to all nodes in graph 20410 20411 20412 for (var j = 0; j < numNodes; j++) { 20413 var node = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]]; // s = "Node: " + node.id; 20414 20415 if (node.isLocked) { 20416 continue; 20417 } 20418 20419 var dx = centerX - node.positionX; 20420 var dy = centerY - node.positionY; 20421 var d = Math.sqrt(dx * dx + dy * dy); 20422 20423 if (d > distThreshold) { 20424 var fx = options.gravity * dx / d; 20425 var fy = options.gravity * dy / d; 20426 node.offsetX += fx; 20427 node.offsetY += fy; // s += ": Applied force: " + fx + ", " + fy; 20428 } // s += ": skypped since it's too close to center"; 20429 // logDebug(s); 20430 20431 } 20432 } 20433 }; 20434 /** 20435 * @brief : This function propagates the existing offsets from 20436 * parent nodes to its descendents. 20437 * @arg layoutInfo : layoutInfo Object 20438 * @arg cy : cytoscape Object 20439 * @arg options : Layout options 20440 */ 20441 20442 20443 var propagateForces = function propagateForces(layoutInfo, options) { 20444 // Inline implementation of a queue, used for traversing the graph in BFS order 20445 var queue = []; 20446 var start = 0; // Points to the start the queue 20447 20448 var end = -1; // Points to the end of the queue 20449 // logDebug('propagateForces'); 20450 // Start by visiting the nodes in the root graph 20451 20452 queue.push.apply(queue, layoutInfo.graphSet[0]); 20453 end += layoutInfo.graphSet[0].length; // Traverse the graph, level by level, 20454 20455 while (start <= end) { 20456 // Get the node to visit and remove it from queue 20457 var nodeId = queue[start++]; 20458 var nodeIndex = layoutInfo.idToIndex[nodeId]; 20459 var node = layoutInfo.layoutNodes[nodeIndex]; 20460 var children = node.children; // We only need to process the node if it's compound 20461 20462 if (0 < children.length && !node.isLocked) { 20463 var offX = node.offsetX; 20464 var offY = node.offsetY; // var s = "Propagating offset from parent node : " + node.id + 20465 // ". OffsetX: " + offX + ". OffsetY: " + offY; 20466 // s += "\n Children: " + children.toString(); 20467 // logDebug(s); 20468 20469 for (var i = 0; i < children.length; i++) { 20470 var childNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[children[i]]]; // Propagate offset 20471 20472 childNode.offsetX += offX; 20473 childNode.offsetY += offY; // Add children to queue to be visited 20474 20475 queue[++end] = children[i]; 20476 } // Reset parent offsets 20477 20478 20479 node.offsetX = 0; 20480 node.offsetY = 0; 20481 } 20482 } 20483 }; 20484 /** 20485 * @brief : Updates the layout model positions, based on 20486 * the accumulated forces 20487 */ 20488 20489 20490 var updatePositions = function updatePositions(layoutInfo, options) { 20491 // var s = 'Updating positions'; 20492 // logDebug(s); 20493 // Reset boundaries for compound nodes 20494 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20495 var n = layoutInfo.layoutNodes[i]; 20496 20497 if (0 < n.children.length) { 20498 // logDebug("Resetting boundaries of compound node: " + n.id); 20499 n.maxX = undefined; 20500 n.minX = undefined; 20501 n.maxY = undefined; 20502 n.minY = undefined; 20503 } 20504 } 20505 20506 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20507 var n = layoutInfo.layoutNodes[i]; 20508 20509 if (0 < n.children.length || n.isLocked) { 20510 // No need to set compound or locked node position 20511 // logDebug("Skipping position update of node: " + n.id); 20512 continue; 20513 } // s = "Node: " + n.id + " Previous position: (" + 20514 // n.positionX + ", " + n.positionY + ")."; 20515 // Limit displacement in order to improve stability 20516 20517 20518 var tempForce = limitForce(n.offsetX, n.offsetY, layoutInfo.temperature); 20519 n.positionX += tempForce.x; 20520 n.positionY += tempForce.y; 20521 n.offsetX = 0; 20522 n.offsetY = 0; 20523 n.minX = n.positionX - n.width; 20524 n.maxX = n.positionX + n.width; 20525 n.minY = n.positionY - n.height; 20526 n.maxY = n.positionY + n.height; // s += " New Position: (" + n.positionX + ", " + n.positionY + ")."; 20527 // logDebug(s); 20528 // Update ancestry boudaries 20529 20530 updateAncestryBoundaries(n, layoutInfo); 20531 } // Update size, position of compund nodes 20532 20533 20534 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20535 var n = layoutInfo.layoutNodes[i]; 20536 20537 if (0 < n.children.length && !n.isLocked) { 20538 n.positionX = (n.maxX + n.minX) / 2; 20539 n.positionY = (n.maxY + n.minY) / 2; 20540 n.width = n.maxX - n.minX; 20541 n.height = n.maxY - n.minY; // s = "Updating position, size of compound node " + n.id; 20542 // s += "\nPositionX: " + n.positionX + ", PositionY: " + n.positionY; 20543 // s += "\nWidth: " + n.width + ", Height: " + n.height; 20544 // logDebug(s); 20545 } 20546 } 20547 }; 20548 /** 20549 * @brief : Limits a force (forceX, forceY) to be not 20550 * greater (in modulo) than max. 20551 8 Preserves force direction. 20552 */ 20553 20554 20555 var limitForce = function limitForce(forceX, forceY, max) { 20556 // var s = "Limiting force: (" + forceX + ", " + forceY + "). Max: " + max; 20557 var force = Math.sqrt(forceX * forceX + forceY * forceY); 20558 20559 if (force > max) { 20560 var res = { 20561 x: max * forceX / force, 20562 y: max * forceY / force 20563 }; 20564 } else { 20565 var res = { 20566 x: forceX, 20567 y: forceY 20568 }; 20569 } // s += ".\nResult: (" + res.x + ", " + res.y + ")"; 20570 // logDebug(s); 20571 20572 20573 return res; 20574 }; 20575 /** 20576 * @brief : Function used for keeping track of compound node 20577 * sizes, since they should bound all their subnodes. 20578 */ 20579 20580 20581 var updateAncestryBoundaries = function updateAncestryBoundaries(node, layoutInfo) { 20582 // var s = "Propagating new position/size of node " + node.id; 20583 var parentId = node.parentId; 20584 20585 if (null == parentId) { 20586 // If there's no parent, we are done 20587 // s += ". No parent node."; 20588 // logDebug(s); 20589 return; 20590 } // Get Parent Node 20591 20592 20593 var p = layoutInfo.layoutNodes[layoutInfo.idToIndex[parentId]]; 20594 var flag = false; // MaxX 20595 20596 if (null == p.maxX || node.maxX + p.padRight > p.maxX) { 20597 p.maxX = node.maxX + p.padRight; 20598 flag = true; // s += "\nNew maxX for parent node " + p.id + ": " + p.maxX; 20599 } // MinX 20600 20601 20602 if (null == p.minX || node.minX - p.padLeft < p.minX) { 20603 p.minX = node.minX - p.padLeft; 20604 flag = true; // s += "\nNew minX for parent node " + p.id + ": " + p.minX; 20605 } // MaxY 20606 20607 20608 if (null == p.maxY || node.maxY + p.padBottom > p.maxY) { 20609 p.maxY = node.maxY + p.padBottom; 20610 flag = true; // s += "\nNew maxY for parent node " + p.id + ": " + p.maxY; 20611 } // MinY 20612 20613 20614 if (null == p.minY || node.minY - p.padTop < p.minY) { 20615 p.minY = node.minY - p.padTop; 20616 flag = true; // s += "\nNew minY for parent node " + p.id + ": " + p.minY; 20617 } // If updated boundaries, propagate changes upward 20618 20619 20620 if (flag) { 20621 // logDebug(s); 20622 return updateAncestryBoundaries(p, layoutInfo); 20623 } // s += ". No changes in boundaries/position of parent node " + p.id; 20624 // logDebug(s); 20625 20626 20627 return; 20628 }; 20629 20630 var separateComponents = function separateComponents(layoutInfo, options) { 20631 var nodes = layoutInfo.layoutNodes; 20632 var components = []; 20633 20634 for (var i = 0; i < nodes.length; i++) { 20635 var node = nodes[i]; 20636 var cid = node.cmptId; 20637 var component = components[cid] = components[cid] || []; 20638 component.push(node); 20639 } 20640 20641 var totalA = 0; 20642 20643 for (var i = 0; i < components.length; i++) { 20644 var c = components[i]; 20645 20646 if (!c) { 20647 continue; 20648 } 20649 20650 c.x1 = Infinity; 20651 c.x2 = -Infinity; 20652 c.y1 = Infinity; 20653 c.y2 = -Infinity; 20654 20655 for (var j = 0; j < c.length; j++) { 20656 var n = c[j]; 20657 c.x1 = Math.min(c.x1, n.positionX - n.width / 2); 20658 c.x2 = Math.max(c.x2, n.positionX + n.width / 2); 20659 c.y1 = Math.min(c.y1, n.positionY - n.height / 2); 20660 c.y2 = Math.max(c.y2, n.positionY + n.height / 2); 20661 } 20662 20663 c.w = c.x2 - c.x1; 20664 c.h = c.y2 - c.y1; 20665 totalA += c.w * c.h; 20666 } 20667 20668 components.sort(function (c1, c2) { 20669 return c2.w * c2.h - c1.w * c1.h; 20670 }); 20671 var x = 0; 20672 var y = 0; 20673 var usedW = 0; 20674 var rowH = 0; 20675 var maxRowW = Math.sqrt(totalA) * layoutInfo.clientWidth / layoutInfo.clientHeight; 20676 20677 for (var i = 0; i < components.length; i++) { 20678 var c = components[i]; 20679 20680 if (!c) { 20681 continue; 20682 } 20683 20684 for (var j = 0; j < c.length; j++) { 20685 var n = c[j]; 20686 20687 if (!n.isLocked) { 20688 n.positionX += x - c.x1; 20689 n.positionY += y - c.y1; 20690 } 20691 } 20692 20693 x += c.w + options.componentSpacing; 20694 usedW += c.w + options.componentSpacing; 20695 rowH = Math.max(rowH, c.h); 20696 20697 if (usedW > maxRowW) { 20698 y += rowH + options.componentSpacing; 20699 x = 0; 20700 usedW = 0; 20701 rowH = 0; 20702 } 20703 } 20704 }; 20705 20706 var defaults$d = { 20707 fit: true, 20708 // whether to fit the viewport to the graph 20709 padding: 30, 20710 // padding used on fit 20711 boundingBox: undefined, 20712 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 20713 avoidOverlap: true, 20714 // prevents node overlap, may overflow boundingBox if not enough space 20715 avoidOverlapPadding: 10, 20716 // extra spacing around nodes when avoidOverlap: true 20717 nodeDimensionsIncludeLabels: false, 20718 // Excludes the label when calculating node bounding boxes for the layout algorithm 20719 spacingFactor: undefined, 20720 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 20721 condense: false, 20722 // uses all available space on false, uses minimal space on true 20723 rows: undefined, 20724 // force num of rows in the grid 20725 cols: undefined, 20726 // force num of columns in the grid 20727 position: function position(node) {}, 20728 // returns { row, col } for element 20729 sort: undefined, 20730 // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 20731 animate: false, 20732 // whether to transition the node positions 20733 animationDuration: 500, 20734 // duration of animation in ms if enabled 20735 animationEasing: undefined, 20736 // easing of animation if enabled 20737 animateFilter: function animateFilter(node, i) { 20738 return true; 20739 }, 20740 // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts 20741 ready: undefined, 20742 // callback on layoutready 20743 stop: undefined, 20744 // callback on layoutstop 20745 transform: function transform(node, position) { 20746 return position; 20747 } // transform a given node position. Useful for changing flow direction in discrete layouts 20748 20749 }; 20750 20751 function GridLayout(options) { 20752 this.options = extend({}, defaults$d, options); 20753 } 20754 20755 GridLayout.prototype.run = function () { 20756 var params = this.options; 20757 var options = params; 20758 var cy = params.cy; 20759 var eles = options.eles; 20760 var nodes = eles.nodes().not(':parent'); 20761 20762 if (options.sort) { 20763 nodes = nodes.sort(options.sort); 20764 } 20765 20766 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 20767 x1: 0, 20768 y1: 0, 20769 w: cy.width(), 20770 h: cy.height() 20771 }); 20772 20773 if (bb.h === 0 || bb.w === 0) { 20774 nodes.layoutPositions(this, options, function (ele) { 20775 return { 20776 x: bb.x1, 20777 y: bb.y1 20778 }; 20779 }); 20780 } else { 20781 // width/height * splits^2 = cells where splits is number of times to split width 20782 var cells = nodes.size(); 20783 var splits = Math.sqrt(cells * bb.h / bb.w); 20784 var rows = Math.round(splits); 20785 var cols = Math.round(bb.w / bb.h * splits); 20786 20787 var small = function small(val) { 20788 if (val == null) { 20789 return Math.min(rows, cols); 20790 } else { 20791 var min = Math.min(rows, cols); 20792 20793 if (min == rows) { 20794 rows = val; 20795 } else { 20796 cols = val; 20797 } 20798 } 20799 }; 20800 20801 var large = function large(val) { 20802 if (val == null) { 20803 return Math.max(rows, cols); 20804 } else { 20805 var max = Math.max(rows, cols); 20806 20807 if (max == rows) { 20808 rows = val; 20809 } else { 20810 cols = val; 20811 } 20812 } 20813 }; 20814 20815 var oRows = options.rows; 20816 var oCols = options.cols != null ? options.cols : options.columns; // if rows or columns were set in options, use those values 20817 20818 if (oRows != null && oCols != null) { 20819 rows = oRows; 20820 cols = oCols; 20821 } else if (oRows != null && oCols == null) { 20822 rows = oRows; 20823 cols = Math.ceil(cells / rows); 20824 } else if (oRows == null && oCols != null) { 20825 cols = oCols; 20826 rows = Math.ceil(cells / cols); 20827 } // otherwise use the automatic values and adjust accordingly 20828 // if rounding was up, see if we can reduce rows or columns 20829 else if (cols * rows > cells) { 20830 var sm = small(); 20831 var lg = large(); // reducing the small side takes away the most cells, so try it first 20832 20833 if ((sm - 1) * lg >= cells) { 20834 small(sm - 1); 20835 } else if ((lg - 1) * sm >= cells) { 20836 large(lg - 1); 20837 } 20838 } else { 20839 // if rounding was too low, add rows or columns 20840 while (cols * rows < cells) { 20841 var _sm = small(); 20842 20843 var _lg = large(); // try to add to larger side first (adds less in multiplication) 20844 20845 20846 if ((_lg + 1) * _sm >= cells) { 20847 large(_lg + 1); 20848 } else { 20849 small(_sm + 1); 20850 } 20851 } 20852 } 20853 20854 var cellWidth = bb.w / cols; 20855 var cellHeight = bb.h / rows; 20856 20857 if (options.condense) { 20858 cellWidth = 0; 20859 cellHeight = 0; 20860 } 20861 20862 if (options.avoidOverlap) { 20863 for (var i = 0; i < nodes.length; i++) { 20864 var node = nodes[i]; 20865 var pos = node._private.position; 20866 20867 if (pos.x == null || pos.y == null) { 20868 // for bb 20869 pos.x = 0; 20870 pos.y = 0; 20871 } 20872 20873 var nbb = node.layoutDimensions(options); 20874 var p = options.avoidOverlapPadding; 20875 var w = nbb.w + p; 20876 var h = nbb.h + p; 20877 cellWidth = Math.max(cellWidth, w); 20878 cellHeight = Math.max(cellHeight, h); 20879 } 20880 } 20881 20882 var cellUsed = {}; // e.g. 'c-0-2' => true 20883 20884 var used = function used(row, col) { 20885 return cellUsed['c-' + row + '-' + col] ? true : false; 20886 }; 20887 20888 var use = function use(row, col) { 20889 cellUsed['c-' + row + '-' + col] = true; 20890 }; // to keep track of current cell position 20891 20892 20893 var row = 0; 20894 var col = 0; 20895 20896 var moveToNextCell = function moveToNextCell() { 20897 col++; 20898 20899 if (col >= cols) { 20900 col = 0; 20901 row++; 20902 } 20903 }; // get a cache of all the manual positions 20904 20905 20906 var id2manPos = {}; 20907 20908 for (var _i = 0; _i < nodes.length; _i++) { 20909 var _node = nodes[_i]; 20910 var rcPos = options.position(_node); 20911 20912 if (rcPos && (rcPos.row !== undefined || rcPos.col !== undefined)) { 20913 // must have at least row or col def'd 20914 var _pos = { 20915 row: rcPos.row, 20916 col: rcPos.col 20917 }; 20918 20919 if (_pos.col === undefined) { 20920 // find unused col 20921 _pos.col = 0; 20922 20923 while (used(_pos.row, _pos.col)) { 20924 _pos.col++; 20925 } 20926 } else if (_pos.row === undefined) { 20927 // find unused row 20928 _pos.row = 0; 20929 20930 while (used(_pos.row, _pos.col)) { 20931 _pos.row++; 20932 } 20933 } 20934 20935 id2manPos[_node.id()] = _pos; 20936 use(_pos.row, _pos.col); 20937 } 20938 } 20939 20940 var getPos = function getPos(element, i) { 20941 var x, y; 20942 20943 if (element.locked() || element.isParent()) { 20944 return false; 20945 } // see if we have a manual position set 20946 20947 20948 var rcPos = id2manPos[element.id()]; 20949 20950 if (rcPos) { 20951 x = rcPos.col * cellWidth + cellWidth / 2 + bb.x1; 20952 y = rcPos.row * cellHeight + cellHeight / 2 + bb.y1; 20953 } else { 20954 // otherwise set automatically 20955 while (used(row, col)) { 20956 moveToNextCell(); 20957 } 20958 20959 x = col * cellWidth + cellWidth / 2 + bb.x1; 20960 y = row * cellHeight + cellHeight / 2 + bb.y1; 20961 use(row, col); 20962 moveToNextCell(); 20963 } 20964 20965 return { 20966 x: x, 20967 y: y 20968 }; 20969 }; 20970 20971 nodes.layoutPositions(this, options, getPos); 20972 } 20973 20974 return this; // chaining 20975 }; 20976 20977 var defaults$e = { 20978 ready: function ready() {}, 20979 // on layoutready 20980 stop: function stop() {} // on layoutstop 20981 20982 }; // constructor 20983 // options : object containing layout options 20984 20985 function NullLayout(options) { 20986 this.options = extend({}, defaults$e, options); 20987 } // runs the layout 20988 20989 20990 NullLayout.prototype.run = function () { 20991 var options = this.options; 20992 var eles = options.eles; // elements to consider in the layout 20993 20994 var layout = this; // cy is automatically populated for us in the constructor 20995 // (disable eslint for next line as this serves as example layout code to external developers) 20996 // eslint-disable-next-line no-unused-vars 20997 20998 var cy = options.cy; 20999 layout.emit('layoutstart'); // puts all nodes at (0, 0) 21000 // n.b. most layouts would use layoutPositions(), instead of positions() and manual events 21001 21002 eles.nodes().positions(function () { 21003 return { 21004 x: 0, 21005 y: 0 21006 }; 21007 }); // trigger layoutready when each node has had its position set at least once 21008 21009 layout.one('layoutready', options.ready); 21010 layout.emit('layoutready'); // trigger layoutstop when the layout stops (e.g. finishes) 21011 21012 layout.one('layoutstop', options.stop); 21013 layout.emit('layoutstop'); 21014 return this; // chaining 21015 }; // called on continuous layouts to stop them before they finish 21016 21017 21018 NullLayout.prototype.stop = function () { 21019 return this; // chaining 21020 }; 21021 21022 var defaults$f = { 21023 positions: undefined, 21024 // map of (node id) => (position obj); or function(node){ return somPos; } 21025 zoom: undefined, 21026 // the zoom level to set (prob want fit = false if set) 21027 pan: undefined, 21028 // the pan level to set (prob want fit = false if set) 21029 fit: true, 21030 // whether to fit to viewport 21031 padding: 30, 21032 // padding on fit 21033 animate: false, 21034 // whether to transition the node positions 21035 animationDuration: 500, 21036 // duration of animation in ms if enabled 21037 animationEasing: undefined, 21038 // easing of animation if enabled 21039 animateFilter: function animateFilter(node, i) { 21040 return true; 21041 }, 21042 // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts 21043 ready: undefined, 21044 // callback on layoutready 21045 stop: undefined, 21046 // callback on layoutstop 21047 transform: function transform(node, position) { 21048 return position; 21049 } // transform a given node position. Useful for changing flow direction in discrete layouts 21050 21051 }; 21052 21053 function PresetLayout(options) { 21054 this.options = extend({}, defaults$f, options); 21055 } 21056 21057 PresetLayout.prototype.run = function () { 21058 var options = this.options; 21059 var eles = options.eles; 21060 var nodes = eles.nodes(); 21061 var posIsFn = fn(options.positions); 21062 21063 function getPosition(node) { 21064 if (options.positions == null) { 21065 return copyPosition(node.position()); 21066 } 21067 21068 if (posIsFn) { 21069 return options.positions(node); 21070 } 21071 21072 var pos = options.positions[node._private.data.id]; 21073 21074 if (pos == null) { 21075 return null; 21076 } 21077 21078 return pos; 21079 } 21080 21081 nodes.layoutPositions(this, options, function (node, i) { 21082 var position = getPosition(node); 21083 21084 if (node.locked() || position == null) { 21085 return false; 21086 } 21087 21088 return position; 21089 }); 21090 return this; // chaining 21091 }; 21092 21093 var defaults$g = { 21094 fit: true, 21095 // whether to fit to viewport 21096 padding: 30, 21097 // fit padding 21098 boundingBox: undefined, 21099 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 21100 animate: false, 21101 // whether to transition the node positions 21102 animationDuration: 500, 21103 // duration of animation in ms if enabled 21104 animationEasing: undefined, 21105 // easing of animation if enabled 21106 animateFilter: function animateFilter(node, i) { 21107 return true; 21108 }, 21109 // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts 21110 ready: undefined, 21111 // callback on layoutready 21112 stop: undefined, 21113 // callback on layoutstop 21114 transform: function transform(node, position) { 21115 return position; 21116 } // transform a given node position. Useful for changing flow direction in discrete layouts 21117 21118 }; 21119 21120 function RandomLayout(options) { 21121 this.options = extend({}, defaults$g, options); 21122 } 21123 21124 RandomLayout.prototype.run = function () { 21125 var options = this.options; 21126 var cy = options.cy; 21127 var eles = options.eles; 21128 var nodes = eles.nodes().not(':parent'); 21129 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 21130 x1: 0, 21131 y1: 0, 21132 w: cy.width(), 21133 h: cy.height() 21134 }); 21135 21136 var getPos = function getPos(node, i) { 21137 return { 21138 x: bb.x1 + Math.round(Math.random() * bb.w), 21139 y: bb.y1 + Math.round(Math.random() * bb.h) 21140 }; 21141 }; 21142 21143 nodes.layoutPositions(this, options, getPos); 21144 return this; // chaining 21145 }; 21146 21147 var layout = [{ 21148 name: 'breadthfirst', 21149 impl: BreadthFirstLayout 21150 }, { 21151 name: 'circle', 21152 impl: CircleLayout 21153 }, { 21154 name: 'concentric', 21155 impl: ConcentricLayout 21156 }, { 21157 name: 'cose', 21158 impl: CoseLayout 21159 }, { 21160 name: 'grid', 21161 impl: GridLayout 21162 }, { 21163 name: 'null', 21164 impl: NullLayout 21165 }, { 21166 name: 'preset', 21167 impl: PresetLayout 21168 }, { 21169 name: 'random', 21170 impl: RandomLayout 21171 }]; 21172 21173 function NullRenderer(options) { 21174 this.options = options; 21175 this.notifications = 0; // for testing 21176 } 21177 21178 var noop$1 = function noop() {}; 21179 21180 var throwImgErr = function throwImgErr() { 21181 throw new Error('A headless instance can not render images'); 21182 }; 21183 21184 NullRenderer.prototype = { 21185 recalculateRenderedStyle: noop$1, 21186 notify: function notify() { 21187 this.notifications++; 21188 }, 21189 init: noop$1, 21190 isHeadless: function isHeadless() { 21191 return true; 21192 }, 21193 png: throwImgErr, 21194 jpg: throwImgErr 21195 }; 21196 21197 var BRp = {}; 21198 BRp.arrowShapeWidth = 0.3; 21199 21200 BRp.registerArrowShapes = function () { 21201 var arrowShapes = this.arrowShapes = {}; 21202 var renderer = this; // Contract for arrow shapes: 21203 // 0, 0 is arrow tip 21204 // (0, 1) is direction towards node 21205 // (1, 0) is right 21206 // 21207 // functional api: 21208 // collide: check x, y in shape 21209 // roughCollide: called before collide, no false negatives 21210 // draw: draw 21211 // spacing: dist(arrowTip, nodeBoundary) 21212 // gap: dist(edgeTip, nodeBoundary), edgeTip may != arrowTip 21213 21214 var bbCollide = function bbCollide(x, y, size, angle, translation, edgeWidth, padding) { 21215 var x1 = translation.x - size / 2 - padding; 21216 var x2 = translation.x + size / 2 + padding; 21217 var y1 = translation.y - size / 2 - padding; 21218 var y2 = translation.y + size / 2 + padding; 21219 var inside = x1 <= x && x <= x2 && y1 <= y && y <= y2; 21220 return inside; 21221 }; 21222 21223 var transform = function transform(x, y, size, angle, translation) { 21224 var xRotated = x * Math.cos(angle) - y * Math.sin(angle); 21225 var yRotated = x * Math.sin(angle) + y * Math.cos(angle); 21226 var xScaled = xRotated * size; 21227 var yScaled = yRotated * size; 21228 var xTranslated = xScaled + translation.x; 21229 var yTranslated = yScaled + translation.y; 21230 return { 21231 x: xTranslated, 21232 y: yTranslated 21233 }; 21234 }; 21235 21236 var transformPoints = function transformPoints(pts, size, angle, translation) { 21237 var retPts = []; 21238 21239 for (var i = 0; i < pts.length; i += 2) { 21240 var x = pts[i]; 21241 var y = pts[i + 1]; 21242 retPts.push(transform(x, y, size, angle, translation)); 21243 } 21244 21245 return retPts; 21246 }; 21247 21248 var pointsToArr = function pointsToArr(pts) { 21249 var ret = []; 21250 21251 for (var i = 0; i < pts.length; i++) { 21252 var p = pts[i]; 21253 ret.push(p.x, p.y); 21254 } 21255 21256 return ret; 21257 }; 21258 21259 var standardGap = function standardGap(edge) { 21260 return edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').pfValue * 2; 21261 }; 21262 21263 var defineArrowShape = function defineArrowShape(name, defn) { 21264 if (string(defn)) { 21265 defn = arrowShapes[defn]; 21266 } 21267 21268 arrowShapes[name] = extend({ 21269 name: name, 21270 points: [-0.15, -0.3, 0.15, -0.3, 0.15, 0.3, -0.15, 0.3], 21271 collide: function collide(x, y, size, angle, translation, padding) { 21272 var points = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 21273 var inside = pointInsidePolygonPoints(x, y, points); 21274 return inside; 21275 }, 21276 roughCollide: bbCollide, 21277 draw: function draw(context, size, angle, translation) { 21278 var points = transformPoints(this.points, size, angle, translation); 21279 renderer.arrowShapeImpl('polygon')(context, points); 21280 }, 21281 spacing: function spacing(edge) { 21282 return 0; 21283 }, 21284 gap: standardGap 21285 }, defn); 21286 }; 21287 21288 defineArrowShape('none', { 21289 collide: falsify, 21290 roughCollide: falsify, 21291 draw: noop, 21292 spacing: zeroify, 21293 gap: zeroify 21294 }); 21295 defineArrowShape('triangle', { 21296 points: [-0.15, -0.3, 0, 0, 0.15, -0.3] 21297 }); 21298 defineArrowShape('arrow', 'triangle'); 21299 defineArrowShape('triangle-backcurve', { 21300 points: arrowShapes['triangle'].points, 21301 controlPoint: [0, -0.15], 21302 roughCollide: bbCollide, 21303 draw: function draw(context, size, angle, translation, edgeWidth) { 21304 var ptsTrans = transformPoints(this.points, size, angle, translation); 21305 var ctrlPt = this.controlPoint; 21306 var ctrlPtTrans = transform(ctrlPt[0], ctrlPt[1], size, angle, translation); 21307 renderer.arrowShapeImpl(this.name)(context, ptsTrans, ctrlPtTrans); 21308 }, 21309 gap: function gap(edge) { 21310 return standardGap(edge) * 0.8; 21311 } 21312 }); 21313 defineArrowShape('triangle-tee', { 21314 points: [0, 0, 0.15, -0.3, -0.15, -0.3, 0, 0], 21315 pointsTee: [-0.15, -0.4, -0.15, -0.5, 0.15, -0.5, 0.15, -0.4], 21316 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 21317 var triPts = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 21318 var teePts = pointsToArr(transformPoints(this.pointsTee, size + 2 * padding, angle, translation)); 21319 var inside = pointInsidePolygonPoints(x, y, triPts) || pointInsidePolygonPoints(x, y, teePts); 21320 return inside; 21321 }, 21322 draw: function draw(context, size, angle, translation, edgeWidth) { 21323 var triPts = transformPoints(this.points, size, angle, translation); 21324 var teePts = transformPoints(this.pointsTee, size, angle, translation); 21325 renderer.arrowShapeImpl(this.name)(context, triPts, teePts); 21326 } 21327 }); 21328 defineArrowShape('triangle-cross', { 21329 points: [0, 0, 0.15, -0.3, -0.15, -0.3, 0, 0], 21330 baseCrossLinePts: [-0.15, -0.4, // first half of the rectangle 21331 -0.15, -0.4, 0.15, -0.4, // second half of the rectangle 21332 0.15, -0.4], 21333 crossLinePts: function crossLinePts(size, edgeWidth) { 21334 // shift points so that the distance between the cross points matches edge width 21335 var p = this.baseCrossLinePts.slice(); 21336 var shiftFactor = edgeWidth / size; 21337 var y0 = 3; 21338 var y1 = 5; 21339 p[y0] = p[y0] - shiftFactor; 21340 p[y1] = p[y1] - shiftFactor; 21341 return p; 21342 }, 21343 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 21344 var triPts = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 21345 var teePts = pointsToArr(transformPoints(this.crossLinePts(size, edgeWidth), size + 2 * padding, angle, translation)); 21346 var inside = pointInsidePolygonPoints(x, y, triPts) || pointInsidePolygonPoints(x, y, teePts); 21347 return inside; 21348 }, 21349 draw: function draw(context, size, angle, translation, edgeWidth) { 21350 var triPts = transformPoints(this.points, size, angle, translation); 21351 var crossLinePts = transformPoints(this.crossLinePts(size, edgeWidth), size, angle, translation); 21352 renderer.arrowShapeImpl(this.name)(context, triPts, crossLinePts); 21353 } 21354 }); 21355 defineArrowShape('vee', { 21356 points: [-0.15, -0.3, 0, 0, 0.15, -0.3, 0, -0.15], 21357 gap: function gap(edge) { 21358 return standardGap(edge) * 0.525; 21359 } 21360 }); 21361 defineArrowShape('circle', { 21362 radius: 0.15, 21363 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 21364 var t = translation; 21365 var inside = Math.pow(t.x - x, 2) + Math.pow(t.y - y, 2) <= Math.pow((size + 2 * padding) * this.radius, 2); 21366 return inside; 21367 }, 21368 draw: function draw(context, size, angle, translation, edgeWidth) { 21369 renderer.arrowShapeImpl(this.name)(context, translation.x, translation.y, this.radius * size); 21370 }, 21371 spacing: function spacing(edge) { 21372 return renderer.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.radius; 21373 } 21374 }); 21375 defineArrowShape('tee', { 21376 points: [-0.15, 0, -0.15, -0.1, 0.15, -0.1, 0.15, 0], 21377 spacing: function spacing(edge) { 21378 return 1; 21379 }, 21380 gap: function gap(edge) { 21381 return 1; 21382 } 21383 }); 21384 defineArrowShape('square', { 21385 points: [-0.15, 0.00, 0.15, 0.00, 0.15, -0.3, -0.15, -0.3] 21386 }); 21387 defineArrowShape('diamond', { 21388 points: [-0.15, -0.15, 0, -0.3, 0.15, -0.15, 0, 0], 21389 gap: function gap(edge) { 21390 return edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').value; 21391 } 21392 }); 21393 defineArrowShape('chevron', { 21394 points: [0, 0, -0.15, -0.15, -0.1, -0.2, 0, -0.1, 0.1, -0.2, 0.15, -0.15], 21395 gap: function gap(edge) { 21396 return 0.95 * edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').value; 21397 } 21398 }); 21399 }; 21400 21401 var BRp$1 = {}; // Project mouse 21402 21403 BRp$1.projectIntoViewport = function (clientX, clientY) { 21404 var cy = this.cy; 21405 var offsets = this.findContainerClientCoords(); 21406 var offsetLeft = offsets[0]; 21407 var offsetTop = offsets[1]; 21408 var scale = offsets[4]; 21409 var pan = cy.pan(); 21410 var zoom = cy.zoom(); 21411 var x = ((clientX - offsetLeft) / scale - pan.x) / zoom; 21412 var y = ((clientY - offsetTop) / scale - pan.y) / zoom; 21413 return [x, y]; 21414 }; 21415 21416 BRp$1.findContainerClientCoords = function () { 21417 if (this.containerBB) { 21418 return this.containerBB; 21419 } 21420 21421 var container = this.container; 21422 var rect = container.getBoundingClientRect(); 21423 var style = window$1.getComputedStyle(container); 21424 21425 var styleValue = function styleValue(name) { 21426 return parseFloat(style.getPropertyValue(name)); 21427 }; 21428 21429 var padding = { 21430 left: styleValue('padding-left'), 21431 right: styleValue('padding-right'), 21432 top: styleValue('padding-top'), 21433 bottom: styleValue('padding-bottom') 21434 }; 21435 var border = { 21436 left: styleValue('border-left-width'), 21437 right: styleValue('border-right-width'), 21438 top: styleValue('border-top-width'), 21439 bottom: styleValue('border-bottom-width') 21440 }; 21441 var clientWidth = container.clientWidth; 21442 var clientHeight = container.clientHeight; 21443 var paddingHor = padding.left + padding.right; 21444 var paddingVer = padding.top + padding.bottom; 21445 var borderHor = border.left + border.right; 21446 var scale = rect.width / (clientWidth + borderHor); 21447 var unscaledW = clientWidth - paddingHor; 21448 var unscaledH = clientHeight - paddingVer; 21449 var left = rect.left + padding.left + border.left; 21450 var top = rect.top + padding.top + border.top; 21451 return this.containerBB = [left, top, unscaledW, unscaledH, scale]; 21452 }; 21453 21454 BRp$1.invalidateContainerClientCoordsCache = function () { 21455 this.containerBB = null; 21456 }; 21457 21458 BRp$1.findNearestElement = function (x, y, interactiveElementsOnly, isTouch) { 21459 return this.findNearestElements(x, y, interactiveElementsOnly, isTouch)[0]; 21460 }; 21461 21462 BRp$1.findNearestElements = function (x, y, interactiveElementsOnly, isTouch) { 21463 var self = this; 21464 var r = this; 21465 var eles = r.getCachedZSortedEles(); 21466 var near = []; // 1 node max, 1 edge max 21467 21468 var zoom = r.cy.zoom(); 21469 var hasCompounds = r.cy.hasCompoundNodes(); 21470 var edgeThreshold = (isTouch ? 24 : 8) / zoom; 21471 var nodeThreshold = (isTouch ? 8 : 2) / zoom; 21472 var labelThreshold = (isTouch ? 8 : 2) / zoom; 21473 var minSqDist = Infinity; 21474 var nearEdge; 21475 var nearNode; 21476 21477 if (interactiveElementsOnly) { 21478 eles = eles.interactive; 21479 } 21480 21481 function addEle(ele, sqDist) { 21482 if (ele.isNode()) { 21483 if (nearNode) { 21484 return; // can't replace node 21485 } else { 21486 nearNode = ele; 21487 near.push(ele); 21488 } 21489 } 21490 21491 if (ele.isEdge() && (sqDist == null || sqDist < minSqDist)) { 21492 if (nearEdge) { 21493 // then replace existing edge 21494 // can replace only if same z-index 21495 if (nearEdge.pstyle('z-compound-depth').value === ele.pstyle('z-compound-depth').value && nearEdge.pstyle('z-compound-depth').value === ele.pstyle('z-compound-depth').value) { 21496 for (var i = 0; i < near.length; i++) { 21497 if (near[i].isEdge()) { 21498 near[i] = ele; 21499 nearEdge = ele; 21500 minSqDist = sqDist != null ? sqDist : minSqDist; 21501 break; 21502 } 21503 } 21504 } 21505 } else { 21506 near.push(ele); 21507 nearEdge = ele; 21508 minSqDist = sqDist != null ? sqDist : minSqDist; 21509 } 21510 } 21511 } 21512 21513 function checkNode(node) { 21514 var width = node.outerWidth() + 2 * nodeThreshold; 21515 var height = node.outerHeight() + 2 * nodeThreshold; 21516 var hw = width / 2; 21517 var hh = height / 2; 21518 var pos = node.position(); 21519 21520 if (pos.x - hw <= x && x <= pos.x + hw // bb check x 21521 && pos.y - hh <= y && y <= pos.y + hh // bb check y 21522 ) { 21523 var shape = r.nodeShapes[self.getNodeShape(node)]; 21524 21525 if (shape.checkPoint(x, y, 0, width, height, pos.x, pos.y)) { 21526 addEle(node, 0); 21527 return true; 21528 } 21529 } 21530 } 21531 21532 function checkEdge(edge) { 21533 var _p = edge._private; 21534 var rs = _p.rscratch; 21535 var styleWidth = edge.pstyle('width').pfValue; 21536 var scale = edge.pstyle('arrow-scale').value; 21537 var width = styleWidth / 2 + edgeThreshold; // more like a distance radius from centre 21538 21539 var widthSq = width * width; 21540 var width2 = width * 2; 21541 var src = _p.source; 21542 var tgt = _p.target; 21543 var sqDist; 21544 21545 if (rs.edgeType === 'segments' || rs.edgeType === 'straight' || rs.edgeType === 'haystack') { 21546 var pts = rs.allpts; 21547 21548 for (var i = 0; i + 3 < pts.length; i += 2) { 21549 if (inLineVicinity(x, y, pts[i], pts[i + 1], pts[i + 2], pts[i + 3], width2) && widthSq > (sqDist = sqdistToFiniteLine(x, y, pts[i], pts[i + 1], pts[i + 2], pts[i + 3]))) { 21550 addEle(edge, sqDist); 21551 return true; 21552 } 21553 } 21554 } else if (rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound') { 21555 var pts = rs.allpts; 21556 21557 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 21558 if (inBezierVicinity(x, y, pts[i], pts[i + 1], pts[i + 2], pts[i + 3], pts[i + 4], pts[i + 5], width2) && widthSq > (sqDist = sqdistToQuadraticBezier(x, y, pts[i], pts[i + 1], pts[i + 2], pts[i + 3], pts[i + 4], pts[i + 5]))) { 21559 addEle(edge, sqDist); 21560 return true; 21561 } 21562 } 21563 } // if we're close to the edge but didn't hit it, maybe we hit its arrows 21564 21565 21566 var src = src || _p.source; 21567 var tgt = tgt || _p.target; 21568 var arSize = self.getArrowWidth(styleWidth, scale); 21569 var arrows = [{ 21570 name: 'source', 21571 x: rs.arrowStartX, 21572 y: rs.arrowStartY, 21573 angle: rs.srcArrowAngle 21574 }, { 21575 name: 'target', 21576 x: rs.arrowEndX, 21577 y: rs.arrowEndY, 21578 angle: rs.tgtArrowAngle 21579 }, { 21580 name: 'mid-source', 21581 x: rs.midX, 21582 y: rs.midY, 21583 angle: rs.midsrcArrowAngle 21584 }, { 21585 name: 'mid-target', 21586 x: rs.midX, 21587 y: rs.midY, 21588 angle: rs.midtgtArrowAngle 21589 }]; 21590 21591 for (var i = 0; i < arrows.length; i++) { 21592 var ar = arrows[i]; 21593 var shape = r.arrowShapes[edge.pstyle(ar.name + '-arrow-shape').value]; 21594 var edgeWidth = edge.pstyle('width').pfValue; 21595 21596 if (shape.roughCollide(x, y, arSize, ar.angle, { 21597 x: ar.x, 21598 y: ar.y 21599 }, edgeWidth, edgeThreshold) && shape.collide(x, y, arSize, ar.angle, { 21600 x: ar.x, 21601 y: ar.y 21602 }, edgeWidth, edgeThreshold)) { 21603 addEle(edge); 21604 return true; 21605 } 21606 } // for compound graphs, hitting edge may actually want a connected node instead (b/c edge may have greater z-index precedence) 21607 21608 21609 if (hasCompounds && near.length > 0) { 21610 checkNode(src); 21611 checkNode(tgt); 21612 } 21613 } 21614 21615 function preprop(obj, name, pre) { 21616 return getPrefixedProperty(obj, name, pre); 21617 } 21618 21619 function checkLabel(ele, prefix) { 21620 var _p = ele._private; 21621 var th = labelThreshold; 21622 var prefixDash; 21623 21624 if (prefix) { 21625 prefixDash = prefix + '-'; 21626 } else { 21627 prefixDash = ''; 21628 } 21629 21630 ele.boundingBox(); 21631 var bb = _p.labelBounds[prefix || 'main']; 21632 var text = ele.pstyle(prefixDash + 'label').value; 21633 var eventsEnabled = ele.pstyle('text-events').strValue === 'yes'; 21634 21635 if (!eventsEnabled || !text) { 21636 return; 21637 } 21638 21639 var rstyle = _p.rstyle; 21640 var lx = preprop(rstyle, 'labelX', prefix); 21641 var ly = preprop(rstyle, 'labelY', prefix); 21642 var theta = preprop(_p.rscratch, 'labelAngle', prefix); 21643 var lx1 = bb.x1 - th; 21644 var lx2 = bb.x2 + th; 21645 var ly1 = bb.y1 - th; 21646 var ly2 = bb.y2 + th; 21647 21648 if (theta) { 21649 var cos = Math.cos(theta); 21650 var sin = Math.sin(theta); 21651 21652 var rotate = function rotate(x, y) { 21653 x = x - lx; 21654 y = y - ly; 21655 return { 21656 x: x * cos - y * sin + lx, 21657 y: x * sin + y * cos + ly 21658 }; 21659 }; 21660 21661 var px1y1 = rotate(lx1, ly1); 21662 var px1y2 = rotate(lx1, ly2); 21663 var px2y1 = rotate(lx2, ly1); 21664 var px2y2 = rotate(lx2, ly2); 21665 var points = [px1y1.x, px1y1.y, px2y1.x, px2y1.y, px2y2.x, px2y2.y, px1y2.x, px1y2.y]; 21666 21667 if (pointInsidePolygonPoints(x, y, points)) { 21668 addEle(ele); 21669 return true; 21670 } 21671 } else { 21672 // do a cheaper bb check 21673 if (inBoundingBox(bb, x, y)) { 21674 addEle(ele); 21675 return true; 21676 } 21677 } 21678 } 21679 21680 for (var i = eles.length - 1; i >= 0; i--) { 21681 // reverse order for precedence 21682 var ele = eles[i]; 21683 21684 if (ele.isNode()) { 21685 checkNode(ele) || checkLabel(ele); 21686 } else { 21687 // then edge 21688 checkEdge(ele) || checkLabel(ele) || checkLabel(ele, 'source') || checkLabel(ele, 'target'); 21689 } 21690 } 21691 21692 return near; 21693 }; // 'Give me everything from this box' 21694 21695 21696 BRp$1.getAllInBox = function (x1, y1, x2, y2) { 21697 var eles = this.getCachedZSortedEles().interactive; 21698 var box = []; 21699 var x1c = Math.min(x1, x2); 21700 var x2c = Math.max(x1, x2); 21701 var y1c = Math.min(y1, y2); 21702 var y2c = Math.max(y1, y2); 21703 x1 = x1c; 21704 x2 = x2c; 21705 y1 = y1c; 21706 y2 = y2c; 21707 var boxBb = makeBoundingBox({ 21708 x1: x1, 21709 y1: y1, 21710 x2: x2, 21711 y2: y2 21712 }); 21713 21714 for (var e = 0; e < eles.length; e++) { 21715 var ele = eles[e]; 21716 21717 if (ele.isNode()) { 21718 var node = ele; 21719 var nodeBb = node.boundingBox({ 21720 includeNodes: true, 21721 includeEdges: false, 21722 includeLabels: false 21723 }); 21724 21725 if (boundingBoxesIntersect(boxBb, nodeBb) && !boundingBoxInBoundingBox(nodeBb, boxBb)) { 21726 box.push(node); 21727 } 21728 } else { 21729 var edge = ele; 21730 var _p = edge._private; 21731 var rs = _p.rscratch; 21732 21733 if (rs.startX != null && rs.startY != null && !inBoundingBox(boxBb, rs.startX, rs.startY)) { 21734 continue; 21735 } 21736 21737 if (rs.endX != null && rs.endY != null && !inBoundingBox(boxBb, rs.endX, rs.endY)) { 21738 continue; 21739 } 21740 21741 if (rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' || rs.edgeType === 'segments' || rs.edgeType === 'haystack') { 21742 var pts = _p.rstyle.bezierPts || _p.rstyle.linePts || _p.rstyle.haystackPts; 21743 var allInside = true; 21744 21745 for (var i = 0; i < pts.length; i++) { 21746 if (!pointInBoundingBox(boxBb, pts[i])) { 21747 allInside = false; 21748 break; 21749 } 21750 } 21751 21752 if (allInside) { 21753 box.push(edge); 21754 } 21755 } else if (rs.edgeType === 'haystack' || rs.edgeType === 'straight') { 21756 box.push(edge); 21757 } 21758 } 21759 } 21760 21761 return box; 21762 }; 21763 21764 var BRp$2 = {}; 21765 21766 BRp$2.calculateArrowAngles = function (edge) { 21767 var rs = edge._private.rscratch; 21768 var isHaystack = rs.edgeType === 'haystack'; 21769 var isBezier = rs.edgeType === 'bezier'; 21770 var isMultibezier = rs.edgeType === 'multibezier'; 21771 var isSegments = rs.edgeType === 'segments'; 21772 var isCompound = rs.edgeType === 'compound'; 21773 var isSelf = rs.edgeType === 'self'; // Displacement gives direction for arrowhead orientation 21774 21775 var dispX, dispY; 21776 var startX, startY, endX, endY, midX, midY; 21777 21778 if (isHaystack) { 21779 startX = rs.haystackPts[0]; 21780 startY = rs.haystackPts[1]; 21781 endX = rs.haystackPts[2]; 21782 endY = rs.haystackPts[3]; 21783 } else { 21784 startX = rs.arrowStartX; 21785 startY = rs.arrowStartY; 21786 endX = rs.arrowEndX; 21787 endY = rs.arrowEndY; 21788 } 21789 21790 midX = rs.midX; 21791 midY = rs.midY; // source 21792 // 21793 21794 if (isSegments) { 21795 dispX = startX - rs.segpts[0]; 21796 dispY = startY - rs.segpts[1]; 21797 } else if (isMultibezier || isCompound || isSelf || isBezier) { 21798 var pts = rs.allpts; 21799 var bX = qbezierAt(pts[0], pts[2], pts[4], 0.1); 21800 var bY = qbezierAt(pts[1], pts[3], pts[5], 0.1); 21801 dispX = startX - bX; 21802 dispY = startY - bY; 21803 } else { 21804 dispX = startX - midX; 21805 dispY = startY - midY; 21806 } 21807 21808 rs.srcArrowAngle = getAngleFromDisp(dispX, dispY); // mid target 21809 // 21810 21811 var midX = rs.midX; 21812 var midY = rs.midY; 21813 21814 if (isHaystack) { 21815 midX = (startX + endX) / 2; 21816 midY = (startY + endY) / 2; 21817 } 21818 21819 dispX = endX - startX; 21820 dispY = endY - startY; 21821 21822 if (isSegments) { 21823 var pts = rs.allpts; 21824 21825 if (pts.length / 2 % 2 === 0) { 21826 var i2 = pts.length / 2; 21827 var i1 = i2 - 2; 21828 dispX = pts[i2] - pts[i1]; 21829 dispY = pts[i2 + 1] - pts[i1 + 1]; 21830 } else { 21831 var i2 = pts.length / 2 - 1; 21832 var i1 = i2 - 2; 21833 var i3 = i2 + 2; 21834 dispX = pts[i2] - pts[i1]; 21835 dispY = pts[i2 + 1] - pts[i1 + 1]; 21836 } 21837 } else if (isMultibezier || isCompound || isSelf) { 21838 var pts = rs.allpts; 21839 var cpts = rs.ctrlpts; 21840 var bp0x, bp0y; 21841 var bp1x, bp1y; 21842 21843 if (cpts.length / 2 % 2 === 0) { 21844 var p0 = pts.length / 2 - 1; // startpt 21845 21846 var ic = p0 + 2; 21847 var p1 = ic + 2; 21848 bp0x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.0); 21849 bp0y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.0); 21850 bp1x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.0001); 21851 bp1y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.0001); 21852 } else { 21853 var ic = pts.length / 2 - 1; // ctrpt 21854 21855 var p0 = ic - 2; // startpt 21856 21857 var p1 = ic + 2; // endpt 21858 21859 bp0x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.4999); 21860 bp0y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.4999); 21861 bp1x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.5); 21862 bp1y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.5); 21863 } 21864 21865 dispX = bp1x - bp0x; 21866 dispY = bp1y - bp0y; 21867 } 21868 21869 rs.midtgtArrowAngle = getAngleFromDisp(dispX, dispY); 21870 rs.midDispX = dispX; 21871 rs.midDispY = dispY; // mid source 21872 // 21873 21874 dispX *= -1; 21875 dispY *= -1; 21876 21877 if (isSegments) { 21878 var pts = rs.allpts; 21879 21880 if (pts.length / 2 % 2 === 0) ; else { 21881 var i2 = pts.length / 2 - 1; 21882 var i3 = i2 + 2; 21883 dispX = -(pts[i3] - pts[i2]); 21884 dispY = -(pts[i3 + 1] - pts[i2 + 1]); 21885 } 21886 } 21887 21888 rs.midsrcArrowAngle = getAngleFromDisp(dispX, dispY); // target 21889 // 21890 21891 if (isSegments) { 21892 dispX = endX - rs.segpts[rs.segpts.length - 2]; 21893 dispY = endY - rs.segpts[rs.segpts.length - 1]; 21894 } else if (isMultibezier || isCompound || isSelf || isBezier) { 21895 var pts = rs.allpts; 21896 var l = pts.length; 21897 var bX = qbezierAt(pts[l - 6], pts[l - 4], pts[l - 2], 0.9); 21898 var bY = qbezierAt(pts[l - 5], pts[l - 3], pts[l - 1], 0.9); 21899 dispX = endX - bX; 21900 dispY = endY - bY; 21901 } else { 21902 dispX = endX - midX; 21903 dispY = endY - midY; 21904 } 21905 21906 rs.tgtArrowAngle = getAngleFromDisp(dispX, dispY); 21907 }; 21908 21909 BRp$2.getArrowWidth = BRp$2.getArrowHeight = function (edgeWidth, scale) { 21910 var cache = this.arrowWidthCache = this.arrowWidthCache || {}; 21911 var cachedVal = cache[edgeWidth + ', ' + scale]; 21912 21913 if (cachedVal) { 21914 return cachedVal; 21915 } 21916 21917 cachedVal = Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29) * scale; 21918 cache[edgeWidth + ', ' + scale] = cachedVal; 21919 return cachedVal; 21920 }; 21921 21922 var BRp$3 = {}; 21923 21924 BRp$3.findHaystackPoints = function (edges) { 21925 for (var i = 0; i < edges.length; i++) { 21926 var edge = edges[i]; 21927 var _p = edge._private; 21928 var rs = _p.rscratch; 21929 21930 if (!rs.haystack) { 21931 var angle = Math.random() * 2 * Math.PI; 21932 rs.source = { 21933 x: Math.cos(angle), 21934 y: Math.sin(angle) 21935 }; 21936 angle = Math.random() * 2 * Math.PI; 21937 rs.target = { 21938 x: Math.cos(angle), 21939 y: Math.sin(angle) 21940 }; 21941 } 21942 21943 var src = _p.source; 21944 var tgt = _p.target; 21945 var srcPos = src.position(); 21946 var tgtPos = tgt.position(); 21947 var srcW = src.width(); 21948 var tgtW = tgt.width(); 21949 var srcH = src.height(); 21950 var tgtH = tgt.height(); 21951 var radius = edge.pstyle('haystack-radius').value; 21952 var halfRadius = radius / 2; // b/c have to half width/height 21953 21954 rs.haystackPts = rs.allpts = [rs.source.x * srcW * halfRadius + srcPos.x, rs.source.y * srcH * halfRadius + srcPos.y, rs.target.x * tgtW * halfRadius + tgtPos.x, rs.target.y * tgtH * halfRadius + tgtPos.y]; 21955 rs.midX = (rs.allpts[0] + rs.allpts[2]) / 2; 21956 rs.midY = (rs.allpts[1] + rs.allpts[3]) / 2; // always override as haystack in case set to different type previously 21957 21958 rs.edgeType = 'haystack'; 21959 rs.haystack = true; 21960 this.storeEdgeProjections(edge); 21961 this.calculateArrowAngles(edge); 21962 this.recalculateEdgeLabelProjections(edge); 21963 this.calculateLabelAngles(edge); 21964 } 21965 }; 21966 21967 BRp$3.findSegmentsPoints = function (edge, pairInfo) { 21968 // Segments (multiple straight lines) 21969 var rs = edge._private.rscratch; 21970 var posPts = pairInfo.posPts, 21971 intersectionPts = pairInfo.intersectionPts, 21972 vectorNormInverse = pairInfo.vectorNormInverse; 21973 var edgeDistances = edge.pstyle('edge-distances').value; 21974 var segmentWs = edge.pstyle('segment-weights'); 21975 var segmentDs = edge.pstyle('segment-distances'); 21976 var segmentsN = Math.min(segmentWs.pfValue.length, segmentDs.pfValue.length); 21977 rs.edgeType = 'segments'; 21978 rs.segpts = []; 21979 21980 for (var s = 0; s < segmentsN; s++) { 21981 var w = segmentWs.pfValue[s]; 21982 var d = segmentDs.pfValue[s]; 21983 var w1 = 1 - w; 21984 var w2 = w; 21985 var midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts; 21986 var adjustedMidpt = { 21987 x: midptPts.x1 * w1 + midptPts.x2 * w2, 21988 y: midptPts.y1 * w1 + midptPts.y2 * w2 21989 }; 21990 rs.segpts.push(adjustedMidpt.x + vectorNormInverse.x * d, adjustedMidpt.y + vectorNormInverse.y * d); 21991 } 21992 }; 21993 21994 BRp$3.findLoopPoints = function (edge, pairInfo, i, edgeIsUnbundled) { 21995 // Self-edge 21996 var rs = edge._private.rscratch; 21997 var dirCounts = pairInfo.dirCounts, 21998 srcPos = pairInfo.srcPos; 21999 var ctrlptDists = edge.pstyle('control-point-distances'); 22000 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22001 var loopDir = edge.pstyle('loop-direction').pfValue; 22002 var loopSwp = edge.pstyle('loop-sweep').pfValue; 22003 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22004 rs.edgeType = 'self'; 22005 var j = i; 22006 var loopDist = stepSize; 22007 22008 if (edgeIsUnbundled) { 22009 j = 0; 22010 loopDist = ctrlptDist; 22011 } 22012 22013 var loopAngle = loopDir - Math.PI / 2; 22014 var outAngle = loopAngle - loopSwp / 2; 22015 var inAngle = loopAngle + loopSwp / 2; // increase by step size for overlapping loops, keyed on direction and sweep values 22016 22017 var dc = String(loopDir + '_' + loopSwp); 22018 j = dirCounts[dc] === undefined ? dirCounts[dc] = 0 : ++dirCounts[dc]; 22019 rs.ctrlpts = [srcPos.x + Math.cos(outAngle) * 1.4 * loopDist * (j / 3 + 1), srcPos.y + Math.sin(outAngle) * 1.4 * loopDist * (j / 3 + 1), srcPos.x + Math.cos(inAngle) * 1.4 * loopDist * (j / 3 + 1), srcPos.y + Math.sin(inAngle) * 1.4 * loopDist * (j / 3 + 1)]; 22020 }; 22021 22022 BRp$3.findCompoundLoopPoints = function (edge, pairInfo, i, edgeIsUnbundled) { 22023 // Compound edge 22024 var rs = edge._private.rscratch; 22025 rs.edgeType = 'compound'; 22026 var srcPos = pairInfo.srcPos, 22027 tgtPos = pairInfo.tgtPos, 22028 srcW = pairInfo.srcW, 22029 srcH = pairInfo.srcH, 22030 tgtW = pairInfo.tgtW, 22031 tgtH = pairInfo.tgtH; 22032 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22033 var ctrlptDists = edge.pstyle('control-point-distances'); 22034 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22035 var j = i; 22036 var loopDist = stepSize; 22037 22038 if (edgeIsUnbundled) { 22039 j = 0; 22040 loopDist = ctrlptDist; 22041 } 22042 22043 var loopW = 50; 22044 var loopaPos = { 22045 x: srcPos.x - srcW / 2, 22046 y: srcPos.y - srcH / 2 22047 }; 22048 var loopbPos = { 22049 x: tgtPos.x - tgtW / 2, 22050 y: tgtPos.y - tgtH / 2 22051 }; 22052 var loopPos = { 22053 x: Math.min(loopaPos.x, loopbPos.x), 22054 y: Math.min(loopaPos.y, loopbPos.y) 22055 }; // avoids cases with impossible beziers 22056 22057 var minCompoundStretch = 0.5; 22058 var compoundStretchA = Math.max(minCompoundStretch, Math.log(srcW * 0.01)); 22059 var compoundStretchB = Math.max(minCompoundStretch, Math.log(tgtW * 0.01)); 22060 rs.ctrlpts = [loopPos.x, loopPos.y - (1 + Math.pow(loopW, 1.12) / 100) * loopDist * (j / 3 + 1) * compoundStretchA, loopPos.x - (1 + Math.pow(loopW, 1.12) / 100) * loopDist * (j / 3 + 1) * compoundStretchB, loopPos.y]; 22061 }; 22062 22063 BRp$3.findStraightEdgePoints = function (edge) { 22064 // Straight edge within bundle 22065 edge._private.rscratch.edgeType = 'straight'; 22066 }; 22067 22068 BRp$3.findBezierPoints = function (edge, pairInfo, i, edgeIsUnbundled, edgeIsSwapped) { 22069 var rs = edge._private.rscratch; 22070 var vectorNormInverse = pairInfo.vectorNormInverse, 22071 posPts = pairInfo.posPts, 22072 intersectionPts = pairInfo.intersectionPts; 22073 var edgeDistances = edge.pstyle('edge-distances').value; 22074 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22075 var ctrlptDists = edge.pstyle('control-point-distances'); 22076 var ctrlptWs = edge.pstyle('control-point-weights'); 22077 var bezierN = ctrlptDists && ctrlptWs ? Math.min(ctrlptDists.value.length, ctrlptWs.value.length) : 1; 22078 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22079 var ctrlptWeight = ctrlptWs.value[0]; // (Multi)bezier 22080 22081 var multi = edgeIsUnbundled; 22082 rs.edgeType = multi ? 'multibezier' : 'bezier'; 22083 rs.ctrlpts = []; 22084 22085 for (var b = 0; b < bezierN; b++) { 22086 var normctrlptDist = (0.5 - pairInfo.eles.length / 2 + i) * stepSize * (edgeIsSwapped ? -1 : 1); 22087 var manctrlptDist = void 0; 22088 var sign = signum(normctrlptDist); 22089 22090 if (multi) { 22091 ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[b] : stepSize; // fall back on step size 22092 22093 ctrlptWeight = ctrlptWs.value[b]; 22094 } 22095 22096 if (edgeIsUnbundled) { 22097 // multi or single unbundled 22098 manctrlptDist = ctrlptDist; 22099 } else { 22100 manctrlptDist = ctrlptDist !== undefined ? sign * ctrlptDist : undefined; 22101 } 22102 22103 var distanceFromMidpoint = manctrlptDist !== undefined ? manctrlptDist : normctrlptDist; 22104 var w1 = 1 - ctrlptWeight; 22105 var w2 = ctrlptWeight; 22106 var midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts; 22107 var adjustedMidpt = { 22108 x: midptPts.x1 * w1 + midptPts.x2 * w2, 22109 y: midptPts.y1 * w1 + midptPts.y2 * w2 22110 }; 22111 rs.ctrlpts.push(adjustedMidpt.x + vectorNormInverse.x * distanceFromMidpoint, adjustedMidpt.y + vectorNormInverse.y * distanceFromMidpoint); 22112 } 22113 }; 22114 22115 BRp$3.findTaxiPoints = function (edge, pairInfo) { 22116 // Taxicab geometry with two turns maximum 22117 var rs = edge._private.rscratch; 22118 rs.edgeType = 'segments'; 22119 var VERTICAL = 'vertical'; 22120 var HORIZONTAL = 'horizontal'; 22121 var LEFTWARD = 'leftward'; 22122 var RIGHTWARD = 'rightward'; 22123 var DOWNWARD = 'downward'; 22124 var UPWARD = 'upward'; 22125 var AUTO = 'auto'; 22126 var posPts = pairInfo.posPts, 22127 srcW = pairInfo.srcW, 22128 srcH = pairInfo.srcH, 22129 tgtW = pairInfo.tgtW, 22130 tgtH = pairInfo.tgtH; 22131 var edgeDistances = edge.pstyle('edge-distances').value; 22132 var dIncludesNodeBody = edgeDistances !== 'node-position'; 22133 var taxiDir = edge.pstyle('taxi-direction').value; 22134 var rawTaxiDir = taxiDir; // unprocessed value 22135 22136 var taxiTurn = edge.pstyle('taxi-turn'); 22137 var taxiTurnPfVal = taxiTurn.pfValue; 22138 var minD = edge.pstyle('taxi-turn-min-distance').pfValue; 22139 var turnIsPercent = taxiTurn.units === '%'; 22140 var dw = dIncludesNodeBody ? (srcW + tgtW) / 2 : 0; 22141 var dh = dIncludesNodeBody ? (srcH + tgtH) / 2 : 0; 22142 var pdx = posPts.x2 - posPts.x1; 22143 var pdy = posPts.y2 - posPts.y1; // take away the effective w/h from the magnitude of the delta value 22144 22145 var subDWH = function subDWH(dxy, dwh) { 22146 if (dxy > 0) { 22147 return Math.max(dxy - dwh, 0); 22148 } else { 22149 return Math.min(dxy + dwh, 0); 22150 } 22151 }; 22152 22153 var dx = subDWH(pdx, dw); 22154 var dy = subDWH(pdy, dh); 22155 var isExplicitDir = false; 22156 22157 if (taxiDir === AUTO) { 22158 taxiDir = Math.abs(dx) > Math.abs(dy) ? HORIZONTAL : VERTICAL; 22159 } else if (taxiDir === UPWARD || taxiDir === DOWNWARD) { 22160 taxiDir = VERTICAL; 22161 isExplicitDir = true; 22162 } else if (taxiDir === LEFTWARD || taxiDir === RIGHTWARD) { 22163 taxiDir = HORIZONTAL; 22164 isExplicitDir = true; 22165 } 22166 22167 var isVert = taxiDir === VERTICAL; 22168 var l = isVert ? dy : dx; 22169 var pl = isVert ? pdy : pdx; 22170 var sgnL = signum(pl); 22171 var forcedDir = false; 22172 22173 if (!(isExplicitDir && turnIsPercent) // forcing in this case would cause weird growing in the opposite direction 22174 && (rawTaxiDir === DOWNWARD && pl < 0 || rawTaxiDir === UPWARD && pl > 0 || rawTaxiDir === LEFTWARD && pl > 0 || rawTaxiDir === RIGHTWARD && pl < 0)) { 22175 sgnL *= -1; 22176 l = sgnL * Math.abs(l); 22177 forcedDir = true; 22178 } 22179 22180 var d = turnIsPercent ? taxiTurnPfVal * l : taxiTurnPfVal * sgnL; 22181 22182 var getIsTooClose = function getIsTooClose(d) { 22183 return Math.abs(d) < minD || Math.abs(d) >= Math.abs(l); 22184 }; 22185 22186 var isTooCloseSrc = getIsTooClose(d); 22187 var isTooCloseTgt = getIsTooClose(l - d); 22188 var isTooClose = isTooCloseSrc || isTooCloseTgt; 22189 22190 if (isTooClose && !forcedDir) { 22191 // non-ideal routing 22192 if (isVert) { 22193 // vertical fallbacks 22194 var lShapeInsideSrc = Math.abs(pl) <= srcH / 2; 22195 var lShapeInsideTgt = Math.abs(pdx) <= tgtW / 2; 22196 22197 if (lShapeInsideSrc) { 22198 // horizontal Z-shape (direction not respected) 22199 var x = (posPts.x1 + posPts.x2) / 2; 22200 var y1 = posPts.y1, 22201 y2 = posPts.y2; 22202 rs.segpts = [x, y1, x, y2]; 22203 } else if (lShapeInsideTgt) { 22204 // vertical Z-shape (distance not respected) 22205 var y = (posPts.y1 + posPts.y2) / 2; 22206 var x1 = posPts.x1, 22207 x2 = posPts.x2; 22208 rs.segpts = [x1, y, x2, y]; 22209 } else { 22210 // L-shape fallback (turn distance not respected, but works well with tree siblings) 22211 rs.segpts = [posPts.x1, posPts.y2]; 22212 } 22213 } else { 22214 // horizontal fallbacks 22215 var _lShapeInsideSrc = Math.abs(pl) <= srcW / 2; 22216 22217 var _lShapeInsideTgt = Math.abs(pdy) <= tgtH / 2; 22218 22219 if (_lShapeInsideSrc) { 22220 // vertical Z-shape (direction not respected) 22221 var _y = (posPts.y1 + posPts.y2) / 2; 22222 22223 var _x = posPts.x1, 22224 _x2 = posPts.x2; 22225 rs.segpts = [_x, _y, _x2, _y]; 22226 } else if (_lShapeInsideTgt) { 22227 // horizontal Z-shape (turn distance not respected) 22228 var _x3 = (posPts.x1 + posPts.x2) / 2; 22229 22230 var _y2 = posPts.y1, 22231 _y3 = posPts.y2; 22232 rs.segpts = [_x3, _y2, _x3, _y3]; 22233 } else { 22234 // L-shape (turn distance not respected, but works well for tree siblings) 22235 rs.segpts = [posPts.x2, posPts.y1]; 22236 } 22237 } 22238 } else { 22239 // ideal routing 22240 if (isVert) { 22241 var _y4 = posPts.y1 + d + (dIncludesNodeBody ? srcH / 2 * sgnL : 0); 22242 22243 var _x4 = posPts.x1, 22244 _x5 = posPts.x2; 22245 rs.segpts = [_x4, _y4, _x5, _y4]; 22246 } else { 22247 // horizontal 22248 var _x6 = posPts.x1 + d + (dIncludesNodeBody ? srcW / 2 * sgnL : 0); 22249 22250 var _y5 = posPts.y1, 22251 _y6 = posPts.y2; 22252 rs.segpts = [_x6, _y5, _x6, _y6]; 22253 } 22254 } 22255 }; 22256 22257 BRp$3.tryToCorrectInvalidPoints = function (edge, pairInfo) { 22258 var rs = edge._private.rscratch; // can only correct beziers for now... 22259 22260 if (rs.edgeType === 'bezier') { 22261 var srcPos = pairInfo.srcPos, 22262 tgtPos = pairInfo.tgtPos, 22263 srcW = pairInfo.srcW, 22264 srcH = pairInfo.srcH, 22265 tgtW = pairInfo.tgtW, 22266 tgtH = pairInfo.tgtH, 22267 srcShape = pairInfo.srcShape, 22268 tgtShape = pairInfo.tgtShape; 22269 var badStart = !number(rs.startX) || !number(rs.startY); 22270 var badAStart = !number(rs.arrowStartX) || !number(rs.arrowStartY); 22271 var badEnd = !number(rs.endX) || !number(rs.endY); 22272 var badAEnd = !number(rs.arrowEndX) || !number(rs.arrowEndY); 22273 var minCpADistFactor = 3; 22274 var arrowW = this.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.arrowShapeWidth; 22275 var minCpADist = minCpADistFactor * arrowW; 22276 var startACpDist = dist({ 22277 x: rs.ctrlpts[0], 22278 y: rs.ctrlpts[1] 22279 }, { 22280 x: rs.startX, 22281 y: rs.startY 22282 }); 22283 var closeStartACp = startACpDist < minCpADist; 22284 var endACpDist = dist({ 22285 x: rs.ctrlpts[0], 22286 y: rs.ctrlpts[1] 22287 }, { 22288 x: rs.endX, 22289 y: rs.endY 22290 }); 22291 var closeEndACp = endACpDist < minCpADist; 22292 var overlapping = false; 22293 22294 if (badStart || badAStart || closeStartACp) { 22295 overlapping = true; // project control point along line from src centre to outside the src shape 22296 // (otherwise intersection will yield nothing) 22297 22298 var cpD = { 22299 // delta 22300 x: rs.ctrlpts[0] - srcPos.x, 22301 y: rs.ctrlpts[1] - srcPos.y 22302 }; 22303 var cpL = Math.sqrt(cpD.x * cpD.x + cpD.y * cpD.y); // length of line 22304 22305 var cpM = { 22306 // normalised delta 22307 x: cpD.x / cpL, 22308 y: cpD.y / cpL 22309 }; 22310 var radius = Math.max(srcW, srcH); 22311 var cpProj = { 22312 // *2 radius guarantees outside shape 22313 x: rs.ctrlpts[0] + cpM.x * 2 * radius, 22314 y: rs.ctrlpts[1] + cpM.y * 2 * radius 22315 }; 22316 var srcCtrlPtIntn = srcShape.intersectLine(srcPos.x, srcPos.y, srcW, srcH, cpProj.x, cpProj.y, 0); 22317 22318 if (closeStartACp) { 22319 rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - startACpDist); 22320 rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - startACpDist); 22321 } else { 22322 rs.ctrlpts[0] = srcCtrlPtIntn[0] + cpM.x * minCpADist; 22323 rs.ctrlpts[1] = srcCtrlPtIntn[1] + cpM.y * minCpADist; 22324 } 22325 } 22326 22327 if (badEnd || badAEnd || closeEndACp) { 22328 overlapping = true; // project control point along line from tgt centre to outside the tgt shape 22329 // (otherwise intersection will yield nothing) 22330 22331 var _cpD = { 22332 // delta 22333 x: rs.ctrlpts[0] - tgtPos.x, 22334 y: rs.ctrlpts[1] - tgtPos.y 22335 }; 22336 22337 var _cpL = Math.sqrt(_cpD.x * _cpD.x + _cpD.y * _cpD.y); // length of line 22338 22339 22340 var _cpM = { 22341 // normalised delta 22342 x: _cpD.x / _cpL, 22343 y: _cpD.y / _cpL 22344 }; 22345 22346 var _radius = Math.max(srcW, srcH); 22347 22348 var _cpProj = { 22349 // *2 radius guarantees outside shape 22350 x: rs.ctrlpts[0] + _cpM.x * 2 * _radius, 22351 y: rs.ctrlpts[1] + _cpM.y * 2 * _radius 22352 }; 22353 var tgtCtrlPtIntn = tgtShape.intersectLine(tgtPos.x, tgtPos.y, tgtW, tgtH, _cpProj.x, _cpProj.y, 0); 22354 22355 if (closeEndACp) { 22356 rs.ctrlpts[0] = rs.ctrlpts[0] + _cpM.x * (minCpADist - endACpDist); 22357 rs.ctrlpts[1] = rs.ctrlpts[1] + _cpM.y * (minCpADist - endACpDist); 22358 } else { 22359 rs.ctrlpts[0] = tgtCtrlPtIntn[0] + _cpM.x * minCpADist; 22360 rs.ctrlpts[1] = tgtCtrlPtIntn[1] + _cpM.y * minCpADist; 22361 } 22362 } 22363 22364 if (overlapping) { 22365 // recalc endpts 22366 this.findEndpoints(edge); 22367 } 22368 } 22369 }; 22370 22371 BRp$3.storeAllpts = function (edge) { 22372 var rs = edge._private.rscratch; 22373 22374 if (rs.edgeType === 'multibezier' || rs.edgeType === 'bezier' || rs.edgeType === 'self' || rs.edgeType === 'compound') { 22375 rs.allpts = []; 22376 rs.allpts.push(rs.startX, rs.startY); 22377 22378 for (var b = 0; b + 1 < rs.ctrlpts.length; b += 2) { 22379 // ctrl pt itself 22380 rs.allpts.push(rs.ctrlpts[b], rs.ctrlpts[b + 1]); // the midpt between ctrlpts as intermediate destination pts 22381 22382 if (b + 3 < rs.ctrlpts.length) { 22383 rs.allpts.push((rs.ctrlpts[b] + rs.ctrlpts[b + 2]) / 2, (rs.ctrlpts[b + 1] + rs.ctrlpts[b + 3]) / 2); 22384 } 22385 } 22386 22387 rs.allpts.push(rs.endX, rs.endY); 22388 var m, mt; 22389 22390 if (rs.ctrlpts.length / 2 % 2 === 0) { 22391 m = rs.allpts.length / 2 - 1; 22392 rs.midX = rs.allpts[m]; 22393 rs.midY = rs.allpts[m + 1]; 22394 } else { 22395 m = rs.allpts.length / 2 - 3; 22396 mt = 0.5; 22397 rs.midX = qbezierAt(rs.allpts[m], rs.allpts[m + 2], rs.allpts[m + 4], mt); 22398 rs.midY = qbezierAt(rs.allpts[m + 1], rs.allpts[m + 3], rs.allpts[m + 5], mt); 22399 } 22400 } else if (rs.edgeType === 'straight') { 22401 // need to calc these after endpts 22402 rs.allpts = [rs.startX, rs.startY, rs.endX, rs.endY]; // default midpt for labels etc 22403 22404 rs.midX = (rs.startX + rs.endX + rs.arrowStartX + rs.arrowEndX) / 4; 22405 rs.midY = (rs.startY + rs.endY + rs.arrowStartY + rs.arrowEndY) / 4; 22406 } else if (rs.edgeType === 'segments') { 22407 rs.allpts = []; 22408 rs.allpts.push(rs.startX, rs.startY); 22409 rs.allpts.push.apply(rs.allpts, rs.segpts); 22410 rs.allpts.push(rs.endX, rs.endY); 22411 22412 if (rs.segpts.length % 4 === 0) { 22413 var i2 = rs.segpts.length / 2; 22414 var i1 = i2 - 2; 22415 rs.midX = (rs.segpts[i1] + rs.segpts[i2]) / 2; 22416 rs.midY = (rs.segpts[i1 + 1] + rs.segpts[i2 + 1]) / 2; 22417 } else { 22418 var _i = rs.segpts.length / 2 - 1; 22419 22420 rs.midX = rs.segpts[_i]; 22421 rs.midY = rs.segpts[_i + 1]; 22422 } 22423 } 22424 }; 22425 22426 BRp$3.checkForInvalidEdgeWarning = function (edge) { 22427 var rs = edge[0]._private.rscratch; 22428 22429 if (rs.nodesOverlap || number(rs.startX) && number(rs.startY) && number(rs.endX) && number(rs.endY)) { 22430 rs.loggedErr = false; 22431 } else { 22432 if (!rs.loggedErr) { 22433 rs.loggedErr = true; 22434 warn('Edge `' + edge.id() + '` has invalid endpoints and so it is impossible to draw. Adjust your edge style (e.g. control points) accordingly or use an alternative edge type. This is expected behaviour when the source node and the target node overlap.'); 22435 } 22436 } 22437 }; 22438 22439 BRp$3.findEdgeControlPoints = function (edges) { 22440 var _this = this; 22441 22442 if (!edges || edges.length === 0) { 22443 return; 22444 } 22445 22446 var r = this; 22447 var cy = r.cy; 22448 var hasCompounds = cy.hasCompoundNodes(); 22449 var hashTable = { 22450 map: new Map$1(), 22451 get: function get(pairId) { 22452 var map2 = this.map.get(pairId[0]); 22453 22454 if (map2 != null) { 22455 return map2.get(pairId[1]); 22456 } else { 22457 return null; 22458 } 22459 }, 22460 set: function set(pairId, val) { 22461 var map2 = this.map.get(pairId[0]); 22462 22463 if (map2 == null) { 22464 map2 = new Map$1(); 22465 this.map.set(pairId[0], map2); 22466 } 22467 22468 map2.set(pairId[1], val); 22469 } 22470 }; 22471 var pairIds = []; 22472 var haystackEdges = []; // create a table of edge (src, tgt) => list of edges between them 22473 22474 for (var i = 0; i < edges.length; i++) { 22475 var edge = edges[i]; 22476 var _p = edge._private; 22477 var curveStyle = edge.pstyle('curve-style').value; // ignore edges who are not to be displayed 22478 // they shouldn't take up space 22479 22480 if (edge.removed() || !edge.takesUpSpace()) { 22481 continue; 22482 } 22483 22484 if (curveStyle === 'haystack') { 22485 haystackEdges.push(edge); 22486 continue; 22487 } 22488 22489 var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments' || curveStyle === 'straight' || curveStyle === 'taxi'; 22490 var edgeIsBezier = curveStyle === 'unbundled-bezier' || curveStyle === 'bezier'; 22491 var src = _p.source; 22492 var tgt = _p.target; 22493 var srcIndex = src.poolIndex(); 22494 var tgtIndex = tgt.poolIndex(); 22495 var pairId = [srcIndex, tgtIndex].sort(); 22496 var tableEntry = hashTable.get(pairId); 22497 22498 if (tableEntry == null) { 22499 tableEntry = { 22500 eles: [] 22501 }; 22502 hashTable.set(pairId, tableEntry); 22503 pairIds.push(pairId); 22504 } 22505 22506 tableEntry.eles.push(edge); 22507 22508 if (edgeIsUnbundled) { 22509 tableEntry.hasUnbundled = true; 22510 } 22511 22512 if (edgeIsBezier) { 22513 tableEntry.hasBezier = true; 22514 } 22515 } // for each pair (src, tgt), create the ctrl pts 22516 // Nested for loop is OK; total number of iterations for both loops = edgeCount 22517 22518 22519 var _loop = function _loop(p) { 22520 var pairId = pairIds[p]; 22521 var pairInfo = hashTable.get(pairId); 22522 var swappedpairInfo = void 0; 22523 22524 if (!pairInfo.hasUnbundled) { 22525 var pllEdges = pairInfo.eles[0].parallelEdges().filter(function (e) { 22526 return e.isBundledBezier(); 22527 }); 22528 clearArray(pairInfo.eles); 22529 pllEdges.forEach(function (edge) { 22530 return pairInfo.eles.push(edge); 22531 }); // for each pair id, the edges should be sorted by index 22532 22533 pairInfo.eles.sort(function (edge1, edge2) { 22534 return edge1.poolIndex() - edge2.poolIndex(); 22535 }); 22536 } 22537 22538 var firstEdge = pairInfo.eles[0]; 22539 var src = firstEdge.source(); 22540 var tgt = firstEdge.target(); // make sure src/tgt distinction is consistent w.r.t. pairId 22541 22542 if (src.poolIndex() > tgt.poolIndex()) { 22543 var temp = src; 22544 src = tgt; 22545 tgt = temp; 22546 } 22547 22548 var srcPos = pairInfo.srcPos = src.position(); 22549 var tgtPos = pairInfo.tgtPos = tgt.position(); 22550 var srcW = pairInfo.srcW = src.outerWidth(); 22551 var srcH = pairInfo.srcH = src.outerHeight(); 22552 var tgtW = pairInfo.tgtW = tgt.outerWidth(); 22553 var tgtH = pairInfo.tgtH = tgt.outerHeight(); 22554 22555 var srcShape = pairInfo.srcShape = r.nodeShapes[_this.getNodeShape(src)]; 22556 22557 var tgtShape = pairInfo.tgtShape = r.nodeShapes[_this.getNodeShape(tgt)]; 22558 22559 pairInfo.dirCounts = { 22560 'north': 0, 22561 'west': 0, 22562 'south': 0, 22563 'east': 0, 22564 'northwest': 0, 22565 'southwest': 0, 22566 'northeast': 0, 22567 'southeast': 0 22568 }; 22569 22570 for (var _i2 = 0; _i2 < pairInfo.eles.length; _i2++) { 22571 var _edge = pairInfo.eles[_i2]; 22572 var rs = _edge[0]._private.rscratch; 22573 22574 var _curveStyle = _edge.pstyle('curve-style').value; 22575 22576 var _edgeIsUnbundled = _curveStyle === 'unbundled-bezier' || _curveStyle === 'segments' || _curveStyle === 'taxi'; // whether the normalised pair order is the reverse of the edge's src-tgt order 22577 22578 22579 var edgeIsSwapped = !src.same(_edge.source()); 22580 22581 if (!pairInfo.calculatedIntersection && src !== tgt && (pairInfo.hasBezier || pairInfo.hasUnbundled)) { 22582 pairInfo.calculatedIntersection = true; // pt outside src shape to calc distance/displacement from src to tgt 22583 22584 var srcOutside = srcShape.intersectLine(srcPos.x, srcPos.y, srcW, srcH, tgtPos.x, tgtPos.y, 0); 22585 var srcIntn = pairInfo.srcIntn = srcOutside; // pt outside tgt shape to calc distance/displacement from src to tgt 22586 22587 var tgtOutside = tgtShape.intersectLine(tgtPos.x, tgtPos.y, tgtW, tgtH, srcPos.x, srcPos.y, 0); 22588 var tgtIntn = pairInfo.tgtIntn = tgtOutside; 22589 var intersectionPts = pairInfo.intersectionPts = { 22590 x1: srcOutside[0], 22591 x2: tgtOutside[0], 22592 y1: srcOutside[1], 22593 y2: tgtOutside[1] 22594 }; 22595 var posPts = pairInfo.posPts = { 22596 x1: srcPos.x, 22597 x2: tgtPos.x, 22598 y1: srcPos.y, 22599 y2: tgtPos.y 22600 }; 22601 var dy = tgtOutside[1] - srcOutside[1]; 22602 var dx = tgtOutside[0] - srcOutside[0]; 22603 var l = Math.sqrt(dx * dx + dy * dy); 22604 var vector = pairInfo.vector = { 22605 x: dx, 22606 y: dy 22607 }; 22608 var vectorNorm = pairInfo.vectorNorm = { 22609 x: vector.x / l, 22610 y: vector.y / l 22611 }; 22612 var vectorNormInverse = { 22613 x: -vectorNorm.y, 22614 y: vectorNorm.x 22615 }; // if node shapes overlap, then no ctrl pts to draw 22616 22617 pairInfo.nodesOverlap = !number(l) || tgtShape.checkPoint(srcOutside[0], srcOutside[1], 0, tgtW, tgtH, tgtPos.x, tgtPos.y) || srcShape.checkPoint(tgtOutside[0], tgtOutside[1], 0, srcW, srcH, srcPos.x, srcPos.y); 22618 pairInfo.vectorNormInverse = vectorNormInverse; 22619 swappedpairInfo = { 22620 nodesOverlap: pairInfo.nodesOverlap, 22621 dirCounts: pairInfo.dirCounts, 22622 calculatedIntersection: true, 22623 hasBezier: pairInfo.hasBezier, 22624 hasUnbundled: pairInfo.hasUnbundled, 22625 eles: pairInfo.eles, 22626 srcPos: tgtPos, 22627 tgtPos: srcPos, 22628 srcW: tgtW, 22629 srcH: tgtH, 22630 tgtW: srcW, 22631 tgtH: srcH, 22632 srcIntn: tgtIntn, 22633 tgtIntn: srcIntn, 22634 srcShape: tgtShape, 22635 tgtShape: srcShape, 22636 posPts: { 22637 x1: posPts.x2, 22638 y1: posPts.y2, 22639 x2: posPts.x1, 22640 y2: posPts.y1 22641 }, 22642 intersectionPts: { 22643 x1: intersectionPts.x2, 22644 y1: intersectionPts.y2, 22645 x2: intersectionPts.x1, 22646 y2: intersectionPts.y1 22647 }, 22648 vector: { 22649 x: -vector.x, 22650 y: -vector.y 22651 }, 22652 vectorNorm: { 22653 x: -vectorNorm.x, 22654 y: -vectorNorm.y 22655 }, 22656 vectorNormInverse: { 22657 x: -vectorNormInverse.x, 22658 y: -vectorNormInverse.y 22659 } 22660 }; 22661 } 22662 22663 var passedPairInfo = edgeIsSwapped ? swappedpairInfo : pairInfo; 22664 rs.nodesOverlap = passedPairInfo.nodesOverlap; 22665 rs.srcIntn = passedPairInfo.srcIntn; 22666 rs.tgtIntn = passedPairInfo.tgtIntn; 22667 22668 if (hasCompounds && (src.isParent() || src.isChild() || tgt.isParent() || tgt.isChild()) && (src.parents().anySame(tgt) || tgt.parents().anySame(src) || src.same(tgt) && src.isParent())) { 22669 _this.findCompoundLoopPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled); 22670 } else if (src === tgt) { 22671 _this.findLoopPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled); 22672 } else if (_curveStyle === 'segments') { 22673 _this.findSegmentsPoints(_edge, passedPairInfo); 22674 } else if (_curveStyle === 'taxi') { 22675 _this.findTaxiPoints(_edge, passedPairInfo); 22676 } else if (_curveStyle === 'straight' || !_edgeIsUnbundled && pairInfo.eles.length % 2 === 1 && _i2 === Math.floor(pairInfo.eles.length / 2)) { 22677 _this.findStraightEdgePoints(_edge); 22678 } else { 22679 _this.findBezierPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled, edgeIsSwapped); 22680 } 22681 22682 _this.findEndpoints(_edge); 22683 22684 _this.tryToCorrectInvalidPoints(_edge, passedPairInfo); 22685 22686 _this.checkForInvalidEdgeWarning(_edge); 22687 22688 _this.storeAllpts(_edge); 22689 22690 _this.storeEdgeProjections(_edge); 22691 22692 _this.calculateArrowAngles(_edge); 22693 22694 _this.recalculateEdgeLabelProjections(_edge); 22695 22696 _this.calculateLabelAngles(_edge); 22697 } // for pair edges 22698 22699 }; 22700 22701 for (var p = 0; p < pairIds.length; p++) { 22702 _loop(p); 22703 } // for pair ids 22704 // haystacks avoid the expense of pairInfo stuff (intersections etc.) 22705 22706 22707 this.findHaystackPoints(haystackEdges); 22708 }; 22709 22710 function getPts(pts) { 22711 var retPts = []; 22712 22713 if (pts == null) { 22714 return; 22715 } 22716 22717 for (var i = 0; i < pts.length; i += 2) { 22718 var x = pts[i]; 22719 var y = pts[i + 1]; 22720 retPts.push({ 22721 x: x, 22722 y: y 22723 }); 22724 } 22725 22726 return retPts; 22727 } 22728 22729 BRp$3.getSegmentPoints = function (edge) { 22730 var rs = edge[0]._private.rscratch; 22731 var type = rs.edgeType; 22732 22733 if (type === 'segments') { 22734 this.recalculateRenderedStyle(edge); 22735 return getPts(rs.segpts); 22736 } 22737 }; 22738 22739 BRp$3.getControlPoints = function (edge) { 22740 var rs = edge[0]._private.rscratch; 22741 var type = rs.edgeType; 22742 22743 if (type === 'bezier' || type === 'multibezier' || type === 'self' || type === 'compound') { 22744 this.recalculateRenderedStyle(edge); 22745 return getPts(rs.ctrlpts); 22746 } 22747 }; 22748 22749 BRp$3.getEdgeMidpoint = function (edge) { 22750 var rs = edge[0]._private.rscratch; 22751 this.recalculateRenderedStyle(edge); 22752 return { 22753 x: rs.midX, 22754 y: rs.midY 22755 }; 22756 }; 22757 22758 var BRp$4 = {}; 22759 22760 BRp$4.manualEndptToPx = function (node, prop) { 22761 var r = this; 22762 var npos = node.position(); 22763 var w = node.outerWidth(); 22764 var h = node.outerHeight(); 22765 22766 if (prop.value.length === 2) { 22767 var p = [prop.pfValue[0], prop.pfValue[1]]; 22768 22769 if (prop.units[0] === '%') { 22770 p[0] = p[0] * w; 22771 } 22772 22773 if (prop.units[1] === '%') { 22774 p[1] = p[1] * h; 22775 } 22776 22777 p[0] += npos.x; 22778 p[1] += npos.y; 22779 return p; 22780 } else { 22781 var angle = prop.pfValue[0]; 22782 angle = -Math.PI / 2 + angle; // start at 12 o'clock 22783 22784 var l = 2 * Math.max(w, h); 22785 var _p = [npos.x + Math.cos(angle) * l, npos.y + Math.sin(angle) * l]; 22786 return r.nodeShapes[this.getNodeShape(node)].intersectLine(npos.x, npos.y, w, h, _p[0], _p[1], 0); 22787 } 22788 }; 22789 22790 BRp$4.findEndpoints = function (edge) { 22791 var r = this; 22792 var intersect; 22793 var source = edge.source()[0]; 22794 var target = edge.target()[0]; 22795 var srcPos = source.position(); 22796 var tgtPos = target.position(); 22797 var tgtArShape = edge.pstyle('target-arrow-shape').value; 22798 var srcArShape = edge.pstyle('source-arrow-shape').value; 22799 var tgtDist = edge.pstyle('target-distance-from-node').pfValue; 22800 var srcDist = edge.pstyle('source-distance-from-node').pfValue; 22801 var curveStyle = edge.pstyle('curve-style').value; 22802 var rs = edge._private.rscratch; 22803 var et = rs.edgeType; 22804 var taxi = curveStyle === 'taxi'; 22805 var self = et === 'self' || et === 'compound'; 22806 var bezier = et === 'bezier' || et === 'multibezier' || self; 22807 var multi = et !== 'bezier'; 22808 var lines = et === 'straight' || et === 'segments'; 22809 var segments = et === 'segments'; 22810 var hasEndpts = bezier || multi || lines; 22811 var overrideEndpts = self || taxi; 22812 var srcManEndpt = edge.pstyle('source-endpoint'); 22813 var srcManEndptVal = overrideEndpts ? 'outside-to-node' : srcManEndpt.value; 22814 var tgtManEndpt = edge.pstyle('target-endpoint'); 22815 var tgtManEndptVal = overrideEndpts ? 'outside-to-node' : tgtManEndpt.value; 22816 rs.srcManEndpt = srcManEndpt; 22817 rs.tgtManEndpt = tgtManEndpt; 22818 var p1; // last known point of edge on target side 22819 22820 var p2; // last known point of edge on source side 22821 22822 var p1_i; // point to intersect with target shape 22823 22824 var p2_i; // point to intersect with source shape 22825 22826 if (bezier) { 22827 var cpStart = [rs.ctrlpts[0], rs.ctrlpts[1]]; 22828 var cpEnd = multi ? [rs.ctrlpts[rs.ctrlpts.length - 2], rs.ctrlpts[rs.ctrlpts.length - 1]] : cpStart; 22829 p1 = cpEnd; 22830 p2 = cpStart; 22831 } else if (lines) { 22832 var srcArrowFromPt = !segments ? [tgtPos.x, tgtPos.y] : rs.segpts.slice(0, 2); 22833 var tgtArrowFromPt = !segments ? [srcPos.x, srcPos.y] : rs.segpts.slice(rs.segpts.length - 2); 22834 p1 = tgtArrowFromPt; 22835 p2 = srcArrowFromPt; 22836 } 22837 22838 if (tgtManEndptVal === 'inside-to-node') { 22839 intersect = [tgtPos.x, tgtPos.y]; 22840 } else if (tgtManEndpt.units) { 22841 intersect = this.manualEndptToPx(target, tgtManEndpt); 22842 } else if (tgtManEndptVal === 'outside-to-line') { 22843 intersect = rs.tgtIntn; // use cached value from ctrlpt calc 22844 } else { 22845 if (tgtManEndptVal === 'outside-to-node' || tgtManEndptVal === 'outside-to-node-or-label') { 22846 p1_i = p1; 22847 } else if (tgtManEndptVal === 'outside-to-line' || tgtManEndptVal === 'outside-to-line-or-label') { 22848 p1_i = [srcPos.x, srcPos.y]; 22849 } 22850 22851 intersect = r.nodeShapes[this.getNodeShape(target)].intersectLine(tgtPos.x, tgtPos.y, target.outerWidth(), target.outerHeight(), p1_i[0], p1_i[1], 0); 22852 22853 if (tgtManEndptVal === 'outside-to-node-or-label' || tgtManEndptVal === 'outside-to-line-or-label') { 22854 var trs = target._private.rscratch; 22855 var lw = trs.labelWidth; 22856 var lh = trs.labelHeight; 22857 var lx = trs.labelX; 22858 var ly = trs.labelY; 22859 var lw2 = lw / 2; 22860 var lh2 = lh / 2; 22861 var va = target.pstyle('text-valign').value; 22862 22863 if (va === 'top') { 22864 ly -= lh2; 22865 } else if (va === 'bottom') { 22866 ly += lh2; 22867 } 22868 22869 var ha = target.pstyle('text-halign').value; 22870 22871 if (ha === 'left') { 22872 lx -= lw2; 22873 } else if (ha === 'right') { 22874 lx += lw2; 22875 } 22876 22877 var labelIntersect = polygonIntersectLine(p1_i[0], p1_i[1], [lx - lw2, ly - lh2, lx + lw2, ly - lh2, lx + lw2, ly + lh2, lx - lw2, ly + lh2], tgtPos.x, tgtPos.y); 22878 22879 if (labelIntersect.length > 0) { 22880 var refPt = srcPos; 22881 var intSqdist = sqdist(refPt, array2point(intersect)); 22882 var labIntSqdist = sqdist(refPt, array2point(labelIntersect)); 22883 var minSqDist = intSqdist; 22884 22885 if (labIntSqdist < intSqdist) { 22886 intersect = labelIntersect; 22887 minSqDist = labIntSqdist; 22888 } 22889 22890 if (labelIntersect.length > 2) { 22891 var labInt2SqDist = sqdist(refPt, { 22892 x: labelIntersect[2], 22893 y: labelIntersect[3] 22894 }); 22895 22896 if (labInt2SqDist < minSqDist) { 22897 intersect = [labelIntersect[2], labelIntersect[3]]; 22898 } 22899 } 22900 } 22901 } 22902 } 22903 22904 var arrowEnd = shortenIntersection(intersect, p1, r.arrowShapes[tgtArShape].spacing(edge) + tgtDist); 22905 var edgeEnd = shortenIntersection(intersect, p1, r.arrowShapes[tgtArShape].gap(edge) + tgtDist); 22906 rs.endX = edgeEnd[0]; 22907 rs.endY = edgeEnd[1]; 22908 rs.arrowEndX = arrowEnd[0]; 22909 rs.arrowEndY = arrowEnd[1]; 22910 22911 if (srcManEndptVal === 'inside-to-node') { 22912 intersect = [srcPos.x, srcPos.y]; 22913 } else if (srcManEndpt.units) { 22914 intersect = this.manualEndptToPx(source, srcManEndpt); 22915 } else if (srcManEndptVal === 'outside-to-line') { 22916 intersect = rs.srcIntn; // use cached value from ctrlpt calc 22917 } else { 22918 if (srcManEndptVal === 'outside-to-node' || srcManEndptVal === 'outside-to-node-or-label') { 22919 p2_i = p2; 22920 } else if (srcManEndptVal === 'outside-to-line' || srcManEndptVal === 'outside-to-line-or-label') { 22921 p2_i = [tgtPos.x, tgtPos.y]; 22922 } 22923 22924 intersect = r.nodeShapes[this.getNodeShape(source)].intersectLine(srcPos.x, srcPos.y, source.outerWidth(), source.outerHeight(), p2_i[0], p2_i[1], 0); 22925 22926 if (srcManEndptVal === 'outside-to-node-or-label' || srcManEndptVal === 'outside-to-line-or-label') { 22927 var srs = source._private.rscratch; 22928 var _lw = srs.labelWidth; 22929 var _lh = srs.labelHeight; 22930 var _lx = srs.labelX; 22931 var _ly = srs.labelY; 22932 22933 var _lw2 = _lw / 2; 22934 22935 var _lh2 = _lh / 2; 22936 22937 var _va = source.pstyle('text-valign').value; 22938 22939 if (_va === 'top') { 22940 _ly -= _lh2; 22941 } else if (_va === 'bottom') { 22942 _ly += _lh2; 22943 } 22944 22945 var _ha = source.pstyle('text-halign').value; 22946 22947 if (_ha === 'left') { 22948 _lx -= _lw2; 22949 } else if (_ha === 'right') { 22950 _lx += _lw2; 22951 } 22952 22953 var _labelIntersect = polygonIntersectLine(p2_i[0], p2_i[1], [_lx - _lw2, _ly - _lh2, _lx + _lw2, _ly - _lh2, _lx + _lw2, _ly + _lh2, _lx - _lw2, _ly + _lh2], srcPos.x, srcPos.y); 22954 22955 if (_labelIntersect.length > 0) { 22956 var _refPt = tgtPos; 22957 22958 var _intSqdist = sqdist(_refPt, array2point(intersect)); 22959 22960 var _labIntSqdist = sqdist(_refPt, array2point(_labelIntersect)); 22961 22962 var _minSqDist = _intSqdist; 22963 22964 if (_labIntSqdist < _intSqdist) { 22965 intersect = [_labelIntersect[0], _labelIntersect[1]]; 22966 _minSqDist = _labIntSqdist; 22967 } 22968 22969 if (_labelIntersect.length > 2) { 22970 var _labInt2SqDist = sqdist(_refPt, { 22971 x: _labelIntersect[2], 22972 y: _labelIntersect[3] 22973 }); 22974 22975 if (_labInt2SqDist < _minSqDist) { 22976 intersect = [_labelIntersect[2], _labelIntersect[3]]; 22977 } 22978 } 22979 } 22980 } 22981 } 22982 22983 var arrowStart = shortenIntersection(intersect, p2, r.arrowShapes[srcArShape].spacing(edge) + srcDist); 22984 var edgeStart = shortenIntersection(intersect, p2, r.arrowShapes[srcArShape].gap(edge) + srcDist); 22985 rs.startX = edgeStart[0]; 22986 rs.startY = edgeStart[1]; 22987 rs.arrowStartX = arrowStart[0]; 22988 rs.arrowStartY = arrowStart[1]; 22989 22990 if (hasEndpts) { 22991 if (!number(rs.startX) || !number(rs.startY) || !number(rs.endX) || !number(rs.endY)) { 22992 rs.badLine = true; 22993 } else { 22994 rs.badLine = false; 22995 } 22996 } 22997 }; 22998 22999 BRp$4.getSourceEndpoint = function (edge) { 23000 var rs = edge[0]._private.rscratch; 23001 this.recalculateRenderedStyle(edge); 23002 23003 switch (rs.edgeType) { 23004 case 'haystack': 23005 return { 23006 x: rs.haystackPts[0], 23007 y: rs.haystackPts[1] 23008 }; 23009 23010 default: 23011 return { 23012 x: rs.arrowStartX, 23013 y: rs.arrowStartY 23014 }; 23015 } 23016 }; 23017 23018 BRp$4.getTargetEndpoint = function (edge) { 23019 var rs = edge[0]._private.rscratch; 23020 this.recalculateRenderedStyle(edge); 23021 23022 switch (rs.edgeType) { 23023 case 'haystack': 23024 return { 23025 x: rs.haystackPts[2], 23026 y: rs.haystackPts[3] 23027 }; 23028 23029 default: 23030 return { 23031 x: rs.arrowEndX, 23032 y: rs.arrowEndY 23033 }; 23034 } 23035 }; 23036 23037 var BRp$5 = {}; 23038 23039 function pushBezierPts(r, edge, pts) { 23040 var qbezierAt$1 = function qbezierAt$1(p1, p2, p3, t) { 23041 return qbezierAt(p1, p2, p3, t); 23042 }; 23043 23044 var _p = edge._private; 23045 var bpts = _p.rstyle.bezierPts; 23046 23047 for (var i = 0; i < r.bezierProjPcts.length; i++) { 23048 var p = r.bezierProjPcts[i]; 23049 bpts.push({ 23050 x: qbezierAt$1(pts[0], pts[2], pts[4], p), 23051 y: qbezierAt$1(pts[1], pts[3], pts[5], p) 23052 }); 23053 } 23054 } 23055 23056 BRp$5.storeEdgeProjections = function (edge) { 23057 var _p = edge._private; 23058 var rs = _p.rscratch; 23059 var et = rs.edgeType; // clear the cached points state 23060 23061 _p.rstyle.bezierPts = null; 23062 _p.rstyle.linePts = null; 23063 _p.rstyle.haystackPts = null; 23064 23065 if (et === 'multibezier' || et === 'bezier' || et === 'self' || et === 'compound') { 23066 _p.rstyle.bezierPts = []; 23067 23068 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 23069 pushBezierPts(this, edge, rs.allpts.slice(i, i + 6)); 23070 } 23071 } else if (et === 'segments') { 23072 var lpts = _p.rstyle.linePts = []; 23073 23074 for (var i = 0; i + 1 < rs.allpts.length; i += 2) { 23075 lpts.push({ 23076 x: rs.allpts[i], 23077 y: rs.allpts[i + 1] 23078 }); 23079 } 23080 } else if (et === 'haystack') { 23081 var hpts = rs.haystackPts; 23082 _p.rstyle.haystackPts = [{ 23083 x: hpts[0], 23084 y: hpts[1] 23085 }, { 23086 x: hpts[2], 23087 y: hpts[3] 23088 }]; 23089 } 23090 23091 _p.rstyle.arrowWidth = this.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.arrowShapeWidth; 23092 }; 23093 23094 BRp$5.recalculateEdgeProjections = function (edges) { 23095 this.findEdgeControlPoints(edges); 23096 }; 23097 23098 var BRp$6 = {}; 23099 23100 BRp$6.recalculateNodeLabelProjection = function (node) { 23101 var content = node.pstyle('label').strValue; 23102 23103 if (emptyString(content)) { 23104 return; 23105 } 23106 23107 var textX, textY; 23108 var _p = node._private; 23109 var nodeWidth = node.width(); 23110 var nodeHeight = node.height(); 23111 var padding = node.padding(); 23112 var nodePos = node.position(); 23113 var textHalign = node.pstyle('text-halign').strValue; 23114 var textValign = node.pstyle('text-valign').strValue; 23115 var rs = _p.rscratch; 23116 var rstyle = _p.rstyle; 23117 23118 switch (textHalign) { 23119 case 'left': 23120 textX = nodePos.x - nodeWidth / 2 - padding; 23121 break; 23122 23123 case 'right': 23124 textX = nodePos.x + nodeWidth / 2 + padding; 23125 break; 23126 23127 default: 23128 // e.g. center 23129 textX = nodePos.x; 23130 } 23131 23132 switch (textValign) { 23133 case 'top': 23134 textY = nodePos.y - nodeHeight / 2 - padding; 23135 break; 23136 23137 case 'bottom': 23138 textY = nodePos.y + nodeHeight / 2 + padding; 23139 break; 23140 23141 default: 23142 // e.g. middle 23143 textY = nodePos.y; 23144 } 23145 23146 rs.labelX = textX; 23147 rs.labelY = textY; 23148 rstyle.labelX = textX; 23149 rstyle.labelY = textY; 23150 this.applyLabelDimensions(node); 23151 }; 23152 23153 var lineAngleFromDelta = function lineAngleFromDelta(dx, dy) { 23154 var angle = Math.atan(dy / dx); 23155 23156 if (dx === 0 && angle < 0) { 23157 angle = angle * -1; 23158 } 23159 23160 return angle; 23161 }; 23162 23163 var lineAngle = function lineAngle(p0, p1) { 23164 var dx = p1.x - p0.x; 23165 var dy = p1.y - p0.y; 23166 return lineAngleFromDelta(dx, dy); 23167 }; 23168 23169 var bezierAngle = function bezierAngle(p0, p1, p2, t) { 23170 var t0 = bound(0, t - 0.001, 1); 23171 var t1 = bound(0, t + 0.001, 1); 23172 var lp0 = qbezierPtAt(p0, p1, p2, t0); 23173 var lp1 = qbezierPtAt(p0, p1, p2, t1); 23174 return lineAngle(lp0, lp1); 23175 }; 23176 23177 BRp$6.recalculateEdgeLabelProjections = function (edge) { 23178 var p; 23179 var _p = edge._private; 23180 var rs = _p.rscratch; 23181 var r = this; 23182 var content = { 23183 mid: edge.pstyle('label').strValue, 23184 source: edge.pstyle('source-label').strValue, 23185 target: edge.pstyle('target-label').strValue 23186 }; 23187 23188 if (content.mid || content.source || content.target) ; else { 23189 return; // no labels => no calcs 23190 } // add center point to style so bounding box calculations can use it 23191 // 23192 23193 23194 p = { 23195 x: rs.midX, 23196 y: rs.midY 23197 }; 23198 23199 var setRs = function setRs(propName, prefix, value) { 23200 setPrefixedProperty(_p.rscratch, propName, prefix, value); 23201 setPrefixedProperty(_p.rstyle, propName, prefix, value); 23202 }; 23203 23204 setRs('labelX', null, p.x); 23205 setRs('labelY', null, p.y); 23206 var midAngle = lineAngleFromDelta(rs.midDispX, rs.midDispY); 23207 setRs('labelAutoAngle', null, midAngle); 23208 23209 var createControlPointInfo = function createControlPointInfo() { 23210 if (createControlPointInfo.cache) { 23211 return createControlPointInfo.cache; 23212 } // use cache so only 1x per edge 23213 23214 23215 var ctrlpts = []; // store each ctrlpt info init 23216 23217 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 23218 var p0 = { 23219 x: rs.allpts[i], 23220 y: rs.allpts[i + 1] 23221 }; 23222 var p1 = { 23223 x: rs.allpts[i + 2], 23224 y: rs.allpts[i + 3] 23225 }; // ctrlpt 23226 23227 var p2 = { 23228 x: rs.allpts[i + 4], 23229 y: rs.allpts[i + 5] 23230 }; 23231 ctrlpts.push({ 23232 p0: p0, 23233 p1: p1, 23234 p2: p2, 23235 startDist: 0, 23236 length: 0, 23237 segments: [] 23238 }); 23239 } 23240 23241 var bpts = _p.rstyle.bezierPts; 23242 var nProjs = r.bezierProjPcts.length; 23243 23244 function addSegment(cp, p0, p1, t0, t1) { 23245 var length = dist(p0, p1); 23246 var prevSegment = cp.segments[cp.segments.length - 1]; 23247 var segment = { 23248 p0: p0, 23249 p1: p1, 23250 t0: t0, 23251 t1: t1, 23252 startDist: prevSegment ? prevSegment.startDist + prevSegment.length : 0, 23253 length: length 23254 }; 23255 cp.segments.push(segment); 23256 cp.length += length; 23257 } // update each ctrlpt with segment info 23258 23259 23260 for (var _i = 0; _i < ctrlpts.length; _i++) { 23261 var cp = ctrlpts[_i]; 23262 var prevCp = ctrlpts[_i - 1]; 23263 23264 if (prevCp) { 23265 cp.startDist = prevCp.startDist + prevCp.length; 23266 } 23267 23268 addSegment(cp, cp.p0, bpts[_i * nProjs], 0, r.bezierProjPcts[0]); // first 23269 23270 for (var j = 0; j < nProjs - 1; j++) { 23271 addSegment(cp, bpts[_i * nProjs + j], bpts[_i * nProjs + j + 1], r.bezierProjPcts[j], r.bezierProjPcts[j + 1]); 23272 } 23273 23274 addSegment(cp, bpts[_i * nProjs + nProjs - 1], cp.p2, r.bezierProjPcts[nProjs - 1], 1); // last 23275 } 23276 23277 return createControlPointInfo.cache = ctrlpts; 23278 }; 23279 23280 var calculateEndProjection = function calculateEndProjection(prefix) { 23281 var angle; 23282 var isSrc = prefix === 'source'; 23283 23284 if (!content[prefix]) { 23285 return; 23286 } 23287 23288 var offset = edge.pstyle(prefix + '-text-offset').pfValue; 23289 23290 switch (rs.edgeType) { 23291 case 'self': 23292 case 'compound': 23293 case 'bezier': 23294 case 'multibezier': 23295 { 23296 var cps = createControlPointInfo(); 23297 var selected; 23298 var startDist = 0; 23299 var totalDist = 0; // find the segment we're on 23300 23301 for (var i = 0; i < cps.length; i++) { 23302 var _cp = cps[isSrc ? i : cps.length - 1 - i]; 23303 23304 for (var j = 0; j < _cp.segments.length; j++) { 23305 var _seg = _cp.segments[isSrc ? j : _cp.segments.length - 1 - j]; 23306 var lastSeg = i === cps.length - 1 && j === _cp.segments.length - 1; 23307 startDist = totalDist; 23308 totalDist += _seg.length; 23309 23310 if (totalDist >= offset || lastSeg) { 23311 selected = { 23312 cp: _cp, 23313 segment: _seg 23314 }; 23315 break; 23316 } 23317 } 23318 23319 if (selected) { 23320 break; 23321 } 23322 } 23323 23324 var cp = selected.cp; 23325 var seg = selected.segment; 23326 var tSegment = (offset - startDist) / seg.length; 23327 var segDt = seg.t1 - seg.t0; 23328 var t = isSrc ? seg.t0 + segDt * tSegment : seg.t1 - segDt * tSegment; 23329 t = bound(0, t, 1); 23330 p = qbezierPtAt(cp.p0, cp.p1, cp.p2, t); 23331 angle = bezierAngle(cp.p0, cp.p1, cp.p2, t); 23332 break; 23333 } 23334 23335 case 'straight': 23336 case 'segments': 23337 case 'haystack': 23338 { 23339 var d = 0, 23340 di, 23341 d0; 23342 var p0, p1; 23343 var l = rs.allpts.length; 23344 23345 for (var _i2 = 0; _i2 + 3 < l; _i2 += 2) { 23346 if (isSrc) { 23347 p0 = { 23348 x: rs.allpts[_i2], 23349 y: rs.allpts[_i2 + 1] 23350 }; 23351 p1 = { 23352 x: rs.allpts[_i2 + 2], 23353 y: rs.allpts[_i2 + 3] 23354 }; 23355 } else { 23356 p0 = { 23357 x: rs.allpts[l - 2 - _i2], 23358 y: rs.allpts[l - 1 - _i2] 23359 }; 23360 p1 = { 23361 x: rs.allpts[l - 4 - _i2], 23362 y: rs.allpts[l - 3 - _i2] 23363 }; 23364 } 23365 23366 di = dist(p0, p1); 23367 d0 = d; 23368 d += di; 23369 23370 if (d >= offset) { 23371 break; 23372 } 23373 } 23374 23375 var pD = offset - d0; 23376 23377 var _t = pD / di; 23378 23379 _t = bound(0, _t, 1); 23380 p = lineAt(p0, p1, _t); 23381 angle = lineAngle(p0, p1); 23382 break; 23383 } 23384 } 23385 23386 setRs('labelX', prefix, p.x); 23387 setRs('labelY', prefix, p.y); 23388 setRs('labelAutoAngle', prefix, angle); 23389 }; 23390 23391 calculateEndProjection('source'); 23392 calculateEndProjection('target'); 23393 this.applyLabelDimensions(edge); 23394 }; 23395 23396 BRp$6.applyLabelDimensions = function (ele) { 23397 this.applyPrefixedLabelDimensions(ele); 23398 23399 if (ele.isEdge()) { 23400 this.applyPrefixedLabelDimensions(ele, 'source'); 23401 this.applyPrefixedLabelDimensions(ele, 'target'); 23402 } 23403 }; 23404 23405 BRp$6.applyPrefixedLabelDimensions = function (ele, prefix) { 23406 var _p = ele._private; 23407 var text = this.getLabelText(ele, prefix); 23408 var labelDims = this.calculateLabelDimensions(ele, text); 23409 var lineHeight = ele.pstyle('line-height').pfValue; 23410 var textWrap = ele.pstyle('text-wrap').strValue; 23411 var lines = getPrefixedProperty(_p.rscratch, 'labelWrapCachedLines', prefix) || []; 23412 var numLines = textWrap !== 'wrap' ? 1 : Math.max(lines.length, 1); 23413 var normPerLineHeight = labelDims.height / numLines; 23414 var labelLineHeight = normPerLineHeight * lineHeight; 23415 var width = labelDims.width; 23416 var height = labelDims.height + (numLines - 1) * (lineHeight - 1) * normPerLineHeight; 23417 setPrefixedProperty(_p.rstyle, 'labelWidth', prefix, width); 23418 setPrefixedProperty(_p.rscratch, 'labelWidth', prefix, width); 23419 setPrefixedProperty(_p.rstyle, 'labelHeight', prefix, height); 23420 setPrefixedProperty(_p.rscratch, 'labelHeight', prefix, height); 23421 setPrefixedProperty(_p.rscratch, 'labelLineHeight', prefix, labelLineHeight); 23422 }; 23423 23424 BRp$6.getLabelText = function (ele, prefix) { 23425 var _p = ele._private; 23426 var pfd = prefix ? prefix + '-' : ''; 23427 var text = ele.pstyle(pfd + 'label').strValue; 23428 var textTransform = ele.pstyle('text-transform').value; 23429 23430 var rscratch = function rscratch(propName, value) { 23431 if (value) { 23432 setPrefixedProperty(_p.rscratch, propName, prefix, value); 23433 return value; 23434 } else { 23435 return getPrefixedProperty(_p.rscratch, propName, prefix); 23436 } 23437 }; // for empty text, skip all processing 23438 23439 23440 if (!text) { 23441 return ''; 23442 } 23443 23444 if (textTransform == 'none') ; else if (textTransform == 'uppercase') { 23445 text = text.toUpperCase(); 23446 } else if (textTransform == 'lowercase') { 23447 text = text.toLowerCase(); 23448 } 23449 23450 var wrapStyle = ele.pstyle('text-wrap').value; 23451 23452 if (wrapStyle === 'wrap') { 23453 var labelKey = rscratch('labelKey'); // save recalc if the label is the same as before 23454 23455 if (labelKey != null && rscratch('labelWrapKey') === labelKey) { 23456 return rscratch('labelWrapCachedText'); 23457 } 23458 23459 var zwsp = "\u200B"; 23460 var lines = text.split('\n'); 23461 var maxW = ele.pstyle('text-max-width').pfValue; 23462 var overflow = ele.pstyle('text-overflow-wrap').value; 23463 var overflowAny = overflow === 'anywhere'; 23464 var wrappedLines = []; 23465 var wordsRegex = /[\s\u200b]+/; 23466 var wordSeparator = overflowAny ? '' : ' '; 23467 23468 for (var l = 0; l < lines.length; l++) { 23469 var line = lines[l]; 23470 var lineDims = this.calculateLabelDimensions(ele, line); 23471 var lineW = lineDims.width; 23472 23473 if (overflowAny) { 23474 var processedLine = line.split('').join(zwsp); 23475 line = processedLine; 23476 } 23477 23478 if (lineW > maxW) { 23479 // line is too long 23480 var words = line.split(wordsRegex); 23481 var subline = ''; 23482 23483 for (var w = 0; w < words.length; w++) { 23484 var word = words[w]; 23485 var testLine = subline.length === 0 ? word : subline + wordSeparator + word; 23486 var testDims = this.calculateLabelDimensions(ele, testLine); 23487 var testW = testDims.width; 23488 23489 if (testW <= maxW) { 23490 // word fits on current line 23491 subline += word + wordSeparator; 23492 } else { 23493 // word starts new line 23494 if (subline) { 23495 wrappedLines.push(subline); 23496 } 23497 23498 subline = word + wordSeparator; 23499 } 23500 } // if there's remaining text, put it in a wrapped line 23501 23502 23503 if (!subline.match(/^[\s\u200b]+$/)) { 23504 wrappedLines.push(subline); 23505 } 23506 } else { 23507 // line is already short enough 23508 wrappedLines.push(line); 23509 } 23510 } // for 23511 23512 23513 rscratch('labelWrapCachedLines', wrappedLines); 23514 text = rscratch('labelWrapCachedText', wrappedLines.join('\n')); 23515 rscratch('labelWrapKey', labelKey); 23516 } else if (wrapStyle === 'ellipsis') { 23517 var _maxW = ele.pstyle('text-max-width').pfValue; 23518 var ellipsized = ''; 23519 var ellipsis = "\u2026"; 23520 var incLastCh = false; 23521 23522 for (var i = 0; i < text.length; i++) { 23523 var widthWithNextCh = this.calculateLabelDimensions(ele, ellipsized + text[i] + ellipsis).width; 23524 23525 if (widthWithNextCh > _maxW) { 23526 break; 23527 } 23528 23529 ellipsized += text[i]; 23530 23531 if (i === text.length - 1) { 23532 incLastCh = true; 23533 } 23534 } 23535 23536 if (!incLastCh) { 23537 ellipsized += ellipsis; 23538 } 23539 23540 return ellipsized; 23541 } // if ellipsize 23542 23543 23544 return text; 23545 }; 23546 23547 BRp$6.getLabelJustification = function (ele) { 23548 var justification = ele.pstyle('text-justification').strValue; 23549 var textHalign = ele.pstyle('text-halign').strValue; 23550 23551 if (justification === 'auto') { 23552 if (ele.isNode()) { 23553 switch (textHalign) { 23554 case 'left': 23555 return 'right'; 23556 23557 case 'right': 23558 return 'left'; 23559 23560 default: 23561 return 'center'; 23562 } 23563 } else { 23564 return 'center'; 23565 } 23566 } else { 23567 return justification; 23568 } 23569 }; 23570 23571 BRp$6.calculateLabelDimensions = function (ele, text) { 23572 var r = this; 23573 var cacheKey = hashString(text, ele._private.labelDimsKey); 23574 var cache = r.labelDimCache || (r.labelDimCache = []); 23575 var existingVal = cache[cacheKey]; 23576 23577 if (existingVal != null) { 23578 return existingVal; 23579 } 23580 23581 var sizeMult = 1; // increase the scale to increase accuracy w.r.t. zoomed text 23582 23583 var fStyle = ele.pstyle('font-style').strValue; 23584 var size = sizeMult * ele.pstyle('font-size').pfValue + 'px'; 23585 var family = ele.pstyle('font-family').strValue; 23586 var weight = ele.pstyle('font-weight').strValue; 23587 var div = this.labelCalcDiv; 23588 23589 if (!div) { 23590 div = this.labelCalcDiv = document.createElement('div'); // eslint-disable-line no-undef 23591 23592 document.body.appendChild(div); // eslint-disable-line no-undef 23593 } 23594 23595 var ds = div.style; // from ele style 23596 23597 ds.fontFamily = family; 23598 ds.fontStyle = fStyle; 23599 ds.fontSize = size; 23600 ds.fontWeight = weight; // forced style 23601 23602 ds.position = 'absolute'; 23603 ds.left = '-9999px'; 23604 ds.top = '-9999px'; 23605 ds.zIndex = '-1'; 23606 ds.visibility = 'hidden'; 23607 ds.pointerEvents = 'none'; 23608 ds.padding = '0'; 23609 ds.lineHeight = '1'; // - newlines must be taken into account for text-wrap:wrap 23610 // - since spaces are not collapsed, each space must be taken into account 23611 23612 ds.whiteSpace = 'pre'; // put label content in div 23613 23614 div.textContent = text; 23615 return cache[cacheKey] = { 23616 width: Math.ceil(div.clientWidth / sizeMult), 23617 height: Math.ceil(div.clientHeight / sizeMult) 23618 }; 23619 }; 23620 23621 BRp$6.calculateLabelAngle = function (ele, prefix) { 23622 var _p = ele._private; 23623 var rs = _p.rscratch; 23624 var isEdge = ele.isEdge(); 23625 var prefixDash = prefix ? prefix + '-' : ''; 23626 var rot = ele.pstyle(prefixDash + 'text-rotation'); 23627 var rotStr = rot.strValue; 23628 23629 if (rotStr === 'none') { 23630 return 0; 23631 } else if (isEdge && rotStr === 'autorotate') { 23632 return rs.labelAutoAngle; 23633 } else if (rotStr === 'autorotate') { 23634 return 0; 23635 } else { 23636 return rot.pfValue; 23637 } 23638 }; 23639 23640 BRp$6.calculateLabelAngles = function (ele) { 23641 var r = this; 23642 var isEdge = ele.isEdge(); 23643 var _p = ele._private; 23644 var rs = _p.rscratch; 23645 rs.labelAngle = r.calculateLabelAngle(ele); 23646 23647 if (isEdge) { 23648 rs.sourceLabelAngle = r.calculateLabelAngle(ele, 'source'); 23649 rs.targetLabelAngle = r.calculateLabelAngle(ele, 'target'); 23650 } 23651 }; 23652 23653 var BRp$7 = {}; 23654 var TOO_SMALL_CUT_RECT = 28; 23655 var warnedCutRect = false; 23656 23657 BRp$7.getNodeShape = function (node) { 23658 var r = this; 23659 var shape = node.pstyle('shape').value; 23660 23661 if (shape === 'cutrectangle' && (node.width() < TOO_SMALL_CUT_RECT || node.height() < TOO_SMALL_CUT_RECT)) { 23662 if (!warnedCutRect) { 23663 warn('The `cutrectangle` node shape can not be used at small sizes so `rectangle` is used instead'); 23664 warnedCutRect = true; 23665 } 23666 23667 return 'rectangle'; 23668 } 23669 23670 if (node.isParent()) { 23671 if (shape === 'rectangle' || shape === 'roundrectangle' || shape === 'cutrectangle' || shape === 'barrel') { 23672 return shape; 23673 } else { 23674 return 'rectangle'; 23675 } 23676 } 23677 23678 if (shape === 'polygon') { 23679 var points = node.pstyle('shape-polygon-points').value; 23680 return r.nodeShapes.makePolygon(points).name; 23681 } 23682 23683 return shape; 23684 }; 23685 23686 var BRp$8 = {}; 23687 23688 BRp$8.registerCalculationListeners = function () { 23689 var cy = this.cy; 23690 var elesToUpdate = cy.collection(); 23691 var r = this; 23692 23693 var enqueue = function enqueue(eles) { 23694 var dirtyStyleCaches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 23695 elesToUpdate.merge(eles); 23696 23697 if (dirtyStyleCaches) { 23698 for (var i = 0; i < eles.length; i++) { 23699 var ele = eles[i]; 23700 var _p = ele._private; 23701 var rstyle = _p.rstyle; 23702 rstyle.clean = false; 23703 rstyle.cleanConnected = false; 23704 } 23705 } 23706 }; 23707 23708 r.binder(cy).on('bounds.* dirty.*', function onDirtyBounds(e) { 23709 var ele = e.target; 23710 enqueue(ele); 23711 }).on('style.* background.*', function onDirtyStyle(e) { 23712 var ele = e.target; 23713 enqueue(ele, false); 23714 }); 23715 23716 var updateEleCalcs = function updateEleCalcs(willDraw) { 23717 if (willDraw) { 23718 var fns = r.onUpdateEleCalcsFns; 23719 23720 for (var i = 0; i < elesToUpdate.length; i++) { 23721 var ele = elesToUpdate[i]; 23722 var rstyle = ele._private.rstyle; 23723 23724 if (ele.isNode() && !rstyle.cleanConnected) { 23725 enqueue(ele.connectedEdges()); 23726 rstyle.cleanConnected = true; 23727 } 23728 } 23729 23730 if (fns) { 23731 for (var i = 0; i < fns.length; i++) { 23732 var fn = fns[i]; 23733 fn(willDraw, elesToUpdate); 23734 } 23735 } 23736 23737 r.recalculateRenderedStyle(elesToUpdate); 23738 elesToUpdate = cy.collection(); 23739 } 23740 }; 23741 23742 r.flushRenderedStyleQueue = function () { 23743 updateEleCalcs(true); 23744 }; 23745 23746 r.beforeRender(updateEleCalcs, r.beforeRenderPriorities.eleCalcs); 23747 }; 23748 23749 BRp$8.onUpdateEleCalcs = function (fn) { 23750 var fns = this.onUpdateEleCalcsFns = this.onUpdateEleCalcsFns || []; 23751 fns.push(fn); 23752 }; 23753 23754 BRp$8.recalculateRenderedStyle = function (eles, useCache) { 23755 var isCleanConnected = function isCleanConnected(ele) { 23756 return ele._private.rstyle.cleanConnected; 23757 }; 23758 23759 var edges = []; 23760 var nodes = []; // the renderer can't be used for calcs when destroyed, e.g. ele.boundingBox() 23761 23762 if (this.destroyed) { 23763 return; 23764 } // use cache by default for perf 23765 23766 23767 if (useCache === undefined) { 23768 useCache = true; 23769 } 23770 23771 for (var i = 0; i < eles.length; i++) { 23772 var ele = eles[i]; 23773 var _p = ele._private; 23774 var rstyle = _p.rstyle; // an edge may be implicitly dirty b/c of one of its connected nodes 23775 // (and a request for recalc may come in between frames) 23776 23777 if (ele.isEdge() && (!isCleanConnected(ele.source()) || !isCleanConnected(ele.target()))) { 23778 rstyle.clean = false; 23779 } // only update if dirty and in graph 23780 23781 23782 if (useCache && rstyle.clean || ele.removed()) { 23783 continue; 23784 } // only update if not display: none 23785 23786 23787 if (ele.pstyle('display').value === 'none') { 23788 continue; 23789 } 23790 23791 if (_p.group === 'nodes') { 23792 nodes.push(ele); 23793 } else { 23794 // edges 23795 edges.push(ele); 23796 } 23797 23798 rstyle.clean = true; 23799 } // update node data from projections 23800 23801 23802 for (var i = 0; i < nodes.length; i++) { 23803 var ele = nodes[i]; 23804 var _p = ele._private; 23805 var rstyle = _p.rstyle; 23806 var pos = ele.position(); 23807 this.recalculateNodeLabelProjection(ele); 23808 rstyle.nodeX = pos.x; 23809 rstyle.nodeY = pos.y; 23810 rstyle.nodeW = ele.pstyle('width').pfValue; 23811 rstyle.nodeH = ele.pstyle('height').pfValue; 23812 } 23813 23814 this.recalculateEdgeProjections(edges); // update edge data from projections 23815 23816 for (var i = 0; i < edges.length; i++) { 23817 var ele = edges[i]; 23818 var _p = ele._private; 23819 var rstyle = _p.rstyle; 23820 var rs = _p.rscratch; // update rstyle positions 23821 23822 rstyle.srcX = rs.arrowStartX; 23823 rstyle.srcY = rs.arrowStartY; 23824 rstyle.tgtX = rs.arrowEndX; 23825 rstyle.tgtY = rs.arrowEndY; 23826 rstyle.midX = rs.midX; 23827 rstyle.midY = rs.midY; 23828 rstyle.labelAngle = rs.labelAngle; 23829 rstyle.sourceLabelAngle = rs.sourceLabelAngle; 23830 rstyle.targetLabelAngle = rs.targetLabelAngle; 23831 } 23832 }; 23833 23834 var BRp$9 = {}; 23835 23836 BRp$9.updateCachedGrabbedEles = function () { 23837 var eles = this.cachedZSortedEles; 23838 23839 if (!eles) { 23840 // just let this be recalculated on the next z sort tick 23841 return; 23842 } 23843 23844 eles.drag = []; 23845 eles.nondrag = []; 23846 var grabTargets = []; 23847 23848 for (var i = 0; i < eles.length; i++) { 23849 var ele = eles[i]; 23850 var rs = ele._private.rscratch; 23851 23852 if (ele.grabbed() && !ele.isParent()) { 23853 grabTargets.push(ele); 23854 } else if (rs.inDragLayer) { 23855 eles.drag.push(ele); 23856 } else { 23857 eles.nondrag.push(ele); 23858 } 23859 } // put the grab target nodes last so it's on top of its neighbourhood 23860 23861 23862 for (var i = 0; i < grabTargets.length; i++) { 23863 var ele = grabTargets[i]; 23864 eles.drag.push(ele); 23865 } 23866 }; 23867 23868 BRp$9.invalidateCachedZSortedEles = function () { 23869 this.cachedZSortedEles = null; 23870 }; 23871 23872 BRp$9.getCachedZSortedEles = function (forceRecalc) { 23873 if (forceRecalc || !this.cachedZSortedEles) { 23874 var eles = this.cy.mutableElements().toArray(); 23875 eles.sort(zIndexSort); 23876 eles.interactive = eles.filter(function (ele) { 23877 return ele.interactive(); 23878 }); 23879 this.cachedZSortedEles = eles; 23880 this.updateCachedGrabbedEles(); 23881 } else { 23882 eles = this.cachedZSortedEles; 23883 } 23884 23885 return eles; 23886 }; 23887 23888 var BRp$a = {}; 23889 [BRp$1, BRp$2, BRp$3, BRp$4, BRp$5, BRp$6, BRp$7, BRp$8, BRp$9].forEach(function (props) { 23890 extend(BRp$a, props); 23891 }); 23892 23893 var BRp$b = {}; 23894 23895 BRp$b.getCachedImage = function (url, crossOrigin, onLoad) { 23896 var r = this; 23897 var imageCache = r.imageCache = r.imageCache || {}; 23898 var cache = imageCache[url]; 23899 23900 if (cache) { 23901 if (!cache.image.complete) { 23902 cache.image.addEventListener('load', onLoad); 23903 } 23904 23905 return cache.image; 23906 } else { 23907 cache = imageCache[url] = imageCache[url] || {}; 23908 var image = cache.image = new Image(); // eslint-disable-line no-undef 23909 23910 image.addEventListener('load', onLoad); 23911 image.addEventListener('error', function () { 23912 image.error = true; 23913 }); // #1582 safari doesn't load data uris with crossOrigin properly 23914 // https://bugs.webkit.org/show_bug.cgi?id=123978 23915 23916 var dataUriPrefix = 'data:'; 23917 var isDataUri = url.substring(0, dataUriPrefix.length).toLowerCase() === dataUriPrefix; 23918 23919 if (!isDataUri) { 23920 image.crossOrigin = crossOrigin; // prevent tainted canvas 23921 } 23922 23923 image.src = url; 23924 return image; 23925 } 23926 }; 23927 23928 var BRp$c = {}; 23929 /* global document, window, ResizeObserver, MutationObserver */ 23930 23931 BRp$c.registerBinding = function (target, event, handler, useCapture) { 23932 // eslint-disable-line no-unused-vars 23933 var args = Array.prototype.slice.apply(arguments, [1]); // copy 23934 23935 var b = this.binder(target); 23936 return b.on.apply(b, args); 23937 }; 23938 23939 BRp$c.binder = function (tgt) { 23940 var r = this; 23941 var tgtIsDom = tgt === window || tgt === document || tgt === document.body || domElement(tgt); 23942 23943 if (r.supportsPassiveEvents == null) { 23944 // from https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection 23945 var supportsPassive = false; 23946 23947 try { 23948 var opts = Object.defineProperty({}, 'passive', { 23949 get: function get() { 23950 supportsPassive = true; 23951 return true; 23952 } 23953 }); 23954 window.addEventListener('test', null, opts); 23955 } catch (err) {// not supported 23956 } 23957 23958 r.supportsPassiveEvents = supportsPassive; 23959 } 23960 23961 var on = function on(event, handler, useCapture) { 23962 var args = Array.prototype.slice.call(arguments); 23963 23964 if (tgtIsDom && r.supportsPassiveEvents) { 23965 // replace useCapture w/ opts obj 23966 args[2] = { 23967 capture: useCapture != null ? useCapture : false, 23968 passive: false, 23969 once: false 23970 }; 23971 } 23972 23973 r.bindings.push({ 23974 target: tgt, 23975 args: args 23976 }); 23977 (tgt.addEventListener || tgt.on).apply(tgt, args); 23978 return this; 23979 }; 23980 23981 return { 23982 on: on, 23983 addEventListener: on, 23984 addListener: on, 23985 bind: on 23986 }; 23987 }; 23988 23989 BRp$c.nodeIsDraggable = function (node) { 23990 return node && node.isNode() && !node.locked() && node.grabbable(); 23991 }; 23992 23993 BRp$c.nodeIsGrabbable = function (node) { 23994 return this.nodeIsDraggable(node) && node.interactive(); 23995 }; 23996 23997 BRp$c.load = function () { 23998 var r = this; 23999 24000 var isSelected = function isSelected(ele) { 24001 return ele.selected(); 24002 }; 24003 24004 var triggerEvents = function triggerEvents(target, names, e, position) { 24005 if (target == null) { 24006 target = r.cy; 24007 } 24008 24009 for (var i = 0; i < names.length; i++) { 24010 var name = names[i]; 24011 target.emit({ 24012 originalEvent: e, 24013 type: name, 24014 position: position 24015 }); 24016 } 24017 }; 24018 24019 var isMultSelKeyDown = function isMultSelKeyDown(e) { 24020 return e.shiftKey || e.metaKey || e.ctrlKey; // maybe e.altKey 24021 }; 24022 24023 var allowPanningPassthrough = function allowPanningPassthrough(down, downs) { 24024 var allowPassthrough = true; 24025 24026 if (r.cy.hasCompoundNodes() && down && down.pannable()) { 24027 // a grabbable compound node below the ele => no passthrough panning 24028 for (var i = 0; downs && i < downs.length; i++) { 24029 var down = downs[i]; 24030 24031 if (down.isNode() && down.isParent()) { 24032 allowPassthrough = false; 24033 break; 24034 } 24035 } 24036 } else { 24037 allowPassthrough = true; 24038 } 24039 24040 return allowPassthrough; 24041 }; 24042 24043 var setGrabbed = function setGrabbed(ele) { 24044 ele[0]._private.grabbed = true; 24045 }; 24046 24047 var setFreed = function setFreed(ele) { 24048 ele[0]._private.grabbed = false; 24049 }; 24050 24051 var setInDragLayer = function setInDragLayer(ele) { 24052 ele[0]._private.rscratch.inDragLayer = true; 24053 }; 24054 24055 var setOutDragLayer = function setOutDragLayer(ele) { 24056 ele[0]._private.rscratch.inDragLayer = false; 24057 }; 24058 24059 var setGrabTarget = function setGrabTarget(ele) { 24060 ele[0]._private.rscratch.isGrabTarget = true; 24061 }; 24062 24063 var removeGrabTarget = function removeGrabTarget(ele) { 24064 ele[0]._private.rscratch.isGrabTarget = false; 24065 }; 24066 24067 var addToDragList = function addToDragList(ele, opts) { 24068 var list = opts.addToList; 24069 var listHasEle = list.has(ele); 24070 24071 if (!listHasEle) { 24072 list.merge(ele); 24073 setGrabbed(ele); 24074 } 24075 }; // helper function to determine which child nodes and inner edges 24076 // of a compound node to be dragged as well as the grabbed and selected nodes 24077 24078 24079 var addDescendantsToDrag = function addDescendantsToDrag(node, opts) { 24080 if (!node.cy().hasCompoundNodes()) { 24081 return; 24082 } 24083 24084 if (opts.inDragLayer == null && opts.addToList == null) { 24085 return; 24086 } // nothing to do 24087 24088 24089 var innerNodes = node.descendants(); 24090 24091 if (opts.inDragLayer) { 24092 innerNodes.forEach(setInDragLayer); 24093 innerNodes.connectedEdges().forEach(setInDragLayer); 24094 } 24095 24096 if (opts.addToList) { 24097 opts.addToList.unmerge(innerNodes); 24098 } 24099 }; // adds the given nodes and its neighbourhood to the drag layer 24100 24101 24102 var addNodesToDrag = function addNodesToDrag(nodes, opts) { 24103 opts = opts || {}; 24104 var hasCompoundNodes = nodes.cy().hasCompoundNodes(); 24105 24106 if (opts.inDragLayer) { 24107 nodes.forEach(setInDragLayer); 24108 nodes.neighborhood().stdFilter(function (ele) { 24109 return !hasCompoundNodes || ele.isEdge(); 24110 }).forEach(setInDragLayer); 24111 } 24112 24113 if (opts.addToList) { 24114 nodes.forEach(function (ele) { 24115 addToDragList(ele, opts); 24116 }); 24117 } 24118 24119 addDescendantsToDrag(nodes, opts); // always add to drag 24120 // also add nodes and edges related to the topmost ancestor 24121 24122 updateAncestorsInDragLayer(nodes, { 24123 inDragLayer: opts.inDragLayer 24124 }); 24125 r.updateCachedGrabbedEles(); 24126 }; 24127 24128 var addNodeToDrag = addNodesToDrag; 24129 24130 var freeDraggedElements = function freeDraggedElements(grabbedEles) { 24131 if (!grabbedEles) { 24132 return; 24133 } // just go over all elements rather than doing a bunch of (possibly expensive) traversals 24134 24135 24136 r.getCachedZSortedEles().forEach(function (ele) { 24137 setFreed(ele); 24138 setOutDragLayer(ele); 24139 removeGrabTarget(ele); 24140 }); 24141 r.updateCachedGrabbedEles(); 24142 }; // helper function to determine which ancestor nodes and edges should go 24143 // to the drag layer (or should be removed from drag layer). 24144 24145 24146 var updateAncestorsInDragLayer = function updateAncestorsInDragLayer(node, opts) { 24147 if (opts.inDragLayer == null && opts.addToList == null) { 24148 return; 24149 } // nothing to do 24150 24151 24152 if (!node.cy().hasCompoundNodes()) { 24153 return; 24154 } // find top-level parent 24155 24156 24157 var parent = node.ancestors().orphans(); // no parent node: no nodes to add to the drag layer 24158 24159 if (parent.same(node)) { 24160 return; 24161 } 24162 24163 var nodes = parent.descendants().spawnSelf().merge(parent).unmerge(node).unmerge(node.descendants()); 24164 var edges = nodes.connectedEdges(); 24165 24166 if (opts.inDragLayer) { 24167 edges.forEach(setInDragLayer); 24168 nodes.forEach(setInDragLayer); 24169 } 24170 24171 if (opts.addToList) { 24172 nodes.forEach(function (ele) { 24173 addToDragList(ele, opts); 24174 }); 24175 } 24176 }; 24177 24178 var blurActiveDomElement = function blurActiveDomElement() { 24179 if (document.activeElement != null && document.activeElement.blur != null) { 24180 document.activeElement.blur(); 24181 } 24182 }; 24183 24184 var haveMutationsApi = typeof MutationObserver !== 'undefined'; 24185 var haveResizeObserverApi = typeof ResizeObserver !== 'undefined'; // watch for when the cy container is removed from the dom 24186 24187 if (haveMutationsApi) { 24188 r.removeObserver = new MutationObserver(function (mutns) { 24189 // eslint-disable-line no-undef 24190 for (var i = 0; i < mutns.length; i++) { 24191 var mutn = mutns[i]; 24192 var rNodes = mutn.removedNodes; 24193 24194 if (rNodes) { 24195 for (var j = 0; j < rNodes.length; j++) { 24196 var rNode = rNodes[j]; 24197 24198 if (rNode === r.container) { 24199 r.destroy(); 24200 break; 24201 } 24202 } 24203 } 24204 } 24205 }); 24206 24207 if (r.container.parentNode) { 24208 r.removeObserver.observe(r.container.parentNode, { 24209 childList: true 24210 }); 24211 } 24212 } else { 24213 r.registerBinding(r.container, 'DOMNodeRemoved', function (e) { 24214 // eslint-disable-line no-unused-vars 24215 r.destroy(); 24216 }); 24217 } 24218 24219 var onResize = util(function () { 24220 r.cy.resize(); 24221 }, 100); 24222 24223 if (haveMutationsApi) { 24224 r.styleObserver = new MutationObserver(onResize); // eslint-disable-line no-undef 24225 24226 r.styleObserver.observe(r.container, { 24227 attributes: true 24228 }); 24229 } // auto resize 24230 24231 24232 r.registerBinding(window, 'resize', onResize); // eslint-disable-line no-undef 24233 24234 if (haveResizeObserverApi) { 24235 r.resizeObserver = new ResizeObserver(onResize); // eslint-disable-line no-undef 24236 24237 r.resizeObserver.observe(r.container); 24238 } 24239 24240 var forEachUp = function forEachUp(domEle, fn) { 24241 while (domEle != null) { 24242 fn(domEle); 24243 domEle = domEle.parentNode; 24244 } 24245 }; 24246 24247 var invalidateCoords = function invalidateCoords() { 24248 r.invalidateContainerClientCoordsCache(); 24249 }; 24250 24251 forEachUp(r.container, function (domEle) { 24252 r.registerBinding(domEle, 'transitionend', invalidateCoords); 24253 r.registerBinding(domEle, 'animationend', invalidateCoords); 24254 r.registerBinding(domEle, 'scroll', invalidateCoords); 24255 }); // stop right click menu from appearing on cy 24256 24257 r.registerBinding(r.container, 'contextmenu', function (e) { 24258 e.preventDefault(); 24259 }); 24260 24261 var inBoxSelection = function inBoxSelection() { 24262 return r.selection[4] !== 0; 24263 }; 24264 24265 var eventInContainer = function eventInContainer(e) { 24266 // save cycles if mouse events aren't to be captured 24267 var containerPageCoords = r.findContainerClientCoords(); 24268 var x = containerPageCoords[0]; 24269 var y = containerPageCoords[1]; 24270 var width = containerPageCoords[2]; 24271 var height = containerPageCoords[3]; 24272 var positions = e.touches ? e.touches : [e]; 24273 var atLeastOnePosInside = false; 24274 24275 for (var i = 0; i < positions.length; i++) { 24276 var p = positions[i]; 24277 24278 if (x <= p.clientX && p.clientX <= x + width && y <= p.clientY && p.clientY <= y + height) { 24279 atLeastOnePosInside = true; 24280 break; 24281 } 24282 } 24283 24284 if (!atLeastOnePosInside) { 24285 return false; 24286 } 24287 24288 var container = r.container; 24289 var target = e.target; 24290 var tParent = target.parentNode; 24291 var containerIsTarget = false; 24292 24293 while (tParent) { 24294 if (tParent === container) { 24295 containerIsTarget = true; 24296 break; 24297 } 24298 24299 tParent = tParent.parentNode; 24300 } 24301 24302 if (!containerIsTarget) { 24303 return false; 24304 } // if target is outisde cy container, then this event is not for us 24305 24306 24307 return true; 24308 }; // Primary key 24309 24310 24311 r.registerBinding(r.container, 'mousedown', function mousedownHandler(e) { 24312 if (!eventInContainer(e)) { 24313 return; 24314 } 24315 24316 e.preventDefault(); 24317 blurActiveDomElement(); 24318 r.hoverData.capture = true; 24319 r.hoverData.which = e.which; 24320 var cy = r.cy; 24321 var gpos = [e.clientX, e.clientY]; 24322 var pos = r.projectIntoViewport(gpos[0], gpos[1]); 24323 var select = r.selection; 24324 var nears = r.findNearestElements(pos[0], pos[1], true, false); 24325 var near = nears[0]; 24326 var draggedElements = r.dragData.possibleDragElements; 24327 r.hoverData.mdownPos = pos; 24328 r.hoverData.mdownGPos = gpos; 24329 24330 var checkForTaphold = function checkForTaphold() { 24331 r.hoverData.tapholdCancelled = false; 24332 clearTimeout(r.hoverData.tapholdTimeout); 24333 r.hoverData.tapholdTimeout = setTimeout(function () { 24334 if (r.hoverData.tapholdCancelled) { 24335 return; 24336 } else { 24337 var ele = r.hoverData.down; 24338 24339 if (ele) { 24340 ele.emit({ 24341 originalEvent: e, 24342 type: 'taphold', 24343 position: { 24344 x: pos[0], 24345 y: pos[1] 24346 } 24347 }); 24348 } else { 24349 cy.emit({ 24350 originalEvent: e, 24351 type: 'taphold', 24352 position: { 24353 x: pos[0], 24354 y: pos[1] 24355 } 24356 }); 24357 } 24358 } 24359 }, r.tapholdDuration); 24360 }; // Right click button 24361 24362 24363 if (e.which == 3) { 24364 r.hoverData.cxtStarted = true; 24365 var cxtEvt = { 24366 originalEvent: e, 24367 type: 'cxttapstart', 24368 position: { 24369 x: pos[0], 24370 y: pos[1] 24371 } 24372 }; 24373 24374 if (near) { 24375 near.activate(); 24376 near.emit(cxtEvt); 24377 r.hoverData.down = near; 24378 } else { 24379 cy.emit(cxtEvt); 24380 } 24381 24382 r.hoverData.downTime = new Date().getTime(); 24383 r.hoverData.cxtDragged = false; // Primary button 24384 } else if (e.which == 1) { 24385 if (near) { 24386 near.activate(); 24387 } // Element dragging 24388 24389 24390 { 24391 // If something is under the cursor and it is draggable, prepare to grab it 24392 if (near != null) { 24393 if (r.nodeIsGrabbable(near)) { 24394 var makeEvent = function makeEvent(type) { 24395 return { 24396 originalEvent: e, 24397 type: type, 24398 position: { 24399 x: pos[0], 24400 y: pos[1] 24401 } 24402 }; 24403 }; 24404 24405 var triggerGrab = function triggerGrab(ele) { 24406 ele.emit(makeEvent('grab')); 24407 }; 24408 24409 setGrabTarget(near); 24410 24411 if (!near.selected()) { 24412 draggedElements = r.dragData.possibleDragElements = cy.collection(); 24413 addNodeToDrag(near, { 24414 addToList: draggedElements 24415 }); 24416 near.emit(makeEvent('grabon')).emit(makeEvent('grab')); 24417 } else { 24418 draggedElements = r.dragData.possibleDragElements = cy.collection(); 24419 var selectedNodes = cy.$(function (ele) { 24420 return ele.isNode() && ele.selected() && r.nodeIsGrabbable(ele); 24421 }); 24422 addNodesToDrag(selectedNodes, { 24423 addToList: draggedElements 24424 }); 24425 near.emit(makeEvent('grabon')); 24426 selectedNodes.forEach(triggerGrab); 24427 } 24428 24429 r.redrawHint('eles', true); 24430 r.redrawHint('drag', true); 24431 } 24432 } 24433 24434 r.hoverData.down = near; 24435 r.hoverData.downs = nears; 24436 r.hoverData.downTime = new Date().getTime(); 24437 } 24438 triggerEvents(near, ['mousedown', 'tapstart', 'vmousedown'], e, { 24439 x: pos[0], 24440 y: pos[1] 24441 }); 24442 24443 if (near == null) { 24444 select[4] = 1; 24445 r.data.bgActivePosistion = { 24446 x: pos[0], 24447 y: pos[1] 24448 }; 24449 r.redrawHint('select', true); 24450 r.redraw(); 24451 } else if (near.pannable()) { 24452 select[4] = 1; // for future pan 24453 } 24454 24455 checkForTaphold(); 24456 } // Initialize selection box coordinates 24457 24458 24459 select[0] = select[2] = pos[0]; 24460 select[1] = select[3] = pos[1]; 24461 }, false); 24462 r.registerBinding(window, 'mousemove', function mousemoveHandler(e) { 24463 // eslint-disable-line no-undef 24464 var capture = r.hoverData.capture; 24465 24466 if (!capture && !eventInContainer(e)) { 24467 return; 24468 } 24469 24470 var preventDefault = false; 24471 var cy = r.cy; 24472 var zoom = cy.zoom(); 24473 var gpos = [e.clientX, e.clientY]; 24474 var pos = r.projectIntoViewport(gpos[0], gpos[1]); 24475 var mdownPos = r.hoverData.mdownPos; 24476 var mdownGPos = r.hoverData.mdownGPos; 24477 var select = r.selection; 24478 var near = null; 24479 24480 if (!r.hoverData.draggingEles && !r.hoverData.dragging && !r.hoverData.selecting) { 24481 near = r.findNearestElement(pos[0], pos[1], true, false); 24482 } 24483 24484 var last = r.hoverData.last; 24485 var down = r.hoverData.down; 24486 var disp = [pos[0] - select[2], pos[1] - select[3]]; 24487 var draggedElements = r.dragData.possibleDragElements; 24488 var isOverThresholdDrag; 24489 24490 if (mdownGPos) { 24491 var dx = gpos[0] - mdownGPos[0]; 24492 var dx2 = dx * dx; 24493 var dy = gpos[1] - mdownGPos[1]; 24494 var dy2 = dy * dy; 24495 var dist2 = dx2 + dy2; 24496 r.hoverData.isOverThresholdDrag = isOverThresholdDrag = dist2 >= r.desktopTapThreshold2; 24497 } 24498 24499 var multSelKeyDown = isMultSelKeyDown(e); 24500 24501 if (isOverThresholdDrag) { 24502 r.hoverData.tapholdCancelled = true; 24503 } 24504 24505 var updateDragDelta = function updateDragDelta() { 24506 var dragDelta = r.hoverData.dragDelta = r.hoverData.dragDelta || []; 24507 24508 if (dragDelta.length === 0) { 24509 dragDelta.push(disp[0]); 24510 dragDelta.push(disp[1]); 24511 } else { 24512 dragDelta[0] += disp[0]; 24513 dragDelta[1] += disp[1]; 24514 } 24515 }; 24516 24517 preventDefault = true; 24518 triggerEvents(near, ['mousemove', 'vmousemove', 'tapdrag'], e, { 24519 x: pos[0], 24520 y: pos[1] 24521 }); 24522 24523 var goIntoBoxMode = function goIntoBoxMode() { 24524 r.data.bgActivePosistion = undefined; 24525 24526 if (!r.hoverData.selecting) { 24527 cy.emit({ 24528 originalEvent: e, 24529 type: 'boxstart', 24530 position: { 24531 x: pos[0], 24532 y: pos[1] 24533 } 24534 }); 24535 } 24536 24537 select[4] = 1; 24538 r.hoverData.selecting = true; 24539 r.redrawHint('select', true); 24540 r.redraw(); 24541 }; // trigger context drag if rmouse down 24542 24543 24544 if (r.hoverData.which === 3) { 24545 // but only if over threshold 24546 if (isOverThresholdDrag) { 24547 var cxtEvt = { 24548 originalEvent: e, 24549 type: 'cxtdrag', 24550 position: { 24551 x: pos[0], 24552 y: pos[1] 24553 } 24554 }; 24555 24556 if (down) { 24557 down.emit(cxtEvt); 24558 } else { 24559 cy.emit(cxtEvt); 24560 } 24561 24562 r.hoverData.cxtDragged = true; 24563 24564 if (!r.hoverData.cxtOver || near !== r.hoverData.cxtOver) { 24565 if (r.hoverData.cxtOver) { 24566 r.hoverData.cxtOver.emit({ 24567 originalEvent: e, 24568 type: 'cxtdragout', 24569 position: { 24570 x: pos[0], 24571 y: pos[1] 24572 } 24573 }); 24574 } 24575 24576 r.hoverData.cxtOver = near; 24577 24578 if (near) { 24579 near.emit({ 24580 originalEvent: e, 24581 type: 'cxtdragover', 24582 position: { 24583 x: pos[0], 24584 y: pos[1] 24585 } 24586 }); 24587 } 24588 } 24589 } // Check if we are drag panning the entire graph 24590 24591 } else if (r.hoverData.dragging) { 24592 preventDefault = true; 24593 24594 if (cy.panningEnabled() && cy.userPanningEnabled()) { 24595 var deltaP; 24596 24597 if (r.hoverData.justStartedPan) { 24598 var mdPos = r.hoverData.mdownPos; 24599 deltaP = { 24600 x: (pos[0] - mdPos[0]) * zoom, 24601 y: (pos[1] - mdPos[1]) * zoom 24602 }; 24603 r.hoverData.justStartedPan = false; 24604 } else { 24605 deltaP = { 24606 x: disp[0] * zoom, 24607 y: disp[1] * zoom 24608 }; 24609 } 24610 24611 cy.panBy(deltaP); 24612 r.hoverData.dragged = true; 24613 } // Needs reproject due to pan changing viewport 24614 24615 24616 pos = r.projectIntoViewport(e.clientX, e.clientY); // Checks primary button down & out of time & mouse not moved much 24617 } else if (select[4] == 1 && (down == null || down.pannable())) { 24618 if (isOverThresholdDrag) { 24619 if (!r.hoverData.dragging && cy.boxSelectionEnabled() && (multSelKeyDown || !cy.panningEnabled() || !cy.userPanningEnabled())) { 24620 goIntoBoxMode(); 24621 } else if (!r.hoverData.selecting && cy.panningEnabled() && cy.userPanningEnabled()) { 24622 var allowPassthrough = allowPanningPassthrough(down, r.hoverData.downs); 24623 24624 if (allowPassthrough) { 24625 r.hoverData.dragging = true; 24626 r.hoverData.justStartedPan = true; 24627 select[4] = 0; 24628 r.data.bgActivePosistion = array2point(mdownPos); 24629 r.redrawHint('select', true); 24630 r.redraw(); 24631 } 24632 } 24633 24634 if (down && down.pannable() && down.active()) { 24635 down.unactivate(); 24636 } 24637 } 24638 } else { 24639 if (down && down.pannable() && down.active()) { 24640 down.unactivate(); 24641 } 24642 24643 if ((!down || !down.grabbed()) && near != last) { 24644 if (last) { 24645 triggerEvents(last, ['mouseout', 'tapdragout'], e, { 24646 x: pos[0], 24647 y: pos[1] 24648 }); 24649 } 24650 24651 if (near) { 24652 triggerEvents(near, ['mouseover', 'tapdragover'], e, { 24653 x: pos[0], 24654 y: pos[1] 24655 }); 24656 } 24657 24658 r.hoverData.last = near; 24659 } 24660 24661 if (down) { 24662 if (isOverThresholdDrag) { 24663 // then we can take action 24664 if (cy.boxSelectionEnabled() && multSelKeyDown) { 24665 // then selection overrides 24666 if (down && down.grabbed()) { 24667 freeDraggedElements(draggedElements); 24668 down.emit('freeon'); 24669 draggedElements.emit('free'); 24670 24671 if (r.dragData.didDrag) { 24672 down.emit('dragfreeon'); 24673 draggedElements.emit('dragfree'); 24674 } 24675 } 24676 24677 goIntoBoxMode(); 24678 } else if (down && down.grabbed() && r.nodeIsDraggable(down)) { 24679 // drag node 24680 var justStartedDrag = !r.dragData.didDrag; 24681 24682 if (justStartedDrag) { 24683 r.redrawHint('eles', true); 24684 } 24685 24686 r.dragData.didDrag = true; // indicate that we actually did drag the node 24687 24688 var toTrigger = cy.collection(); // now, add the elements to the drag layer if not done already 24689 24690 if (!r.hoverData.draggingEles) { 24691 addNodesToDrag(draggedElements, { 24692 inDragLayer: true 24693 }); 24694 } 24695 24696 var totalShift = { 24697 x: 0, 24698 y: 0 24699 }; 24700 24701 if (number(disp[0]) && number(disp[1])) { 24702 totalShift.x += disp[0]; 24703 totalShift.y += disp[1]; 24704 24705 if (justStartedDrag) { 24706 var dragDelta = r.hoverData.dragDelta; 24707 24708 if (dragDelta && number(dragDelta[0]) && number(dragDelta[1])) { 24709 totalShift.x += dragDelta[0]; 24710 totalShift.y += dragDelta[1]; 24711 } 24712 } 24713 } 24714 24715 for (var i = 0; i < draggedElements.length; i++) { 24716 var dEle = draggedElements[i]; 24717 24718 if (r.nodeIsDraggable(dEle) && dEle.grabbed()) { 24719 toTrigger.merge(dEle); 24720 } 24721 } 24722 24723 r.hoverData.draggingEles = true; 24724 toTrigger.silentShift(totalShift).emit('position drag'); 24725 r.redrawHint('drag', true); 24726 r.redraw(); 24727 } 24728 } else { 24729 // otherwise save drag delta for when we actually start dragging so the relative grab pos is constant 24730 updateDragDelta(); 24731 } 24732 } // prevent the dragging from triggering text selection on the page 24733 24734 24735 preventDefault = true; 24736 } 24737 24738 select[2] = pos[0]; 24739 select[3] = pos[1]; 24740 24741 if (preventDefault) { 24742 if (e.stopPropagation) e.stopPropagation(); 24743 if (e.preventDefault) e.preventDefault(); 24744 return false; 24745 } 24746 }, false); 24747 r.registerBinding(window, 'mouseup', function mouseupHandler(e) { 24748 // eslint-disable-line no-undef 24749 var capture = r.hoverData.capture; 24750 24751 if (!capture) { 24752 return; 24753 } 24754 24755 r.hoverData.capture = false; 24756 var cy = r.cy; 24757 var pos = r.projectIntoViewport(e.clientX, e.clientY); 24758 var select = r.selection; 24759 var near = r.findNearestElement(pos[0], pos[1], true, false); 24760 var draggedElements = r.dragData.possibleDragElements; 24761 var down = r.hoverData.down; 24762 var multSelKeyDown = isMultSelKeyDown(e); 24763 24764 if (r.data.bgActivePosistion) { 24765 r.redrawHint('select', true); 24766 r.redraw(); 24767 } 24768 24769 r.hoverData.tapholdCancelled = true; 24770 r.data.bgActivePosistion = undefined; // not active bg now 24771 24772 if (down) { 24773 down.unactivate(); 24774 } 24775 24776 if (r.hoverData.which === 3) { 24777 var cxtEvt = { 24778 originalEvent: e, 24779 type: 'cxttapend', 24780 position: { 24781 x: pos[0], 24782 y: pos[1] 24783 } 24784 }; 24785 24786 if (down) { 24787 down.emit(cxtEvt); 24788 } else { 24789 cy.emit(cxtEvt); 24790 } 24791 24792 if (!r.hoverData.cxtDragged) { 24793 var cxtTap = { 24794 originalEvent: e, 24795 type: 'cxttap', 24796 position: { 24797 x: pos[0], 24798 y: pos[1] 24799 } 24800 }; 24801 24802 if (down) { 24803 down.emit(cxtTap); 24804 } else { 24805 cy.emit(cxtTap); 24806 } 24807 } 24808 24809 r.hoverData.cxtDragged = false; 24810 r.hoverData.which = null; 24811 } else if (r.hoverData.which === 1) { 24812 triggerEvents(near, ['mouseup', 'tapend', 'vmouseup'], e, { 24813 x: pos[0], 24814 y: pos[1] 24815 }); 24816 24817 if (!r.dragData.didDrag // didn't move a node around 24818 && !r.hoverData.dragged // didn't pan 24819 && !r.hoverData.selecting // not box selection 24820 && !r.hoverData.isOverThresholdDrag // didn't move too much 24821 ) { 24822 triggerEvents(down, ['click', 'tap', 'vclick'], e, { 24823 x: pos[0], 24824 y: pos[1] 24825 }); 24826 } // Deselect all elements if nothing is currently under the mouse cursor and we aren't dragging something 24827 24828 24829 if (down == null && // not mousedown on node 24830 !r.dragData.didDrag // didn't move the node around 24831 && !r.hoverData.selecting // not box selection 24832 && !r.hoverData.dragged // didn't pan 24833 && !isMultSelKeyDown(e)) { 24834 cy.$(isSelected).unselect(['tapunselect']); 24835 24836 if (draggedElements.length > 0) { 24837 r.redrawHint('eles', true); 24838 } 24839 24840 r.dragData.possibleDragElements = draggedElements = cy.collection(); 24841 } // Single selection 24842 24843 24844 if (near == down && !r.dragData.didDrag && !r.hoverData.selecting) { 24845 if (near != null && near._private.selectable) { 24846 if (r.hoverData.dragging) ; else if (cy.selectionType() === 'additive' || multSelKeyDown) { 24847 if (near.selected()) { 24848 near.unselect(['tapunselect']); 24849 } else { 24850 near.select(['tapselect']); 24851 } 24852 } else { 24853 if (!multSelKeyDown) { 24854 cy.$(isSelected).unmerge(near).unselect(['tapunselect']); 24855 near.select(['tapselect']); 24856 } 24857 } 24858 24859 r.redrawHint('eles', true); 24860 } 24861 } 24862 24863 if (r.hoverData.selecting) { 24864 var box = cy.collection(r.getAllInBox(select[0], select[1], select[2], select[3])); 24865 r.redrawHint('select', true); 24866 24867 if (box.length > 0) { 24868 r.redrawHint('eles', true); 24869 } 24870 24871 cy.emit({ 24872 type: 'boxend', 24873 originalEvent: e, 24874 position: { 24875 x: pos[0], 24876 y: pos[1] 24877 } 24878 }); 24879 24880 var eleWouldBeSelected = function eleWouldBeSelected(ele) { 24881 return ele.selectable() && !ele.selected(); 24882 }; 24883 24884 if (cy.selectionType() === 'additive') { 24885 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 24886 } else { 24887 if (!multSelKeyDown) { 24888 cy.$(isSelected).unmerge(box).unselect(); 24889 } 24890 24891 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 24892 } // always need redraw in case eles unselectable 24893 24894 24895 r.redraw(); 24896 } // Cancel drag pan 24897 24898 24899 if (r.hoverData.dragging) { 24900 r.hoverData.dragging = false; 24901 r.redrawHint('select', true); 24902 r.redrawHint('eles', true); 24903 r.redraw(); 24904 } 24905 24906 if (!select[4]) { 24907 r.redrawHint('drag', true); 24908 r.redrawHint('eles', true); 24909 var downWasGrabbed = down && down.grabbed(); 24910 freeDraggedElements(draggedElements); 24911 24912 if (downWasGrabbed) { 24913 down.emit('freeon'); 24914 draggedElements.emit('free'); 24915 24916 if (r.dragData.didDrag) { 24917 down.emit('dragfreeon'); 24918 draggedElements.emit('dragfree'); 24919 } 24920 } 24921 } 24922 } // else not right mouse 24923 24924 24925 select[4] = 0; 24926 r.hoverData.down = null; 24927 r.hoverData.cxtStarted = false; 24928 r.hoverData.draggingEles = false; 24929 r.hoverData.selecting = false; 24930 r.hoverData.isOverThresholdDrag = false; 24931 r.dragData.didDrag = false; 24932 r.hoverData.dragged = false; 24933 r.hoverData.dragDelta = []; 24934 r.hoverData.mdownPos = null; 24935 r.hoverData.mdownGPos = null; 24936 }, false); 24937 24938 var wheelHandler = function wheelHandler(e) { 24939 if (r.scrollingPage) { 24940 return; 24941 } // while scrolling, ignore wheel-to-zoom 24942 24943 24944 var cy = r.cy; 24945 var pos = r.projectIntoViewport(e.clientX, e.clientY); 24946 var rpos = [pos[0] * cy.zoom() + cy.pan().x, pos[1] * cy.zoom() + cy.pan().y]; 24947 24948 if (r.hoverData.draggingEles || r.hoverData.dragging || r.hoverData.cxtStarted || inBoxSelection()) { 24949 // if pan dragging or cxt dragging, wheel movements make no zoom 24950 e.preventDefault(); 24951 return; 24952 } 24953 24954 if (cy.panningEnabled() && cy.userPanningEnabled() && cy.zoomingEnabled() && cy.userZoomingEnabled()) { 24955 e.preventDefault(); 24956 r.data.wheelZooming = true; 24957 clearTimeout(r.data.wheelTimeout); 24958 r.data.wheelTimeout = setTimeout(function () { 24959 r.data.wheelZooming = false; 24960 r.redrawHint('eles', true); 24961 r.redraw(); 24962 }, 150); 24963 var diff; 24964 24965 if (e.deltaY != null) { 24966 diff = e.deltaY / -250; 24967 } else if (e.wheelDeltaY != null) { 24968 diff = e.wheelDeltaY / 1000; 24969 } else { 24970 diff = e.wheelDelta / 1000; 24971 } 24972 24973 diff = diff * r.wheelSensitivity; 24974 var needsWheelFix = e.deltaMode === 1; 24975 24976 if (needsWheelFix) { 24977 // fixes slow wheel events on ff/linux and ff/windows 24978 diff *= 33; 24979 } 24980 24981 cy.zoom({ 24982 level: cy.zoom() * Math.pow(10, diff), 24983 renderedPosition: { 24984 x: rpos[0], 24985 y: rpos[1] 24986 } 24987 }); 24988 } 24989 }; // Functions to help with whether mouse wheel should trigger zooming 24990 // -- 24991 24992 24993 r.registerBinding(r.container, 'wheel', wheelHandler, true); // disable nonstandard wheel events 24994 // r.registerBinding(r.container, 'mousewheel', wheelHandler, true); 24995 // r.registerBinding(r.container, 'DOMMouseScroll', wheelHandler, true); 24996 // r.registerBinding(r.container, 'MozMousePixelScroll', wheelHandler, true); // older firefox 24997 24998 r.registerBinding(window, 'scroll', function scrollHandler(e) { 24999 // eslint-disable-line no-unused-vars 25000 r.scrollingPage = true; 25001 clearTimeout(r.scrollingPageTimeout); 25002 r.scrollingPageTimeout = setTimeout(function () { 25003 r.scrollingPage = false; 25004 }, 250); 25005 }, true); // Functions to help with handling mouseout/mouseover on the Cytoscape container 25006 // Handle mouseout on Cytoscape container 25007 25008 r.registerBinding(r.container, 'mouseout', function mouseOutHandler(e) { 25009 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25010 r.cy.emit({ 25011 originalEvent: e, 25012 type: 'mouseout', 25013 position: { 25014 x: pos[0], 25015 y: pos[1] 25016 } 25017 }); 25018 }, false); 25019 r.registerBinding(r.container, 'mouseover', function mouseOverHandler(e) { 25020 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25021 r.cy.emit({ 25022 originalEvent: e, 25023 type: 'mouseover', 25024 position: { 25025 x: pos[0], 25026 y: pos[1] 25027 } 25028 }); 25029 }, false); 25030 var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom 25031 25032 var distance1, distance1Sq; // initial distance between finger 1 and finger 2 for pinch-to-zoom 25033 25034 var center1, modelCenter1; // center point on start pinch to zoom 25035 25036 var offsetLeft, offsetTop; 25037 var containerWidth, containerHeight; 25038 var twoFingersStartInside; 25039 25040 var distance = function distance(x1, y1, x2, y2) { 25041 return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); 25042 }; 25043 25044 var distanceSq = function distanceSq(x1, y1, x2, y2) { 25045 return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); 25046 }; 25047 25048 var touchstartHandler; 25049 r.registerBinding(r.container, 'touchstart', touchstartHandler = function touchstartHandler(e) { 25050 if (!eventInContainer(e)) { 25051 return; 25052 } 25053 25054 blurActiveDomElement(); 25055 r.touchData.capture = true; 25056 r.data.bgActivePosistion = undefined; 25057 var cy = r.cy; 25058 var now = r.touchData.now; 25059 var earlier = r.touchData.earlier; 25060 25061 if (e.touches[0]) { 25062 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25063 now[0] = pos[0]; 25064 now[1] = pos[1]; 25065 } 25066 25067 if (e.touches[1]) { 25068 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25069 now[2] = pos[0]; 25070 now[3] = pos[1]; 25071 } 25072 25073 if (e.touches[2]) { 25074 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25075 now[4] = pos[0]; 25076 now[5] = pos[1]; 25077 } // record starting points for pinch-to-zoom 25078 25079 25080 if (e.touches[1]) { 25081 r.touchData.singleTouchMoved = true; 25082 freeDraggedElements(r.dragData.touchDragEles); 25083 var offsets = r.findContainerClientCoords(); 25084 offsetLeft = offsets[0]; 25085 offsetTop = offsets[1]; 25086 containerWidth = offsets[2]; 25087 containerHeight = offsets[3]; 25088 f1x1 = e.touches[0].clientX - offsetLeft; 25089 f1y1 = e.touches[0].clientY - offsetTop; 25090 f2x1 = e.touches[1].clientX - offsetLeft; 25091 f2y1 = e.touches[1].clientY - offsetTop; 25092 twoFingersStartInside = 0 <= f1x1 && f1x1 <= containerWidth && 0 <= f2x1 && f2x1 <= containerWidth && 0 <= f1y1 && f1y1 <= containerHeight && 0 <= f2y1 && f2y1 <= containerHeight; 25093 var pan = cy.pan(); 25094 var zoom = cy.zoom(); 25095 distance1 = distance(f1x1, f1y1, f2x1, f2y1); 25096 distance1Sq = distanceSq(f1x1, f1y1, f2x1, f2y1); 25097 center1 = [(f1x1 + f2x1) / 2, (f1y1 + f2y1) / 2]; 25098 modelCenter1 = [(center1[0] - pan.x) / zoom, (center1[1] - pan.y) / zoom]; // consider context tap 25099 25100 var cxtDistThreshold = 200; 25101 var cxtDistThresholdSq = cxtDistThreshold * cxtDistThreshold; 25102 25103 if (distance1Sq < cxtDistThresholdSq && !e.touches[2]) { 25104 var near1 = r.findNearestElement(now[0], now[1], true, true); 25105 var near2 = r.findNearestElement(now[2], now[3], true, true); 25106 25107 if (near1 && near1.isNode()) { 25108 near1.activate().emit({ 25109 originalEvent: e, 25110 type: 'cxttapstart', 25111 position: { 25112 x: now[0], 25113 y: now[1] 25114 } 25115 }); 25116 r.touchData.start = near1; 25117 } else if (near2 && near2.isNode()) { 25118 near2.activate().emit({ 25119 originalEvent: e, 25120 type: 'cxttapstart', 25121 position: { 25122 x: now[0], 25123 y: now[1] 25124 } 25125 }); 25126 r.touchData.start = near2; 25127 } else { 25128 cy.emit({ 25129 originalEvent: e, 25130 type: 'cxttapstart', 25131 position: { 25132 x: now[0], 25133 y: now[1] 25134 } 25135 }); 25136 } 25137 25138 if (r.touchData.start) { 25139 r.touchData.start._private.grabbed = false; 25140 } 25141 25142 r.touchData.cxt = true; 25143 r.touchData.cxtDragged = false; 25144 r.data.bgActivePosistion = undefined; 25145 r.redraw(); 25146 return; 25147 } 25148 } 25149 25150 if (e.touches[2]) { 25151 // ignore 25152 // safari on ios pans the page otherwise (normally you should be able to preventdefault on touchmove...) 25153 if (cy.boxSelectionEnabled()) { 25154 e.preventDefault(); 25155 } 25156 } else if (e.touches[1]) ; else if (e.touches[0]) { 25157 var nears = r.findNearestElements(now[0], now[1], true, true); 25158 var near = nears[0]; 25159 25160 if (near != null) { 25161 near.activate(); 25162 r.touchData.start = near; 25163 r.touchData.starts = nears; 25164 25165 if (r.nodeIsGrabbable(near)) { 25166 var draggedEles = r.dragData.touchDragEles = cy.collection(); 25167 var selectedNodes = null; 25168 r.redrawHint('eles', true); 25169 r.redrawHint('drag', true); 25170 25171 if (near.selected()) { 25172 // reset drag elements, since near will be added again 25173 selectedNodes = cy.$(function (ele) { 25174 return ele.selected() && r.nodeIsGrabbable(ele); 25175 }); 25176 addNodesToDrag(selectedNodes, { 25177 addToList: draggedEles 25178 }); 25179 } else { 25180 addNodeToDrag(near, { 25181 addToList: draggedEles 25182 }); 25183 } 25184 25185 setGrabTarget(near); 25186 25187 var makeEvent = function makeEvent(type) { 25188 return { 25189 originalEvent: e, 25190 type: type, 25191 position: { 25192 x: now[0], 25193 y: now[1] 25194 } 25195 }; 25196 }; 25197 25198 near.emit(makeEvent('grabon')); 25199 25200 if (selectedNodes) { 25201 selectedNodes.forEach(function (n) { 25202 n.emit(makeEvent('grab')); 25203 }); 25204 } else { 25205 near.emit(makeEvent('grab')); 25206 } 25207 } 25208 } 25209 25210 triggerEvents(near, ['touchstart', 'tapstart', 'vmousedown'], e, { 25211 x: now[0], 25212 y: now[1] 25213 }); 25214 25215 if (near == null) { 25216 r.data.bgActivePosistion = { 25217 x: pos[0], 25218 y: pos[1] 25219 }; 25220 r.redrawHint('select', true); 25221 r.redraw(); 25222 } // Tap, taphold 25223 // ----- 25224 25225 25226 r.touchData.singleTouchMoved = false; 25227 r.touchData.singleTouchStartTime = +new Date(); 25228 clearTimeout(r.touchData.tapholdTimeout); 25229 r.touchData.tapholdTimeout = setTimeout(function () { 25230 if (r.touchData.singleTouchMoved === false && !r.pinching // if pinching, then taphold unselect shouldn't take effect 25231 && !r.touchData.selecting // box selection shouldn't allow taphold through 25232 ) { 25233 triggerEvents(r.touchData.start, ['taphold'], e, { 25234 x: now[0], 25235 y: now[1] 25236 }); 25237 } 25238 }, r.tapholdDuration); 25239 } 25240 25241 if (e.touches.length >= 1) { 25242 var sPos = r.touchData.startPosition = []; 25243 25244 for (var i = 0; i < now.length; i++) { 25245 sPos[i] = earlier[i] = now[i]; 25246 } 25247 25248 var touch0 = e.touches[0]; 25249 r.touchData.startGPosition = [touch0.clientX, touch0.clientY]; 25250 } 25251 }, false); 25252 var touchmoveHandler; 25253 r.registerBinding(window, 'touchmove', touchmoveHandler = function touchmoveHandler(e) { 25254 // eslint-disable-line no-undef 25255 var capture = r.touchData.capture; 25256 25257 if (!capture && !eventInContainer(e)) { 25258 return; 25259 } 25260 25261 var select = r.selection; 25262 var cy = r.cy; 25263 var now = r.touchData.now; 25264 var earlier = r.touchData.earlier; 25265 var zoom = cy.zoom(); 25266 25267 if (e.touches[0]) { 25268 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25269 now[0] = pos[0]; 25270 now[1] = pos[1]; 25271 } 25272 25273 if (e.touches[1]) { 25274 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25275 now[2] = pos[0]; 25276 now[3] = pos[1]; 25277 } 25278 25279 if (e.touches[2]) { 25280 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25281 now[4] = pos[0]; 25282 now[5] = pos[1]; 25283 } 25284 25285 var startGPos = r.touchData.startGPosition; 25286 var isOverThresholdDrag; 25287 25288 if (capture && e.touches[0] && startGPos) { 25289 var disp = []; 25290 25291 for (var j = 0; j < now.length; j++) { 25292 disp[j] = now[j] - earlier[j]; 25293 } 25294 25295 var dx = e.touches[0].clientX - startGPos[0]; 25296 var dx2 = dx * dx; 25297 var dy = e.touches[0].clientY - startGPos[1]; 25298 var dy2 = dy * dy; 25299 var dist2 = dx2 + dy2; 25300 isOverThresholdDrag = dist2 >= r.touchTapThreshold2; 25301 } // context swipe cancelling 25302 25303 25304 if (capture && r.touchData.cxt) { 25305 e.preventDefault(); 25306 var f1x2 = e.touches[0].clientX - offsetLeft, 25307 f1y2 = e.touches[0].clientY - offsetTop; 25308 var f2x2 = e.touches[1].clientX - offsetLeft, 25309 f2y2 = e.touches[1].clientY - offsetTop; // var distance2 = distance( f1x2, f1y2, f2x2, f2y2 ); 25310 25311 var distance2Sq = distanceSq(f1x2, f1y2, f2x2, f2y2); 25312 var factorSq = distance2Sq / distance1Sq; 25313 var distThreshold = 150; 25314 var distThresholdSq = distThreshold * distThreshold; 25315 var factorThreshold = 1.5; 25316 var factorThresholdSq = factorThreshold * factorThreshold; // cancel ctx gestures if the distance b/t the fingers increases 25317 25318 if (factorSq >= factorThresholdSq || distance2Sq >= distThresholdSq) { 25319 r.touchData.cxt = false; 25320 r.data.bgActivePosistion = undefined; 25321 r.redrawHint('select', true); 25322 var cxtEvt = { 25323 originalEvent: e, 25324 type: 'cxttapend', 25325 position: { 25326 x: now[0], 25327 y: now[1] 25328 } 25329 }; 25330 25331 if (r.touchData.start) { 25332 r.touchData.start.unactivate().emit(cxtEvt); 25333 r.touchData.start = null; 25334 } else { 25335 cy.emit(cxtEvt); 25336 } 25337 } 25338 } // context swipe 25339 25340 25341 if (capture && r.touchData.cxt) { 25342 var cxtEvt = { 25343 originalEvent: e, 25344 type: 'cxtdrag', 25345 position: { 25346 x: now[0], 25347 y: now[1] 25348 } 25349 }; 25350 r.data.bgActivePosistion = undefined; 25351 r.redrawHint('select', true); 25352 25353 if (r.touchData.start) { 25354 r.touchData.start.emit(cxtEvt); 25355 } else { 25356 cy.emit(cxtEvt); 25357 } 25358 25359 if (r.touchData.start) { 25360 r.touchData.start._private.grabbed = false; 25361 } 25362 25363 r.touchData.cxtDragged = true; 25364 var near = r.findNearestElement(now[0], now[1], true, true); 25365 25366 if (!r.touchData.cxtOver || near !== r.touchData.cxtOver) { 25367 if (r.touchData.cxtOver) { 25368 r.touchData.cxtOver.emit({ 25369 originalEvent: e, 25370 type: 'cxtdragout', 25371 position: { 25372 x: now[0], 25373 y: now[1] 25374 } 25375 }); 25376 } 25377 25378 r.touchData.cxtOver = near; 25379 25380 if (near) { 25381 near.emit({ 25382 originalEvent: e, 25383 type: 'cxtdragover', 25384 position: { 25385 x: now[0], 25386 y: now[1] 25387 } 25388 }); 25389 } 25390 } // box selection 25391 25392 } else if (capture && e.touches[2] && cy.boxSelectionEnabled()) { 25393 e.preventDefault(); 25394 r.data.bgActivePosistion = undefined; 25395 this.lastThreeTouch = +new Date(); 25396 25397 if (!r.touchData.selecting) { 25398 cy.emit({ 25399 originalEvent: e, 25400 type: 'boxstart', 25401 position: { 25402 x: now[0], 25403 y: now[1] 25404 } 25405 }); 25406 } 25407 25408 r.touchData.selecting = true; 25409 r.touchData.didSelect = true; 25410 select[4] = 1; 25411 25412 if (!select || select.length === 0 || select[0] === undefined) { 25413 select[0] = (now[0] + now[2] + now[4]) / 3; 25414 select[1] = (now[1] + now[3] + now[5]) / 3; 25415 select[2] = (now[0] + now[2] + now[4]) / 3 + 1; 25416 select[3] = (now[1] + now[3] + now[5]) / 3 + 1; 25417 } else { 25418 select[2] = (now[0] + now[2] + now[4]) / 3; 25419 select[3] = (now[1] + now[3] + now[5]) / 3; 25420 } 25421 25422 r.redrawHint('select', true); 25423 r.redraw(); // pinch to zoom 25424 } else if (capture && e.touches[1] && !r.touchData.didSelect // don't allow box selection to degrade to pinch-to-zoom 25425 && cy.zoomingEnabled() && cy.panningEnabled() && cy.userZoomingEnabled() && cy.userPanningEnabled()) { 25426 // two fingers => pinch to zoom 25427 e.preventDefault(); 25428 r.data.bgActivePosistion = undefined; 25429 r.redrawHint('select', true); 25430 var draggedEles = r.dragData.touchDragEles; 25431 25432 if (draggedEles) { 25433 r.redrawHint('drag', true); 25434 25435 for (var i = 0; i < draggedEles.length; i++) { 25436 var de_p = draggedEles[i]._private; 25437 de_p.grabbed = false; 25438 de_p.rscratch.inDragLayer = false; 25439 } 25440 } 25441 25442 var _start = r.touchData.start; // (x2, y2) for fingers 1 and 2 25443 25444 var f1x2 = e.touches[0].clientX - offsetLeft, 25445 f1y2 = e.touches[0].clientY - offsetTop; 25446 var f2x2 = e.touches[1].clientX - offsetLeft, 25447 f2y2 = e.touches[1].clientY - offsetTop; 25448 var distance2 = distance(f1x2, f1y2, f2x2, f2y2); // var distance2Sq = distanceSq( f1x2, f1y2, f2x2, f2y2 ); 25449 // var factor = Math.sqrt( distance2Sq ) / Math.sqrt( distance1Sq ); 25450 25451 var factor = distance2 / distance1; 25452 25453 if (twoFingersStartInside) { 25454 // delta finger1 25455 var df1x = f1x2 - f1x1; 25456 var df1y = f1y2 - f1y1; // delta finger 2 25457 25458 var df2x = f2x2 - f2x1; 25459 var df2y = f2y2 - f2y1; // translation is the normalised vector of the two fingers movement 25460 // i.e. so pinching cancels out and moving together pans 25461 25462 var tx = (df1x + df2x) / 2; 25463 var ty = (df1y + df2y) / 2; // now calculate the zoom 25464 25465 var zoom1 = cy.zoom(); 25466 var zoom2 = zoom1 * factor; 25467 var pan1 = cy.pan(); // the model center point converted to the current rendered pos 25468 25469 var ctrx = modelCenter1[0] * zoom1 + pan1.x; 25470 var ctry = modelCenter1[1] * zoom1 + pan1.y; 25471 var pan2 = { 25472 x: -zoom2 / zoom1 * (ctrx - pan1.x - tx) + ctrx, 25473 y: -zoom2 / zoom1 * (ctry - pan1.y - ty) + ctry 25474 }; // remove dragged eles 25475 25476 if (_start && _start.active()) { 25477 var draggedEles = r.dragData.touchDragEles; 25478 freeDraggedElements(draggedEles); 25479 r.redrawHint('drag', true); 25480 r.redrawHint('eles', true); 25481 25482 _start.unactivate().emit('freeon'); 25483 25484 draggedEles.emit('free'); 25485 25486 if (r.dragData.didDrag) { 25487 _start.emit('dragfreeon'); 25488 25489 draggedEles.emit('dragfree'); 25490 } 25491 } 25492 25493 cy.viewport({ 25494 zoom: zoom2, 25495 pan: pan2, 25496 cancelOnFailedZoom: true 25497 }); 25498 distance1 = distance2; 25499 f1x1 = f1x2; 25500 f1y1 = f1y2; 25501 f2x1 = f2x2; 25502 f2y1 = f2y2; 25503 r.pinching = true; 25504 } // Re-project 25505 25506 25507 if (e.touches[0]) { 25508 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25509 now[0] = pos[0]; 25510 now[1] = pos[1]; 25511 } 25512 25513 if (e.touches[1]) { 25514 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25515 now[2] = pos[0]; 25516 now[3] = pos[1]; 25517 } 25518 25519 if (e.touches[2]) { 25520 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25521 now[4] = pos[0]; 25522 now[5] = pos[1]; 25523 } 25524 } else if (e.touches[0] && !r.touchData.didSelect // don't allow box selection to degrade to single finger events like panning 25525 ) { 25526 var start = r.touchData.start; 25527 var last = r.touchData.last; 25528 var near; 25529 25530 if (!r.hoverData.draggingEles && !r.swipePanning) { 25531 near = r.findNearestElement(now[0], now[1], true, true); 25532 } 25533 25534 if (capture && start != null) { 25535 e.preventDefault(); 25536 } // dragging nodes 25537 25538 25539 if (capture && start != null && r.nodeIsDraggable(start)) { 25540 if (isOverThresholdDrag) { 25541 // then dragging can happen 25542 var draggedEles = r.dragData.touchDragEles; 25543 var justStartedDrag = !r.dragData.didDrag; 25544 25545 if (justStartedDrag) { 25546 addNodesToDrag(draggedEles, { 25547 inDragLayer: true 25548 }); 25549 } 25550 25551 r.dragData.didDrag = true; 25552 var totalShift = { 25553 x: 0, 25554 y: 0 25555 }; 25556 25557 if (number(disp[0]) && number(disp[1])) { 25558 totalShift.x += disp[0]; 25559 totalShift.y += disp[1]; 25560 25561 if (justStartedDrag) { 25562 r.redrawHint('eles', true); 25563 var dragDelta = r.touchData.dragDelta; 25564 25565 if (dragDelta && number(dragDelta[0]) && number(dragDelta[1])) { 25566 totalShift.x += dragDelta[0]; 25567 totalShift.y += dragDelta[1]; 25568 } 25569 } 25570 } 25571 25572 r.hoverData.draggingEles = true; 25573 draggedEles.silentShift(totalShift).emit('position drag'); 25574 r.redrawHint('drag', true); 25575 25576 if (r.touchData.startPosition[0] == earlier[0] && r.touchData.startPosition[1] == earlier[1]) { 25577 r.redrawHint('eles', true); 25578 } 25579 25580 r.redraw(); 25581 } else { 25582 // otherise keep track of drag delta for later 25583 var dragDelta = r.touchData.dragDelta = r.touchData.dragDelta || []; 25584 25585 if (dragDelta.length === 0) { 25586 dragDelta.push(disp[0]); 25587 dragDelta.push(disp[1]); 25588 } else { 25589 dragDelta[0] += disp[0]; 25590 dragDelta[1] += disp[1]; 25591 } 25592 } 25593 } // touchmove 25594 25595 25596 { 25597 triggerEvents(start || near, ['touchmove', 'tapdrag', 'vmousemove'], e, { 25598 x: now[0], 25599 y: now[1] 25600 }); 25601 25602 if ((!start || !start.grabbed()) && near != last) { 25603 if (last) { 25604 last.emit({ 25605 originalEvent: e, 25606 type: 'tapdragout', 25607 position: { 25608 x: now[0], 25609 y: now[1] 25610 } 25611 }); 25612 } 25613 25614 if (near) { 25615 near.emit({ 25616 originalEvent: e, 25617 type: 'tapdragover', 25618 position: { 25619 x: now[0], 25620 y: now[1] 25621 } 25622 }); 25623 } 25624 } 25625 25626 r.touchData.last = near; 25627 } // check to cancel taphold 25628 25629 if (capture) { 25630 for (var i = 0; i < now.length; i++) { 25631 if (now[i] && r.touchData.startPosition[i] && isOverThresholdDrag) { 25632 r.touchData.singleTouchMoved = true; 25633 } 25634 } 25635 } // panning 25636 25637 25638 if (capture && (start == null || start.pannable()) && cy.panningEnabled() && cy.userPanningEnabled()) { 25639 var allowPassthrough = allowPanningPassthrough(start, r.touchData.starts); 25640 25641 if (allowPassthrough) { 25642 e.preventDefault(); 25643 25644 if (!r.data.bgActivePosistion) { 25645 r.data.bgActivePosistion = array2point(r.touchData.startPosition); 25646 } 25647 25648 if (r.swipePanning) { 25649 cy.panBy({ 25650 x: disp[0] * zoom, 25651 y: disp[1] * zoom 25652 }); 25653 } else if (isOverThresholdDrag) { 25654 r.swipePanning = true; 25655 cy.panBy({ 25656 x: dx * zoom, 25657 y: dy * zoom 25658 }); 25659 25660 if (start) { 25661 start.unactivate(); 25662 r.redrawHint('select', true); 25663 r.touchData.start = null; 25664 } 25665 } 25666 } // Re-project 25667 25668 25669 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25670 now[0] = pos[0]; 25671 now[1] = pos[1]; 25672 } 25673 } 25674 25675 for (var j = 0; j < now.length; j++) { 25676 earlier[j] = now[j]; 25677 } // the active bg indicator should be removed when making a swipe that is neither for dragging nodes or panning 25678 25679 25680 if (capture && e.touches.length > 0 && !r.hoverData.draggingEles && !r.swipePanning && r.data.bgActivePosistion != null) { 25681 r.data.bgActivePosistion = undefined; 25682 r.redrawHint('select', true); 25683 r.redraw(); 25684 } 25685 }, false); 25686 var touchcancelHandler; 25687 r.registerBinding(window, 'touchcancel', touchcancelHandler = function touchcancelHandler(e) { 25688 // eslint-disable-line no-unused-vars 25689 var start = r.touchData.start; 25690 r.touchData.capture = false; 25691 25692 if (start) { 25693 start.unactivate(); 25694 } 25695 }); 25696 var touchendHandler; 25697 r.registerBinding(window, 'touchend', touchendHandler = function touchendHandler(e) { 25698 // eslint-disable-line no-unused-vars 25699 var start = r.touchData.start; 25700 var capture = r.touchData.capture; 25701 25702 if (capture) { 25703 if (e.touches.length === 0) { 25704 r.touchData.capture = false; 25705 } 25706 25707 e.preventDefault(); 25708 } else { 25709 return; 25710 } 25711 25712 var select = r.selection; 25713 r.swipePanning = false; 25714 r.hoverData.draggingEles = false; 25715 var cy = r.cy; 25716 var zoom = cy.zoom(); 25717 var now = r.touchData.now; 25718 var earlier = r.touchData.earlier; 25719 25720 if (e.touches[0]) { 25721 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25722 now[0] = pos[0]; 25723 now[1] = pos[1]; 25724 } 25725 25726 if (e.touches[1]) { 25727 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25728 now[2] = pos[0]; 25729 now[3] = pos[1]; 25730 } 25731 25732 if (e.touches[2]) { 25733 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25734 now[4] = pos[0]; 25735 now[5] = pos[1]; 25736 } 25737 25738 if (start) { 25739 start.unactivate(); 25740 } 25741 25742 var ctxTapend; 25743 25744 if (r.touchData.cxt) { 25745 ctxTapend = { 25746 originalEvent: e, 25747 type: 'cxttapend', 25748 position: { 25749 x: now[0], 25750 y: now[1] 25751 } 25752 }; 25753 25754 if (start) { 25755 start.emit(ctxTapend); 25756 } else { 25757 cy.emit(ctxTapend); 25758 } 25759 25760 if (!r.touchData.cxtDragged) { 25761 var ctxTap = { 25762 originalEvent: e, 25763 type: 'cxttap', 25764 position: { 25765 x: now[0], 25766 y: now[1] 25767 } 25768 }; 25769 25770 if (start) { 25771 start.emit(ctxTap); 25772 } else { 25773 cy.emit(ctxTap); 25774 } 25775 } 25776 25777 if (r.touchData.start) { 25778 r.touchData.start._private.grabbed = false; 25779 } 25780 25781 r.touchData.cxt = false; 25782 r.touchData.start = null; 25783 r.redraw(); 25784 return; 25785 } // no more box selection if we don't have three fingers 25786 25787 25788 if (!e.touches[2] && cy.boxSelectionEnabled() && r.touchData.selecting) { 25789 r.touchData.selecting = false; 25790 var box = cy.collection(r.getAllInBox(select[0], select[1], select[2], select[3])); 25791 select[0] = undefined; 25792 select[1] = undefined; 25793 select[2] = undefined; 25794 select[3] = undefined; 25795 select[4] = 0; 25796 r.redrawHint('select', true); 25797 cy.emit({ 25798 type: 'boxend', 25799 originalEvent: e, 25800 position: { 25801 x: now[0], 25802 y: now[1] 25803 } 25804 }); 25805 25806 var eleWouldBeSelected = function eleWouldBeSelected(ele) { 25807 return ele.selectable() && !ele.selected(); 25808 }; 25809 25810 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 25811 25812 if (box.nonempty()) { 25813 r.redrawHint('eles', true); 25814 } 25815 25816 r.redraw(); 25817 } 25818 25819 if (start != null) { 25820 start.unactivate(); 25821 } 25822 25823 if (e.touches[2]) { 25824 r.data.bgActivePosistion = undefined; 25825 r.redrawHint('select', true); 25826 } else if (e.touches[1]) ; else if (e.touches[0]) ; else if (!e.touches[0]) { 25827 r.data.bgActivePosistion = undefined; 25828 r.redrawHint('select', true); 25829 var draggedEles = r.dragData.touchDragEles; 25830 25831 if (start != null) { 25832 var startWasGrabbed = start._private.grabbed; 25833 freeDraggedElements(draggedEles); 25834 r.redrawHint('drag', true); 25835 r.redrawHint('eles', true); 25836 25837 if (startWasGrabbed) { 25838 start.emit('freeon'); 25839 draggedEles.emit('free'); 25840 25841 if (r.dragData.didDrag) { 25842 start.emit('dragfreeon'); 25843 draggedEles.emit('dragfree'); 25844 } 25845 } 25846 25847 triggerEvents(start, ['touchend', 'tapend', 'vmouseup', 'tapdragout'], e, { 25848 x: now[0], 25849 y: now[1] 25850 }); 25851 start.unactivate(); 25852 r.touchData.start = null; 25853 } else { 25854 var near = r.findNearestElement(now[0], now[1], true, true); 25855 triggerEvents(near, ['touchend', 'tapend', 'vmouseup', 'tapdragout'], e, { 25856 x: now[0], 25857 y: now[1] 25858 }); 25859 } 25860 25861 var dx = r.touchData.startPosition[0] - now[0]; 25862 var dx2 = dx * dx; 25863 var dy = r.touchData.startPosition[1] - now[1]; 25864 var dy2 = dy * dy; 25865 var dist2 = dx2 + dy2; 25866 var rdist2 = dist2 * zoom * zoom; // Tap event, roughly same as mouse click event for touch 25867 25868 if (!r.touchData.singleTouchMoved) { 25869 if (!start) { 25870 cy.$(':selected').unselect(['tapunselect']); 25871 } 25872 25873 triggerEvents(start, ['tap', 'vclick'], e, { 25874 x: now[0], 25875 y: now[1] 25876 }); 25877 } // Prepare to select the currently touched node, only if it hasn't been dragged past a certain distance 25878 25879 25880 if (start != null && !r.dragData.didDrag // didn't drag nodes around 25881 && start._private.selectable && rdist2 < r.touchTapThreshold2 && !r.pinching // pinch to zoom should not affect selection 25882 ) { 25883 if (cy.selectionType() === 'single') { 25884 cy.$(isSelected).unmerge(start).unselect(['tapunselect']); 25885 start.select(['tapselect']); 25886 } else { 25887 if (start.selected()) { 25888 start.unselect(['tapunselect']); 25889 } else { 25890 start.select(['tapselect']); 25891 } 25892 } 25893 25894 r.redrawHint('eles', true); 25895 } 25896 25897 r.touchData.singleTouchMoved = true; 25898 } 25899 25900 for (var j = 0; j < now.length; j++) { 25901 earlier[j] = now[j]; 25902 } 25903 25904 r.dragData.didDrag = false; // reset for next touchstart 25905 25906 if (e.touches.length === 0) { 25907 r.touchData.dragDelta = []; 25908 r.touchData.startPosition = null; 25909 r.touchData.startGPosition = null; 25910 r.touchData.didSelect = false; 25911 } 25912 25913 if (e.touches.length < 2) { 25914 if (e.touches.length === 1) { 25915 // the old start global pos'n may not be the same finger that remains 25916 r.touchData.startGPosition = [e.touches[0].clientX, e.touches[0].clientY]; 25917 } 25918 25919 r.pinching = false; 25920 r.redrawHint('eles', true); 25921 r.redraw(); 25922 } //r.redraw(); 25923 25924 }, false); // fallback compatibility layer for ms pointer events 25925 25926 if (typeof TouchEvent === 'undefined') { 25927 var pointers = []; 25928 25929 var makeTouch = function makeTouch(e) { 25930 return { 25931 clientX: e.clientX, 25932 clientY: e.clientY, 25933 force: 1, 25934 identifier: e.pointerId, 25935 pageX: e.pageX, 25936 pageY: e.pageY, 25937 radiusX: e.width / 2, 25938 radiusY: e.height / 2, 25939 screenX: e.screenX, 25940 screenY: e.screenY, 25941 target: e.target 25942 }; 25943 }; 25944 25945 var makePointer = function makePointer(e) { 25946 return { 25947 event: e, 25948 touch: makeTouch(e) 25949 }; 25950 }; 25951 25952 var addPointer = function addPointer(e) { 25953 pointers.push(makePointer(e)); 25954 }; 25955 25956 var removePointer = function removePointer(e) { 25957 for (var i = 0; i < pointers.length; i++) { 25958 var p = pointers[i]; 25959 25960 if (p.event.pointerId === e.pointerId) { 25961 pointers.splice(i, 1); 25962 return; 25963 } 25964 } 25965 }; 25966 25967 var updatePointer = function updatePointer(e) { 25968 var p = pointers.filter(function (p) { 25969 return p.event.pointerId === e.pointerId; 25970 })[0]; 25971 p.event = e; 25972 p.touch = makeTouch(e); 25973 }; 25974 25975 var addTouchesToEvent = function addTouchesToEvent(e) { 25976 e.touches = pointers.map(function (p) { 25977 return p.touch; 25978 }); 25979 }; 25980 25981 var pointerIsMouse = function pointerIsMouse(e) { 25982 return e.pointerType === 'mouse' || e.pointerType === 4; 25983 }; 25984 25985 r.registerBinding(r.container, 'pointerdown', function (e) { 25986 if (pointerIsMouse(e)) { 25987 return; 25988 } // mouse already handled 25989 25990 25991 e.preventDefault(); 25992 addPointer(e); 25993 addTouchesToEvent(e); 25994 touchstartHandler(e); 25995 }); 25996 r.registerBinding(r.container, 'pointerup', function (e) { 25997 if (pointerIsMouse(e)) { 25998 return; 25999 } // mouse already handled 26000 26001 26002 removePointer(e); 26003 addTouchesToEvent(e); 26004 touchendHandler(e); 26005 }); 26006 r.registerBinding(r.container, 'pointercancel', function (e) { 26007 if (pointerIsMouse(e)) { 26008 return; 26009 } // mouse already handled 26010 26011 26012 removePointer(e); 26013 addTouchesToEvent(e); 26014 touchcancelHandler(e); 26015 }); 26016 r.registerBinding(r.container, 'pointermove', function (e) { 26017 if (pointerIsMouse(e)) { 26018 return; 26019 } // mouse already handled 26020 26021 26022 e.preventDefault(); 26023 updatePointer(e); 26024 addTouchesToEvent(e); 26025 touchmoveHandler(e); 26026 }); 26027 } 26028 }; 26029 26030 var BRp$d = {}; 26031 26032 BRp$d.generatePolygon = function (name, points) { 26033 return this.nodeShapes[name] = { 26034 renderer: this, 26035 name: name, 26036 points: points, 26037 draw: function draw(context, centerX, centerY, width, height) { 26038 this.renderer.nodeShapeImpl('polygon', context, centerX, centerY, width, height, this.points); 26039 }, 26040 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26041 return polygonIntersectLine(x, y, this.points, nodeX, nodeY, width / 2, height / 2, padding); 26042 }, 26043 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26044 return pointInsidePolygon(x, y, this.points, centerX, centerY, width, height, [0, -1], padding); 26045 } 26046 }; 26047 }; 26048 26049 BRp$d.generateEllipse = function () { 26050 return this.nodeShapes['ellipse'] = { 26051 renderer: this, 26052 name: 'ellipse', 26053 draw: function draw(context, centerX, centerY, width, height) { 26054 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26055 }, 26056 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26057 return intersectLineEllipse(x, y, nodeX, nodeY, width / 2 + padding, height / 2 + padding); 26058 }, 26059 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26060 return checkInEllipse(x, y, width, height, centerX, centerY, padding); 26061 } 26062 }; 26063 }; 26064 26065 BRp$d.generateRoundPolygon = function (name, points) { 26066 // Pre-compute control points 26067 // Since these points depend on the radius length (which in turns depend on the width/height of the node) we will only pre-compute 26068 // the unit vectors. 26069 // For simplicity the layout will be: 26070 // [ p0, UnitVectorP0P1, p1, UniVectorP1P2, ..., pn, UnitVectorPnP0 ] 26071 var allPoints = new Array(points.length * 2); 26072 26073 for (var i = 0; i < points.length / 2; i++) { 26074 var sourceIndex = i * 2; 26075 var destIndex = void 0; 26076 26077 if (i < points.length / 2 - 1) { 26078 destIndex = (i + 1) * 2; 26079 } else { 26080 destIndex = 0; 26081 } 26082 26083 allPoints[i * 4] = points[sourceIndex]; 26084 allPoints[i * 4 + 1] = points[sourceIndex + 1]; 26085 var xDest = points[destIndex] - points[sourceIndex]; 26086 var yDest = points[destIndex + 1] - points[sourceIndex + 1]; 26087 var norm = Math.sqrt(xDest * xDest + yDest * yDest); 26088 allPoints[i * 4 + 2] = xDest / norm; 26089 allPoints[i * 4 + 3] = yDest / norm; 26090 } 26091 26092 return this.nodeShapes[name] = { 26093 renderer: this, 26094 name: name, 26095 points: allPoints, 26096 draw: function draw(context, centerX, centerY, width, height) { 26097 this.renderer.nodeShapeImpl('round-polygon', context, centerX, centerY, width, height, this.points); 26098 }, 26099 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26100 return roundPolygonIntersectLine(x, y, this.points, nodeX, nodeY, width, height); 26101 }, 26102 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26103 return pointInsideRoundPolygon(x, y, this.points, centerX, centerY, width, height); 26104 } 26105 }; 26106 }; 26107 26108 BRp$d.generateRoundRectangle = function () { 26109 return this.nodeShapes['round-rectangle'] = this.nodeShapes['roundrectangle'] = { 26110 renderer: this, 26111 name: 'round-rectangle', 26112 points: generateUnitNgonPointsFitToSquare(4, 0), 26113 draw: function draw(context, centerX, centerY, width, height) { 26114 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26115 }, 26116 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26117 return roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding); 26118 }, 26119 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26120 var cornerRadius = getRoundRectangleRadius(width, height); 26121 var diam = cornerRadius * 2; // Check hBox 26122 26123 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - diam, [0, -1], padding)) { 26124 return true; 26125 } // Check vBox 26126 26127 26128 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - diam, height, [0, -1], padding)) { 26129 return true; 26130 } // Check top left quarter circle 26131 26132 26133 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY - height / 2 + cornerRadius, padding)) { 26134 return true; 26135 } // Check top right quarter circle 26136 26137 26138 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY - height / 2 + cornerRadius, padding)) { 26139 return true; 26140 } // Check bottom right quarter circle 26141 26142 26143 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26144 return true; 26145 } // Check bottom left quarter circle 26146 26147 26148 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26149 return true; 26150 } 26151 26152 return false; 26153 } 26154 }; 26155 }; 26156 26157 BRp$d.generateCutRectangle = function () { 26158 return this.nodeShapes['cut-rectangle'] = this.nodeShapes['cutrectangle'] = { 26159 renderer: this, 26160 name: 'cut-rectangle', 26161 cornerLength: getCutRectangleCornerLength(), 26162 points: generateUnitNgonPointsFitToSquare(4, 0), 26163 draw: function draw(context, centerX, centerY, width, height) { 26164 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26165 }, 26166 generateCutTrianglePts: function generateCutTrianglePts(width, height, centerX, centerY) { 26167 var cl = this.cornerLength; 26168 var hh = height / 2; 26169 var hw = width / 2; 26170 var xBegin = centerX - hw; 26171 var xEnd = centerX + hw; 26172 var yBegin = centerY - hh; 26173 var yEnd = centerY + hh; // points are in clockwise order, inner (imaginary) triangle pt on [4, 5] 26174 26175 return { 26176 topLeft: [xBegin, yBegin + cl, xBegin + cl, yBegin, xBegin + cl, yBegin + cl], 26177 topRight: [xEnd - cl, yBegin, xEnd, yBegin + cl, xEnd - cl, yBegin + cl], 26178 bottomRight: [xEnd, yEnd - cl, xEnd - cl, yEnd, xEnd - cl, yEnd - cl], 26179 bottomLeft: [xBegin + cl, yEnd, xBegin, yEnd - cl, xBegin + cl, yEnd - cl] 26180 }; 26181 }, 26182 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26183 var cPts = this.generateCutTrianglePts(width + 2 * padding, height + 2 * padding, nodeX, nodeY); 26184 var pts = [].concat.apply([], [cPts.topLeft.splice(0, 4), cPts.topRight.splice(0, 4), cPts.bottomRight.splice(0, 4), cPts.bottomLeft.splice(0, 4)]); 26185 return polygonIntersectLine(x, y, pts, nodeX, nodeY); 26186 }, 26187 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26188 // Check hBox 26189 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - 2 * this.cornerLength, [0, -1], padding)) { 26190 return true; 26191 } // Check vBox 26192 26193 26194 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - 2 * this.cornerLength, height, [0, -1], padding)) { 26195 return true; 26196 } 26197 26198 var cutTrianglePts = this.generateCutTrianglePts(width, height, centerX, centerY); 26199 return pointInsidePolygonPoints(x, y, cutTrianglePts.topLeft) || pointInsidePolygonPoints(x, y, cutTrianglePts.topRight) || pointInsidePolygonPoints(x, y, cutTrianglePts.bottomRight) || pointInsidePolygonPoints(x, y, cutTrianglePts.bottomLeft); 26200 } 26201 }; 26202 }; 26203 26204 BRp$d.generateBarrel = function () { 26205 return this.nodeShapes['barrel'] = { 26206 renderer: this, 26207 name: 'barrel', 26208 points: generateUnitNgonPointsFitToSquare(4, 0), 26209 draw: function draw(context, centerX, centerY, width, height) { 26210 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26211 }, 26212 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26213 // use two fixed t values for the bezier curve approximation 26214 var t0 = 0.15; 26215 var t1 = 0.5; 26216 var t2 = 0.85; 26217 var bPts = this.generateBarrelBezierPts(width + 2 * padding, height + 2 * padding, nodeX, nodeY); 26218 26219 var approximateBarrelCurvePts = function approximateBarrelCurvePts(pts) { 26220 // approximate curve pts based on the two t values 26221 var m0 = qbezierPtAt({ 26222 x: pts[0], 26223 y: pts[1] 26224 }, { 26225 x: pts[2], 26226 y: pts[3] 26227 }, { 26228 x: pts[4], 26229 y: pts[5] 26230 }, t0); 26231 var m1 = qbezierPtAt({ 26232 x: pts[0], 26233 y: pts[1] 26234 }, { 26235 x: pts[2], 26236 y: pts[3] 26237 }, { 26238 x: pts[4], 26239 y: pts[5] 26240 }, t1); 26241 var m2 = qbezierPtAt({ 26242 x: pts[0], 26243 y: pts[1] 26244 }, { 26245 x: pts[2], 26246 y: pts[3] 26247 }, { 26248 x: pts[4], 26249 y: pts[5] 26250 }, t2); 26251 return [pts[0], pts[1], m0.x, m0.y, m1.x, m1.y, m2.x, m2.y, pts[4], pts[5]]; 26252 }; 26253 26254 var pts = [].concat(approximateBarrelCurvePts(bPts.topLeft), approximateBarrelCurvePts(bPts.topRight), approximateBarrelCurvePts(bPts.bottomRight), approximateBarrelCurvePts(bPts.bottomLeft)); 26255 return polygonIntersectLine(x, y, pts, nodeX, nodeY); 26256 }, 26257 generateBarrelBezierPts: function generateBarrelBezierPts(width, height, centerX, centerY) { 26258 var hh = height / 2; 26259 var hw = width / 2; 26260 var xBegin = centerX - hw; 26261 var xEnd = centerX + hw; 26262 var yBegin = centerY - hh; 26263 var yEnd = centerY + hh; 26264 var curveConstants = getBarrelCurveConstants(width, height); 26265 var hOffset = curveConstants.heightOffset; 26266 var wOffset = curveConstants.widthOffset; 26267 var ctrlPtXOffset = curveConstants.ctrlPtOffsetPct * width; // points are in clockwise order, inner (imaginary) control pt on [4, 5] 26268 26269 var pts = { 26270 topLeft: [xBegin, yBegin + hOffset, xBegin + ctrlPtXOffset, yBegin, xBegin + wOffset, yBegin], 26271 topRight: [xEnd - wOffset, yBegin, xEnd - ctrlPtXOffset, yBegin, xEnd, yBegin + hOffset], 26272 bottomRight: [xEnd, yEnd - hOffset, xEnd - ctrlPtXOffset, yEnd, xEnd - wOffset, yEnd], 26273 bottomLeft: [xBegin + wOffset, yEnd, xBegin + ctrlPtXOffset, yEnd, xBegin, yEnd - hOffset] 26274 }; 26275 pts.topLeft.isTop = true; 26276 pts.topRight.isTop = true; 26277 pts.bottomLeft.isBottom = true; 26278 pts.bottomRight.isBottom = true; 26279 return pts; 26280 }, 26281 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26282 var curveConstants = getBarrelCurveConstants(width, height); 26283 var hOffset = curveConstants.heightOffset; 26284 var wOffset = curveConstants.widthOffset; // Check hBox 26285 26286 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - 2 * hOffset, [0, -1], padding)) { 26287 return true; 26288 } // Check vBox 26289 26290 26291 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - 2 * wOffset, height, [0, -1], padding)) { 26292 return true; 26293 } 26294 26295 var barrelCurvePts = this.generateBarrelBezierPts(width, height, centerX, centerY); 26296 26297 var getCurveT = function getCurveT(x, y, curvePts) { 26298 var x0 = curvePts[4]; 26299 var x1 = curvePts[2]; 26300 var x2 = curvePts[0]; 26301 var y0 = curvePts[5]; // var y1 = curvePts[ 3 ]; 26302 26303 var y2 = curvePts[1]; 26304 var xMin = Math.min(x0, x2); 26305 var xMax = Math.max(x0, x2); 26306 var yMin = Math.min(y0, y2); 26307 var yMax = Math.max(y0, y2); 26308 26309 if (xMin <= x && x <= xMax && yMin <= y && y <= yMax) { 26310 var coeff = bezierPtsToQuadCoeff(x0, x1, x2); 26311 var roots = solveQuadratic(coeff[0], coeff[1], coeff[2], x); 26312 var validRoots = roots.filter(function (r) { 26313 return 0 <= r && r <= 1; 26314 }); 26315 26316 if (validRoots.length > 0) { 26317 return validRoots[0]; 26318 } 26319 } 26320 26321 return null; 26322 }; 26323 26324 var curveRegions = Object.keys(barrelCurvePts); 26325 26326 for (var i = 0; i < curveRegions.length; i++) { 26327 var corner = curveRegions[i]; 26328 var cornerPts = barrelCurvePts[corner]; 26329 var t = getCurveT(x, y, cornerPts); 26330 26331 if (t == null) { 26332 continue; 26333 } 26334 26335 var y0 = cornerPts[5]; 26336 var y1 = cornerPts[3]; 26337 var y2 = cornerPts[1]; 26338 var bezY = qbezierAt(y0, y1, y2, t); 26339 26340 if (cornerPts.isTop && bezY <= y) { 26341 return true; 26342 } 26343 26344 if (cornerPts.isBottom && y <= bezY) { 26345 return true; 26346 } 26347 } 26348 26349 return false; 26350 } 26351 }; 26352 }; 26353 26354 BRp$d.generateBottomRoundrectangle = function () { 26355 return this.nodeShapes['bottom-round-rectangle'] = this.nodeShapes['bottomroundrectangle'] = { 26356 renderer: this, 26357 name: 'bottom-round-rectangle', 26358 points: generateUnitNgonPointsFitToSquare(4, 0), 26359 draw: function draw(context, centerX, centerY, width, height) { 26360 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26361 }, 26362 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26363 var topStartX = nodeX - (width / 2 + padding); 26364 var topStartY = nodeY - (height / 2 + padding); 26365 var topEndY = topStartY; 26366 var topEndX = nodeX + (width / 2 + padding); 26367 var topIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); 26368 26369 if (topIntersections.length > 0) { 26370 return topIntersections; 26371 } 26372 26373 return roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding); 26374 }, 26375 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26376 var cornerRadius = getRoundRectangleRadius(width, height); 26377 var diam = 2 * cornerRadius; // Check hBox 26378 26379 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - diam, [0, -1], padding)) { 26380 return true; 26381 } // Check vBox 26382 26383 26384 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - diam, height, [0, -1], padding)) { 26385 return true; 26386 } // check non-rounded top side 26387 26388 26389 var outerWidth = width / 2 + 2 * padding; 26390 var outerHeight = height / 2 + 2 * padding; 26391 var points = [centerX - outerWidth, centerY - outerHeight, centerX - outerWidth, centerY, centerX + outerWidth, centerY, centerX + outerWidth, centerY - outerHeight]; 26392 26393 if (pointInsidePolygonPoints(x, y, points)) { 26394 return true; 26395 } // Check bottom right quarter circle 26396 26397 26398 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26399 return true; 26400 } // Check bottom left quarter circle 26401 26402 26403 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26404 return true; 26405 } 26406 26407 return false; 26408 } 26409 }; 26410 }; 26411 26412 BRp$d.registerNodeShapes = function () { 26413 var nodeShapes = this.nodeShapes = {}; 26414 var renderer = this; 26415 this.generateEllipse(); 26416 this.generatePolygon('triangle', generateUnitNgonPointsFitToSquare(3, 0)); 26417 this.generateRoundPolygon('round-triangle', generateUnitNgonPointsFitToSquare(3, 0)); 26418 this.generatePolygon('rectangle', generateUnitNgonPointsFitToSquare(4, 0)); 26419 nodeShapes['square'] = nodeShapes['rectangle']; 26420 this.generateRoundRectangle(); 26421 this.generateCutRectangle(); 26422 this.generateBarrel(); 26423 this.generateBottomRoundrectangle(); 26424 { 26425 var diamondPoints = [0, 1, 1, 0, 0, -1, -1, 0]; 26426 this.generatePolygon('diamond', diamondPoints); 26427 this.generateRoundPolygon('round-diamond', diamondPoints); 26428 } 26429 this.generatePolygon('pentagon', generateUnitNgonPointsFitToSquare(5, 0)); 26430 this.generateRoundPolygon('round-pentagon', generateUnitNgonPointsFitToSquare(5, 0)); 26431 this.generatePolygon('hexagon', generateUnitNgonPointsFitToSquare(6, 0)); 26432 this.generateRoundPolygon('round-hexagon', generateUnitNgonPointsFitToSquare(6, 0)); 26433 this.generatePolygon('heptagon', generateUnitNgonPointsFitToSquare(7, 0)); 26434 this.generateRoundPolygon('round-heptagon', generateUnitNgonPointsFitToSquare(7, 0)); 26435 this.generatePolygon('octagon', generateUnitNgonPointsFitToSquare(8, 0)); 26436 this.generateRoundPolygon('round-octagon', generateUnitNgonPointsFitToSquare(8, 0)); 26437 var star5Points = new Array(20); 26438 { 26439 var outerPoints = generateUnitNgonPoints(5, 0); 26440 var innerPoints = generateUnitNgonPoints(5, Math.PI / 5); // Outer radius is 1; inner radius of star is smaller 26441 26442 var innerRadius = 0.5 * (3 - Math.sqrt(5)); 26443 innerRadius *= 1.57; 26444 26445 for (var i = 0; i < innerPoints.length / 2; i++) { 26446 innerPoints[i * 2] *= innerRadius; 26447 innerPoints[i * 2 + 1] *= innerRadius; 26448 } 26449 26450 for (var i = 0; i < 20 / 4; i++) { 26451 star5Points[i * 4] = outerPoints[i * 2]; 26452 star5Points[i * 4 + 1] = outerPoints[i * 2 + 1]; 26453 star5Points[i * 4 + 2] = innerPoints[i * 2]; 26454 star5Points[i * 4 + 3] = innerPoints[i * 2 + 1]; 26455 } 26456 } 26457 star5Points = fitPolygonToSquare(star5Points); 26458 this.generatePolygon('star', star5Points); 26459 this.generatePolygon('vee', [-1, -1, 0, -0.333, 1, -1, 0, 1]); 26460 this.generatePolygon('rhomboid', [-1, -1, 0.333, -1, 1, 1, -0.333, 1]); 26461 this.nodeShapes['concavehexagon'] = this.generatePolygon('concave-hexagon', [-1, -0.95, -0.75, 0, -1, 0.95, 1, 0.95, 0.75, 0, 1, -0.95]); 26462 { 26463 var tagPoints = [-1, -1, 0.25, -1, 1, 0, 0.25, 1, -1, 1]; 26464 this.generatePolygon('tag', tagPoints); 26465 this.generateRoundPolygon('round-tag', tagPoints); 26466 } 26467 26468 nodeShapes.makePolygon = function (points) { 26469 // use caching on user-specified polygons so they are as fast as native shapes 26470 var key = points.join('$'); 26471 var name = 'polygon-' + key; 26472 var shape; 26473 26474 if (shape = this[name]) { 26475 // got cached shape 26476 return shape; 26477 } // create and cache new shape 26478 26479 26480 return renderer.generatePolygon(name, points); 26481 }; 26482 }; 26483 26484 var BRp$e = {}; 26485 26486 BRp$e.timeToRender = function () { 26487 return this.redrawTotalTime / this.redrawCount; 26488 }; 26489 26490 BRp$e.redraw = function (options) { 26491 options = options || staticEmptyObject(); 26492 var r = this; 26493 26494 if (r.averageRedrawTime === undefined) { 26495 r.averageRedrawTime = 0; 26496 } 26497 26498 if (r.lastRedrawTime === undefined) { 26499 r.lastRedrawTime = 0; 26500 } 26501 26502 if (r.lastDrawTime === undefined) { 26503 r.lastDrawTime = 0; 26504 } 26505 26506 r.requestedFrame = true; 26507 r.renderOptions = options; 26508 }; 26509 26510 BRp$e.beforeRender = function (fn, priority) { 26511 // the renderer can't add tick callbacks when destroyed 26512 if (this.destroyed) { 26513 return; 26514 } 26515 26516 if (priority == null) { 26517 error('Priority is not optional for beforeRender'); 26518 } 26519 26520 var cbs = this.beforeRenderCallbacks; 26521 cbs.push({ 26522 fn: fn, 26523 priority: priority 26524 }); // higher priority callbacks executed first 26525 26526 cbs.sort(function (a, b) { 26527 return b.priority - a.priority; 26528 }); 26529 }; 26530 26531 var beforeRenderCallbacks = function beforeRenderCallbacks(r, willDraw, startTime) { 26532 var cbs = r.beforeRenderCallbacks; 26533 26534 for (var i = 0; i < cbs.length; i++) { 26535 cbs[i].fn(willDraw, startTime); 26536 } 26537 }; 26538 26539 BRp$e.startRenderLoop = function () { 26540 var r = this; 26541 var cy = r.cy; 26542 26543 if (r.renderLoopStarted) { 26544 return; 26545 } else { 26546 r.renderLoopStarted = true; 26547 } 26548 26549 var renderFn = function renderFn(requestTime) { 26550 if (r.destroyed) { 26551 return; 26552 } 26553 26554 if (cy.batching()) ; else if (r.requestedFrame && !r.skipFrame) { 26555 beforeRenderCallbacks(r, true, requestTime); 26556 var startTime = performanceNow(); 26557 r.render(r.renderOptions); 26558 var endTime = r.lastDrawTime = performanceNow(); 26559 26560 if (r.averageRedrawTime === undefined) { 26561 r.averageRedrawTime = endTime - startTime; 26562 } 26563 26564 if (r.redrawCount === undefined) { 26565 r.redrawCount = 0; 26566 } 26567 26568 r.redrawCount++; 26569 26570 if (r.redrawTotalTime === undefined) { 26571 r.redrawTotalTime = 0; 26572 } 26573 26574 var duration = endTime - startTime; 26575 r.redrawTotalTime += duration; 26576 r.lastRedrawTime = duration; // use a weighted average with a bias from the previous average so we don't spike so easily 26577 26578 r.averageRedrawTime = r.averageRedrawTime / 2 + duration / 2; 26579 r.requestedFrame = false; 26580 } else { 26581 beforeRenderCallbacks(r, false, requestTime); 26582 } 26583 26584 r.skipFrame = false; 26585 requestAnimationFrame(renderFn); 26586 }; 26587 26588 requestAnimationFrame(renderFn); 26589 }; 26590 26591 var BaseRenderer = function BaseRenderer(options) { 26592 this.init(options); 26593 }; 26594 26595 var BR = BaseRenderer; 26596 var BRp$f = BR.prototype; 26597 BRp$f.clientFunctions = ['redrawHint', 'render', 'renderTo', 'matchCanvasSize', 'nodeShapeImpl', 'arrowShapeImpl']; 26598 26599 BRp$f.init = function (options) { 26600 var r = this; 26601 r.options = options; 26602 r.cy = options.cy; 26603 var ctr = r.container = options.cy.container(); // prepend a stylesheet in the head such that 26604 26605 if (window$1) { 26606 var document = window$1.document; 26607 var head = document.head; 26608 var stylesheetId = '__________cytoscape_stylesheet'; 26609 var className = '__________cytoscape_container'; 26610 var stylesheetAlreadyExists = document.getElementById(stylesheetId) != null; 26611 26612 if (ctr.className.indexOf(className) < 0) { 26613 ctr.className = (ctr.className || '') + ' ' + className; 26614 } 26615 26616 if (!stylesheetAlreadyExists) { 26617 var stylesheet = document.createElement('style'); 26618 stylesheet.id = stylesheetId; 26619 stylesheet.innerHTML = '.' + className + ' { position: relative; }'; 26620 head.insertBefore(stylesheet, head.children[0]); // first so lowest priority 26621 } 26622 26623 var computedStyle = window$1.getComputedStyle(ctr); 26624 var position = computedStyle.getPropertyValue('position'); 26625 26626 if (position === 'static') { 26627 warn('A Cytoscape container has style position:static and so can not use UI extensions properly'); 26628 } 26629 } 26630 26631 r.selection = [undefined, undefined, undefined, undefined, 0]; // Coordinates for selection box, plus enabled flag 26632 26633 r.bezierProjPcts = [0.05, 0.225, 0.4, 0.5, 0.6, 0.775, 0.95]; //--Pointer-related data 26634 26635 r.hoverData = { 26636 down: null, 26637 last: null, 26638 downTime: null, 26639 triggerMode: null, 26640 dragging: false, 26641 initialPan: [null, null], 26642 capture: false 26643 }; 26644 r.dragData = { 26645 possibleDragElements: [] 26646 }; 26647 r.touchData = { 26648 start: null, 26649 capture: false, 26650 // These 3 fields related to tap, taphold events 26651 startPosition: [null, null, null, null, null, null], 26652 singleTouchStartTime: null, 26653 singleTouchMoved: true, 26654 now: [null, null, null, null, null, null], 26655 earlier: [null, null, null, null, null, null] 26656 }; 26657 r.redraws = 0; 26658 r.showFps = options.showFps; 26659 r.debug = options.debug; 26660 r.hideEdgesOnViewport = options.hideEdgesOnViewport; 26661 r.textureOnViewport = options.textureOnViewport; 26662 r.wheelSensitivity = options.wheelSensitivity; 26663 r.motionBlurEnabled = options.motionBlur; // on by default 26664 26665 r.forcedPixelRatio = number(options.pixelRatio) ? options.pixelRatio : null; 26666 r.motionBlur = options.motionBlur; // for initial kick off 26667 26668 r.motionBlurOpacity = options.motionBlurOpacity; 26669 r.motionBlurTransparency = 1 - r.motionBlurOpacity; 26670 r.motionBlurPxRatio = 1; 26671 r.mbPxRBlurry = 1; //0.8; 26672 26673 r.minMbLowQualFrames = 4; 26674 r.fullQualityMb = false; 26675 r.clearedForMotionBlur = []; 26676 r.desktopTapThreshold = options.desktopTapThreshold; 26677 r.desktopTapThreshold2 = options.desktopTapThreshold * options.desktopTapThreshold; 26678 r.touchTapThreshold = options.touchTapThreshold; 26679 r.touchTapThreshold2 = options.touchTapThreshold * options.touchTapThreshold; 26680 r.tapholdDuration = 500; 26681 r.bindings = []; 26682 r.beforeRenderCallbacks = []; 26683 r.beforeRenderPriorities = { 26684 // higher priority execs before lower one 26685 animations: 400, 26686 eleCalcs: 300, 26687 eleTxrDeq: 200, 26688 lyrTxrDeq: 150, 26689 lyrTxrSkip: 100 26690 }; 26691 r.registerNodeShapes(); 26692 r.registerArrowShapes(); 26693 r.registerCalculationListeners(); 26694 }; 26695 26696 BRp$f.notify = function (eventName, eles) { 26697 var r = this; 26698 var cy = r.cy; // the renderer can't be notified after it's destroyed 26699 26700 if (this.destroyed) { 26701 return; 26702 } 26703 26704 if (eventName === 'init') { 26705 r.load(); 26706 return; 26707 } 26708 26709 if (eventName === 'destroy') { 26710 r.destroy(); 26711 return; 26712 } 26713 26714 if (eventName === 'add' || eventName === 'remove' || eventName === 'move' && cy.hasCompoundNodes() || eventName === 'load' || eventName === 'zorder' || eventName === 'mount') { 26715 r.invalidateCachedZSortedEles(); 26716 } 26717 26718 if (eventName === 'viewport') { 26719 r.redrawHint('select', true); 26720 } 26721 26722 if (eventName === 'load' || eventName === 'resize' || eventName === 'mount') { 26723 r.invalidateContainerClientCoordsCache(); 26724 r.matchCanvasSize(r.container); 26725 } 26726 26727 r.redrawHint('eles', true); 26728 r.redrawHint('drag', true); 26729 this.startRenderLoop(); 26730 this.redraw(); 26731 }; 26732 26733 BRp$f.destroy = function () { 26734 var r = this; 26735 r.destroyed = true; 26736 r.cy.stopAnimationLoop(); 26737 26738 for (var i = 0; i < r.bindings.length; i++) { 26739 var binding = r.bindings[i]; 26740 var b = binding; 26741 var tgt = b.target; 26742 (tgt.off || tgt.removeEventListener).apply(tgt, b.args); 26743 } 26744 26745 r.bindings = []; 26746 r.beforeRenderCallbacks = []; 26747 r.onUpdateEleCalcsFns = []; 26748 26749 if (r.removeObserver) { 26750 r.removeObserver.disconnect(); 26751 } 26752 26753 if (r.styleObserver) { 26754 r.styleObserver.disconnect(); 26755 } 26756 26757 if (r.resizeObserver) { 26758 r.resizeObserver.disconnect(); 26759 } 26760 26761 if (r.labelCalcDiv) { 26762 try { 26763 document.body.removeChild(r.labelCalcDiv); // eslint-disable-line no-undef 26764 } catch (e) {// ie10 issue #1014 26765 } 26766 } 26767 }; 26768 26769 BRp$f.isHeadless = function () { 26770 return false; 26771 }; 26772 26773 [BRp, BRp$a, BRp$b, BRp$c, BRp$d, BRp$e].forEach(function (props) { 26774 extend(BRp$f, props); 26775 }); 26776 26777 var fullFpsTime = 1000 / 60; // assume 60 frames per second 26778 26779 var defs = { 26780 setupDequeueing: function setupDequeueing(opts) { 26781 return function setupDequeueingImpl() { 26782 var self = this; 26783 var r = this.renderer; 26784 26785 if (self.dequeueingSetup) { 26786 return; 26787 } else { 26788 self.dequeueingSetup = true; 26789 } 26790 26791 var queueRedraw = util(function () { 26792 r.redrawHint('eles', true); 26793 r.redrawHint('drag', true); 26794 r.redraw(); 26795 }, opts.deqRedrawThreshold); 26796 26797 var dequeue = function dequeue(willDraw, frameStartTime) { 26798 var startTime = performanceNow(); 26799 var avgRenderTime = r.averageRedrawTime; 26800 var renderTime = r.lastRedrawTime; 26801 var deqd = []; 26802 var extent = r.cy.extent(); 26803 var pixelRatio = r.getPixelRatio(); // if we aren't in a tick that causes a draw, then the rendered style 26804 // queue won't automatically be flushed before dequeueing starts 26805 26806 if (!willDraw) { 26807 r.flushRenderedStyleQueue(); 26808 } 26809 26810 while (true) { 26811 // eslint-disable-line no-constant-condition 26812 var now = performanceNow(); 26813 var duration = now - startTime; 26814 var frameDuration = now - frameStartTime; 26815 26816 if (renderTime < fullFpsTime) { 26817 // if we're rendering faster than the ideal fps, then do dequeueing 26818 // during all of the remaining frame time 26819 var timeAvailable = fullFpsTime - (willDraw ? avgRenderTime : 0); 26820 26821 if (frameDuration >= opts.deqFastCost * timeAvailable) { 26822 break; 26823 } 26824 } else { 26825 if (willDraw) { 26826 if (duration >= opts.deqCost * renderTime || duration >= opts.deqAvgCost * avgRenderTime) { 26827 break; 26828 } 26829 } else if (frameDuration >= opts.deqNoDrawCost * fullFpsTime) { 26830 break; 26831 } 26832 } 26833 26834 var thisDeqd = opts.deq(self, pixelRatio, extent); 26835 26836 if (thisDeqd.length > 0) { 26837 for (var i = 0; i < thisDeqd.length; i++) { 26838 deqd.push(thisDeqd[i]); 26839 } 26840 } else { 26841 break; 26842 } 26843 } // callbacks on dequeue 26844 26845 26846 if (deqd.length > 0) { 26847 opts.onDeqd(self, deqd); 26848 26849 if (!willDraw && opts.shouldRedraw(self, deqd, pixelRatio, extent)) { 26850 queueRedraw(); 26851 } 26852 } 26853 }; 26854 26855 var priority = opts.priority || noop; 26856 r.beforeRender(dequeue, priority(self)); 26857 }; 26858 } 26859 }; 26860 26861 // Uses keys so elements may share the same cache. 26862 26863 var ElementTextureCacheLookup = 26864 /*#__PURE__*/ 26865 function () { 26866 function ElementTextureCacheLookup(getKey) { 26867 var doesEleInvalidateKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : falsify; 26868 26869 _classCallCheck(this, ElementTextureCacheLookup); 26870 26871 this.idsByKey = new Map$1(); 26872 this.keyForId = new Map$1(); 26873 this.cachesByLvl = new Map$1(); 26874 this.lvls = []; 26875 this.getKey = getKey; 26876 this.doesEleInvalidateKey = doesEleInvalidateKey; 26877 } 26878 26879 _createClass(ElementTextureCacheLookup, [{ 26880 key: "getIdsFor", 26881 value: function getIdsFor(key) { 26882 if (key == null) { 26883 error("Can not get id list for null key"); 26884 } 26885 26886 var idsByKey = this.idsByKey; 26887 var ids = this.idsByKey.get(key); 26888 26889 if (!ids) { 26890 ids = new Set$1(); 26891 idsByKey.set(key, ids); 26892 } 26893 26894 return ids; 26895 } 26896 }, { 26897 key: "addIdForKey", 26898 value: function addIdForKey(key, id) { 26899 if (key != null) { 26900 this.getIdsFor(key).add(id); 26901 } 26902 } 26903 }, { 26904 key: "deleteIdForKey", 26905 value: function deleteIdForKey(key, id) { 26906 if (key != null) { 26907 this.getIdsFor(key)["delete"](id); 26908 } 26909 } 26910 }, { 26911 key: "getNumberOfIdsForKey", 26912 value: function getNumberOfIdsForKey(key) { 26913 if (key == null) { 26914 return 0; 26915 } else { 26916 return this.getIdsFor(key).size; 26917 } 26918 } 26919 }, { 26920 key: "updateKeyMappingFor", 26921 value: function updateKeyMappingFor(ele) { 26922 var id = ele.id(); 26923 var prevKey = this.keyForId.get(id); 26924 var currKey = this.getKey(ele); 26925 this.deleteIdForKey(prevKey, id); 26926 this.addIdForKey(currKey, id); 26927 this.keyForId.set(id, currKey); 26928 } 26929 }, { 26930 key: "deleteKeyMappingFor", 26931 value: function deleteKeyMappingFor(ele) { 26932 var id = ele.id(); 26933 var prevKey = this.keyForId.get(id); 26934 this.deleteIdForKey(prevKey, id); 26935 this.keyForId["delete"](id); 26936 } 26937 }, { 26938 key: "keyHasChangedFor", 26939 value: function keyHasChangedFor(ele) { 26940 var id = ele.id(); 26941 var prevKey = this.keyForId.get(id); 26942 var newKey = this.getKey(ele); 26943 return prevKey !== newKey; 26944 } 26945 }, { 26946 key: "isInvalid", 26947 value: function isInvalid(ele) { 26948 return this.keyHasChangedFor(ele) || this.doesEleInvalidateKey(ele); 26949 } 26950 }, { 26951 key: "getCachesAt", 26952 value: function getCachesAt(lvl) { 26953 var cachesByLvl = this.cachesByLvl, 26954 lvls = this.lvls; 26955 var caches = cachesByLvl.get(lvl); 26956 26957 if (!caches) { 26958 caches = new Map$1(); 26959 cachesByLvl.set(lvl, caches); 26960 lvls.push(lvl); 26961 } 26962 26963 return caches; 26964 } 26965 }, { 26966 key: "getCache", 26967 value: function getCache(key, lvl) { 26968 return this.getCachesAt(lvl).get(key); 26969 } 26970 }, { 26971 key: "get", 26972 value: function get(ele, lvl) { 26973 var key = this.getKey(ele); 26974 var cache = this.getCache(key, lvl); // getting for an element may need to add to the id list b/c eles can share keys 26975 26976 if (cache != null) { 26977 this.updateKeyMappingFor(ele); 26978 } 26979 26980 return cache; 26981 } 26982 }, { 26983 key: "getForCachedKey", 26984 value: function getForCachedKey(ele, lvl) { 26985 var key = this.keyForId.get(ele.id()); // n.b. use cached key, not newly computed key 26986 26987 var cache = this.getCache(key, lvl); 26988 return cache; 26989 } 26990 }, { 26991 key: "hasCache", 26992 value: function hasCache(key, lvl) { 26993 return this.getCachesAt(lvl).has(key); 26994 } 26995 }, { 26996 key: "has", 26997 value: function has(ele, lvl) { 26998 var key = this.getKey(ele); 26999 return this.hasCache(key, lvl); 27000 } 27001 }, { 27002 key: "setCache", 27003 value: function setCache(key, lvl, cache) { 27004 cache.key = key; 27005 this.getCachesAt(lvl).set(key, cache); 27006 } 27007 }, { 27008 key: "set", 27009 value: function set(ele, lvl, cache) { 27010 var key = this.getKey(ele); 27011 this.setCache(key, lvl, cache); 27012 this.updateKeyMappingFor(ele); 27013 } 27014 }, { 27015 key: "deleteCache", 27016 value: function deleteCache(key, lvl) { 27017 this.getCachesAt(lvl)["delete"](key); 27018 } 27019 }, { 27020 key: "delete", 27021 value: function _delete(ele, lvl) { 27022 var key = this.getKey(ele); 27023 this.deleteCache(key, lvl); 27024 } 27025 }, { 27026 key: "invalidateKey", 27027 value: function invalidateKey(key) { 27028 var _this = this; 27029 27030 this.lvls.forEach(function (lvl) { 27031 return _this.deleteCache(key, lvl); 27032 }); 27033 } // returns true if no other eles reference the invalidated cache (n.b. other eles may need the cache with the same key) 27034 27035 }, { 27036 key: "invalidate", 27037 value: function invalidate(ele) { 27038 var id = ele.id(); 27039 var key = this.keyForId.get(id); // n.b. use stored key rather than current (potential key) 27040 27041 this.deleteKeyMappingFor(ele); 27042 var entireKeyInvalidated = this.doesEleInvalidateKey(ele); 27043 27044 if (entireKeyInvalidated) { 27045 // clear mapping for current key 27046 this.invalidateKey(key); 27047 } 27048 27049 return entireKeyInvalidated || this.getNumberOfIdsForKey(key) === 0; 27050 } 27051 }]); 27052 27053 return ElementTextureCacheLookup; 27054 }(); 27055 27056 var minTxrH = 25; // the size of the texture cache for small height eles (special case) 27057 27058 var txrStepH = 50; // the min size of the regular cache, and the size it increases with each step up 27059 27060 var minLvl = -4; // when scaling smaller than that we don't need to re-render 27061 27062 var maxLvl = 3; // when larger than this scale just render directly (caching is not helpful) 27063 27064 var maxZoom = 7.99; // beyond this zoom level, layered textures are not used 27065 27066 var eleTxrSpacing = 8; // spacing between elements on textures to avoid blitting overlaps 27067 27068 var defTxrWidth = 1024; // default/minimum texture width 27069 27070 var maxTxrW = 1024; // the maximum width of a texture 27071 27072 var maxTxrH = 1024; // the maximum height of a texture 27073 27074 var minUtility = 0.2; // if usage of texture is less than this, it is retired 27075 27076 var maxFullness = 0.8; // fullness of texture after which queue removal is checked 27077 27078 var maxFullnessChecks = 10; // dequeued after this many checks 27079 27080 var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 27081 27082 var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time 27083 27084 var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 27085 27086 var deqFastCost = 0.9; // % of frame time to be used when >60fps 27087 27088 var deqRedrawThreshold = 100; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 27089 27090 var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch 27091 27092 var getTxrReasons = { 27093 dequeue: 'dequeue', 27094 downscale: 'downscale', 27095 highQuality: 'highQuality' 27096 }; 27097 var initDefaults = defaults({ 27098 getKey: null, 27099 doesEleInvalidateKey: falsify, 27100 drawElement: null, 27101 getBoundingBox: null, 27102 getRotationPoint: null, 27103 getRotationOffset: null, 27104 isVisible: trueify, 27105 allowEdgeTxrCaching: true, 27106 allowParentTxrCaching: true 27107 }); 27108 27109 var ElementTextureCache = function ElementTextureCache(renderer, initOptions) { 27110 var self = this; 27111 self.renderer = renderer; 27112 self.onDequeues = []; 27113 var opts = initDefaults(initOptions); 27114 extend(self, opts); 27115 self.lookup = new ElementTextureCacheLookup(opts.getKey, opts.doesEleInvalidateKey); 27116 self.setupDequeueing(); 27117 }; 27118 27119 var ETCp = ElementTextureCache.prototype; 27120 ETCp.reasons = getTxrReasons; // the list of textures in which new subtextures for elements can be placed 27121 27122 ETCp.getTextureQueue = function (txrH) { 27123 var self = this; 27124 self.eleImgCaches = self.eleImgCaches || {}; 27125 return self.eleImgCaches[txrH] = self.eleImgCaches[txrH] || []; 27126 }; // the list of usused textures which can be recycled (in use in texture queue) 27127 27128 27129 ETCp.getRetiredTextureQueue = function (txrH) { 27130 var self = this; 27131 var rtxtrQs = self.eleImgCaches.retired = self.eleImgCaches.retired || {}; 27132 var rtxtrQ = rtxtrQs[txrH] = rtxtrQs[txrH] || []; 27133 return rtxtrQ; 27134 }; // queue of element draw requests at different scale levels 27135 27136 27137 ETCp.getElementQueue = function () { 27138 var self = this; 27139 var q = self.eleCacheQueue = self.eleCacheQueue || new Heap(function (a, b) { 27140 return b.reqs - a.reqs; 27141 }); 27142 return q; 27143 }; // queue of element draw requests at different scale levels (element id lookup) 27144 27145 27146 ETCp.getElementKeyToQueue = function () { 27147 var self = this; 27148 var k2q = self.eleKeyToCacheQueue = self.eleKeyToCacheQueue || {}; 27149 return k2q; 27150 }; 27151 27152 ETCp.getElement = function (ele, bb, pxRatio, lvl, reason) { 27153 var self = this; 27154 var r = this.renderer; 27155 var zoom = r.cy.zoom(); 27156 var lookup = this.lookup; 27157 27158 if (bb.w === 0 || bb.h === 0 || isNaN(bb.w) || isNaN(bb.h) || !ele.visible()) { 27159 return null; 27160 } 27161 27162 if (!self.allowEdgeTxrCaching && ele.isEdge() || !self.allowParentTxrCaching && ele.isParent()) { 27163 return null; 27164 } 27165 27166 if (lvl == null) { 27167 lvl = Math.ceil(log2(zoom * pxRatio)); 27168 } 27169 27170 if (lvl < minLvl) { 27171 lvl = minLvl; 27172 } else if (zoom >= maxZoom || lvl > maxLvl) { 27173 return null; 27174 } 27175 27176 var scale = Math.pow(2, lvl); 27177 var eleScaledH = bb.h * scale; 27178 var eleScaledW = bb.w * scale; 27179 var scaledLabelShown = r.eleTextBiggerThanMin(ele, scale); 27180 27181 if (!this.isVisible(ele, scaledLabelShown)) { 27182 return null; 27183 } 27184 27185 var eleCache = lookup.get(ele, lvl); // if this get was on an unused/invalidated cache, then restore the texture usage metric 27186 27187 if (eleCache && eleCache.invalidated) { 27188 eleCache.invalidated = false; 27189 eleCache.texture.invalidatedWidth -= eleCache.width; 27190 } 27191 27192 if (eleCache) { 27193 return eleCache; 27194 } 27195 27196 var txrH; // which texture height this ele belongs to 27197 27198 if (eleScaledH <= minTxrH) { 27199 txrH = minTxrH; 27200 } else if (eleScaledH <= txrStepH) { 27201 txrH = txrStepH; 27202 } else { 27203 txrH = Math.ceil(eleScaledH / txrStepH) * txrStepH; 27204 } 27205 27206 if (eleScaledH > maxTxrH || eleScaledW > maxTxrW) { 27207 return null; // caching large elements is not efficient 27208 } 27209 27210 var txrQ = self.getTextureQueue(txrH); // first try the second last one in case it has space at the end 27211 27212 var txr = txrQ[txrQ.length - 2]; 27213 27214 var addNewTxr = function addNewTxr() { 27215 return self.recycleTexture(txrH, eleScaledW) || self.addTexture(txrH, eleScaledW); 27216 }; // try the last one if there is no second last one 27217 27218 27219 if (!txr) { 27220 txr = txrQ[txrQ.length - 1]; 27221 } // if the last one doesn't exist, we need a first one 27222 27223 27224 if (!txr) { 27225 txr = addNewTxr(); 27226 } // if there's no room in the current texture, we need a new one 27227 27228 27229 if (txr.width - txr.usedWidth < eleScaledW) { 27230 txr = addNewTxr(); 27231 } 27232 27233 var scalableFrom = function scalableFrom(otherCache) { 27234 return otherCache && otherCache.scaledLabelShown === scaledLabelShown; 27235 }; 27236 27237 var deqing = reason && reason === getTxrReasons.dequeue; 27238 var highQualityReq = reason && reason === getTxrReasons.highQuality; 27239 var downscaleReq = reason && reason === getTxrReasons.downscale; 27240 var higherCache; // the nearest cache with a higher level 27241 27242 for (var l = lvl + 1; l <= maxLvl; l++) { 27243 var c = lookup.get(ele, l); 27244 27245 if (c) { 27246 higherCache = c; 27247 break; 27248 } 27249 } 27250 27251 var oneUpCache = higherCache && higherCache.level === lvl + 1 ? higherCache : null; 27252 27253 var downscale = function downscale() { 27254 txr.context.drawImage(oneUpCache.texture.canvas, oneUpCache.x, 0, oneUpCache.width, oneUpCache.height, txr.usedWidth, 0, eleScaledW, eleScaledH); 27255 }; // reset ele area in texture 27256 27257 27258 txr.context.setTransform(1, 0, 0, 1, 0, 0); 27259 txr.context.clearRect(txr.usedWidth, 0, eleScaledW, txrH); 27260 27261 if (scalableFrom(oneUpCache)) { 27262 // then we can relatively cheaply rescale the existing image w/o rerendering 27263 downscale(); 27264 } else if (scalableFrom(higherCache)) { 27265 // then use the higher cache for now and queue the next level down 27266 // to cheaply scale towards the smaller level 27267 if (highQualityReq) { 27268 for (var _l = higherCache.level; _l > lvl; _l--) { 27269 oneUpCache = self.getElement(ele, bb, pxRatio, _l, getTxrReasons.downscale); 27270 } 27271 27272 downscale(); 27273 } else { 27274 self.queueElement(ele, higherCache.level - 1); 27275 return higherCache; 27276 } 27277 } else { 27278 var lowerCache; // the nearest cache with a lower level 27279 27280 if (!deqing && !highQualityReq && !downscaleReq) { 27281 for (var _l2 = lvl - 1; _l2 >= minLvl; _l2--) { 27282 var _c = lookup.get(ele, _l2); 27283 27284 if (_c) { 27285 lowerCache = _c; 27286 break; 27287 } 27288 } 27289 } 27290 27291 if (scalableFrom(lowerCache)) { 27292 // then use the lower quality cache for now and queue the better one for later 27293 self.queueElement(ele, lvl); 27294 return lowerCache; 27295 } 27296 27297 txr.context.translate(txr.usedWidth, 0); 27298 txr.context.scale(scale, scale); 27299 this.drawElement(txr.context, ele, bb, scaledLabelShown, false); 27300 txr.context.scale(1 / scale, 1 / scale); 27301 txr.context.translate(-txr.usedWidth, 0); 27302 } 27303 27304 eleCache = { 27305 x: txr.usedWidth, 27306 texture: txr, 27307 level: lvl, 27308 scale: scale, 27309 width: eleScaledW, 27310 height: eleScaledH, 27311 scaledLabelShown: scaledLabelShown 27312 }; 27313 txr.usedWidth += Math.ceil(eleScaledW + eleTxrSpacing); 27314 txr.eleCaches.push(eleCache); 27315 lookup.set(ele, lvl, eleCache); 27316 self.checkTextureFullness(txr); 27317 return eleCache; 27318 }; 27319 27320 ETCp.invalidateElements = function (eles) { 27321 for (var i = 0; i < eles.length; i++) { 27322 this.invalidateElement(eles[i]); 27323 } 27324 }; 27325 27326 ETCp.invalidateElement = function (ele) { 27327 var self = this; 27328 var lookup = self.lookup; 27329 var caches = []; 27330 var invalid = lookup.isInvalid(ele); 27331 27332 if (!invalid) { 27333 return; // override the invalidation request if the element key has not changed 27334 } 27335 27336 for (var lvl = minLvl; lvl <= maxLvl; lvl++) { 27337 var cache = lookup.getForCachedKey(ele, lvl); 27338 27339 if (cache) { 27340 caches.push(cache); 27341 } 27342 } 27343 27344 var noOtherElesUseCache = lookup.invalidate(ele); 27345 27346 if (noOtherElesUseCache) { 27347 for (var i = 0; i < caches.length; i++) { 27348 var _cache = caches[i]; 27349 var txr = _cache.texture; // remove space from the texture it belongs to 27350 27351 txr.invalidatedWidth += _cache.width; // mark the cache as invalidated 27352 27353 _cache.invalidated = true; // retire the texture if its utility is low 27354 27355 self.checkTextureUtility(txr); 27356 } 27357 } // remove from queue since the old req was for the old state 27358 27359 27360 self.removeFromQueue(ele); 27361 }; 27362 27363 ETCp.checkTextureUtility = function (txr) { 27364 // invalidate all entries in the cache if the cache size is small 27365 if (txr.invalidatedWidth >= minUtility * txr.width) { 27366 this.retireTexture(txr); 27367 } 27368 }; 27369 27370 ETCp.checkTextureFullness = function (txr) { 27371 // if texture has been mostly filled and passed over several times, remove 27372 // it from the queue so we don't need to waste time looking at it to put new things 27373 var self = this; 27374 var txrQ = self.getTextureQueue(txr.height); 27375 27376 if (txr.usedWidth / txr.width > maxFullness && txr.fullnessChecks >= maxFullnessChecks) { 27377 removeFromArray(txrQ, txr); 27378 } else { 27379 txr.fullnessChecks++; 27380 } 27381 }; 27382 27383 ETCp.retireTexture = function (txr) { 27384 var self = this; 27385 var txrH = txr.height; 27386 var txrQ = self.getTextureQueue(txrH); 27387 var lookup = this.lookup; // retire the texture from the active / searchable queue: 27388 27389 removeFromArray(txrQ, txr); 27390 txr.retired = true; // remove the refs from the eles to the caches: 27391 27392 var eleCaches = txr.eleCaches; 27393 27394 for (var i = 0; i < eleCaches.length; i++) { 27395 var eleCache = eleCaches[i]; 27396 lookup.deleteCache(eleCache.key, eleCache.level); 27397 } 27398 27399 clearArray(eleCaches); // add the texture to a retired queue so it can be recycled in future: 27400 27401 var rtxtrQ = self.getRetiredTextureQueue(txrH); 27402 rtxtrQ.push(txr); 27403 }; 27404 27405 ETCp.addTexture = function (txrH, minW) { 27406 var self = this; 27407 var txrQ = self.getTextureQueue(txrH); 27408 var txr = {}; 27409 txrQ.push(txr); 27410 txr.eleCaches = []; 27411 txr.height = txrH; 27412 txr.width = Math.max(defTxrWidth, minW); 27413 txr.usedWidth = 0; 27414 txr.invalidatedWidth = 0; 27415 txr.fullnessChecks = 0; 27416 txr.canvas = self.renderer.makeOffscreenCanvas(txr.width, txr.height); 27417 txr.context = txr.canvas.getContext('2d'); 27418 return txr; 27419 }; 27420 27421 ETCp.recycleTexture = function (txrH, minW) { 27422 var self = this; 27423 var txrQ = self.getTextureQueue(txrH); 27424 var rtxtrQ = self.getRetiredTextureQueue(txrH); 27425 27426 for (var i = 0; i < rtxtrQ.length; i++) { 27427 var txr = rtxtrQ[i]; 27428 27429 if (txr.width >= minW) { 27430 txr.retired = false; 27431 txr.usedWidth = 0; 27432 txr.invalidatedWidth = 0; 27433 txr.fullnessChecks = 0; 27434 clearArray(txr.eleCaches); 27435 txr.context.setTransform(1, 0, 0, 1, 0, 0); 27436 txr.context.clearRect(0, 0, txr.width, txr.height); 27437 removeFromArray(rtxtrQ, txr); 27438 txrQ.push(txr); 27439 return txr; 27440 } 27441 } 27442 }; 27443 27444 ETCp.queueElement = function (ele, lvl) { 27445 var self = this; 27446 var q = self.getElementQueue(); 27447 var k2q = self.getElementKeyToQueue(); 27448 var key = this.getKey(ele); 27449 var existingReq = k2q[key]; 27450 27451 if (existingReq) { 27452 // use the max lvl b/c in between lvls are cheap to make 27453 existingReq.level = Math.max(existingReq.level, lvl); 27454 existingReq.eles.merge(ele); 27455 existingReq.reqs++; 27456 q.updateItem(existingReq); 27457 } else { 27458 var req = { 27459 eles: ele.spawn().merge(ele), 27460 level: lvl, 27461 reqs: 1, 27462 key: key 27463 }; 27464 q.push(req); 27465 k2q[key] = req; 27466 } 27467 }; 27468 27469 ETCp.dequeue = function (pxRatio 27470 /*, extent*/ 27471 ) { 27472 var self = this; 27473 var q = self.getElementQueue(); 27474 var k2q = self.getElementKeyToQueue(); 27475 var dequeued = []; 27476 var lookup = self.lookup; 27477 27478 for (var i = 0; i < maxDeqSize; i++) { 27479 if (q.size() > 0) { 27480 var req = q.pop(); 27481 var key = req.key; 27482 var ele = req.eles[0]; // all eles have the same key 27483 27484 var cacheExists = lookup.hasCache(ele, req.level); // clear out the key to req lookup 27485 27486 k2q[key] = null; // dequeueing isn't necessary with an existing cache 27487 27488 if (cacheExists) { 27489 continue; 27490 } 27491 27492 dequeued.push(req); 27493 var bb = self.getBoundingBox(ele); 27494 self.getElement(ele, bb, pxRatio, req.level, getTxrReasons.dequeue); 27495 } else { 27496 break; 27497 } 27498 } 27499 27500 return dequeued; 27501 }; 27502 27503 ETCp.removeFromQueue = function (ele) { 27504 var self = this; 27505 var q = self.getElementQueue(); 27506 var k2q = self.getElementKeyToQueue(); 27507 var key = this.getKey(ele); 27508 var req = k2q[key]; 27509 27510 if (req != null) { 27511 if (req.eles.length === 1) { 27512 // remove if last ele in the req 27513 // bring to front of queue 27514 req.reqs = MAX_INT; 27515 q.updateItem(req); 27516 q.pop(); // remove from queue 27517 27518 k2q[key] = null; // remove from lookup map 27519 } else { 27520 // otherwise just remove ele from req 27521 req.eles.unmerge(ele); 27522 } 27523 } 27524 }; 27525 27526 ETCp.onDequeue = function (fn) { 27527 this.onDequeues.push(fn); 27528 }; 27529 27530 ETCp.offDequeue = function (fn) { 27531 removeFromArray(this.onDequeues, fn); 27532 }; 27533 27534 ETCp.setupDequeueing = defs.setupDequeueing({ 27535 deqRedrawThreshold: deqRedrawThreshold, 27536 deqCost: deqCost, 27537 deqAvgCost: deqAvgCost, 27538 deqNoDrawCost: deqNoDrawCost, 27539 deqFastCost: deqFastCost, 27540 deq: function deq(self, pxRatio, extent) { 27541 return self.dequeue(pxRatio, extent); 27542 }, 27543 onDeqd: function onDeqd(self, deqd) { 27544 for (var i = 0; i < self.onDequeues.length; i++) { 27545 var fn = self.onDequeues[i]; 27546 fn(deqd); 27547 } 27548 }, 27549 shouldRedraw: function shouldRedraw(self, deqd, pxRatio, extent) { 27550 for (var i = 0; i < deqd.length; i++) { 27551 var eles = deqd[i].eles; 27552 27553 for (var j = 0; j < eles.length; j++) { 27554 var bb = eles[j].boundingBox(); 27555 27556 if (boundingBoxesIntersect(bb, extent)) { 27557 return true; 27558 } 27559 } 27560 } 27561 27562 return false; 27563 }, 27564 priority: function priority(self) { 27565 return self.renderer.beforeRenderPriorities.eleTxrDeq; 27566 } 27567 }); 27568 27569 var defNumLayers = 1; // default number of layers to use 27570 27571 var minLvl$1 = -4; // when scaling smaller than that we don't need to re-render 27572 27573 var maxLvl$1 = 2; // when larger than this scale just render directly (caching is not helpful) 27574 27575 var maxZoom$1 = 3.99; // beyond this zoom level, layered textures are not used 27576 27577 var deqRedrawThreshold$1 = 50; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 27578 27579 var refineEleDebounceTime = 50; // time to debounce sharper ele texture updates 27580 27581 var deqCost$1 = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 27582 27583 var deqAvgCost$1 = 0.1; // % of add'l rendering cost compared to average overall redraw time 27584 27585 var deqNoDrawCost$1 = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 27586 27587 var deqFastCost$1 = 0.9; // % of frame time to be used when >60fps 27588 27589 var maxDeqSize$1 = 1; // number of eles to dequeue and render at higher texture in each batch 27590 27591 var invalidThreshold = 250; // time threshold for disabling b/c of invalidations 27592 27593 var maxLayerArea = 4000 * 4000; // layers can't be bigger than this 27594 27595 var useHighQualityEleTxrReqs = true; // whether to use high quality ele txr requests (generally faster and cheaper in the longterm) 27596 // var log = function(){ console.log.apply( console, arguments ); }; 27597 27598 var LayeredTextureCache = function LayeredTextureCache(renderer) { 27599 var self = this; 27600 var r = self.renderer = renderer; 27601 var cy = r.cy; 27602 self.layersByLevel = {}; // e.g. 2 => [ layer1, layer2, ..., layerN ] 27603 27604 self.firstGet = true; 27605 self.lastInvalidationTime = performanceNow() - 2 * invalidThreshold; 27606 self.skipping = false; 27607 self.eleTxrDeqs = cy.collection(); 27608 self.scheduleElementRefinement = util(function () { 27609 self.refineElementTextures(self.eleTxrDeqs); 27610 self.eleTxrDeqs.unmerge(self.eleTxrDeqs); 27611 }, refineEleDebounceTime); 27612 r.beforeRender(function (willDraw, now) { 27613 if (now - self.lastInvalidationTime <= invalidThreshold) { 27614 self.skipping = true; 27615 } else { 27616 self.skipping = false; 27617 } 27618 }, r.beforeRenderPriorities.lyrTxrSkip); 27619 27620 var qSort = function qSort(a, b) { 27621 return b.reqs - a.reqs; 27622 }; 27623 27624 self.layersQueue = new Heap(qSort); 27625 self.setupDequeueing(); 27626 }; 27627 27628 var LTCp = LayeredTextureCache.prototype; 27629 var layerIdPool = 0; 27630 var MAX_INT$1 = Math.pow(2, 53) - 1; 27631 27632 LTCp.makeLayer = function (bb, lvl) { 27633 var scale = Math.pow(2, lvl); 27634 var w = Math.ceil(bb.w * scale); 27635 var h = Math.ceil(bb.h * scale); 27636 var canvas = this.renderer.makeOffscreenCanvas(w, h); 27637 var layer = { 27638 id: layerIdPool = ++layerIdPool % MAX_INT$1, 27639 bb: bb, 27640 level: lvl, 27641 width: w, 27642 height: h, 27643 canvas: canvas, 27644 context: canvas.getContext('2d'), 27645 eles: [], 27646 elesQueue: [], 27647 reqs: 0 27648 }; // log('make layer %s with w %s and h %s and lvl %s', layer.id, layer.width, layer.height, layer.level); 27649 27650 var cxt = layer.context; 27651 var dx = -layer.bb.x1; 27652 var dy = -layer.bb.y1; // do the transform on creation to save cycles (it's the same for all eles) 27653 27654 cxt.scale(scale, scale); 27655 cxt.translate(dx, dy); 27656 return layer; 27657 }; 27658 27659 LTCp.getLayers = function (eles, pxRatio, lvl) { 27660 var self = this; 27661 var r = self.renderer; 27662 var cy = r.cy; 27663 var zoom = cy.zoom(); 27664 var firstGet = self.firstGet; 27665 self.firstGet = false; // log('--\nget layers with %s eles', eles.length); 27666 //log eles.map(function(ele){ return ele.id() }) ); 27667 27668 if (lvl == null) { 27669 lvl = Math.ceil(log2(zoom * pxRatio)); 27670 27671 if (lvl < minLvl$1) { 27672 lvl = minLvl$1; 27673 } else if (zoom >= maxZoom$1 || lvl > maxLvl$1) { 27674 return null; 27675 } 27676 } 27677 27678 self.validateLayersElesOrdering(lvl, eles); 27679 var layersByLvl = self.layersByLevel; 27680 var scale = Math.pow(2, lvl); 27681 var layers = layersByLvl[lvl] = layersByLvl[lvl] || []; 27682 var bb; 27683 var lvlComplete = self.levelIsComplete(lvl, eles); 27684 var tmpLayers; 27685 27686 var checkTempLevels = function checkTempLevels() { 27687 var canUseAsTmpLvl = function canUseAsTmpLvl(l) { 27688 self.validateLayersElesOrdering(l, eles); 27689 27690 if (self.levelIsComplete(l, eles)) { 27691 tmpLayers = layersByLvl[l]; 27692 return true; 27693 } 27694 }; 27695 27696 var checkLvls = function checkLvls(dir) { 27697 if (tmpLayers) { 27698 return; 27699 } 27700 27701 for (var l = lvl + dir; minLvl$1 <= l && l <= maxLvl$1; l += dir) { 27702 if (canUseAsTmpLvl(l)) { 27703 break; 27704 } 27705 } 27706 }; 27707 27708 checkLvls(+1); 27709 checkLvls(-1); // remove the invalid layers; they will be replaced as needed later in this function 27710 27711 for (var i = layers.length - 1; i >= 0; i--) { 27712 var layer = layers[i]; 27713 27714 if (layer.invalid) { 27715 removeFromArray(layers, layer); 27716 } 27717 } 27718 }; 27719 27720 if (!lvlComplete) { 27721 // if the current level is incomplete, then use the closest, best quality layerset temporarily 27722 // and later queue the current layerset so we can get the proper quality level soon 27723 checkTempLevels(); 27724 } else { 27725 // log('level complete, using existing layers\n--'); 27726 return layers; 27727 } 27728 27729 var getBb = function getBb() { 27730 if (!bb) { 27731 bb = makeBoundingBox(); 27732 27733 for (var i = 0; i < eles.length; i++) { 27734 updateBoundingBox(bb, eles[i].boundingBox()); 27735 } 27736 } 27737 27738 return bb; 27739 }; 27740 27741 var makeLayer = function makeLayer(opts) { 27742 opts = opts || {}; 27743 var after = opts.after; 27744 getBb(); 27745 var area = bb.w * scale * (bb.h * scale); 27746 27747 if (area > maxLayerArea) { 27748 return null; 27749 } 27750 27751 var layer = self.makeLayer(bb, lvl); 27752 27753 if (after != null) { 27754 var index = layers.indexOf(after) + 1; 27755 layers.splice(index, 0, layer); 27756 } else if (opts.insert === undefined || opts.insert) { 27757 // no after specified => first layer made so put at start 27758 layers.unshift(layer); 27759 } // if( tmpLayers ){ 27760 //self.queueLayer( layer ); 27761 // } 27762 27763 27764 return layer; 27765 }; 27766 27767 if (self.skipping && !firstGet) { 27768 // log('skip layers'); 27769 return null; 27770 } // log('do layers'); 27771 27772 27773 var layer = null; 27774 var maxElesPerLayer = eles.length / defNumLayers; 27775 var allowLazyQueueing = !firstGet; 27776 27777 for (var i = 0; i < eles.length; i++) { 27778 var ele = eles[i]; 27779 var rs = ele._private.rscratch; 27780 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; // log('look at ele', ele.id()); 27781 27782 var existingLayer = caches[lvl]; 27783 27784 if (existingLayer) { 27785 // reuse layer for later eles 27786 // log('reuse layer for', ele.id()); 27787 layer = existingLayer; 27788 continue; 27789 } 27790 27791 if (!layer || layer.eles.length >= maxElesPerLayer || !boundingBoxInBoundingBox(layer.bb, ele.boundingBox())) { 27792 // log('make new layer for ele %s', ele.id()); 27793 layer = makeLayer({ 27794 insert: true, 27795 after: layer 27796 }); // if now layer can be built then we can't use layers at this level 27797 27798 if (!layer) { 27799 return null; 27800 } // log('new layer with id %s', layer.id); 27801 27802 } 27803 27804 if (tmpLayers || allowLazyQueueing) { 27805 // log('queue ele %s in layer %s', ele.id(), layer.id); 27806 self.queueLayer(layer, ele); 27807 } else { 27808 // log('draw ele %s in layer %s', ele.id(), layer.id); 27809 self.drawEleInLayer(layer, ele, lvl, pxRatio); 27810 } 27811 27812 layer.eles.push(ele); 27813 caches[lvl] = layer; 27814 } // log('--'); 27815 27816 27817 if (tmpLayers) { 27818 // then we only queued the current layerset and can't draw it yet 27819 return tmpLayers; 27820 } 27821 27822 if (allowLazyQueueing) { 27823 // log('lazy queue level', lvl); 27824 return null; 27825 } 27826 27827 return layers; 27828 }; // a layer may want to use an ele cache of a higher level to avoid blurriness 27829 // so the layer level might not equal the ele level 27830 27831 27832 LTCp.getEleLevelForLayerLevel = function (lvl, pxRatio) { 27833 return lvl; 27834 }; 27835 27836 LTCp.drawEleInLayer = function (layer, ele, lvl, pxRatio) { 27837 var self = this; 27838 var r = this.renderer; 27839 var context = layer.context; 27840 var bb = ele.boundingBox(); 27841 27842 if (bb.w === 0 || bb.h === 0 || !ele.visible()) { 27843 return; 27844 } 27845 27846 lvl = self.getEleLevelForLayerLevel(lvl, pxRatio); 27847 27848 { 27849 r.setImgSmoothing(context, false); 27850 } 27851 27852 { 27853 r.drawCachedElement(context, ele, null, null, lvl, useHighQualityEleTxrReqs); 27854 } 27855 27856 { 27857 r.setImgSmoothing(context, true); 27858 } 27859 }; 27860 27861 LTCp.levelIsComplete = function (lvl, eles) { 27862 var self = this; 27863 var layers = self.layersByLevel[lvl]; 27864 27865 if (!layers || layers.length === 0) { 27866 return false; 27867 } 27868 27869 var numElesInLayers = 0; 27870 27871 for (var i = 0; i < layers.length; i++) { 27872 var layer = layers[i]; // if there are any eles needed to be drawn yet, the level is not complete 27873 27874 if (layer.reqs > 0) { 27875 return false; 27876 } // if the layer is invalid, the level is not complete 27877 27878 27879 if (layer.invalid) { 27880 return false; 27881 } 27882 27883 numElesInLayers += layer.eles.length; 27884 } // we should have exactly the number of eles passed in to be complete 27885 27886 27887 if (numElesInLayers !== eles.length) { 27888 return false; 27889 } 27890 27891 return true; 27892 }; 27893 27894 LTCp.validateLayersElesOrdering = function (lvl, eles) { 27895 var layers = this.layersByLevel[lvl]; 27896 27897 if (!layers) { 27898 return; 27899 } // if in a layer the eles are not in the same order, then the layer is invalid 27900 // (i.e. there is an ele in between the eles in the layer) 27901 27902 27903 for (var i = 0; i < layers.length; i++) { 27904 var layer = layers[i]; 27905 var offset = -1; // find the offset 27906 27907 for (var j = 0; j < eles.length; j++) { 27908 if (layer.eles[0] === eles[j]) { 27909 offset = j; 27910 break; 27911 } 27912 } 27913 27914 if (offset < 0) { 27915 // then the layer has nonexistant elements and is invalid 27916 this.invalidateLayer(layer); 27917 continue; 27918 } // the eles in the layer must be in the same continuous order, else the layer is invalid 27919 27920 27921 var o = offset; 27922 27923 for (var j = 0; j < layer.eles.length; j++) { 27924 if (layer.eles[j] !== eles[o + j]) { 27925 // log('invalidate based on ordering', layer.id); 27926 this.invalidateLayer(layer); 27927 break; 27928 } 27929 } 27930 } 27931 }; 27932 27933 LTCp.updateElementsInLayers = function (eles, update) { 27934 var self = this; 27935 var isEles = element(eles[0]); // collect udpated elements (cascaded from the layers) and update each 27936 // layer itself along the way 27937 27938 for (var i = 0; i < eles.length; i++) { 27939 var req = isEles ? null : eles[i]; 27940 var ele = isEles ? eles[i] : eles[i].ele; 27941 var rs = ele._private.rscratch; 27942 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; 27943 27944 for (var l = minLvl$1; l <= maxLvl$1; l++) { 27945 var layer = caches[l]; 27946 27947 if (!layer) { 27948 continue; 27949 } // if update is a request from the ele cache, then it affects only 27950 // the matching level 27951 27952 27953 if (req && self.getEleLevelForLayerLevel(layer.level) !== req.level) { 27954 continue; 27955 } 27956 27957 update(layer, ele, req); 27958 } 27959 } 27960 }; 27961 27962 LTCp.haveLayers = function () { 27963 var self = this; 27964 var haveLayers = false; 27965 27966 for (var l = minLvl$1; l <= maxLvl$1; l++) { 27967 var layers = self.layersByLevel[l]; 27968 27969 if (layers && layers.length > 0) { 27970 haveLayers = true; 27971 break; 27972 } 27973 } 27974 27975 return haveLayers; 27976 }; 27977 27978 LTCp.invalidateElements = function (eles) { 27979 var self = this; 27980 27981 if (eles.length === 0) { 27982 return; 27983 } 27984 27985 self.lastInvalidationTime = performanceNow(); // log('update invalidate layer time from eles'); 27986 27987 if (eles.length === 0 || !self.haveLayers()) { 27988 return; 27989 } 27990 27991 self.updateElementsInLayers(eles, function invalAssocLayers(layer, ele, req) { 27992 self.invalidateLayer(layer); 27993 }); 27994 }; 27995 27996 LTCp.invalidateLayer = function (layer) { 27997 // log('update invalidate layer time'); 27998 this.lastInvalidationTime = performanceNow(); 27999 28000 if (layer.invalid) { 28001 return; 28002 } // save cycles 28003 28004 28005 var lvl = layer.level; 28006 var eles = layer.eles; 28007 var layers = this.layersByLevel[lvl]; // log('invalidate layer', layer.id ); 28008 28009 removeFromArray(layers, layer); // layer.eles = []; 28010 28011 layer.elesQueue = []; 28012 layer.invalid = true; 28013 28014 if (layer.replacement) { 28015 layer.replacement.invalid = true; 28016 } 28017 28018 for (var i = 0; i < eles.length; i++) { 28019 var caches = eles[i]._private.rscratch.imgLayerCaches; 28020 28021 if (caches) { 28022 caches[lvl] = null; 28023 } 28024 } 28025 }; 28026 28027 LTCp.refineElementTextures = function (eles) { 28028 var self = this; // log('refine', eles.length); 28029 28030 self.updateElementsInLayers(eles, function refineEachEle(layer, ele, req) { 28031 var rLyr = layer.replacement; 28032 28033 if (!rLyr) { 28034 rLyr = layer.replacement = self.makeLayer(layer.bb, layer.level); 28035 rLyr.replaces = layer; 28036 rLyr.eles = layer.eles; // log('make replacement layer %s for %s with level %s', rLyr.id, layer.id, rLyr.level); 28037 } 28038 28039 if (!rLyr.reqs) { 28040 for (var i = 0; i < rLyr.eles.length; i++) { 28041 self.queueLayer(rLyr, rLyr.eles[i]); 28042 } // log('queue replacement layer refinement', rLyr.id); 28043 28044 } 28045 }); 28046 }; 28047 28048 LTCp.enqueueElementRefinement = function (ele) { 28049 28050 this.eleTxrDeqs.merge(ele); 28051 this.scheduleElementRefinement(); 28052 }; 28053 28054 LTCp.queueLayer = function (layer, ele) { 28055 var self = this; 28056 var q = self.layersQueue; 28057 var elesQ = layer.elesQueue; 28058 var hasId = elesQ.hasId = elesQ.hasId || {}; // if a layer is going to be replaced, queuing is a waste of time 28059 28060 if (layer.replacement) { 28061 return; 28062 } 28063 28064 if (ele) { 28065 if (hasId[ele.id()]) { 28066 return; 28067 } 28068 28069 elesQ.push(ele); 28070 hasId[ele.id()] = true; 28071 } 28072 28073 if (layer.reqs) { 28074 layer.reqs++; 28075 q.updateItem(layer); 28076 } else { 28077 layer.reqs = 1; 28078 q.push(layer); 28079 } 28080 }; 28081 28082 LTCp.dequeue = function (pxRatio) { 28083 var self = this; 28084 var q = self.layersQueue; 28085 var deqd = []; 28086 var eleDeqs = 0; 28087 28088 while (eleDeqs < maxDeqSize$1) { 28089 if (q.size() === 0) { 28090 break; 28091 } 28092 28093 var layer = q.peek(); // if a layer has been or will be replaced, then don't waste time with it 28094 28095 if (layer.replacement) { 28096 // log('layer %s in queue skipped b/c it already has a replacement', layer.id); 28097 q.pop(); 28098 continue; 28099 } // if this is a replacement layer that has been superceded, then forget it 28100 28101 28102 if (layer.replaces && layer !== layer.replaces.replacement) { 28103 // log('layer is no longer the most uptodate replacement; dequeued', layer.id) 28104 q.pop(); 28105 continue; 28106 } 28107 28108 if (layer.invalid) { 28109 // log('replacement layer %s is invalid; dequeued', layer.id); 28110 q.pop(); 28111 continue; 28112 } 28113 28114 var ele = layer.elesQueue.shift(); 28115 28116 if (ele) { 28117 // log('dequeue layer %s', layer.id); 28118 self.drawEleInLayer(layer, ele, layer.level, pxRatio); 28119 eleDeqs++; 28120 } 28121 28122 if (deqd.length === 0) { 28123 // we need only one entry in deqd to queue redrawing etc 28124 deqd.push(true); 28125 } // if the layer has all its eles done, then remove from the queue 28126 28127 28128 if (layer.elesQueue.length === 0) { 28129 q.pop(); 28130 layer.reqs = 0; // log('dequeue of layer %s complete', layer.id); 28131 // when a replacement layer is dequeued, it replaces the old layer in the level 28132 28133 if (layer.replaces) { 28134 self.applyLayerReplacement(layer); 28135 } 28136 28137 self.requestRedraw(); 28138 } 28139 } 28140 28141 return deqd; 28142 }; 28143 28144 LTCp.applyLayerReplacement = function (layer) { 28145 var self = this; 28146 var layersInLevel = self.layersByLevel[layer.level]; 28147 var replaced = layer.replaces; 28148 var index = layersInLevel.indexOf(replaced); // if the replaced layer is not in the active list for the level, then replacing 28149 // refs would be a mistake (i.e. overwriting the true active layer) 28150 28151 if (index < 0 || replaced.invalid) { 28152 // log('replacement layer would have no effect', layer.id); 28153 return; 28154 } 28155 28156 layersInLevel[index] = layer; // replace level ref 28157 // replace refs in eles 28158 28159 for (var i = 0; i < layer.eles.length; i++) { 28160 var _p = layer.eles[i]._private; 28161 var cache = _p.imgLayerCaches = _p.imgLayerCaches || {}; 28162 28163 if (cache) { 28164 cache[layer.level] = layer; 28165 } 28166 } // log('apply replacement layer %s over %s', layer.id, replaced.id); 28167 28168 28169 self.requestRedraw(); 28170 }; 28171 28172 LTCp.requestRedraw = util(function () { 28173 var r = this.renderer; 28174 r.redrawHint('eles', true); 28175 r.redrawHint('drag', true); 28176 r.redraw(); 28177 }, 100); 28178 LTCp.setupDequeueing = defs.setupDequeueing({ 28179 deqRedrawThreshold: deqRedrawThreshold$1, 28180 deqCost: deqCost$1, 28181 deqAvgCost: deqAvgCost$1, 28182 deqNoDrawCost: deqNoDrawCost$1, 28183 deqFastCost: deqFastCost$1, 28184 deq: function deq(self, pxRatio) { 28185 return self.dequeue(pxRatio); 28186 }, 28187 onDeqd: noop, 28188 shouldRedraw: trueify, 28189 priority: function priority(self) { 28190 return self.renderer.beforeRenderPriorities.lyrTxrDeq; 28191 } 28192 }); 28193 28194 var CRp = {}; 28195 var impl; 28196 28197 function polygon(context, points) { 28198 for (var i = 0; i < points.length; i++) { 28199 var pt = points[i]; 28200 context.lineTo(pt.x, pt.y); 28201 } 28202 } 28203 28204 function triangleBackcurve(context, points, controlPoint) { 28205 var firstPt; 28206 28207 for (var i = 0; i < points.length; i++) { 28208 var pt = points[i]; 28209 28210 if (i === 0) { 28211 firstPt = pt; 28212 } 28213 28214 context.lineTo(pt.x, pt.y); 28215 } 28216 28217 context.quadraticCurveTo(controlPoint.x, controlPoint.y, firstPt.x, firstPt.y); 28218 } 28219 28220 function triangleTee(context, trianglePoints, teePoints) { 28221 if (context.beginPath) { 28222 context.beginPath(); 28223 } 28224 28225 var triPts = trianglePoints; 28226 28227 for (var i = 0; i < triPts.length; i++) { 28228 var pt = triPts[i]; 28229 context.lineTo(pt.x, pt.y); 28230 } 28231 28232 var teePts = teePoints; 28233 var firstTeePt = teePoints[0]; 28234 context.moveTo(firstTeePt.x, firstTeePt.y); 28235 28236 for (var i = 1; i < teePts.length; i++) { 28237 var pt = teePts[i]; 28238 context.lineTo(pt.x, pt.y); 28239 } 28240 28241 if (context.closePath) { 28242 context.closePath(); 28243 } 28244 } 28245 28246 function circle(context, rx, ry, r) { 28247 context.arc(rx, ry, r, 0, Math.PI * 2, false); 28248 } 28249 28250 CRp.arrowShapeImpl = function (name) { 28251 return (impl || (impl = { 28252 'polygon': polygon, 28253 'triangle-backcurve': triangleBackcurve, 28254 'triangle-tee': triangleTee, 28255 'triangle-cross': triangleTee, 28256 'circle': circle 28257 }))[name]; 28258 }; 28259 28260 var CRp$1 = {}; 28261 28262 CRp$1.drawElement = function (context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity) { 28263 var r = this; 28264 28265 if (ele.isNode()) { 28266 r.drawNode(context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity); 28267 } else { 28268 r.drawEdge(context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity); 28269 } 28270 }; 28271 28272 CRp$1.drawElementOverlay = function (context, ele) { 28273 var r = this; 28274 28275 if (ele.isNode()) { 28276 r.drawNodeOverlay(context, ele); 28277 } else { 28278 r.drawEdgeOverlay(context, ele); 28279 } 28280 }; 28281 28282 CRp$1.drawCachedElementPortion = function (context, ele, eleTxrCache, pxRatio, lvl, reason, getRotation, getOpacity) { 28283 var r = this; 28284 var bb = eleTxrCache.getBoundingBox(ele); 28285 28286 if (bb.w === 0 || bb.h === 0) { 28287 return; 28288 } // ignore zero size case 28289 28290 28291 var eleCache = eleTxrCache.getElement(ele, bb, pxRatio, lvl, reason); 28292 28293 if (eleCache != null) { 28294 var opacity = getOpacity(r, ele); 28295 28296 if (opacity === 0) { 28297 return; 28298 } 28299 28300 var theta = getRotation(r, ele); 28301 var x1 = bb.x1, 28302 y1 = bb.y1, 28303 w = bb.w, 28304 h = bb.h; 28305 var x, y, sx, sy, smooth; 28306 28307 if (theta !== 0) { 28308 var rotPt = eleTxrCache.getRotationPoint(ele); 28309 sx = rotPt.x; 28310 sy = rotPt.y; 28311 context.translate(sx, sy); 28312 context.rotate(theta); 28313 smooth = r.getImgSmoothing(context); 28314 28315 if (!smooth) { 28316 r.setImgSmoothing(context, true); 28317 } 28318 28319 var off = eleTxrCache.getRotationOffset(ele); 28320 x = off.x; 28321 y = off.y; 28322 } else { 28323 x = x1; 28324 y = y1; 28325 } 28326 28327 var oldGlobalAlpha; 28328 28329 if (opacity !== 1) { 28330 oldGlobalAlpha = context.globalAlpha; 28331 context.globalAlpha = oldGlobalAlpha * opacity; 28332 } 28333 28334 context.drawImage(eleCache.texture.canvas, eleCache.x, 0, eleCache.width, eleCache.height, x, y, w, h); 28335 28336 if (opacity !== 1) { 28337 context.globalAlpha = oldGlobalAlpha; 28338 } 28339 28340 if (theta !== 0) { 28341 context.rotate(-theta); 28342 context.translate(-sx, -sy); 28343 28344 if (!smooth) { 28345 r.setImgSmoothing(context, false); 28346 } 28347 } 28348 } else { 28349 eleTxrCache.drawElement(context, ele); // direct draw fallback 28350 } 28351 }; 28352 28353 var getZeroRotation = function getZeroRotation() { 28354 return 0; 28355 }; 28356 28357 var getLabelRotation = function getLabelRotation(r, ele) { 28358 return r.getTextAngle(ele, null); 28359 }; 28360 28361 var getSourceLabelRotation = function getSourceLabelRotation(r, ele) { 28362 return r.getTextAngle(ele, 'source'); 28363 }; 28364 28365 var getTargetLabelRotation = function getTargetLabelRotation(r, ele) { 28366 return r.getTextAngle(ele, 'target'); 28367 }; 28368 28369 var getOpacity = function getOpacity(r, ele) { 28370 return ele.effectiveOpacity(); 28371 }; 28372 28373 var getTextOpacity = function getTextOpacity(e, ele) { 28374 return ele.pstyle('text-opacity').pfValue * ele.effectiveOpacity(); 28375 }; 28376 28377 CRp$1.drawCachedElement = function (context, ele, pxRatio, extent, lvl, requestHighQuality) { 28378 var r = this; 28379 var _r$data = r.data, 28380 eleTxrCache = _r$data.eleTxrCache, 28381 lblTxrCache = _r$data.lblTxrCache, 28382 slbTxrCache = _r$data.slbTxrCache, 28383 tlbTxrCache = _r$data.tlbTxrCache; 28384 var bb = ele.boundingBox(); 28385 var reason = requestHighQuality === true ? eleTxrCache.reasons.highQuality : null; 28386 28387 if (bb.w === 0 || bb.h === 0 || !ele.visible()) { 28388 return; 28389 } 28390 28391 if (!extent || boundingBoxesIntersect(bb, extent)) { 28392 var isEdge = ele.isEdge(); 28393 28394 var badLine = ele.element()._private.rscratch.badLine; 28395 28396 r.drawCachedElementPortion(context, ele, eleTxrCache, pxRatio, lvl, reason, getZeroRotation, getOpacity); 28397 28398 if (!isEdge || !badLine) { 28399 r.drawCachedElementPortion(context, ele, lblTxrCache, pxRatio, lvl, reason, getLabelRotation, getTextOpacity); 28400 } 28401 28402 if (isEdge && !badLine) { 28403 r.drawCachedElementPortion(context, ele, slbTxrCache, pxRatio, lvl, reason, getSourceLabelRotation, getTextOpacity); 28404 r.drawCachedElementPortion(context, ele, tlbTxrCache, pxRatio, lvl, reason, getTargetLabelRotation, getTextOpacity); 28405 } 28406 28407 r.drawElementOverlay(context, ele); 28408 } 28409 }; 28410 28411 CRp$1.drawElements = function (context, eles) { 28412 var r = this; 28413 28414 for (var i = 0; i < eles.length; i++) { 28415 var ele = eles[i]; 28416 r.drawElement(context, ele); 28417 } 28418 }; 28419 28420 CRp$1.drawCachedElements = function (context, eles, pxRatio, extent) { 28421 var r = this; 28422 28423 for (var i = 0; i < eles.length; i++) { 28424 var ele = eles[i]; 28425 r.drawCachedElement(context, ele, pxRatio, extent); 28426 } 28427 }; 28428 28429 CRp$1.drawCachedNodes = function (context, eles, pxRatio, extent) { 28430 var r = this; 28431 28432 for (var i = 0; i < eles.length; i++) { 28433 var ele = eles[i]; 28434 28435 if (!ele.isNode()) { 28436 continue; 28437 } 28438 28439 r.drawCachedElement(context, ele, pxRatio, extent); 28440 } 28441 }; 28442 28443 CRp$1.drawLayeredElements = function (context, eles, pxRatio, extent) { 28444 var r = this; 28445 var layers = r.data.lyrTxrCache.getLayers(eles, pxRatio); 28446 28447 if (layers) { 28448 for (var i = 0; i < layers.length; i++) { 28449 var layer = layers[i]; 28450 var bb = layer.bb; 28451 28452 if (bb.w === 0 || bb.h === 0) { 28453 continue; 28454 } 28455 28456 context.drawImage(layer.canvas, bb.x1, bb.y1, bb.w, bb.h); 28457 } 28458 } else { 28459 // fall back on plain caching if no layers 28460 r.drawCachedElements(context, eles, pxRatio, extent); 28461 } 28462 }; 28463 28464 /* global Path2D */ 28465 var CRp$2 = {}; 28466 28467 CRp$2.drawEdge = function (context, edge, shiftToOriginWithBb) { 28468 var drawLabel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 28469 var shouldDrawOverlay = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 28470 var shouldDrawOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 28471 var r = this; 28472 var rs = edge._private.rscratch; 28473 28474 if (shouldDrawOpacity && !edge.visible()) { 28475 return; 28476 } // if bezier ctrl pts can not be calculated, then die 28477 28478 28479 if (rs.badLine || rs.allpts == null || isNaN(rs.allpts[0])) { 28480 // isNaN in case edge is impossible and browser bugs (e.g. safari) 28481 return; 28482 } 28483 28484 var bb; 28485 28486 if (shiftToOriginWithBb) { 28487 bb = shiftToOriginWithBb; 28488 context.translate(-bb.x1, -bb.y1); 28489 } 28490 28491 var opacity = shouldDrawOpacity ? edge.pstyle('opacity').value : 1; 28492 var lineStyle = edge.pstyle('line-style').value; 28493 var edgeWidth = edge.pstyle('width').pfValue; 28494 var lineCap = edge.pstyle('line-cap').value; 28495 28496 var drawLine = function drawLine() { 28497 var strokeOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : opacity; 28498 context.lineWidth = edgeWidth; 28499 context.lineCap = lineCap; 28500 r.eleStrokeStyle(context, edge, strokeOpacity); 28501 r.drawEdgePath(edge, context, rs.allpts, lineStyle); 28502 context.lineCap = 'butt'; // reset for other drawing functions 28503 }; 28504 28505 var drawOverlay = function drawOverlay() { 28506 if (!shouldDrawOverlay) { 28507 return; 28508 } 28509 28510 r.drawEdgeOverlay(context, edge); 28511 }; 28512 28513 var drawArrows = function drawArrows() { 28514 var arrowOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : opacity; 28515 r.drawArrowheads(context, edge, arrowOpacity); 28516 }; 28517 28518 var drawText = function drawText() { 28519 r.drawElementText(context, edge, null, drawLabel); 28520 }; 28521 28522 context.lineJoin = 'round'; 28523 var ghost = edge.pstyle('ghost').value === 'yes'; 28524 28525 if (ghost) { 28526 var gx = edge.pstyle('ghost-offset-x').pfValue; 28527 var gy = edge.pstyle('ghost-offset-y').pfValue; 28528 var ghostOpacity = edge.pstyle('ghost-opacity').value; 28529 var effectiveGhostOpacity = opacity * ghostOpacity; 28530 context.translate(gx, gy); 28531 drawLine(effectiveGhostOpacity); 28532 drawArrows(effectiveGhostOpacity); 28533 context.translate(-gx, -gy); 28534 } 28535 28536 drawLine(); 28537 drawArrows(); 28538 drawOverlay(); 28539 drawText(); 28540 28541 if (shiftToOriginWithBb) { 28542 context.translate(bb.x1, bb.y1); 28543 } 28544 }; 28545 28546 CRp$2.drawEdgeOverlay = function (context, edge) { 28547 if (!edge.visible()) { 28548 return; 28549 } 28550 28551 var overlayOpacity = edge.pstyle('overlay-opacity').value; 28552 28553 if (overlayOpacity === 0) { 28554 return; 28555 } 28556 28557 var r = this; 28558 var usePaths = r.usePaths(); 28559 var rs = edge._private.rscratch; 28560 var overlayPadding = edge.pstyle('overlay-padding').pfValue; 28561 var overlayWidth = 2 * overlayPadding; 28562 var overlayColor = edge.pstyle('overlay-color').value; 28563 context.lineWidth = overlayWidth; 28564 28565 if (rs.edgeType === 'self' && !usePaths) { 28566 context.lineCap = 'butt'; 28567 } else { 28568 context.lineCap = 'round'; 28569 } 28570 28571 r.colorStrokeStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); 28572 r.drawEdgePath(edge, context, rs.allpts, 'solid'); 28573 }; 28574 28575 CRp$2.drawEdgePath = function (edge, context, pts, type) { 28576 var rs = edge._private.rscratch; 28577 var canvasCxt = context; 28578 var path; 28579 var pathCacheHit = false; 28580 var usePaths = this.usePaths(); 28581 var lineDashPattern = edge.pstyle('line-dash-pattern').pfValue; 28582 var lineDashOffset = edge.pstyle('line-dash-offset').pfValue; 28583 28584 if (usePaths) { 28585 var pathCacheKey = pts.join('$'); 28586 var keyMatches = rs.pathCacheKey && rs.pathCacheKey === pathCacheKey; 28587 28588 if (keyMatches) { 28589 path = context = rs.pathCache; 28590 pathCacheHit = true; 28591 } else { 28592 path = context = new Path2D(); 28593 rs.pathCacheKey = pathCacheKey; 28594 rs.pathCache = path; 28595 } 28596 } 28597 28598 if (canvasCxt.setLineDash) { 28599 // for very outofdate browsers 28600 switch (type) { 28601 case 'dotted': 28602 canvasCxt.setLineDash([1, 1]); 28603 break; 28604 28605 case 'dashed': 28606 canvasCxt.setLineDash(lineDashPattern); 28607 canvasCxt.lineDashOffset = lineDashOffset; 28608 break; 28609 28610 case 'solid': 28611 canvasCxt.setLineDash([]); 28612 break; 28613 } 28614 } 28615 28616 if (!pathCacheHit && !rs.badLine) { 28617 if (context.beginPath) { 28618 context.beginPath(); 28619 } 28620 28621 context.moveTo(pts[0], pts[1]); 28622 28623 switch (rs.edgeType) { 28624 case 'bezier': 28625 case 'self': 28626 case 'compound': 28627 case 'multibezier': 28628 for (var i = 2; i + 3 < pts.length; i += 4) { 28629 context.quadraticCurveTo(pts[i], pts[i + 1], pts[i + 2], pts[i + 3]); 28630 } 28631 28632 break; 28633 28634 case 'straight': 28635 case 'segments': 28636 case 'haystack': 28637 for (var _i = 2; _i + 1 < pts.length; _i += 2) { 28638 context.lineTo(pts[_i], pts[_i + 1]); 28639 } 28640 28641 break; 28642 } 28643 } 28644 28645 context = canvasCxt; 28646 28647 if (usePaths) { 28648 context.stroke(path); 28649 } else { 28650 context.stroke(); 28651 } // reset any line dashes 28652 28653 28654 if (context.setLineDash) { 28655 // for very outofdate browsers 28656 context.setLineDash([]); 28657 } 28658 }; 28659 28660 CRp$2.drawArrowheads = function (context, edge, opacity) { 28661 var rs = edge._private.rscratch; 28662 var isHaystack = rs.edgeType === 'haystack'; 28663 28664 if (!isHaystack) { 28665 this.drawArrowhead(context, edge, 'source', rs.arrowStartX, rs.arrowStartY, rs.srcArrowAngle, opacity); 28666 } 28667 28668 this.drawArrowhead(context, edge, 'mid-target', rs.midX, rs.midY, rs.midtgtArrowAngle, opacity); 28669 this.drawArrowhead(context, edge, 'mid-source', rs.midX, rs.midY, rs.midsrcArrowAngle, opacity); 28670 28671 if (!isHaystack) { 28672 this.drawArrowhead(context, edge, 'target', rs.arrowEndX, rs.arrowEndY, rs.tgtArrowAngle, opacity); 28673 } 28674 }; 28675 28676 CRp$2.drawArrowhead = function (context, edge, prefix, x, y, angle, opacity) { 28677 if (isNaN(x) || x == null || isNaN(y) || y == null || isNaN(angle) || angle == null) { 28678 return; 28679 } 28680 28681 var self = this; 28682 var arrowShape = edge.pstyle(prefix + '-arrow-shape').value; 28683 28684 if (arrowShape === 'none') { 28685 return; 28686 } 28687 28688 var arrowClearFill = edge.pstyle(prefix + '-arrow-fill').value === 'hollow' ? 'both' : 'filled'; 28689 var arrowFill = edge.pstyle(prefix + '-arrow-fill').value; 28690 var edgeWidth = edge.pstyle('width').pfValue; 28691 var edgeOpacity = edge.pstyle('opacity').value; 28692 28693 if (opacity === undefined) { 28694 opacity = edgeOpacity; 28695 } 28696 28697 var gco = context.globalCompositeOperation; 28698 28699 if (opacity !== 1 || arrowFill === 'hollow') { 28700 // then extra clear is needed 28701 context.globalCompositeOperation = 'destination-out'; 28702 self.colorFillStyle(context, 255, 255, 255, 1); 28703 self.colorStrokeStyle(context, 255, 255, 255, 1); 28704 self.drawArrowShape(edge, context, arrowClearFill, edgeWidth, arrowShape, x, y, angle); 28705 context.globalCompositeOperation = gco; 28706 } // otherwise, the opaque arrow clears it for free :) 28707 28708 28709 var color = edge.pstyle(prefix + '-arrow-color').value; 28710 self.colorFillStyle(context, color[0], color[1], color[2], opacity); 28711 self.colorStrokeStyle(context, color[0], color[1], color[2], opacity); 28712 self.drawArrowShape(edge, context, arrowFill, edgeWidth, arrowShape, x, y, angle); 28713 }; 28714 28715 CRp$2.drawArrowShape = function (edge, context, fill, edgeWidth, shape, x, y, angle) { 28716 var r = this; 28717 var usePaths = this.usePaths() && shape !== 'triangle-cross'; 28718 var pathCacheHit = false; 28719 var path; 28720 var canvasContext = context; 28721 var translation = { 28722 x: x, 28723 y: y 28724 }; 28725 var scale = edge.pstyle('arrow-scale').value; 28726 var size = this.getArrowWidth(edgeWidth, scale); 28727 var shapeImpl = r.arrowShapes[shape]; 28728 28729 if (usePaths) { 28730 var cache = r.arrowPathCache = r.arrowPathCache || []; 28731 var key = hashString(shape); 28732 var cachedPath = cache[key]; 28733 28734 if (cachedPath != null) { 28735 path = context = cachedPath; 28736 pathCacheHit = true; 28737 } else { 28738 path = context = new Path2D(); 28739 cache[key] = path; 28740 } 28741 } 28742 28743 if (!pathCacheHit) { 28744 if (context.beginPath) { 28745 context.beginPath(); 28746 } 28747 28748 if (usePaths) { 28749 // store in the path cache with values easily manipulated later 28750 shapeImpl.draw(context, 1, 0, { 28751 x: 0, 28752 y: 0 28753 }, 1); 28754 } else { 28755 shapeImpl.draw(context, size, angle, translation, edgeWidth); 28756 } 28757 28758 if (context.closePath) { 28759 context.closePath(); 28760 } 28761 } 28762 28763 context = canvasContext; 28764 28765 if (usePaths) { 28766 // set transform to arrow position/orientation 28767 context.translate(x, y); 28768 context.rotate(angle); 28769 context.scale(size, size); 28770 } 28771 28772 if (fill === 'filled' || fill === 'both') { 28773 if (usePaths) { 28774 context.fill(path); 28775 } else { 28776 context.fill(); 28777 } 28778 } 28779 28780 if (fill === 'hollow' || fill === 'both') { 28781 context.lineWidth = (shapeImpl.matchEdgeWidth ? edgeWidth : 1) / (usePaths ? size : 1); 28782 context.lineJoin = 'miter'; 28783 28784 if (usePaths) { 28785 context.stroke(path); 28786 } else { 28787 context.stroke(); 28788 } 28789 } 28790 28791 if (usePaths) { 28792 // reset transform by applying inverse 28793 context.scale(1 / size, 1 / size); 28794 context.rotate(-angle); 28795 context.translate(-x, -y); 28796 } 28797 }; 28798 28799 var CRp$3 = {}; 28800 28801 CRp$3.safeDrawImage = function (context, img, ix, iy, iw, ih, x, y, w, h) { 28802 // detect problematic cases for old browsers with bad images (cheaper than try-catch) 28803 if (iw <= 0 || ih <= 0 || w <= 0 || h <= 0) { 28804 return; 28805 } 28806 28807 context.drawImage(img, ix, iy, iw, ih, x, y, w, h); 28808 }; 28809 28810 CRp$3.drawInscribedImage = function (context, img, node, index, nodeOpacity) { 28811 var r = this; 28812 var pos = node.position(); 28813 var nodeX = pos.x; 28814 var nodeY = pos.y; 28815 var styleObj = node.cy().style(); 28816 var getIndexedStyle = styleObj.getIndexedStyle.bind(styleObj); 28817 var fit = getIndexedStyle(node, 'background-fit', 'value', index); 28818 var repeat = getIndexedStyle(node, 'background-repeat', 'value', index); 28819 var nodeW = node.width(); 28820 var nodeH = node.height(); 28821 var paddingX2 = node.padding() * 2; 28822 var nodeTW = nodeW + (getIndexedStyle(node, 'background-width-relative-to', 'value', index) === 'inner' ? 0 : paddingX2); 28823 var nodeTH = nodeH + (getIndexedStyle(node, 'background-height-relative-to', 'value', index) === 'inner' ? 0 : paddingX2); 28824 var rs = node._private.rscratch; 28825 var clip = getIndexedStyle(node, 'background-clip', 'value', index); 28826 var shouldClip = clip === 'node'; 28827 var imgOpacity = getIndexedStyle(node, 'background-image-opacity', 'value', index) * nodeOpacity; 28828 var imgW = img.width || img.cachedW; 28829 var imgH = img.height || img.cachedH; // workaround for broken browsers like ie 28830 28831 if (null == imgW || null == imgH) { 28832 document.body.appendChild(img); // eslint-disable-line no-undef 28833 28834 imgW = img.cachedW = img.width || img.offsetWidth; 28835 imgH = img.cachedH = img.height || img.offsetHeight; 28836 document.body.removeChild(img); // eslint-disable-line no-undef 28837 } 28838 28839 var w = imgW; 28840 var h = imgH; 28841 28842 if (getIndexedStyle(node, 'background-width', 'value', index) !== 'auto') { 28843 if (getIndexedStyle(node, 'background-width', 'units', index) === '%') { 28844 w = getIndexedStyle(node, 'background-width', 'pfValue', index) * nodeTW; 28845 } else { 28846 w = getIndexedStyle(node, 'background-width', 'pfValue', index); 28847 } 28848 } 28849 28850 if (getIndexedStyle(node, 'background-height', 'value', index) !== 'auto') { 28851 if (getIndexedStyle(node, 'background-height', 'units', index) === '%') { 28852 h = getIndexedStyle(node, 'background-height', 'pfValue', index) * nodeTH; 28853 } else { 28854 h = getIndexedStyle(node, 'background-height', 'pfValue', index); 28855 } 28856 } 28857 28858 if (w === 0 || h === 0) { 28859 return; // no point in drawing empty image (and chrome is broken in this case) 28860 } 28861 28862 if (fit === 'contain') { 28863 var scale = Math.min(nodeTW / w, nodeTH / h); 28864 w *= scale; 28865 h *= scale; 28866 } else if (fit === 'cover') { 28867 var scale = Math.max(nodeTW / w, nodeTH / h); 28868 w *= scale; 28869 h *= scale; 28870 } 28871 28872 var x = nodeX - nodeTW / 2; // left 28873 28874 var posXUnits = getIndexedStyle(node, 'background-position-x', 'units', index); 28875 var posXPfVal = getIndexedStyle(node, 'background-position-x', 'pfValue', index); 28876 28877 if (posXUnits === '%') { 28878 x += (nodeTW - w) * posXPfVal; 28879 } else { 28880 x += posXPfVal; 28881 } 28882 28883 var offXUnits = getIndexedStyle(node, 'background-offset-x', 'units', index); 28884 var offXPfVal = getIndexedStyle(node, 'background-offset-x', 'pfValue', index); 28885 28886 if (offXUnits === '%') { 28887 x += (nodeTW - w) * offXPfVal; 28888 } else { 28889 x += offXPfVal; 28890 } 28891 28892 var y = nodeY - nodeTH / 2; // top 28893 28894 var posYUnits = getIndexedStyle(node, 'background-position-y', 'units', index); 28895 var posYPfVal = getIndexedStyle(node, 'background-position-y', 'pfValue', index); 28896 28897 if (posYUnits === '%') { 28898 y += (nodeTH - h) * posYPfVal; 28899 } else { 28900 y += posYPfVal; 28901 } 28902 28903 var offYUnits = getIndexedStyle(node, 'background-offset-y', 'units', index); 28904 var offYPfVal = getIndexedStyle(node, 'background-offset-y', 'pfValue', index); 28905 28906 if (offYUnits === '%') { 28907 y += (nodeTH - h) * offYPfVal; 28908 } else { 28909 y += offYPfVal; 28910 } 28911 28912 if (rs.pathCache) { 28913 x -= nodeX; 28914 y -= nodeY; 28915 nodeX = 0; 28916 nodeY = 0; 28917 } 28918 28919 var gAlpha = context.globalAlpha; 28920 context.globalAlpha = imgOpacity; 28921 28922 if (repeat === 'no-repeat') { 28923 if (shouldClip) { 28924 context.save(); 28925 28926 if (rs.pathCache) { 28927 context.clip(rs.pathCache); 28928 } else { 28929 r.nodeShapes[r.getNodeShape(node)].draw(context, nodeX, nodeY, nodeTW, nodeTH); 28930 context.clip(); 28931 } 28932 } 28933 28934 r.safeDrawImage(context, img, 0, 0, imgW, imgH, x, y, w, h); 28935 28936 if (shouldClip) { 28937 context.restore(); 28938 } 28939 } else { 28940 var pattern = context.createPattern(img, repeat); 28941 context.fillStyle = pattern; 28942 r.nodeShapes[r.getNodeShape(node)].draw(context, nodeX, nodeY, nodeTW, nodeTH); 28943 context.translate(x, y); 28944 context.fill(); 28945 context.translate(-x, -y); 28946 } 28947 28948 context.globalAlpha = gAlpha; 28949 }; 28950 28951 var CRp$4 = {}; 28952 28953 CRp$4.eleTextBiggerThanMin = function (ele, scale) { 28954 if (!scale) { 28955 var zoom = ele.cy().zoom(); 28956 var pxRatio = this.getPixelRatio(); 28957 var lvl = Math.ceil(log2(zoom * pxRatio)); // the effective texture level 28958 28959 scale = Math.pow(2, lvl); 28960 } 28961 28962 var computedSize = ele.pstyle('font-size').pfValue * scale; 28963 var minSize = ele.pstyle('min-zoomed-font-size').pfValue; 28964 28965 if (computedSize < minSize) { 28966 return false; 28967 } 28968 28969 return true; 28970 }; 28971 28972 CRp$4.drawElementText = function (context, ele, shiftToOriginWithBb, force, prefix) { 28973 var useEleOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 28974 var r = this; 28975 28976 if (force == null) { 28977 if (useEleOpacity && !r.eleTextBiggerThanMin(ele)) { 28978 return; 28979 } 28980 } else if (force === false) { 28981 return; 28982 } 28983 28984 if (ele.isNode()) { 28985 var label = ele.pstyle('label'); 28986 28987 if (!label || !label.value) { 28988 return; 28989 } 28990 28991 var justification = r.getLabelJustification(ele); 28992 context.textAlign = justification; 28993 context.textBaseline = 'bottom'; 28994 } else { 28995 var badLine = ele.element()._private.rscratch.badLine; 28996 28997 var _label = ele.pstyle('label'); 28998 28999 var srcLabel = ele.pstyle('source-label'); 29000 var tgtLabel = ele.pstyle('target-label'); 29001 29002 if (badLine || (!_label || !_label.value) && (!srcLabel || !srcLabel.value) && (!tgtLabel || !tgtLabel.value)) { 29003 return; 29004 } 29005 29006 context.textAlign = 'center'; 29007 context.textBaseline = 'bottom'; 29008 } 29009 29010 var applyRotation = !shiftToOriginWithBb; 29011 var bb; 29012 29013 if (shiftToOriginWithBb) { 29014 bb = shiftToOriginWithBb; 29015 context.translate(-bb.x1, -bb.y1); 29016 } 29017 29018 if (prefix == null) { 29019 r.drawText(context, ele, null, applyRotation, useEleOpacity); 29020 29021 if (ele.isEdge()) { 29022 r.drawText(context, ele, 'source', applyRotation, useEleOpacity); 29023 r.drawText(context, ele, 'target', applyRotation, useEleOpacity); 29024 } 29025 } else { 29026 r.drawText(context, ele, prefix, applyRotation, useEleOpacity); 29027 } 29028 29029 if (shiftToOriginWithBb) { 29030 context.translate(bb.x1, bb.y1); 29031 } 29032 }; 29033 29034 CRp$4.getFontCache = function (context) { 29035 var cache; 29036 this.fontCaches = this.fontCaches || []; 29037 29038 for (var i = 0; i < this.fontCaches.length; i++) { 29039 cache = this.fontCaches[i]; 29040 29041 if (cache.context === context) { 29042 return cache; 29043 } 29044 } 29045 29046 cache = { 29047 context: context 29048 }; 29049 this.fontCaches.push(cache); 29050 return cache; 29051 }; // set up canvas context with font 29052 // returns transformed text string 29053 29054 29055 CRp$4.setupTextStyle = function (context, ele) { 29056 var useEleOpacity = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 29057 // Font style 29058 var labelStyle = ele.pstyle('font-style').strValue; 29059 var labelSize = ele.pstyle('font-size').pfValue + 'px'; 29060 var labelFamily = ele.pstyle('font-family').strValue; 29061 var labelWeight = ele.pstyle('font-weight').strValue; 29062 var opacity = useEleOpacity ? ele.effectiveOpacity() * ele.pstyle('text-opacity').value : 1; 29063 var outlineOpacity = ele.pstyle('text-outline-opacity').value * opacity; 29064 var color = ele.pstyle('color').value; 29065 var outlineColor = ele.pstyle('text-outline-color').value; 29066 context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily; 29067 context.lineJoin = 'round'; // so text outlines aren't jagged 29068 29069 this.colorFillStyle(context, color[0], color[1], color[2], opacity); 29070 this.colorStrokeStyle(context, outlineColor[0], outlineColor[1], outlineColor[2], outlineOpacity); 29071 }; // TODO ensure re-used 29072 29073 29074 function roundRect(ctx, x, y, width, height) { 29075 var radius = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 5; 29076 ctx.beginPath(); 29077 ctx.moveTo(x + radius, y); 29078 ctx.lineTo(x + width - radius, y); 29079 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 29080 ctx.lineTo(x + width, y + height - radius); 29081 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 29082 ctx.lineTo(x + radius, y + height); 29083 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 29084 ctx.lineTo(x, y + radius); 29085 ctx.quadraticCurveTo(x, y, x + radius, y); 29086 ctx.closePath(); 29087 ctx.fill(); 29088 } 29089 29090 CRp$4.getTextAngle = function (ele, prefix) { 29091 var theta; 29092 var _p = ele._private; 29093 var rscratch = _p.rscratch; 29094 var pdash = prefix ? prefix + '-' : ''; 29095 var rotation = ele.pstyle(pdash + 'text-rotation'); 29096 var textAngle = getPrefixedProperty(rscratch, 'labelAngle', prefix); 29097 29098 if (rotation.strValue === 'autorotate') { 29099 theta = ele.isEdge() ? textAngle : 0; 29100 } else if (rotation.strValue === 'none') { 29101 theta = 0; 29102 } else { 29103 theta = rotation.pfValue; 29104 } 29105 29106 return theta; 29107 }; 29108 29109 CRp$4.drawText = function (context, ele, prefix) { 29110 var applyRotation = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 29111 var useEleOpacity = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 29112 var _p = ele._private; 29113 var rscratch = _p.rscratch; 29114 var parentOpacity = useEleOpacity ? ele.effectiveOpacity() : 1; 29115 29116 if (useEleOpacity && (parentOpacity === 0 || ele.pstyle('text-opacity').value === 0)) { 29117 return; 29118 } // use 'main' as an alias for the main label (i.e. null prefix) 29119 29120 29121 if (prefix === 'main') { 29122 prefix = null; 29123 } 29124 29125 var textX = getPrefixedProperty(rscratch, 'labelX', prefix); 29126 var textY = getPrefixedProperty(rscratch, 'labelY', prefix); 29127 var orgTextX, orgTextY; // used for rotation 29128 29129 var text = this.getLabelText(ele, prefix); 29130 29131 if (text != null && text !== '' && !isNaN(textX) && !isNaN(textY)) { 29132 this.setupTextStyle(context, ele, useEleOpacity); 29133 var pdash = prefix ? prefix + '-' : ''; 29134 var textW = getPrefixedProperty(rscratch, 'labelWidth', prefix); 29135 var textH = getPrefixedProperty(rscratch, 'labelHeight', prefix); 29136 var marginX = ele.pstyle(pdash + 'text-margin-x').pfValue; 29137 var marginY = ele.pstyle(pdash + 'text-margin-y').pfValue; 29138 var isEdge = ele.isEdge(); 29139 var halign = ele.pstyle('text-halign').value; 29140 var valign = ele.pstyle('text-valign').value; 29141 29142 if (isEdge) { 29143 halign = 'center'; 29144 valign = 'center'; 29145 } 29146 29147 textX += marginX; 29148 textY += marginY; 29149 var theta; 29150 29151 if (!applyRotation) { 29152 theta = 0; 29153 } else { 29154 theta = this.getTextAngle(ele, prefix); 29155 } 29156 29157 if (theta !== 0) { 29158 orgTextX = textX; 29159 orgTextY = textY; 29160 context.translate(orgTextX, orgTextY); 29161 context.rotate(theta); 29162 textX = 0; 29163 textY = 0; 29164 } 29165 29166 switch (valign) { 29167 case 'top': 29168 break; 29169 29170 case 'center': 29171 textY += textH / 2; 29172 break; 29173 29174 case 'bottom': 29175 textY += textH; 29176 break; 29177 } 29178 29179 var backgroundOpacity = ele.pstyle('text-background-opacity').value; 29180 var borderOpacity = ele.pstyle('text-border-opacity').value; 29181 var textBorderWidth = ele.pstyle('text-border-width').pfValue; 29182 var backgroundPadding = ele.pstyle('text-background-padding').pfValue; 29183 29184 if (backgroundOpacity > 0 || textBorderWidth > 0 && borderOpacity > 0) { 29185 var bgX = textX - backgroundPadding; 29186 29187 switch (halign) { 29188 case 'left': 29189 bgX -= textW; 29190 break; 29191 29192 case 'center': 29193 bgX -= textW / 2; 29194 break; 29195 } 29196 29197 var bgY = textY - textH - backgroundPadding; 29198 var bgW = textW + 2 * backgroundPadding; 29199 var bgH = textH + 2 * backgroundPadding; 29200 29201 if (backgroundOpacity > 0) { 29202 var textFill = context.fillStyle; 29203 var textBackgroundColor = ele.pstyle('text-background-color').value; 29204 context.fillStyle = 'rgba(' + textBackgroundColor[0] + ',' + textBackgroundColor[1] + ',' + textBackgroundColor[2] + ',' + backgroundOpacity * parentOpacity + ')'; 29205 var styleShape = ele.pstyle('text-background-shape').strValue; 29206 29207 if (styleShape.indexOf('round') === 0) { 29208 roundRect(context, bgX, bgY, bgW, bgH, 2); 29209 } else { 29210 context.fillRect(bgX, bgY, bgW, bgH); 29211 } 29212 29213 context.fillStyle = textFill; 29214 } 29215 29216 if (textBorderWidth > 0 && borderOpacity > 0) { 29217 var textStroke = context.strokeStyle; 29218 var textLineWidth = context.lineWidth; 29219 var textBorderColor = ele.pstyle('text-border-color').value; 29220 var textBorderStyle = ele.pstyle('text-border-style').value; 29221 context.strokeStyle = 'rgba(' + textBorderColor[0] + ',' + textBorderColor[1] + ',' + textBorderColor[2] + ',' + borderOpacity * parentOpacity + ')'; 29222 context.lineWidth = textBorderWidth; 29223 29224 if (context.setLineDash) { 29225 // for very outofdate browsers 29226 switch (textBorderStyle) { 29227 case 'dotted': 29228 context.setLineDash([1, 1]); 29229 break; 29230 29231 case 'dashed': 29232 context.setLineDash([4, 2]); 29233 break; 29234 29235 case 'double': 29236 context.lineWidth = textBorderWidth / 4; // 50% reserved for white between the two borders 29237 29238 context.setLineDash([]); 29239 break; 29240 29241 case 'solid': 29242 context.setLineDash([]); 29243 break; 29244 } 29245 } 29246 29247 context.strokeRect(bgX, bgY, bgW, bgH); 29248 29249 if (textBorderStyle === 'double') { 29250 var whiteWidth = textBorderWidth / 2; 29251 context.strokeRect(bgX + whiteWidth, bgY + whiteWidth, bgW - whiteWidth * 2, bgH - whiteWidth * 2); 29252 } 29253 29254 if (context.setLineDash) { 29255 // for very outofdate browsers 29256 context.setLineDash([]); 29257 } 29258 29259 context.lineWidth = textLineWidth; 29260 context.strokeStyle = textStroke; 29261 } 29262 } 29263 29264 var lineWidth = 2 * ele.pstyle('text-outline-width').pfValue; // *2 b/c the stroke is drawn centred on the middle 29265 29266 if (lineWidth > 0) { 29267 context.lineWidth = lineWidth; 29268 } 29269 29270 if (ele.pstyle('text-wrap').value === 'wrap') { 29271 var lines = getPrefixedProperty(rscratch, 'labelWrapCachedLines', prefix); 29272 var lineHeight = getPrefixedProperty(rscratch, 'labelLineHeight', prefix); 29273 var halfTextW = textW / 2; 29274 var justification = this.getLabelJustification(ele); 29275 29276 if (justification === 'auto') ; else if (halign === 'left') { 29277 // auto justification : right 29278 if (justification === 'left') { 29279 textX += -textW; 29280 } else if (justification === 'center') { 29281 textX += -halfTextW; 29282 } // else same as auto 29283 29284 } else if (halign === 'center') { 29285 // auto justfication : center 29286 if (justification === 'left') { 29287 textX += -halfTextW; 29288 } else if (justification === 'right') { 29289 textX += halfTextW; 29290 } // else same as auto 29291 29292 } else if (halign === 'right') { 29293 // auto justification : left 29294 if (justification === 'center') { 29295 textX += halfTextW; 29296 } else if (justification === 'right') { 29297 textX += textW; 29298 } // else same as auto 29299 29300 } 29301 29302 switch (valign) { 29303 case 'top': 29304 textY -= (lines.length - 1) * lineHeight; 29305 break; 29306 29307 case 'center': 29308 case 'bottom': 29309 textY -= (lines.length - 1) * lineHeight; 29310 break; 29311 } 29312 29313 for (var l = 0; l < lines.length; l++) { 29314 if (lineWidth > 0) { 29315 context.strokeText(lines[l], textX, textY); 29316 } 29317 29318 context.fillText(lines[l], textX, textY); 29319 textY += lineHeight; 29320 } 29321 } else { 29322 if (lineWidth > 0) { 29323 context.strokeText(text, textX, textY); 29324 } 29325 29326 context.fillText(text, textX, textY); 29327 } 29328 29329 if (theta !== 0) { 29330 context.rotate(-theta); 29331 context.translate(-orgTextX, -orgTextY); 29332 } 29333 } 29334 }; 29335 29336 /* global Path2D */ 29337 var CRp$5 = {}; 29338 29339 CRp$5.drawNode = function (context, node, shiftToOriginWithBb) { 29340 var drawLabel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 29341 var shouldDrawOverlay = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 29342 var shouldDrawOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 29343 var r = this; 29344 var nodeWidth, nodeHeight; 29345 var _p = node._private; 29346 var rs = _p.rscratch; 29347 var pos = node.position(); 29348 29349 if (!number(pos.x) || !number(pos.y)) { 29350 return; // can't draw node with undefined position 29351 } 29352 29353 if (shouldDrawOpacity && !node.visible()) { 29354 return; 29355 } 29356 29357 var eleOpacity = shouldDrawOpacity ? node.effectiveOpacity() : 1; 29358 var usePaths = r.usePaths(); 29359 var path; 29360 var pathCacheHit = false; 29361 var padding = node.padding(); 29362 nodeWidth = node.width() + 2 * padding; 29363 nodeHeight = node.height() + 2 * padding; // 29364 // setup shift 29365 29366 var bb; 29367 29368 if (shiftToOriginWithBb) { 29369 bb = shiftToOriginWithBb; 29370 context.translate(-bb.x1, -bb.y1); 29371 } // 29372 // load bg image 29373 29374 29375 var bgImgProp = node.pstyle('background-image'); 29376 var urls = bgImgProp.value; 29377 var urlDefined = new Array(urls.length); 29378 var image = new Array(urls.length); 29379 var numImages = 0; 29380 29381 for (var i = 0; i < urls.length; i++) { 29382 var url = urls[i]; 29383 var defd = urlDefined[i] = url != null && url !== 'none'; 29384 29385 if (defd) { 29386 var bgImgCrossOrigin = node.cy().style().getIndexedStyle(node, 'background-image-crossorigin', 'value', i); 29387 numImages++; // get image, and if not loaded then ask to redraw when later loaded 29388 29389 image[i] = r.getCachedImage(url, bgImgCrossOrigin, function () { 29390 _p.backgroundTimestamp = Date.now(); 29391 node.emitAndNotify('background'); 29392 }); 29393 } 29394 } // 29395 // setup styles 29396 29397 29398 var darkness = node.pstyle('background-blacken').value; 29399 var borderWidth = node.pstyle('border-width').pfValue; 29400 var bgOpacity = node.pstyle('background-opacity').value * eleOpacity; 29401 var borderColor = node.pstyle('border-color').value; 29402 var borderStyle = node.pstyle('border-style').value; 29403 var borderOpacity = node.pstyle('border-opacity').value * eleOpacity; 29404 context.lineJoin = 'miter'; // so borders are square with the node shape 29405 29406 var setupShapeColor = function setupShapeColor() { 29407 var bgOpy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : bgOpacity; 29408 r.eleFillStyle(context, node, bgOpy); 29409 }; 29410 29411 var setupBorderColor = function setupBorderColor() { 29412 var bdrOpy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : borderOpacity; 29413 r.colorStrokeStyle(context, borderColor[0], borderColor[1], borderColor[2], bdrOpy); 29414 }; // 29415 // setup shape 29416 29417 29418 var styleShape = node.pstyle('shape').strValue; 29419 var shapePts = node.pstyle('shape-polygon-points').pfValue; 29420 29421 if (usePaths) { 29422 context.translate(pos.x, pos.y); 29423 var pathCache = r.nodePathCache = r.nodePathCache || []; 29424 var key = hashStrings(styleShape === 'polygon' ? styleShape + ',' + shapePts.join(',') : styleShape, '' + nodeHeight, '' + nodeWidth); 29425 var cachedPath = pathCache[key]; 29426 29427 if (cachedPath != null) { 29428 path = cachedPath; 29429 pathCacheHit = true; 29430 rs.pathCache = path; 29431 } else { 29432 path = new Path2D(); 29433 pathCache[key] = rs.pathCache = path; 29434 } 29435 } 29436 29437 var drawShape = function drawShape() { 29438 if (!pathCacheHit) { 29439 var npos = pos; 29440 29441 if (usePaths) { 29442 npos = { 29443 x: 0, 29444 y: 0 29445 }; 29446 } 29447 29448 r.nodeShapes[r.getNodeShape(node)].draw(path || context, npos.x, npos.y, nodeWidth, nodeHeight); 29449 } 29450 29451 if (usePaths) { 29452 context.fill(path); 29453 } else { 29454 context.fill(); 29455 } 29456 }; 29457 29458 var drawImages = function drawImages() { 29459 var nodeOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : eleOpacity; 29460 var prevBging = _p.backgrounding; 29461 var totalCompleted = 0; 29462 29463 for (var _i = 0; _i < image.length; _i++) { 29464 if (urlDefined[_i] && image[_i].complete && !image[_i].error) { 29465 totalCompleted++; 29466 r.drawInscribedImage(context, image[_i], node, _i, nodeOpacity); 29467 } 29468 } 29469 29470 _p.backgrounding = !(totalCompleted === numImages); 29471 29472 if (prevBging !== _p.backgrounding) { 29473 // update style b/c :backgrounding state changed 29474 node.updateStyle(false); 29475 } 29476 }; 29477 29478 var drawPie = function drawPie() { 29479 var redrawShape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 29480 var pieOpacity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : eleOpacity; 29481 29482 if (r.hasPie(node)) { 29483 r.drawPie(context, node, pieOpacity); // redraw/restore path if steps after pie need it 29484 29485 if (redrawShape) { 29486 if (!usePaths) { 29487 r.nodeShapes[r.getNodeShape(node)].draw(context, pos.x, pos.y, nodeWidth, nodeHeight); 29488 } 29489 } 29490 } 29491 }; 29492 29493 var darken = function darken() { 29494 var darkenOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : eleOpacity; 29495 var opacity = (darkness > 0 ? darkness : -darkness) * darkenOpacity; 29496 var c = darkness > 0 ? 0 : 255; 29497 29498 if (darkness !== 0) { 29499 r.colorFillStyle(context, c, c, c, opacity); 29500 29501 if (usePaths) { 29502 context.fill(path); 29503 } else { 29504 context.fill(); 29505 } 29506 } 29507 }; 29508 29509 var drawBorder = function drawBorder() { 29510 if (borderWidth > 0) { 29511 context.lineWidth = borderWidth; 29512 context.lineCap = 'butt'; 29513 29514 if (context.setLineDash) { 29515 // for very outofdate browsers 29516 switch (borderStyle) { 29517 case 'dotted': 29518 context.setLineDash([1, 1]); 29519 break; 29520 29521 case 'dashed': 29522 context.setLineDash([4, 2]); 29523 break; 29524 29525 case 'solid': 29526 case 'double': 29527 context.setLineDash([]); 29528 break; 29529 } 29530 } 29531 29532 if (usePaths) { 29533 context.stroke(path); 29534 } else { 29535 context.stroke(); 29536 } 29537 29538 if (borderStyle === 'double') { 29539 context.lineWidth = borderWidth / 3; 29540 var gco = context.globalCompositeOperation; 29541 context.globalCompositeOperation = 'destination-out'; 29542 29543 if (usePaths) { 29544 context.stroke(path); 29545 } else { 29546 context.stroke(); 29547 } 29548 29549 context.globalCompositeOperation = gco; 29550 } // reset in case we changed the border style 29551 29552 29553 if (context.setLineDash) { 29554 // for very outofdate browsers 29555 context.setLineDash([]); 29556 } 29557 } 29558 }; 29559 29560 var drawOverlay = function drawOverlay() { 29561 if (shouldDrawOverlay) { 29562 r.drawNodeOverlay(context, node, pos, nodeWidth, nodeHeight); 29563 } 29564 }; 29565 29566 var drawText = function drawText() { 29567 r.drawElementText(context, node, null, drawLabel); 29568 }; 29569 29570 var ghost = node.pstyle('ghost').value === 'yes'; 29571 29572 if (ghost) { 29573 var gx = node.pstyle('ghost-offset-x').pfValue; 29574 var gy = node.pstyle('ghost-offset-y').pfValue; 29575 var ghostOpacity = node.pstyle('ghost-opacity').value; 29576 var effGhostOpacity = ghostOpacity * eleOpacity; 29577 context.translate(gx, gy); 29578 setupShapeColor(ghostOpacity * bgOpacity); 29579 drawShape(); 29580 drawImages(effGhostOpacity); 29581 drawPie(darkness !== 0 || borderWidth !== 0); 29582 darken(effGhostOpacity); 29583 setupBorderColor(ghostOpacity * borderOpacity); 29584 drawBorder(); 29585 context.translate(-gx, -gy); 29586 } 29587 29588 setupShapeColor(); 29589 drawShape(); 29590 drawImages(); 29591 drawPie(darkness !== 0 || borderWidth !== 0); 29592 darken(); 29593 setupBorderColor(); 29594 drawBorder(); 29595 29596 if (usePaths) { 29597 context.translate(-pos.x, -pos.y); 29598 } 29599 29600 drawText(); 29601 drawOverlay(); // 29602 // clean up shift 29603 29604 if (shiftToOriginWithBb) { 29605 context.translate(bb.x1, bb.y1); 29606 } 29607 }; 29608 29609 CRp$5.drawNodeOverlay = function (context, node, pos, nodeWidth, nodeHeight) { 29610 var r = this; 29611 29612 if (!node.visible()) { 29613 return; 29614 } 29615 29616 var overlayPadding = node.pstyle('overlay-padding').pfValue; 29617 var overlayOpacity = node.pstyle('overlay-opacity').value; 29618 var overlayColor = node.pstyle('overlay-color').value; 29619 29620 if (overlayOpacity > 0) { 29621 pos = pos || node.position(); 29622 29623 if (nodeWidth == null || nodeHeight == null) { 29624 var padding = node.padding(); 29625 nodeWidth = node.width() + 2 * padding; 29626 nodeHeight = node.height() + 2 * padding; 29627 } 29628 29629 r.colorFillStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); 29630 r.nodeShapes['roundrectangle'].draw(context, pos.x, pos.y, nodeWidth + overlayPadding * 2, nodeHeight + overlayPadding * 2); 29631 context.fill(); 29632 } 29633 }; // does the node have at least one pie piece? 29634 29635 29636 CRp$5.hasPie = function (node) { 29637 node = node[0]; // ensure ele ref 29638 29639 return node._private.hasPie; 29640 }; 29641 29642 CRp$5.drawPie = function (context, node, nodeOpacity, pos) { 29643 node = node[0]; // ensure ele ref 29644 29645 pos = pos || node.position(); 29646 var cyStyle = node.cy().style(); 29647 var pieSize = node.pstyle('pie-size'); 29648 var x = pos.x; 29649 var y = pos.y; 29650 var nodeW = node.width(); 29651 var nodeH = node.height(); 29652 var radius = Math.min(nodeW, nodeH) / 2; // must fit in node 29653 29654 var lastPercent = 0; // what % to continue drawing pie slices from on [0, 1] 29655 29656 var usePaths = this.usePaths(); 29657 29658 if (usePaths) { 29659 x = 0; 29660 y = 0; 29661 } 29662 29663 if (pieSize.units === '%') { 29664 radius = radius * pieSize.pfValue; 29665 } else if (pieSize.pfValue !== undefined) { 29666 radius = pieSize.pfValue / 2; 29667 } 29668 29669 for (var i = 1; i <= cyStyle.pieBackgroundN; i++) { 29670 // 1..N 29671 var size = node.pstyle('pie-' + i + '-background-size').value; 29672 var color = node.pstyle('pie-' + i + '-background-color').value; 29673 var opacity = node.pstyle('pie-' + i + '-background-opacity').value * nodeOpacity; 29674 var percent = size / 100; // map integer range [0, 100] to [0, 1] 29675 // percent can't push beyond 1 29676 29677 if (percent + lastPercent > 1) { 29678 percent = 1 - lastPercent; 29679 } 29680 29681 var angleStart = 1.5 * Math.PI + 2 * Math.PI * lastPercent; // start at 12 o'clock and go clockwise 29682 29683 var angleDelta = 2 * Math.PI * percent; 29684 var angleEnd = angleStart + angleDelta; // ignore if 29685 // - zero size 29686 // - we're already beyond the full circle 29687 // - adding the current slice would go beyond the full circle 29688 29689 if (size === 0 || lastPercent >= 1 || lastPercent + percent > 1) { 29690 continue; 29691 } 29692 29693 context.beginPath(); 29694 context.moveTo(x, y); 29695 context.arc(x, y, radius, angleStart, angleEnd); 29696 context.closePath(); 29697 this.colorFillStyle(context, color[0], color[1], color[2], opacity); 29698 context.fill(); 29699 lastPercent += percent; 29700 } 29701 }; 29702 29703 var CRp$6 = {}; 29704 var motionBlurDelay = 100; // var isFirefox = typeof InstallTrigger !== 'undefined'; 29705 29706 CRp$6.getPixelRatio = function () { 29707 var context = this.data.contexts[0]; 29708 29709 if (this.forcedPixelRatio != null) { 29710 return this.forcedPixelRatio; 29711 } 29712 29713 var backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; 29714 return (window.devicePixelRatio || 1) / backingStore; // eslint-disable-line no-undef 29715 }; 29716 29717 CRp$6.paintCache = function (context) { 29718 var caches = this.paintCaches = this.paintCaches || []; 29719 var needToCreateCache = true; 29720 var cache; 29721 29722 for (var i = 0; i < caches.length; i++) { 29723 cache = caches[i]; 29724 29725 if (cache.context === context) { 29726 needToCreateCache = false; 29727 break; 29728 } 29729 } 29730 29731 if (needToCreateCache) { 29732 cache = { 29733 context: context 29734 }; 29735 caches.push(cache); 29736 } 29737 29738 return cache; 29739 }; 29740 29741 CRp$6.createGradientStyleFor = function (context, shapeStyleName, ele, fill, opacity) { 29742 var gradientStyle; 29743 var usePaths = this.usePaths(); 29744 var colors = ele.pstyle(shapeStyleName + '-gradient-stop-colors').value, 29745 positions = ele.pstyle(shapeStyleName + '-gradient-stop-positions').pfValue; 29746 29747 if (fill === 'radial-gradient') { 29748 if (ele.isEdge()) { 29749 var start = ele.sourceEndpoint(), 29750 end = ele.targetEndpoint(), 29751 mid = ele.midpoint(); 29752 var d1 = dist(start, mid); 29753 var d2 = dist(end, mid); 29754 gradientStyle = context.createRadialGradient(mid.x, mid.y, 0, mid.x, mid.y, Math.max(d1, d2)); 29755 } else { 29756 var pos = usePaths ? { 29757 x: 0, 29758 y: 0 29759 } : ele.position(), 29760 width = ele.paddedWidth(), 29761 height = ele.paddedHeight(); 29762 gradientStyle = context.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, Math.max(width, height)); 29763 } 29764 } else { 29765 if (ele.isEdge()) { 29766 var _start = ele.sourceEndpoint(), 29767 _end = ele.targetEndpoint(); 29768 29769 gradientStyle = context.createLinearGradient(_start.x, _start.y, _end.x, _end.y); 29770 } else { 29771 var _pos = usePaths ? { 29772 x: 0, 29773 y: 0 29774 } : ele.position(), 29775 _width = ele.paddedWidth(), 29776 _height = ele.paddedHeight(), 29777 halfWidth = _width / 2, 29778 halfHeight = _height / 2; 29779 29780 var direction = ele.pstyle('background-gradient-direction').value; 29781 29782 switch (direction) { 29783 case 'to-bottom': 29784 gradientStyle = context.createLinearGradient(_pos.x, _pos.y - halfHeight, _pos.x, _pos.y + halfHeight); 29785 break; 29786 29787 case 'to-top': 29788 gradientStyle = context.createLinearGradient(_pos.x, _pos.y + halfHeight, _pos.x, _pos.y - halfHeight); 29789 break; 29790 29791 case 'to-left': 29792 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y, _pos.x - halfWidth, _pos.y); 29793 break; 29794 29795 case 'to-right': 29796 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y, _pos.x + halfWidth, _pos.y); 29797 break; 29798 29799 case 'to-bottom-right': 29800 case 'to-right-bottom': 29801 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y - halfHeight, _pos.x + halfWidth, _pos.y + halfHeight); 29802 break; 29803 29804 case 'to-top-right': 29805 case 'to-right-top': 29806 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y + halfHeight, _pos.x + halfWidth, _pos.y - halfHeight); 29807 break; 29808 29809 case 'to-bottom-left': 29810 case 'to-left-bottom': 29811 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y - halfHeight, _pos.x - halfWidth, _pos.y + halfHeight); 29812 break; 29813 29814 case 'to-top-left': 29815 case 'to-left-top': 29816 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y + halfHeight, _pos.x - halfWidth, _pos.y - halfHeight); 29817 break; 29818 } 29819 } 29820 } 29821 29822 if (!gradientStyle) return null; // invalid gradient style 29823 29824 var hasPositions = positions.length === colors.length; 29825 var length = colors.length; 29826 29827 for (var i = 0; i < length; i++) { 29828 gradientStyle.addColorStop(hasPositions ? positions[i] : i / (length - 1), 'rgba(' + colors[i][0] + ',' + colors[i][1] + ',' + colors[i][2] + ',' + opacity + ')'); 29829 } 29830 29831 return gradientStyle; 29832 }; 29833 29834 CRp$6.gradientFillStyle = function (context, ele, fill, opacity) { 29835 var gradientStyle = this.createGradientStyleFor(context, 'background', ele, fill, opacity); 29836 if (!gradientStyle) return null; // error 29837 29838 context.fillStyle = gradientStyle; 29839 }; 29840 29841 CRp$6.colorFillStyle = function (context, r, g, b, a) { 29842 context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; // turn off for now, seems context does its own caching 29843 // var cache = this.paintCache(context); 29844 // var fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 29845 // if( cache.fillStyle !== fillStyle ){ 29846 // context.fillStyle = cache.fillStyle = fillStyle; 29847 // } 29848 }; 29849 29850 CRp$6.eleFillStyle = function (context, ele, opacity) { 29851 var backgroundFill = ele.pstyle('background-fill').value; 29852 29853 if (backgroundFill === 'linear-gradient' || backgroundFill === 'radial-gradient') { 29854 this.gradientFillStyle(context, ele, backgroundFill, opacity); 29855 } else { 29856 var backgroundColor = ele.pstyle('background-color').value; 29857 this.colorFillStyle(context, backgroundColor[0], backgroundColor[1], backgroundColor[2], opacity); 29858 } 29859 }; 29860 29861 CRp$6.gradientStrokeStyle = function (context, ele, fill, opacity) { 29862 var gradientStyle = this.createGradientStyleFor(context, 'line', ele, fill, opacity); 29863 if (!gradientStyle) return null; // error 29864 29865 context.strokeStyle = gradientStyle; 29866 }; 29867 29868 CRp$6.colorStrokeStyle = function (context, r, g, b, a) { 29869 context.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; // turn off for now, seems context does its own caching 29870 // var cache = this.paintCache(context); 29871 // var strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 29872 // if( cache.strokeStyle !== strokeStyle ){ 29873 // context.strokeStyle = cache.strokeStyle = strokeStyle; 29874 // } 29875 }; 29876 29877 CRp$6.eleStrokeStyle = function (context, ele, opacity) { 29878 var lineFill = ele.pstyle('line-fill').value; 29879 29880 if (lineFill === 'linear-gradient' || lineFill === 'radial-gradient') { 29881 this.gradientStrokeStyle(context, ele, lineFill, opacity); 29882 } else { 29883 var lineColor = ele.pstyle('line-color').value; 29884 this.colorStrokeStyle(context, lineColor[0], lineColor[1], lineColor[2], opacity); 29885 } 29886 }; // Resize canvas 29887 29888 29889 CRp$6.matchCanvasSize = function (container) { 29890 var r = this; 29891 var data = r.data; 29892 var bb = r.findContainerClientCoords(); 29893 var width = bb[2]; 29894 var height = bb[3]; 29895 var pixelRatio = r.getPixelRatio(); 29896 var mbPxRatio = r.motionBlurPxRatio; 29897 29898 if (container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE] || container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]) { 29899 pixelRatio = mbPxRatio; 29900 } 29901 29902 var canvasWidth = width * pixelRatio; 29903 var canvasHeight = height * pixelRatio; 29904 var canvas; 29905 29906 if (canvasWidth === r.canvasWidth && canvasHeight === r.canvasHeight) { 29907 return; // save cycles if same 29908 } 29909 29910 r.fontCaches = null; // resizing resets the style 29911 29912 var canvasContainer = data.canvasContainer; 29913 canvasContainer.style.width = width + 'px'; 29914 canvasContainer.style.height = height + 'px'; 29915 29916 for (var i = 0; i < r.CANVAS_LAYERS; i++) { 29917 canvas = data.canvases[i]; 29918 canvas.width = canvasWidth; 29919 canvas.height = canvasHeight; 29920 canvas.style.width = width + 'px'; 29921 canvas.style.height = height + 'px'; 29922 } 29923 29924 for (var i = 0; i < r.BUFFER_COUNT; i++) { 29925 canvas = data.bufferCanvases[i]; 29926 canvas.width = canvasWidth; 29927 canvas.height = canvasHeight; 29928 canvas.style.width = width + 'px'; 29929 canvas.style.height = height + 'px'; 29930 } 29931 29932 r.textureMult = 1; 29933 29934 if (pixelRatio <= 1) { 29935 canvas = data.bufferCanvases[r.TEXTURE_BUFFER]; 29936 r.textureMult = 2; 29937 canvas.width = canvasWidth * r.textureMult; 29938 canvas.height = canvasHeight * r.textureMult; 29939 } 29940 29941 r.canvasWidth = canvasWidth; 29942 r.canvasHeight = canvasHeight; 29943 }; 29944 29945 CRp$6.renderTo = function (cxt, zoom, pan, pxRatio) { 29946 this.render({ 29947 forcedContext: cxt, 29948 forcedZoom: zoom, 29949 forcedPan: pan, 29950 drawAllLayers: true, 29951 forcedPxRatio: pxRatio 29952 }); 29953 }; 29954 29955 CRp$6.render = function (options) { 29956 options = options || staticEmptyObject(); 29957 var forcedContext = options.forcedContext; 29958 var drawAllLayers = options.drawAllLayers; 29959 var drawOnlyNodeLayer = options.drawOnlyNodeLayer; 29960 var forcedZoom = options.forcedZoom; 29961 var forcedPan = options.forcedPan; 29962 var r = this; 29963 var pixelRatio = options.forcedPxRatio === undefined ? this.getPixelRatio() : options.forcedPxRatio; 29964 var cy = r.cy; 29965 var data = r.data; 29966 var needDraw = data.canvasNeedsRedraw; 29967 var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming); 29968 var motionBlur = options.motionBlur !== undefined ? options.motionBlur : r.motionBlur; 29969 var mbPxRatio = r.motionBlurPxRatio; 29970 var hasCompoundNodes = cy.hasCompoundNodes(); 29971 var inNodeDragGesture = r.hoverData.draggingEles; 29972 var inBoxSelection = r.hoverData.selecting || r.touchData.selecting ? true : false; 29973 motionBlur = motionBlur && !forcedContext && r.motionBlurEnabled && !inBoxSelection; 29974 var motionBlurFadeEffect = motionBlur; 29975 29976 if (!forcedContext) { 29977 if (r.prevPxRatio !== pixelRatio) { 29978 r.invalidateContainerClientCoordsCache(); 29979 r.matchCanvasSize(r.container); 29980 r.redrawHint('eles', true); 29981 r.redrawHint('drag', true); 29982 } 29983 29984 r.prevPxRatio = pixelRatio; 29985 } 29986 29987 if (!forcedContext && r.motionBlurTimeout) { 29988 clearTimeout(r.motionBlurTimeout); 29989 } 29990 29991 if (motionBlur) { 29992 if (r.mbFrames == null) { 29993 r.mbFrames = 0; 29994 } 29995 29996 r.mbFrames++; 29997 29998 if (r.mbFrames < 3) { 29999 // need several frames before even high quality motionblur 30000 motionBlurFadeEffect = false; 30001 } // go to lower quality blurry frames when several m/b frames have been rendered (avoids flashing) 30002 30003 30004 if (r.mbFrames > r.minMbLowQualFrames) { 30005 //r.fullQualityMb = false; 30006 r.motionBlurPxRatio = r.mbPxRBlurry; 30007 } 30008 } 30009 30010 if (r.clearingMotionBlur) { 30011 r.motionBlurPxRatio = 1; 30012 } // b/c drawToContext() may be async w.r.t. redraw(), keep track of last texture frame 30013 // because a rogue async texture frame would clear needDraw 30014 30015 30016 if (r.textureDrawLastFrame && !textureDraw) { 30017 needDraw[r.NODE] = true; 30018 needDraw[r.SELECT_BOX] = true; 30019 } 30020 30021 var style = cy.style(); 30022 var zoom = cy.zoom(); 30023 var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom; 30024 var pan = cy.pan(); 30025 var effectivePan = { 30026 x: pan.x, 30027 y: pan.y 30028 }; 30029 var vp = { 30030 zoom: zoom, 30031 pan: { 30032 x: pan.x, 30033 y: pan.y 30034 } 30035 }; 30036 var prevVp = r.prevViewport; 30037 var viewportIsDiff = prevVp === undefined || vp.zoom !== prevVp.zoom || vp.pan.x !== prevVp.pan.x || vp.pan.y !== prevVp.pan.y; // we want the low quality motionblur only when the viewport is being manipulated etc (where it's not noticed) 30038 30039 if (!viewportIsDiff && !(inNodeDragGesture && !hasCompoundNodes)) { 30040 r.motionBlurPxRatio = 1; 30041 } 30042 30043 if (forcedPan) { 30044 effectivePan = forcedPan; 30045 } // apply pixel ratio 30046 30047 30048 effectiveZoom *= pixelRatio; 30049 effectivePan.x *= pixelRatio; 30050 effectivePan.y *= pixelRatio; 30051 var eles = r.getCachedZSortedEles(); 30052 30053 function mbclear(context, x, y, w, h) { 30054 var gco = context.globalCompositeOperation; 30055 context.globalCompositeOperation = 'destination-out'; 30056 r.colorFillStyle(context, 255, 255, 255, r.motionBlurTransparency); 30057 context.fillRect(x, y, w, h); 30058 context.globalCompositeOperation = gco; 30059 } 30060 30061 function setContextTransform(context, clear) { 30062 var ePan, eZoom, w, h; 30063 30064 if (!r.clearingMotionBlur && (context === data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] || context === data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG])) { 30065 ePan = { 30066 x: pan.x * mbPxRatio, 30067 y: pan.y * mbPxRatio 30068 }; 30069 eZoom = zoom * mbPxRatio; 30070 w = r.canvasWidth * mbPxRatio; 30071 h = r.canvasHeight * mbPxRatio; 30072 } else { 30073 ePan = effectivePan; 30074 eZoom = effectiveZoom; 30075 w = r.canvasWidth; 30076 h = r.canvasHeight; 30077 } 30078 30079 context.setTransform(1, 0, 0, 1, 0, 0); 30080 30081 if (clear === 'motionBlur') { 30082 mbclear(context, 0, 0, w, h); 30083 } else if (!forcedContext && (clear === undefined || clear)) { 30084 context.clearRect(0, 0, w, h); 30085 } 30086 30087 if (!drawAllLayers) { 30088 context.translate(ePan.x, ePan.y); 30089 context.scale(eZoom, eZoom); 30090 } 30091 30092 if (forcedPan) { 30093 context.translate(forcedPan.x, forcedPan.y); 30094 } 30095 30096 if (forcedZoom) { 30097 context.scale(forcedZoom, forcedZoom); 30098 } 30099 } 30100 30101 if (!textureDraw) { 30102 r.textureDrawLastFrame = false; 30103 } 30104 30105 if (textureDraw) { 30106 r.textureDrawLastFrame = true; 30107 30108 if (!r.textureCache) { 30109 r.textureCache = {}; 30110 r.textureCache.bb = cy.mutableElements().boundingBox(); 30111 r.textureCache.texture = r.data.bufferCanvases[r.TEXTURE_BUFFER]; 30112 var cxt = r.data.bufferContexts[r.TEXTURE_BUFFER]; 30113 cxt.setTransform(1, 0, 0, 1, 0, 0); 30114 cxt.clearRect(0, 0, r.canvasWidth * r.textureMult, r.canvasHeight * r.textureMult); 30115 r.render({ 30116 forcedContext: cxt, 30117 drawOnlyNodeLayer: true, 30118 forcedPxRatio: pixelRatio * r.textureMult 30119 }); 30120 var vp = r.textureCache.viewport = { 30121 zoom: cy.zoom(), 30122 pan: cy.pan(), 30123 width: r.canvasWidth, 30124 height: r.canvasHeight 30125 }; 30126 vp.mpan = { 30127 x: (0 - vp.pan.x) / vp.zoom, 30128 y: (0 - vp.pan.y) / vp.zoom 30129 }; 30130 } 30131 30132 needDraw[r.DRAG] = false; 30133 needDraw[r.NODE] = false; 30134 var context = data.contexts[r.NODE]; 30135 var texture = r.textureCache.texture; 30136 var vp = r.textureCache.viewport; 30137 context.setTransform(1, 0, 0, 1, 0, 0); 30138 30139 if (motionBlur) { 30140 mbclear(context, 0, 0, vp.width, vp.height); 30141 } else { 30142 context.clearRect(0, 0, vp.width, vp.height); 30143 } 30144 30145 var outsideBgColor = style.core('outside-texture-bg-color').value; 30146 var outsideBgOpacity = style.core('outside-texture-bg-opacity').value; 30147 r.colorFillStyle(context, outsideBgColor[0], outsideBgColor[1], outsideBgColor[2], outsideBgOpacity); 30148 context.fillRect(0, 0, vp.width, vp.height); 30149 var zoom = cy.zoom(); 30150 setContextTransform(context, false); 30151 context.clearRect(vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio); 30152 context.drawImage(texture, vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio); 30153 } else if (r.textureOnViewport && !forcedContext) { 30154 // clear the cache since we don't need it 30155 r.textureCache = null; 30156 } 30157 30158 var extent = cy.extent(); 30159 var vpManip = r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming || r.hoverData.draggingEles || r.cy.animated(); 30160 var hideEdges = r.hideEdgesOnViewport && vpManip; 30161 var needMbClear = []; 30162 needMbClear[r.NODE] = !needDraw[r.NODE] && motionBlur && !r.clearedForMotionBlur[r.NODE] || r.clearingMotionBlur; 30163 30164 if (needMbClear[r.NODE]) { 30165 r.clearedForMotionBlur[r.NODE] = true; 30166 } 30167 30168 needMbClear[r.DRAG] = !needDraw[r.DRAG] && motionBlur && !r.clearedForMotionBlur[r.DRAG] || r.clearingMotionBlur; 30169 30170 if (needMbClear[r.DRAG]) { 30171 r.clearedForMotionBlur[r.DRAG] = true; 30172 } 30173 30174 if (needDraw[r.NODE] || drawAllLayers || drawOnlyNodeLayer || needMbClear[r.NODE]) { 30175 var useBuffer = motionBlur && !needMbClear[r.NODE] && mbPxRatio !== 1; 30176 var context = forcedContext || (useBuffer ? r.data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] : data.contexts[r.NODE]); 30177 var clear = motionBlur && !useBuffer ? 'motionBlur' : undefined; 30178 setContextTransform(context, clear); 30179 30180 if (hideEdges) { 30181 r.drawCachedNodes(context, eles.nondrag, pixelRatio, extent); 30182 } else { 30183 r.drawLayeredElements(context, eles.nondrag, pixelRatio, extent); 30184 } 30185 30186 if (r.debug) { 30187 r.drawDebugPoints(context, eles.nondrag); 30188 } 30189 30190 if (!drawAllLayers && !motionBlur) { 30191 needDraw[r.NODE] = false; 30192 } 30193 } 30194 30195 if (!drawOnlyNodeLayer && (needDraw[r.DRAG] || drawAllLayers || needMbClear[r.DRAG])) { 30196 var useBuffer = motionBlur && !needMbClear[r.DRAG] && mbPxRatio !== 1; 30197 var context = forcedContext || (useBuffer ? r.data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG] : data.contexts[r.DRAG]); 30198 setContextTransform(context, motionBlur && !useBuffer ? 'motionBlur' : undefined); 30199 30200 if (hideEdges) { 30201 r.drawCachedNodes(context, eles.drag, pixelRatio, extent); 30202 } else { 30203 r.drawCachedElements(context, eles.drag, pixelRatio, extent); 30204 } 30205 30206 if (r.debug) { 30207 r.drawDebugPoints(context, eles.drag); 30208 } 30209 30210 if (!drawAllLayers && !motionBlur) { 30211 needDraw[r.DRAG] = false; 30212 } 30213 } 30214 30215 if (r.showFps || !drawOnlyNodeLayer && needDraw[r.SELECT_BOX] && !drawAllLayers) { 30216 var context = forcedContext || data.contexts[r.SELECT_BOX]; 30217 setContextTransform(context); 30218 30219 if (r.selection[4] == 1 && (r.hoverData.selecting || r.touchData.selecting)) { 30220 var zoom = r.cy.zoom(); 30221 var borderWidth = style.core('selection-box-border-width').value / zoom; 30222 context.lineWidth = borderWidth; 30223 context.fillStyle = 'rgba(' + style.core('selection-box-color').value[0] + ',' + style.core('selection-box-color').value[1] + ',' + style.core('selection-box-color').value[2] + ',' + style.core('selection-box-opacity').value + ')'; 30224 context.fillRect(r.selection[0], r.selection[1], r.selection[2] - r.selection[0], r.selection[3] - r.selection[1]); 30225 30226 if (borderWidth > 0) { 30227 context.strokeStyle = 'rgba(' + style.core('selection-box-border-color').value[0] + ',' + style.core('selection-box-border-color').value[1] + ',' + style.core('selection-box-border-color').value[2] + ',' + style.core('selection-box-opacity').value + ')'; 30228 context.strokeRect(r.selection[0], r.selection[1], r.selection[2] - r.selection[0], r.selection[3] - r.selection[1]); 30229 } 30230 } 30231 30232 if (data.bgActivePosistion && !r.hoverData.selecting) { 30233 var zoom = r.cy.zoom(); 30234 var pos = data.bgActivePosistion; 30235 context.fillStyle = 'rgba(' + style.core('active-bg-color').value[0] + ',' + style.core('active-bg-color').value[1] + ',' + style.core('active-bg-color').value[2] + ',' + style.core('active-bg-opacity').value + ')'; 30236 context.beginPath(); 30237 context.arc(pos.x, pos.y, style.core('active-bg-size').pfValue / zoom, 0, 2 * Math.PI); 30238 context.fill(); 30239 } 30240 30241 var timeToRender = r.lastRedrawTime; 30242 30243 if (r.showFps && timeToRender) { 30244 timeToRender = Math.round(timeToRender); 30245 var fps = Math.round(1000 / timeToRender); 30246 context.setTransform(1, 0, 0, 1, 0, 0); 30247 context.fillStyle = 'rgba(255, 0, 0, 0.75)'; 30248 context.strokeStyle = 'rgba(255, 0, 0, 0.75)'; 30249 context.lineWidth = 1; 30250 context.fillText('1 frame = ' + timeToRender + ' ms = ' + fps + ' fps', 0, 20); 30251 var maxFps = 60; 30252 context.strokeRect(0, 30, 250, 20); 30253 context.fillRect(0, 30, 250 * Math.min(fps / maxFps, 1), 20); 30254 } 30255 30256 if (!drawAllLayers) { 30257 needDraw[r.SELECT_BOX] = false; 30258 } 30259 } // motionblur: blit rendered blurry frames 30260 30261 30262 if (motionBlur && mbPxRatio !== 1) { 30263 var cxtNode = data.contexts[r.NODE]; 30264 var txtNode = r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE]; 30265 var cxtDrag = data.contexts[r.DRAG]; 30266 var txtDrag = r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]; 30267 30268 var drawMotionBlur = function drawMotionBlur(cxt, txt, needClear) { 30269 cxt.setTransform(1, 0, 0, 1, 0, 0); 30270 30271 if (needClear || !motionBlurFadeEffect) { 30272 cxt.clearRect(0, 0, r.canvasWidth, r.canvasHeight); 30273 } else { 30274 mbclear(cxt, 0, 0, r.canvasWidth, r.canvasHeight); 30275 } 30276 30277 var pxr = mbPxRatio; 30278 cxt.drawImage(txt, // img 30279 0, 0, // sx, sy 30280 r.canvasWidth * pxr, r.canvasHeight * pxr, // sw, sh 30281 0, 0, // x, y 30282 r.canvasWidth, r.canvasHeight // w, h 30283 ); 30284 }; 30285 30286 if (needDraw[r.NODE] || needMbClear[r.NODE]) { 30287 drawMotionBlur(cxtNode, txtNode, needMbClear[r.NODE]); 30288 needDraw[r.NODE] = false; 30289 } 30290 30291 if (needDraw[r.DRAG] || needMbClear[r.DRAG]) { 30292 drawMotionBlur(cxtDrag, txtDrag, needMbClear[r.DRAG]); 30293 needDraw[r.DRAG] = false; 30294 } 30295 } 30296 30297 r.prevViewport = vp; 30298 30299 if (r.clearingMotionBlur) { 30300 r.clearingMotionBlur = false; 30301 r.motionBlurCleared = true; 30302 r.motionBlur = true; 30303 } 30304 30305 if (motionBlur) { 30306 r.motionBlurTimeout = setTimeout(function () { 30307 r.motionBlurTimeout = null; 30308 r.clearedForMotionBlur[r.NODE] = false; 30309 r.clearedForMotionBlur[r.DRAG] = false; 30310 r.motionBlur = false; 30311 r.clearingMotionBlur = !textureDraw; 30312 r.mbFrames = 0; 30313 needDraw[r.NODE] = true; 30314 needDraw[r.DRAG] = true; 30315 r.redraw(); 30316 }, motionBlurDelay); 30317 } 30318 30319 if (!forcedContext) { 30320 cy.emit('render'); 30321 } 30322 }; 30323 30324 var CRp$7 = {}; // @O Polygon drawing 30325 30326 CRp$7.drawPolygonPath = function (context, x, y, width, height, points) { 30327 var halfW = width / 2; 30328 var halfH = height / 2; 30329 30330 if (context.beginPath) { 30331 context.beginPath(); 30332 } 30333 30334 context.moveTo(x + halfW * points[0], y + halfH * points[1]); 30335 30336 for (var i = 1; i < points.length / 2; i++) { 30337 context.lineTo(x + halfW * points[i * 2], y + halfH * points[i * 2 + 1]); 30338 } 30339 30340 context.closePath(); 30341 }; 30342 30343 CRp$7.drawRoundPolygonPath = function (context, x, y, width, height, points) { 30344 var halfW = width / 2; 30345 var halfH = height / 2; 30346 var cornerRadius = getRoundPolygonRadius(width, height); 30347 30348 if (context.beginPath) { 30349 context.beginPath(); 30350 } 30351 30352 for (var _i = 0; _i < points.length / 4; _i++) { 30353 var sourceUv = void 0, 30354 destUv = void 0; 30355 30356 if (_i === 0) { 30357 sourceUv = points.length - 2; 30358 } else { 30359 sourceUv = _i * 4 - 2; 30360 } 30361 30362 destUv = _i * 4 + 2; 30363 var px = x + halfW * points[_i * 4]; 30364 var py = y + halfH * points[_i * 4 + 1]; 30365 var cosTheta = -points[sourceUv] * points[destUv] - points[sourceUv + 1] * points[destUv + 1]; 30366 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 30367 var cp0x = px - offset * points[sourceUv]; 30368 var cp0y = py - offset * points[sourceUv + 1]; 30369 var cp1x = px + offset * points[destUv]; 30370 var cp1y = py + offset * points[destUv + 1]; 30371 30372 if (_i === 0) { 30373 context.moveTo(cp0x, cp0y); 30374 } else { 30375 context.lineTo(cp0x, cp0y); 30376 } 30377 30378 context.arcTo(px, py, cp1x, cp1y, cornerRadius); 30379 } 30380 30381 context.closePath(); 30382 }; // Round rectangle drawing 30383 30384 30385 CRp$7.drawRoundRectanglePath = function (context, x, y, width, height) { 30386 var halfWidth = width / 2; 30387 var halfHeight = height / 2; 30388 var cornerRadius = getRoundRectangleRadius(width, height); 30389 30390 if (context.beginPath) { 30391 context.beginPath(); 30392 } // Start at top middle 30393 30394 30395 context.moveTo(x, y - halfHeight); // Arc from middle top to right side 30396 30397 context.arcTo(x + halfWidth, y - halfHeight, x + halfWidth, y, cornerRadius); // Arc from right side to bottom 30398 30399 context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius); // Arc from bottom to left side 30400 30401 context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius); // Arc from left side to topBorder 30402 30403 context.arcTo(x - halfWidth, y - halfHeight, x, y - halfHeight, cornerRadius); // Join line 30404 30405 context.lineTo(x, y - halfHeight); 30406 context.closePath(); 30407 }; 30408 30409 CRp$7.drawBottomRoundRectanglePath = function (context, x, y, width, height) { 30410 var halfWidth = width / 2; 30411 var halfHeight = height / 2; 30412 var cornerRadius = getRoundRectangleRadius(width, height); 30413 30414 if (context.beginPath) { 30415 context.beginPath(); 30416 } // Start at top middle 30417 30418 30419 context.moveTo(x, y - halfHeight); 30420 context.lineTo(x + halfWidth, y - halfHeight); 30421 context.lineTo(x + halfWidth, y); 30422 context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius); 30423 context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius); 30424 context.lineTo(x - halfWidth, y - halfHeight); 30425 context.lineTo(x, y - halfHeight); 30426 context.closePath(); 30427 }; 30428 30429 CRp$7.drawCutRectanglePath = function (context, x, y, width, height) { 30430 var halfWidth = width / 2; 30431 var halfHeight = height / 2; 30432 var cornerLength = getCutRectangleCornerLength(); 30433 30434 if (context.beginPath) { 30435 context.beginPath(); 30436 } 30437 30438 context.moveTo(x - halfWidth + cornerLength, y - halfHeight); 30439 context.lineTo(x + halfWidth - cornerLength, y - halfHeight); 30440 context.lineTo(x + halfWidth, y - halfHeight + cornerLength); 30441 context.lineTo(x + halfWidth, y + halfHeight - cornerLength); 30442 context.lineTo(x + halfWidth - cornerLength, y + halfHeight); 30443 context.lineTo(x - halfWidth + cornerLength, y + halfHeight); 30444 context.lineTo(x - halfWidth, y + halfHeight - cornerLength); 30445 context.lineTo(x - halfWidth, y - halfHeight + cornerLength); 30446 context.closePath(); 30447 }; 30448 30449 CRp$7.drawBarrelPath = function (context, x, y, width, height) { 30450 var halfWidth = width / 2; 30451 var halfHeight = height / 2; 30452 var xBegin = x - halfWidth; 30453 var xEnd = x + halfWidth; 30454 var yBegin = y - halfHeight; 30455 var yEnd = y + halfHeight; 30456 var barrelCurveConstants = getBarrelCurveConstants(width, height); 30457 var wOffset = barrelCurveConstants.widthOffset; 30458 var hOffset = barrelCurveConstants.heightOffset; 30459 var ctrlPtXOffset = barrelCurveConstants.ctrlPtOffsetPct * wOffset; 30460 30461 if (context.beginPath) { 30462 context.beginPath(); 30463 } 30464 30465 context.moveTo(xBegin, yBegin + hOffset); 30466 context.lineTo(xBegin, yEnd - hOffset); 30467 context.quadraticCurveTo(xBegin + ctrlPtXOffset, yEnd, xBegin + wOffset, yEnd); 30468 context.lineTo(xEnd - wOffset, yEnd); 30469 context.quadraticCurveTo(xEnd - ctrlPtXOffset, yEnd, xEnd, yEnd - hOffset); 30470 context.lineTo(xEnd, yBegin + hOffset); 30471 context.quadraticCurveTo(xEnd - ctrlPtXOffset, yBegin, xEnd - wOffset, yBegin); 30472 context.lineTo(xBegin + wOffset, yBegin); 30473 context.quadraticCurveTo(xBegin + ctrlPtXOffset, yBegin, xBegin, yBegin + hOffset); 30474 context.closePath(); 30475 }; 30476 30477 var sin0 = Math.sin(0); 30478 var cos0 = Math.cos(0); 30479 var sin = {}; 30480 var cos = {}; 30481 var ellipseStepSize = Math.PI / 40; 30482 30483 for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize) { 30484 sin[i] = Math.sin(i); 30485 cos[i] = Math.cos(i); 30486 } 30487 30488 CRp$7.drawEllipsePath = function (context, centerX, centerY, width, height) { 30489 if (context.beginPath) { 30490 context.beginPath(); 30491 } 30492 30493 if (context.ellipse) { 30494 context.ellipse(centerX, centerY, width / 2, height / 2, 0, 0, 2 * Math.PI); 30495 } else { 30496 var xPos, yPos; 30497 var rw = width / 2; 30498 var rh = height / 2; 30499 30500 for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize) { 30501 xPos = centerX - rw * sin[i] * sin0 + rw * cos[i] * cos0; 30502 yPos = centerY + rh * cos[i] * sin0 + rh * sin[i] * cos0; 30503 30504 if (i === 0) { 30505 context.moveTo(xPos, yPos); 30506 } else { 30507 context.lineTo(xPos, yPos); 30508 } 30509 } 30510 } 30511 30512 context.closePath(); 30513 }; 30514 30515 /* global atob, ArrayBuffer, Uint8Array, Blob */ 30516 var CRp$8 = {}; 30517 30518 CRp$8.createBuffer = function (w, h) { 30519 var buffer = document.createElement('canvas'); // eslint-disable-line no-undef 30520 30521 buffer.width = w; 30522 buffer.height = h; 30523 return [buffer, buffer.getContext('2d')]; 30524 }; 30525 30526 CRp$8.bufferCanvasImage = function (options) { 30527 var cy = this.cy; 30528 var eles = cy.mutableElements(); 30529 var bb = eles.boundingBox(); 30530 var ctrRect = this.findContainerClientCoords(); 30531 var width = options.full ? Math.ceil(bb.w) : ctrRect[2]; 30532 var height = options.full ? Math.ceil(bb.h) : ctrRect[3]; 30533 var specdMaxDims = number(options.maxWidth) || number(options.maxHeight); 30534 var pxRatio = this.getPixelRatio(); 30535 var scale = 1; 30536 30537 if (options.scale !== undefined) { 30538 width *= options.scale; 30539 height *= options.scale; 30540 scale = options.scale; 30541 } else if (specdMaxDims) { 30542 var maxScaleW = Infinity; 30543 var maxScaleH = Infinity; 30544 30545 if (number(options.maxWidth)) { 30546 maxScaleW = scale * options.maxWidth / width; 30547 } 30548 30549 if (number(options.maxHeight)) { 30550 maxScaleH = scale * options.maxHeight / height; 30551 } 30552 30553 scale = Math.min(maxScaleW, maxScaleH); 30554 width *= scale; 30555 height *= scale; 30556 } 30557 30558 if (!specdMaxDims) { 30559 width *= pxRatio; 30560 height *= pxRatio; 30561 scale *= pxRatio; 30562 } 30563 30564 var buffCanvas = document.createElement('canvas'); // eslint-disable-line no-undef 30565 30566 buffCanvas.width = width; 30567 buffCanvas.height = height; 30568 buffCanvas.style.width = width + 'px'; 30569 buffCanvas.style.height = height + 'px'; 30570 var buffCxt = buffCanvas.getContext('2d'); // Rasterize the layers, but only if container has nonzero size 30571 30572 if (width > 0 && height > 0) { 30573 buffCxt.clearRect(0, 0, width, height); 30574 buffCxt.globalCompositeOperation = 'source-over'; 30575 var zsortedEles = this.getCachedZSortedEles(); 30576 30577 if (options.full) { 30578 // draw the full bounds of the graph 30579 buffCxt.translate(-bb.x1 * scale, -bb.y1 * scale); 30580 buffCxt.scale(scale, scale); 30581 this.drawElements(buffCxt, zsortedEles); 30582 buffCxt.scale(1 / scale, 1 / scale); 30583 buffCxt.translate(bb.x1 * scale, bb.y1 * scale); 30584 } else { 30585 // draw the current view 30586 var pan = cy.pan(); 30587 var translation = { 30588 x: pan.x * scale, 30589 y: pan.y * scale 30590 }; 30591 scale *= cy.zoom(); 30592 buffCxt.translate(translation.x, translation.y); 30593 buffCxt.scale(scale, scale); 30594 this.drawElements(buffCxt, zsortedEles); 30595 buffCxt.scale(1 / scale, 1 / scale); 30596 buffCxt.translate(-translation.x, -translation.y); 30597 } // need to fill bg at end like this in order to fill cleared transparent pixels in jpgs 30598 30599 30600 if (options.bg) { 30601 buffCxt.globalCompositeOperation = 'destination-over'; 30602 buffCxt.fillStyle = options.bg; 30603 buffCxt.rect(0, 0, width, height); 30604 buffCxt.fill(); 30605 } 30606 } 30607 30608 return buffCanvas; 30609 }; 30610 30611 function b64ToBlob(b64, mimeType) { 30612 var bytes = atob(b64); 30613 var buff = new ArrayBuffer(bytes.length); 30614 var buffUint8 = new Uint8Array(buff); 30615 30616 for (var i = 0; i < bytes.length; i++) { 30617 buffUint8[i] = bytes.charCodeAt(i); 30618 } 30619 30620 return new Blob([buff], { 30621 type: mimeType 30622 }); 30623 } 30624 30625 function b64UriToB64(b64uri) { 30626 var i = b64uri.indexOf(','); 30627 return b64uri.substr(i + 1); 30628 } 30629 30630 function output(options, canvas, mimeType) { 30631 var getB64Uri = function getB64Uri() { 30632 return canvas.toDataURL(mimeType, options.quality); 30633 }; 30634 30635 switch (options.output) { 30636 case 'blob-promise': 30637 return new Promise$1(function (resolve, reject) { 30638 try { 30639 canvas.toBlob(function (blob) { 30640 if (blob != null) { 30641 resolve(blob); 30642 } else { 30643 reject(new Error('`canvas.toBlob()` sent a null value in its callback')); 30644 } 30645 }, mimeType, options.quality); 30646 } catch (err) { 30647 reject(err); 30648 } 30649 }); 30650 30651 case 'blob': 30652 return b64ToBlob(b64UriToB64(getB64Uri()), mimeType); 30653 30654 case 'base64': 30655 return b64UriToB64(getB64Uri()); 30656 30657 case 'base64uri': 30658 default: 30659 return getB64Uri(); 30660 } 30661 } 30662 30663 CRp$8.png = function (options) { 30664 return output(options, this.bufferCanvasImage(options), 'image/png'); 30665 }; 30666 30667 CRp$8.jpg = function (options) { 30668 return output(options, this.bufferCanvasImage(options), 'image/jpeg'); 30669 }; 30670 30671 var CRp$9 = {}; 30672 30673 CRp$9.nodeShapeImpl = function (name, context, centerX, centerY, width, height, points) { 30674 switch (name) { 30675 case 'ellipse': 30676 return this.drawEllipsePath(context, centerX, centerY, width, height); 30677 30678 case 'polygon': 30679 return this.drawPolygonPath(context, centerX, centerY, width, height, points); 30680 30681 case 'round-polygon': 30682 return this.drawRoundPolygonPath(context, centerX, centerY, width, height, points); 30683 30684 case 'roundrectangle': 30685 case 'round-rectangle': 30686 return this.drawRoundRectanglePath(context, centerX, centerY, width, height); 30687 30688 case 'cutrectangle': 30689 case 'cut-rectangle': 30690 return this.drawCutRectanglePath(context, centerX, centerY, width, height); 30691 30692 case 'bottomroundrectangle': 30693 case 'bottom-round-rectangle': 30694 return this.drawBottomRoundRectanglePath(context, centerX, centerY, width, height); 30695 30696 case 'barrel': 30697 return this.drawBarrelPath(context, centerX, centerY, width, height); 30698 } 30699 }; 30700 30701 var CR = CanvasRenderer; 30702 var CRp$a = CanvasRenderer.prototype; 30703 CRp$a.CANVAS_LAYERS = 3; // 30704 30705 CRp$a.SELECT_BOX = 0; 30706 CRp$a.DRAG = 1; 30707 CRp$a.NODE = 2; 30708 CRp$a.BUFFER_COUNT = 3; // 30709 30710 CRp$a.TEXTURE_BUFFER = 0; 30711 CRp$a.MOTIONBLUR_BUFFER_NODE = 1; 30712 CRp$a.MOTIONBLUR_BUFFER_DRAG = 2; 30713 30714 function CanvasRenderer(options) { 30715 var r = this; 30716 r.data = { 30717 canvases: new Array(CRp$a.CANVAS_LAYERS), 30718 contexts: new Array(CRp$a.CANVAS_LAYERS), 30719 canvasNeedsRedraw: new Array(CRp$a.CANVAS_LAYERS), 30720 bufferCanvases: new Array(CRp$a.BUFFER_COUNT), 30721 bufferContexts: new Array(CRp$a.CANVAS_LAYERS) 30722 }; 30723 var tapHlOffAttr = '-webkit-tap-highlight-color'; 30724 var tapHlOffStyle = 'rgba(0,0,0,0)'; 30725 r.data.canvasContainer = document.createElement('div'); // eslint-disable-line no-undef 30726 30727 var containerStyle = r.data.canvasContainer.style; 30728 r.data.canvasContainer.style[tapHlOffAttr] = tapHlOffStyle; 30729 containerStyle.position = 'relative'; 30730 containerStyle.zIndex = '0'; 30731 containerStyle.overflow = 'hidden'; 30732 var container = options.cy.container(); 30733 container.appendChild(r.data.canvasContainer); 30734 container.style[tapHlOffAttr] = tapHlOffStyle; 30735 var styleMap = { 30736 '-webkit-user-select': 'none', 30737 '-moz-user-select': '-moz-none', 30738 'user-select': 'none', 30739 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', 30740 'outline-style': 'none' 30741 }; 30742 30743 if (ms()) { 30744 styleMap['-ms-touch-action'] = 'none'; 30745 styleMap['touch-action'] = 'none'; 30746 } 30747 30748 for (var i = 0; i < CRp$a.CANVAS_LAYERS; i++) { 30749 var canvas = r.data.canvases[i] = document.createElement('canvas'); // eslint-disable-line no-undef 30750 30751 r.data.contexts[i] = canvas.getContext('2d'); 30752 Object.keys(styleMap).forEach(function (k) { 30753 canvas.style[k] = styleMap[k]; 30754 }); 30755 canvas.style.position = 'absolute'; 30756 canvas.setAttribute('data-id', 'layer' + i); 30757 canvas.style.zIndex = String(CRp$a.CANVAS_LAYERS - i); 30758 r.data.canvasContainer.appendChild(canvas); 30759 r.data.canvasNeedsRedraw[i] = false; 30760 } 30761 30762 r.data.topCanvas = r.data.canvases[0]; 30763 r.data.canvases[CRp$a.NODE].setAttribute('data-id', 'layer' + CRp$a.NODE + '-node'); 30764 r.data.canvases[CRp$a.SELECT_BOX].setAttribute('data-id', 'layer' + CRp$a.SELECT_BOX + '-selectbox'); 30765 r.data.canvases[CRp$a.DRAG].setAttribute('data-id', 'layer' + CRp$a.DRAG + '-drag'); 30766 30767 for (var i = 0; i < CRp$a.BUFFER_COUNT; i++) { 30768 r.data.bufferCanvases[i] = document.createElement('canvas'); // eslint-disable-line no-undef 30769 30770 r.data.bufferContexts[i] = r.data.bufferCanvases[i].getContext('2d'); 30771 r.data.bufferCanvases[i].style.position = 'absolute'; 30772 r.data.bufferCanvases[i].setAttribute('data-id', 'buffer' + i); 30773 r.data.bufferCanvases[i].style.zIndex = String(-i - 1); 30774 r.data.bufferCanvases[i].style.visibility = 'hidden'; //r.data.canvasContainer.appendChild(r.data.bufferCanvases[i]); 30775 } 30776 30777 r.pathsEnabled = true; 30778 var emptyBb = makeBoundingBox(); 30779 30780 var getBoxCenter = function getBoxCenter(bb) { 30781 return { 30782 x: (bb.x1 + bb.x2) / 2, 30783 y: (bb.y1 + bb.y2) / 2 30784 }; 30785 }; 30786 30787 var getCenterOffset = function getCenterOffset(bb) { 30788 return { 30789 x: -bb.w / 2, 30790 y: -bb.h / 2 30791 }; 30792 }; 30793 30794 var backgroundTimestampHasChanged = function backgroundTimestampHasChanged(ele) { 30795 var _p = ele[0]._private; 30796 var same = _p.oldBackgroundTimestamp === _p.backgroundTimestamp; 30797 return !same; 30798 }; 30799 30800 var getStyleKey = function getStyleKey(ele) { 30801 return ele[0]._private.nodeKey; 30802 }; 30803 30804 var getLabelKey = function getLabelKey(ele) { 30805 return ele[0]._private.labelStyleKey; 30806 }; 30807 30808 var getSourceLabelKey = function getSourceLabelKey(ele) { 30809 return ele[0]._private.sourceLabelStyleKey; 30810 }; 30811 30812 var getTargetLabelKey = function getTargetLabelKey(ele) { 30813 return ele[0]._private.targetLabelStyleKey; 30814 }; 30815 30816 var drawElement = function drawElement(context, ele, bb, scaledLabelShown, useEleOpacity) { 30817 return r.drawElement(context, ele, bb, false, false, useEleOpacity); 30818 }; 30819 30820 var drawLabel = function drawLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 30821 return r.drawElementText(context, ele, bb, scaledLabelShown, 'main', useEleOpacity); 30822 }; 30823 30824 var drawSourceLabel = function drawSourceLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 30825 return r.drawElementText(context, ele, bb, scaledLabelShown, 'source', useEleOpacity); 30826 }; 30827 30828 var drawTargetLabel = function drawTargetLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 30829 return r.drawElementText(context, ele, bb, scaledLabelShown, 'target', useEleOpacity); 30830 }; 30831 30832 var getElementBox = function getElementBox(ele) { 30833 ele.boundingBox(); 30834 return ele[0]._private.bodyBounds; 30835 }; 30836 30837 var getLabelBox = function getLabelBox(ele) { 30838 ele.boundingBox(); 30839 return ele[0]._private.labelBounds.main || emptyBb; 30840 }; 30841 30842 var getSourceLabelBox = function getSourceLabelBox(ele) { 30843 ele.boundingBox(); 30844 return ele[0]._private.labelBounds.source || emptyBb; 30845 }; 30846 30847 var getTargetLabelBox = function getTargetLabelBox(ele) { 30848 ele.boundingBox(); 30849 return ele[0]._private.labelBounds.target || emptyBb; 30850 }; 30851 30852 var isLabelVisibleAtScale = function isLabelVisibleAtScale(ele, scaledLabelShown) { 30853 return scaledLabelShown; 30854 }; 30855 30856 var getElementRotationPoint = function getElementRotationPoint(ele) { 30857 return getBoxCenter(getElementBox(ele)); 30858 }; 30859 30860 var addTextMargin = function addTextMargin(prefix, pt, ele) { 30861 var pre = prefix ? prefix + '-' : ''; 30862 return { 30863 x: pt.x + ele.pstyle(pre + 'text-margin-x').pfValue, 30864 y: pt.y + ele.pstyle(pre + 'text-margin-y').pfValue 30865 }; 30866 }; 30867 30868 var getRsPt = function getRsPt(ele, x, y) { 30869 var rs = ele[0]._private.rscratch; 30870 return { 30871 x: rs[x], 30872 y: rs[y] 30873 }; 30874 }; 30875 30876 var getLabelRotationPoint = function getLabelRotationPoint(ele) { 30877 return addTextMargin('', getRsPt(ele, 'labelX', 'labelY'), ele); 30878 }; 30879 30880 var getSourceLabelRotationPoint = function getSourceLabelRotationPoint(ele) { 30881 return addTextMargin('source', getRsPt(ele, 'sourceLabelX', 'sourceLabelY'), ele); 30882 }; 30883 30884 var getTargetLabelRotationPoint = function getTargetLabelRotationPoint(ele) { 30885 return addTextMargin('target', getRsPt(ele, 'targetLabelX', 'targetLabelY'), ele); 30886 }; 30887 30888 var getElementRotationOffset = function getElementRotationOffset(ele) { 30889 return getCenterOffset(getElementBox(ele)); 30890 }; 30891 30892 var getSourceLabelRotationOffset = function getSourceLabelRotationOffset(ele) { 30893 return getCenterOffset(getSourceLabelBox(ele)); 30894 }; 30895 30896 var getTargetLabelRotationOffset = function getTargetLabelRotationOffset(ele) { 30897 return getCenterOffset(getTargetLabelBox(ele)); 30898 }; 30899 30900 var getLabelRotationOffset = function getLabelRotationOffset(ele) { 30901 var bb = getLabelBox(ele); 30902 var p = getCenterOffset(getLabelBox(ele)); 30903 30904 if (ele.isNode()) { 30905 switch (ele.pstyle('text-halign').value) { 30906 case 'left': 30907 p.x = -bb.w; 30908 break; 30909 30910 case 'right': 30911 p.x = 0; 30912 break; 30913 } 30914 30915 switch (ele.pstyle('text-valign').value) { 30916 case 'top': 30917 p.y = -bb.h; 30918 break; 30919 30920 case 'bottom': 30921 p.y = 0; 30922 break; 30923 } 30924 } 30925 30926 return p; 30927 }; 30928 30929 var eleTxrCache = r.data.eleTxrCache = new ElementTextureCache(r, { 30930 getKey: getStyleKey, 30931 doesEleInvalidateKey: backgroundTimestampHasChanged, 30932 drawElement: drawElement, 30933 getBoundingBox: getElementBox, 30934 getRotationPoint: getElementRotationPoint, 30935 getRotationOffset: getElementRotationOffset, 30936 allowEdgeTxrCaching: false, 30937 allowParentTxrCaching: false 30938 }); 30939 var lblTxrCache = r.data.lblTxrCache = new ElementTextureCache(r, { 30940 getKey: getLabelKey, 30941 drawElement: drawLabel, 30942 getBoundingBox: getLabelBox, 30943 getRotationPoint: getLabelRotationPoint, 30944 getRotationOffset: getLabelRotationOffset, 30945 isVisible: isLabelVisibleAtScale 30946 }); 30947 var slbTxrCache = r.data.slbTxrCache = new ElementTextureCache(r, { 30948 getKey: getSourceLabelKey, 30949 drawElement: drawSourceLabel, 30950 getBoundingBox: getSourceLabelBox, 30951 getRotationPoint: getSourceLabelRotationPoint, 30952 getRotationOffset: getSourceLabelRotationOffset, 30953 isVisible: isLabelVisibleAtScale 30954 }); 30955 var tlbTxrCache = r.data.tlbTxrCache = new ElementTextureCache(r, { 30956 getKey: getTargetLabelKey, 30957 drawElement: drawTargetLabel, 30958 getBoundingBox: getTargetLabelBox, 30959 getRotationPoint: getTargetLabelRotationPoint, 30960 getRotationOffset: getTargetLabelRotationOffset, 30961 isVisible: isLabelVisibleAtScale 30962 }); 30963 var lyrTxrCache = r.data.lyrTxrCache = new LayeredTextureCache(r); 30964 r.onUpdateEleCalcs(function invalidateTextureCaches(willDraw, eles) { 30965 // each cache should check for sub-key diff to see that the update affects that cache particularly 30966 eleTxrCache.invalidateElements(eles); 30967 lblTxrCache.invalidateElements(eles); 30968 slbTxrCache.invalidateElements(eles); 30969 tlbTxrCache.invalidateElements(eles); // any change invalidates the layers 30970 30971 lyrTxrCache.invalidateElements(eles); // update the old bg timestamp so diffs can be done in the ele txr caches 30972 30973 for (var _i = 0; _i < eles.length; _i++) { 30974 var _p = eles[_i]._private; 30975 _p.oldBackgroundTimestamp = _p.backgroundTimestamp; 30976 } 30977 }); 30978 30979 var refineInLayers = function refineInLayers(reqs) { 30980 for (var i = 0; i < reqs.length; i++) { 30981 lyrTxrCache.enqueueElementRefinement(reqs[i].ele); 30982 } 30983 }; 30984 30985 eleTxrCache.onDequeue(refineInLayers); 30986 lblTxrCache.onDequeue(refineInLayers); 30987 slbTxrCache.onDequeue(refineInLayers); 30988 tlbTxrCache.onDequeue(refineInLayers); 30989 } 30990 30991 CRp$a.redrawHint = function (group, bool) { 30992 var r = this; 30993 30994 switch (group) { 30995 case 'eles': 30996 r.data.canvasNeedsRedraw[CRp$a.NODE] = bool; 30997 break; 30998 30999 case 'drag': 31000 r.data.canvasNeedsRedraw[CRp$a.DRAG] = bool; 31001 break; 31002 31003 case 'select': 31004 r.data.canvasNeedsRedraw[CRp$a.SELECT_BOX] = bool; 31005 break; 31006 } 31007 }; // whether to use Path2D caching for drawing 31008 31009 31010 var pathsImpld = typeof Path2D !== 'undefined'; 31011 31012 CRp$a.path2dEnabled = function (on) { 31013 if (on === undefined) { 31014 return this.pathsEnabled; 31015 } 31016 31017 this.pathsEnabled = on ? true : false; 31018 }; 31019 31020 CRp$a.usePaths = function () { 31021 return pathsImpld && this.pathsEnabled; 31022 }; 31023 31024 CRp$a.setImgSmoothing = function (context, bool) { 31025 if (context.imageSmoothingEnabled != null) { 31026 context.imageSmoothingEnabled = bool; 31027 } else { 31028 context.webkitImageSmoothingEnabled = bool; 31029 context.mozImageSmoothingEnabled = bool; 31030 context.msImageSmoothingEnabled = bool; 31031 } 31032 }; 31033 31034 CRp$a.getImgSmoothing = function (context) { 31035 if (context.imageSmoothingEnabled != null) { 31036 return context.imageSmoothingEnabled; 31037 } else { 31038 return context.webkitImageSmoothingEnabled || context.mozImageSmoothingEnabled || context.msImageSmoothingEnabled; 31039 } 31040 }; 31041 31042 CRp$a.makeOffscreenCanvas = function (width, height) { 31043 var canvas; 31044 31045 if ((typeof OffscreenCanvas === "undefined" ? "undefined" : _typeof(OffscreenCanvas)) !== ( "undefined" )) { 31046 canvas = new OffscreenCanvas(width, height); 31047 } else { 31048 canvas = document.createElement('canvas'); // eslint-disable-line no-undef 31049 31050 canvas.width = width; 31051 canvas.height = height; 31052 } 31053 31054 return canvas; 31055 }; 31056 31057 [CRp, CRp$1, CRp$2, CRp$3, CRp$4, CRp$5, CRp$6, CRp$7, CRp$8, CRp$9].forEach(function (props) { 31058 extend(CRp$a, props); 31059 }); 31060 31061 var renderer = [{ 31062 name: 'null', 31063 impl: NullRenderer 31064 }, { 31065 name: 'base', 31066 impl: BR 31067 }, { 31068 name: 'canvas', 31069 impl: CR 31070 }]; 31071 31072 var incExts = [{ 31073 type: 'layout', 31074 extensions: layout 31075 }, { 31076 type: 'renderer', 31077 extensions: renderer 31078 }]; 31079 31080 var extensions = {}; // registered modules for extensions, indexed by name 31081 31082 var modules = {}; 31083 31084 function setExtension(type, name, registrant) { 31085 var ext = registrant; 31086 31087 var overrideErr = function overrideErr(field) { 31088 error('Can not register `' + name + '` for `' + type + '` since `' + field + '` already exists in the prototype and can not be overridden'); 31089 }; 31090 31091 if (type === 'core') { 31092 if (Core.prototype[name]) { 31093 return overrideErr(name); 31094 } else { 31095 Core.prototype[name] = registrant; 31096 } 31097 } else if (type === 'collection') { 31098 if (Collection.prototype[name]) { 31099 return overrideErr(name); 31100 } else { 31101 Collection.prototype[name] = registrant; 31102 } 31103 } else if (type === 'layout') { 31104 // fill in missing layout functions in the prototype 31105 var Layout = function Layout(options) { 31106 this.options = options; 31107 registrant.call(this, options); // make sure layout has _private for use w/ std apis like .on() 31108 31109 if (!plainObject(this._private)) { 31110 this._private = {}; 31111 } 31112 31113 this._private.cy = options.cy; 31114 this._private.listeners = []; 31115 this.createEmitter(); 31116 }; 31117 31118 var layoutProto = Layout.prototype = Object.create(registrant.prototype); 31119 var optLayoutFns = []; 31120 31121 for (var i = 0; i < optLayoutFns.length; i++) { 31122 var fnName = optLayoutFns[i]; 31123 31124 layoutProto[fnName] = layoutProto[fnName] || function () { 31125 return this; 31126 }; 31127 } // either .start() or .run() is defined, so autogen the other 31128 31129 31130 if (layoutProto.start && !layoutProto.run) { 31131 layoutProto.run = function () { 31132 this.start(); 31133 return this; 31134 }; 31135 } else if (!layoutProto.start && layoutProto.run) { 31136 layoutProto.start = function () { 31137 this.run(); 31138 return this; 31139 }; 31140 } 31141 31142 var regStop = registrant.prototype.stop; 31143 31144 layoutProto.stop = function () { 31145 var opts = this.options; 31146 31147 if (opts && opts.animate) { 31148 var anis = this.animations; 31149 31150 if (anis) { 31151 for (var _i = 0; _i < anis.length; _i++) { 31152 anis[_i].stop(); 31153 } 31154 } 31155 } 31156 31157 if (regStop) { 31158 regStop.call(this); 31159 } else { 31160 this.emit('layoutstop'); 31161 } 31162 31163 return this; 31164 }; 31165 31166 if (!layoutProto.destroy) { 31167 layoutProto.destroy = function () { 31168 return this; 31169 }; 31170 } 31171 31172 layoutProto.cy = function () { 31173 return this._private.cy; 31174 }; 31175 31176 var getCy = function getCy(layout) { 31177 return layout._private.cy; 31178 }; 31179 31180 var emitterOpts = { 31181 addEventFields: function addEventFields(layout, evt) { 31182 evt.layout = layout; 31183 evt.cy = getCy(layout); 31184 evt.target = layout; 31185 }, 31186 bubble: function bubble() { 31187 return true; 31188 }, 31189 parent: function parent(layout) { 31190 return getCy(layout); 31191 } 31192 }; 31193 extend(layoutProto, { 31194 createEmitter: function createEmitter() { 31195 this._private.emitter = new Emitter(emitterOpts, this); 31196 return this; 31197 }, 31198 emitter: function emitter() { 31199 return this._private.emitter; 31200 }, 31201 on: function on(evt, cb) { 31202 this.emitter().on(evt, cb); 31203 return this; 31204 }, 31205 one: function one(evt, cb) { 31206 this.emitter().one(evt, cb); 31207 return this; 31208 }, 31209 once: function once(evt, cb) { 31210 this.emitter().one(evt, cb); 31211 return this; 31212 }, 31213 removeListener: function removeListener(evt, cb) { 31214 this.emitter().removeListener(evt, cb); 31215 return this; 31216 }, 31217 removeAllListeners: function removeAllListeners() { 31218 this.emitter().removeAllListeners(); 31219 return this; 31220 }, 31221 emit: function emit(evt, params) { 31222 this.emitter().emit(evt, params); 31223 return this; 31224 } 31225 }); 31226 define$3.eventAliasesOn(layoutProto); 31227 ext = Layout; // replace with our wrapped layout 31228 } else if (type === 'renderer' && name !== 'null' && name !== 'base') { 31229 // user registered renderers inherit from base 31230 var BaseRenderer = getExtension('renderer', 'base'); 31231 var bProto = BaseRenderer.prototype; 31232 var RegistrantRenderer = registrant; 31233 var rProto = registrant.prototype; 31234 31235 var Renderer = function Renderer() { 31236 BaseRenderer.apply(this, arguments); 31237 RegistrantRenderer.apply(this, arguments); 31238 }; 31239 31240 var proto = Renderer.prototype; 31241 31242 for (var pName in bProto) { 31243 var pVal = bProto[pName]; 31244 var existsInR = rProto[pName] != null; 31245 31246 if (existsInR) { 31247 return overrideErr(pName); 31248 } 31249 31250 proto[pName] = pVal; // take impl from base 31251 } 31252 31253 for (var _pName in rProto) { 31254 proto[_pName] = rProto[_pName]; // take impl from registrant 31255 } 31256 31257 bProto.clientFunctions.forEach(function (name) { 31258 proto[name] = proto[name] || function () { 31259 error('Renderer does not implement `renderer.' + name + '()` on its prototype'); 31260 }; 31261 }); 31262 ext = Renderer; 31263 } 31264 31265 return setMap({ 31266 map: extensions, 31267 keys: [type, name], 31268 value: ext 31269 }); 31270 } 31271 31272 function getExtension(type, name) { 31273 return getMap({ 31274 map: extensions, 31275 keys: [type, name] 31276 }); 31277 } 31278 31279 function setModule(type, name, moduleType, moduleName, registrant) { 31280 return setMap({ 31281 map: modules, 31282 keys: [type, name, moduleType, moduleName], 31283 value: registrant 31284 }); 31285 } 31286 31287 function getModule(type, name, moduleType, moduleName) { 31288 return getMap({ 31289 map: modules, 31290 keys: [type, name, moduleType, moduleName] 31291 }); 31292 } 31293 31294 var extension = function extension() { 31295 // e.g. extension('renderer', 'svg') 31296 if (arguments.length === 2) { 31297 return getExtension.apply(null, arguments); 31298 } // e.g. extension('renderer', 'svg', { ... }) 31299 else if (arguments.length === 3) { 31300 return setExtension.apply(null, arguments); 31301 } // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse') 31302 else if (arguments.length === 4) { 31303 return getModule.apply(null, arguments); 31304 } // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... }) 31305 else if (arguments.length === 5) { 31306 return setModule.apply(null, arguments); 31307 } else { 31308 error('Invalid extension access syntax'); 31309 } 31310 }; // allows a core instance to access extensions internally 31311 31312 31313 Core.prototype.extension = extension; // included extensions 31314 31315 incExts.forEach(function (group) { 31316 group.extensions.forEach(function (ext) { 31317 setExtension(group.type, ext.name, ext.impl); 31318 }); 31319 }); 31320 31321 // (useful for init) 31322 31323 var Stylesheet = function Stylesheet() { 31324 if (!(this instanceof Stylesheet)) { 31325 return new Stylesheet(); 31326 } 31327 31328 this.length = 0; 31329 }; 31330 31331 var sheetfn = Stylesheet.prototype; 31332 31333 sheetfn.instanceString = function () { 31334 return 'stylesheet'; 31335 }; // just store the selector to be parsed later 31336 31337 31338 sheetfn.selector = function (selector) { 31339 var i = this.length++; 31340 this[i] = { 31341 selector: selector, 31342 properties: [] 31343 }; 31344 return this; // chaining 31345 }; // just store the property to be parsed later 31346 31347 31348 sheetfn.css = function (name, value) { 31349 var i = this.length - 1; 31350 31351 if (string(name)) { 31352 this[i].properties.push({ 31353 name: name, 31354 value: value 31355 }); 31356 } else if (plainObject(name)) { 31357 var map = name; 31358 var propNames = Object.keys(map); 31359 31360 for (var j = 0; j < propNames.length; j++) { 31361 var key = propNames[j]; 31362 var mapVal = map[key]; 31363 31364 if (mapVal == null) { 31365 continue; 31366 } 31367 31368 var prop = Style.properties[key] || Style.properties[dash2camel(key)]; 31369 31370 if (prop == null) { 31371 continue; 31372 } 31373 31374 var _name = prop.name; 31375 var _value = mapVal; 31376 this[i].properties.push({ 31377 name: _name, 31378 value: _value 31379 }); 31380 } 31381 } 31382 31383 return this; // chaining 31384 }; 31385 31386 sheetfn.style = sheetfn.css; // generate a real style object from the dummy stylesheet 31387 31388 sheetfn.generateStyle = function (cy) { 31389 var style = new Style(cy); 31390 return this.appendToStyle(style); 31391 }; // append a dummy stylesheet object on a real style object 31392 31393 31394 sheetfn.appendToStyle = function (style) { 31395 for (var i = 0; i < this.length; i++) { 31396 var context = this[i]; 31397 var selector = context.selector; 31398 var props = context.properties; 31399 style.selector(selector); // apply selector 31400 31401 for (var j = 0; j < props.length; j++) { 31402 var prop = props[j]; 31403 style.css(prop.name, prop.value); // apply property 31404 } 31405 } 31406 31407 return style; 31408 }; 31409 31410 var version = "3.13.3"; 31411 31412 var cytoscape = function cytoscape(options) { 31413 // if no options specified, use default 31414 if (options === undefined) { 31415 options = {}; 31416 } // create instance 31417 31418 31419 if (plainObject(options)) { 31420 return new Core(options); 31421 } // allow for registration of extensions 31422 else if (string(options)) { 31423 return extension.apply(extension, arguments); 31424 } 31425 }; // e.g. cytoscape.use( require('cytoscape-foo'), bar ) 31426 31427 31428 cytoscape.use = function (ext) { 31429 var args = Array.prototype.slice.call(arguments, 1); // args to pass to ext 31430 31431 args.unshift(cytoscape); // cytoscape is first arg to ext 31432 31433 ext.apply(null, args); 31434 return this; 31435 }; 31436 31437 cytoscape.warnings = function (bool) { 31438 return warnings(bool); 31439 }; // replaced by build system 31440 31441 31442 cytoscape.version = version; // expose public apis (mostly for extensions) 31443 31444 cytoscape.stylesheet = cytoscape.Stylesheet = Stylesheet; 31445 31446 export default cytoscape;