github.com/mweagle/Sparta@v1.15.0/resources/describe/cytoscape.js/dist/cytoscape.cjs.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 'use strict'; 24 25 function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 26 27 var util = _interopDefault(require('lodash.debounce')); 28 var Heap = _interopDefault(require('heap')); 29 30 function _typeof(obj) { 31 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 32 _typeof = function (obj) { 33 return typeof obj; 34 }; 35 } else { 36 _typeof = function (obj) { 37 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 38 }; 39 } 40 41 return _typeof(obj); 42 } 43 44 function _classCallCheck(instance, Constructor) { 45 if (!(instance instanceof Constructor)) { 46 throw new TypeError("Cannot call a class as a function"); 47 } 48 } 49 50 function _defineProperties(target, props) { 51 for (var i = 0; i < props.length; i++) { 52 var descriptor = props[i]; 53 descriptor.enumerable = descriptor.enumerable || false; 54 descriptor.configurable = true; 55 if ("value" in descriptor) descriptor.writable = true; 56 Object.defineProperty(target, descriptor.key, descriptor); 57 } 58 } 59 60 function _createClass(Constructor, protoProps, staticProps) { 61 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 62 if (staticProps) _defineProperties(Constructor, staticProps); 63 return Constructor; 64 } 65 66 function _defineProperty(obj, key, value) { 67 if (key in obj) { 68 Object.defineProperty(obj, key, { 69 value: value, 70 enumerable: true, 71 configurable: true, 72 writable: true 73 }); 74 } else { 75 obj[key] = value; 76 } 77 78 return obj; 79 } 80 81 function _slicedToArray(arr, i) { 82 return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); 83 } 84 85 function _arrayWithHoles(arr) { 86 if (Array.isArray(arr)) return arr; 87 } 88 89 function _iterableToArrayLimit(arr, i) { 90 var _arr = []; 91 var _n = true; 92 var _d = false; 93 var _e = undefined; 94 95 try { 96 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 97 _arr.push(_s.value); 98 99 if (i && _arr.length === i) break; 100 } 101 } catch (err) { 102 _d = true; 103 _e = err; 104 } finally { 105 try { 106 if (!_n && _i["return"] != null) _i["return"](); 107 } finally { 108 if (_d) throw _e; 109 } 110 } 111 112 return _arr; 113 } 114 115 function _nonIterableRest() { 116 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 117 } 118 119 var window$1 = typeof window === 'undefined' ? null : window; // eslint-disable-line no-undef 120 121 var navigator = window$1 ? window$1.navigator : null; 122 var document$1 = window$1 ? window$1.document : null; 123 124 var typeofstr = _typeof(''); 125 126 var typeofobj = _typeof({}); 127 128 var typeoffn = _typeof(function () {}); 129 130 var typeofhtmlele = typeof HTMLElement === "undefined" ? "undefined" : _typeof(HTMLElement); 131 132 var instanceStr = function instanceStr(obj) { 133 return obj && obj.instanceString && fn(obj.instanceString) ? obj.instanceString() : null; 134 }; 135 136 var string = function string(obj) { 137 return obj != null && _typeof(obj) == typeofstr; 138 }; 139 var fn = function fn(obj) { 140 return obj != null && _typeof(obj) === typeoffn; 141 }; 142 var array = function array(obj) { 143 return Array.isArray ? Array.isArray(obj) : obj != null && obj instanceof Array; 144 }; 145 var plainObject = function plainObject(obj) { 146 return obj != null && _typeof(obj) === typeofobj && !array(obj) && obj.constructor === Object; 147 }; 148 var object = function object(obj) { 149 return obj != null && _typeof(obj) === typeofobj; 150 }; 151 var number = function number(obj) { 152 return obj != null && _typeof(obj) === _typeof(1) && !isNaN(obj); 153 }; 154 var integer = function integer(obj) { 155 return number(obj) && Math.floor(obj) === obj; 156 }; 157 var htmlElement = function htmlElement(obj) { 158 if ('undefined' === typeofhtmlele) { 159 return undefined; 160 } else { 161 return null != obj && obj instanceof HTMLElement; 162 } 163 }; 164 var elementOrCollection = function elementOrCollection(obj) { 165 return element(obj) || collection(obj); 166 }; 167 var element = function element(obj) { 168 return instanceStr(obj) === 'collection' && obj._private.single; 169 }; 170 var collection = function collection(obj) { 171 return instanceStr(obj) === 'collection' && !obj._private.single; 172 }; 173 var core = function core(obj) { 174 return instanceStr(obj) === 'core'; 175 }; 176 var stylesheet = function stylesheet(obj) { 177 return instanceStr(obj) === 'stylesheet'; 178 }; 179 var event = function event(obj) { 180 return instanceStr(obj) === 'event'; 181 }; 182 var emptyString = function emptyString(obj) { 183 if (obj === undefined || obj === null) { 184 // null is empty 185 return true; 186 } else if (obj === '' || obj.match(/^\s+$/)) { 187 return true; // empty string is empty 188 } 189 190 return false; // otherwise, we don't know what we've got 191 }; 192 var domElement = function domElement(obj) { 193 if (typeof HTMLElement === 'undefined') { 194 return false; // we're not in a browser so it doesn't matter 195 } else { 196 return obj instanceof HTMLElement; 197 } 198 }; 199 var boundingBox = function boundingBox(obj) { 200 return plainObject(obj) && number(obj.x1) && number(obj.x2) && number(obj.y1) && number(obj.y2); 201 }; 202 var promise = function promise(obj) { 203 return object(obj) && fn(obj.then); 204 }; 205 var ms = function ms() { 206 return navigator && navigator.userAgent.match(/msie|trident|edge/i); 207 }; // probably a better way to detect this... 208 209 var memoize = function memoize(fn, keyFn) { 210 if (!keyFn) { 211 keyFn = function keyFn() { 212 if (arguments.length === 1) { 213 return arguments[0]; 214 } else if (arguments.length === 0) { 215 return 'undefined'; 216 } 217 218 var args = []; 219 220 for (var i = 0; i < arguments.length; i++) { 221 args.push(arguments[i]); 222 } 223 224 return args.join('$'); 225 }; 226 } 227 228 var memoizedFn = function memoizedFn() { 229 var self = this; 230 var args = arguments; 231 var ret; 232 var k = keyFn.apply(self, args); 233 var cache = memoizedFn.cache; 234 235 if (!(ret = cache[k])) { 236 ret = cache[k] = fn.apply(self, args); 237 } 238 239 return ret; 240 }; 241 242 memoizedFn.cache = {}; 243 return memoizedFn; 244 }; 245 246 var camel2dash = memoize(function (str) { 247 return str.replace(/([A-Z])/g, function (v) { 248 return '-' + v.toLowerCase(); 249 }); 250 }); 251 var dash2camel = memoize(function (str) { 252 return str.replace(/(-\w)/g, function (v) { 253 return v[1].toUpperCase(); 254 }); 255 }); 256 var prependCamel = memoize(function (prefix, str) { 257 return prefix + str[0].toUpperCase() + str.substring(1); 258 }, function (prefix, str) { 259 return prefix + '$' + str; 260 }); 261 var capitalize = function capitalize(str) { 262 if (emptyString(str)) { 263 return str; 264 } 265 266 return str.charAt(0).toUpperCase() + str.substring(1); 267 }; 268 269 var number$1 = '(?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))'; 270 var rgba = 'rgb[a]?\\((' + number$1 + '[%]?)\\s*,\\s*(' + number$1 + '[%]?)\\s*,\\s*(' + number$1 + '[%]?)(?:\\s*,\\s*(' + number$1 + '))?\\)'; 271 var rgbaNoBackRefs = 'rgb[a]?\\((?:' + number$1 + '[%]?)\\s*,\\s*(?:' + number$1 + '[%]?)\\s*,\\s*(?:' + number$1 + '[%]?)(?:\\s*,\\s*(?:' + number$1 + '))?\\)'; 272 var hsla = 'hsl[a]?\\((' + number$1 + ')\\s*,\\s*(' + number$1 + '[%])\\s*,\\s*(' + number$1 + '[%])(?:\\s*,\\s*(' + number$1 + '))?\\)'; 273 var hslaNoBackRefs = 'hsl[a]?\\((?:' + number$1 + ')\\s*,\\s*(?:' + number$1 + '[%])\\s*,\\s*(?:' + number$1 + '[%])(?:\\s*,\\s*(?:' + number$1 + '))?\\)'; 274 var hex3 = '\\#[0-9a-fA-F]{3}'; 275 var hex6 = '\\#[0-9a-fA-F]{6}'; 276 277 var ascending = function ascending(a, b) { 278 if (a < b) { 279 return -1; 280 } else if (a > b) { 281 return 1; 282 } else { 283 return 0; 284 } 285 }; 286 var descending = function descending(a, b) { 287 return -1 * ascending(a, b); 288 }; 289 290 var extend = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { 291 var args = arguments; 292 293 for (var i = 1; i < args.length; i++) { 294 var obj = args[i]; 295 296 if (obj == null) { 297 continue; 298 } 299 300 var keys = Object.keys(obj); 301 302 for (var j = 0; j < keys.length; j++) { 303 var k = keys[j]; 304 tgt[k] = obj[k]; 305 } 306 } 307 308 return tgt; 309 }; 310 311 var hex2tuple = function hex2tuple(hex) { 312 if (!(hex.length === 4 || hex.length === 7) || hex[0] !== '#') { 313 return; 314 } 315 316 var shortHex = hex.length === 4; 317 var r, g, b; 318 var base = 16; 319 320 if (shortHex) { 321 r = parseInt(hex[1] + hex[1], base); 322 g = parseInt(hex[2] + hex[2], base); 323 b = parseInt(hex[3] + hex[3], base); 324 } else { 325 r = parseInt(hex[1] + hex[2], base); 326 g = parseInt(hex[3] + hex[4], base); 327 b = parseInt(hex[5] + hex[6], base); 328 } 329 330 return [r, g, b]; 331 }; // get [r, g, b, a] from hsl(0, 0, 0) or hsla(0, 0, 0, 0) 332 333 var hsl2tuple = function hsl2tuple(hsl) { 334 var ret; 335 var h, s, l, a, r, g, b; 336 337 function hue2rgb(p, q, t) { 338 if (t < 0) t += 1; 339 if (t > 1) t -= 1; 340 if (t < 1 / 6) return p + (q - p) * 6 * t; 341 if (t < 1 / 2) return q; 342 if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 343 return p; 344 } 345 346 var m = new RegExp('^' + hsla + '$').exec(hsl); 347 348 if (m) { 349 // get hue 350 h = parseInt(m[1]); 351 352 if (h < 0) { 353 h = (360 - -1 * h % 360) % 360; 354 } else if (h > 360) { 355 h = h % 360; 356 } 357 358 h /= 360; // normalise on [0, 1] 359 360 s = parseFloat(m[2]); 361 362 if (s < 0 || s > 100) { 363 return; 364 } // saturation is [0, 100] 365 366 367 s = s / 100; // normalise on [0, 1] 368 369 l = parseFloat(m[3]); 370 371 if (l < 0 || l > 100) { 372 return; 373 } // lightness is [0, 100] 374 375 376 l = l / 100; // normalise on [0, 1] 377 378 a = m[4]; 379 380 if (a !== undefined) { 381 a = parseFloat(a); 382 383 if (a < 0 || a > 1) { 384 return; 385 } // alpha is [0, 1] 386 387 } // now, convert to rgb 388 // code from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript 389 390 391 if (s === 0) { 392 r = g = b = Math.round(l * 255); // achromatic 393 } else { 394 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 395 var p = 2 * l - q; 396 r = Math.round(255 * hue2rgb(p, q, h + 1 / 3)); 397 g = Math.round(255 * hue2rgb(p, q, h)); 398 b = Math.round(255 * hue2rgb(p, q, h - 1 / 3)); 399 } 400 401 ret = [r, g, b, a]; 402 } 403 404 return ret; 405 }; // get [r, g, b, a] from rgb(0, 0, 0) or rgba(0, 0, 0, 0) 406 407 var rgb2tuple = function rgb2tuple(rgb) { 408 var ret; 409 var m = new RegExp('^' + rgba + '$').exec(rgb); 410 411 if (m) { 412 ret = []; 413 var isPct = []; 414 415 for (var i = 1; i <= 3; i++) { 416 var channel = m[i]; 417 418 if (channel[channel.length - 1] === '%') { 419 isPct[i] = true; 420 } 421 422 channel = parseFloat(channel); 423 424 if (isPct[i]) { 425 channel = channel / 100 * 255; // normalise to [0, 255] 426 } 427 428 if (channel < 0 || channel > 255) { 429 return; 430 } // invalid channel value 431 432 433 ret.push(Math.floor(channel)); 434 } 435 436 var atLeastOneIsPct = isPct[1] || isPct[2] || isPct[3]; 437 var allArePct = isPct[1] && isPct[2] && isPct[3]; 438 439 if (atLeastOneIsPct && !allArePct) { 440 return; 441 } // must all be percent values if one is 442 443 444 var alpha = m[4]; 445 446 if (alpha !== undefined) { 447 alpha = parseFloat(alpha); 448 449 if (alpha < 0 || alpha > 1) { 450 return; 451 } // invalid alpha value 452 453 454 ret.push(alpha); 455 } 456 } 457 458 return ret; 459 }; 460 var colorname2tuple = function colorname2tuple(color) { 461 return colors[color.toLowerCase()]; 462 }; 463 var color2tuple = function color2tuple(color) { 464 return (array(color) ? color : null) || colorname2tuple(color) || hex2tuple(color) || rgb2tuple(color) || hsl2tuple(color); 465 }; 466 var colors = { 467 // special colour names 468 transparent: [0, 0, 0, 0], 469 // NB alpha === 0 470 // regular colours 471 aliceblue: [240, 248, 255], 472 antiquewhite: [250, 235, 215], 473 aqua: [0, 255, 255], 474 aquamarine: [127, 255, 212], 475 azure: [240, 255, 255], 476 beige: [245, 245, 220], 477 bisque: [255, 228, 196], 478 black: [0, 0, 0], 479 blanchedalmond: [255, 235, 205], 480 blue: [0, 0, 255], 481 blueviolet: [138, 43, 226], 482 brown: [165, 42, 42], 483 burlywood: [222, 184, 135], 484 cadetblue: [95, 158, 160], 485 chartreuse: [127, 255, 0], 486 chocolate: [210, 105, 30], 487 coral: [255, 127, 80], 488 cornflowerblue: [100, 149, 237], 489 cornsilk: [255, 248, 220], 490 crimson: [220, 20, 60], 491 cyan: [0, 255, 255], 492 darkblue: [0, 0, 139], 493 darkcyan: [0, 139, 139], 494 darkgoldenrod: [184, 134, 11], 495 darkgray: [169, 169, 169], 496 darkgreen: [0, 100, 0], 497 darkgrey: [169, 169, 169], 498 darkkhaki: [189, 183, 107], 499 darkmagenta: [139, 0, 139], 500 darkolivegreen: [85, 107, 47], 501 darkorange: [255, 140, 0], 502 darkorchid: [153, 50, 204], 503 darkred: [139, 0, 0], 504 darksalmon: [233, 150, 122], 505 darkseagreen: [143, 188, 143], 506 darkslateblue: [72, 61, 139], 507 darkslategray: [47, 79, 79], 508 darkslategrey: [47, 79, 79], 509 darkturquoise: [0, 206, 209], 510 darkviolet: [148, 0, 211], 511 deeppink: [255, 20, 147], 512 deepskyblue: [0, 191, 255], 513 dimgray: [105, 105, 105], 514 dimgrey: [105, 105, 105], 515 dodgerblue: [30, 144, 255], 516 firebrick: [178, 34, 34], 517 floralwhite: [255, 250, 240], 518 forestgreen: [34, 139, 34], 519 fuchsia: [255, 0, 255], 520 gainsboro: [220, 220, 220], 521 ghostwhite: [248, 248, 255], 522 gold: [255, 215, 0], 523 goldenrod: [218, 165, 32], 524 gray: [128, 128, 128], 525 grey: [128, 128, 128], 526 green: [0, 128, 0], 527 greenyellow: [173, 255, 47], 528 honeydew: [240, 255, 240], 529 hotpink: [255, 105, 180], 530 indianred: [205, 92, 92], 531 indigo: [75, 0, 130], 532 ivory: [255, 255, 240], 533 khaki: [240, 230, 140], 534 lavender: [230, 230, 250], 535 lavenderblush: [255, 240, 245], 536 lawngreen: [124, 252, 0], 537 lemonchiffon: [255, 250, 205], 538 lightblue: [173, 216, 230], 539 lightcoral: [240, 128, 128], 540 lightcyan: [224, 255, 255], 541 lightgoldenrodyellow: [250, 250, 210], 542 lightgray: [211, 211, 211], 543 lightgreen: [144, 238, 144], 544 lightgrey: [211, 211, 211], 545 lightpink: [255, 182, 193], 546 lightsalmon: [255, 160, 122], 547 lightseagreen: [32, 178, 170], 548 lightskyblue: [135, 206, 250], 549 lightslategray: [119, 136, 153], 550 lightslategrey: [119, 136, 153], 551 lightsteelblue: [176, 196, 222], 552 lightyellow: [255, 255, 224], 553 lime: [0, 255, 0], 554 limegreen: [50, 205, 50], 555 linen: [250, 240, 230], 556 magenta: [255, 0, 255], 557 maroon: [128, 0, 0], 558 mediumaquamarine: [102, 205, 170], 559 mediumblue: [0, 0, 205], 560 mediumorchid: [186, 85, 211], 561 mediumpurple: [147, 112, 219], 562 mediumseagreen: [60, 179, 113], 563 mediumslateblue: [123, 104, 238], 564 mediumspringgreen: [0, 250, 154], 565 mediumturquoise: [72, 209, 204], 566 mediumvioletred: [199, 21, 133], 567 midnightblue: [25, 25, 112], 568 mintcream: [245, 255, 250], 569 mistyrose: [255, 228, 225], 570 moccasin: [255, 228, 181], 571 navajowhite: [255, 222, 173], 572 navy: [0, 0, 128], 573 oldlace: [253, 245, 230], 574 olive: [128, 128, 0], 575 olivedrab: [107, 142, 35], 576 orange: [255, 165, 0], 577 orangered: [255, 69, 0], 578 orchid: [218, 112, 214], 579 palegoldenrod: [238, 232, 170], 580 palegreen: [152, 251, 152], 581 paleturquoise: [175, 238, 238], 582 palevioletred: [219, 112, 147], 583 papayawhip: [255, 239, 213], 584 peachpuff: [255, 218, 185], 585 peru: [205, 133, 63], 586 pink: [255, 192, 203], 587 plum: [221, 160, 221], 588 powderblue: [176, 224, 230], 589 purple: [128, 0, 128], 590 red: [255, 0, 0], 591 rosybrown: [188, 143, 143], 592 royalblue: [65, 105, 225], 593 saddlebrown: [139, 69, 19], 594 salmon: [250, 128, 114], 595 sandybrown: [244, 164, 96], 596 seagreen: [46, 139, 87], 597 seashell: [255, 245, 238], 598 sienna: [160, 82, 45], 599 silver: [192, 192, 192], 600 skyblue: [135, 206, 235], 601 slateblue: [106, 90, 205], 602 slategray: [112, 128, 144], 603 slategrey: [112, 128, 144], 604 snow: [255, 250, 250], 605 springgreen: [0, 255, 127], 606 steelblue: [70, 130, 180], 607 tan: [210, 180, 140], 608 teal: [0, 128, 128], 609 thistle: [216, 191, 216], 610 tomato: [255, 99, 71], 611 turquoise: [64, 224, 208], 612 violet: [238, 130, 238], 613 wheat: [245, 222, 179], 614 white: [255, 255, 255], 615 whitesmoke: [245, 245, 245], 616 yellow: [255, 255, 0], 617 yellowgreen: [154, 205, 50] 618 }; 619 620 var setMap = function setMap(options) { 621 var obj = options.map; 622 var keys = options.keys; 623 var l = keys.length; 624 625 for (var i = 0; i < l; i++) { 626 var key = keys[i]; 627 628 if (plainObject(key)) { 629 throw Error('Tried to set map with object key'); 630 } 631 632 if (i < keys.length - 1) { 633 // extend the map if necessary 634 if (obj[key] == null) { 635 obj[key] = {}; 636 } 637 638 obj = obj[key]; 639 } else { 640 // set the value 641 obj[key] = options.value; 642 } 643 } 644 }; // gets the value in a map even if it's not built in places 645 646 var getMap = function getMap(options) { 647 var obj = options.map; 648 var keys = options.keys; 649 var l = keys.length; 650 651 for (var i = 0; i < l; i++) { 652 var key = keys[i]; 653 654 if (plainObject(key)) { 655 throw Error('Tried to get map with object key'); 656 } 657 658 obj = obj[key]; 659 660 if (obj == null) { 661 return obj; 662 } 663 } 664 665 return obj; 666 }; // deletes the entry in the map 667 668 var performance = window$1 ? window$1.performance : null; 669 var pnow = performance && performance.now ? function () { 670 return performance.now(); 671 } : function () { 672 return Date.now(); 673 }; 674 675 var raf = function () { 676 if (window$1) { 677 if (window$1.requestAnimationFrame) { 678 return function (fn) { 679 window$1.requestAnimationFrame(fn); 680 }; 681 } else if (window$1.mozRequestAnimationFrame) { 682 return function (fn) { 683 window$1.mozRequestAnimationFrame(fn); 684 }; 685 } else if (window$1.webkitRequestAnimationFrame) { 686 return function (fn) { 687 window$1.webkitRequestAnimationFrame(fn); 688 }; 689 } else if (window$1.msRequestAnimationFrame) { 690 return function (fn) { 691 window$1.msRequestAnimationFrame(fn); 692 }; 693 } 694 } 695 696 return function (fn) { 697 if (fn) { 698 setTimeout(function () { 699 fn(pnow()); 700 }, 1000 / 60); 701 } 702 }; 703 }(); 704 705 var requestAnimationFrame = function requestAnimationFrame(fn) { 706 return raf(fn); 707 }; 708 var performanceNow = pnow; 709 710 var DEFAULT_SEED = 5381; 711 var hashIterableInts = function hashIterableInts(iterator) { 712 var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_SEED; 713 // djb2/string-hash 714 var hash = seed; 715 var entry; 716 717 for (;;) { 718 entry = iterator.next(); 719 720 if (entry.done) { 721 break; 722 } 723 724 hash = (hash << 5) + hash + entry.value | 0; 725 } 726 727 return hash; 728 }; 729 var hashInt = function hashInt(num) { 730 var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_SEED; 731 // djb2/string-hash 732 return (seed << 5) + seed + num | 0; 733 }; 734 var hashIntsArray = function hashIntsArray(ints, seed) { 735 var entry = { 736 value: 0, 737 done: false 738 }; 739 var i = 0; 740 var length = ints.length; 741 var iterator = { 742 next: function next() { 743 if (i < length) { 744 entry.value = ints[i++]; 745 } else { 746 entry.done = true; 747 } 748 749 return entry; 750 } 751 }; 752 return hashIterableInts(iterator, seed); 753 }; 754 var hashString = function hashString(str, seed) { 755 var entry = { 756 value: 0, 757 done: false 758 }; 759 var i = 0; 760 var length = str.length; 761 var iterator = { 762 next: function next() { 763 if (i < length) { 764 entry.value = str.charCodeAt(i++); 765 } else { 766 entry.done = true; 767 } 768 769 return entry; 770 } 771 }; 772 return hashIterableInts(iterator, seed); 773 }; 774 var hashStrings = function hashStrings() { 775 return hashStringsArray(arguments); 776 }; 777 var hashStringsArray = function hashStringsArray(strs) { 778 var hash; 779 780 for (var i = 0; i < strs.length; i++) { 781 var str = strs[i]; 782 783 if (i === 0) { 784 hash = hashString(str); 785 } else { 786 hash = hashString(str, hash); 787 } 788 } 789 790 return hash; 791 }; 792 793 /*global console */ 794 var warningsEnabled = true; 795 var warnSupported = console.warn != null; // eslint-disable-line no-console 796 797 var traceSupported = console.trace != null; // eslint-disable-line no-console 798 799 var MAX_INT = Number.MAX_SAFE_INTEGER || 9007199254740991; 800 var trueify = function trueify() { 801 return true; 802 }; 803 var falsify = function falsify() { 804 return false; 805 }; 806 var zeroify = function zeroify() { 807 return 0; 808 }; 809 var noop = function noop() {}; 810 var error = function error(msg) { 811 throw new Error(msg); 812 }; 813 var warnings = function warnings(enabled) { 814 if (enabled !== undefined) { 815 warningsEnabled = !!enabled; 816 } else { 817 return warningsEnabled; 818 } 819 }; 820 var warn = function warn(msg) { 821 /* eslint-disable no-console */ 822 if (!warnings()) { 823 return; 824 } 825 826 if (warnSupported) { 827 console.warn(msg); 828 } else { 829 console.log(msg); 830 831 if (traceSupported) { 832 console.trace(); 833 } 834 } 835 }; 836 /* eslint-enable */ 837 838 var clone = function clone(obj) { 839 return extend({}, obj); 840 }; // gets a shallow copy of the argument 841 842 var copy = function copy(obj) { 843 if (obj == null) { 844 return obj; 845 } 846 847 if (array(obj)) { 848 return obj.slice(); 849 } else if (plainObject(obj)) { 850 return clone(obj); 851 } else { 852 return obj; 853 } 854 }; 855 var copyArray = function copyArray(arr) { 856 return arr.slice(); 857 }; 858 var uuid = function uuid(a, b 859 /* placeholders */ 860 ) { 861 for ( // loop :) 862 b = a = ''; // b - result , a - numeric letiable 863 a++ < 36; // 864 b += a * 51 & 52 // if "a" is not 9 or 14 or 19 or 24 865 ? // return a random number or 4 866 (a ^ 15 // if "a" is not 15 867 ? // genetate a random number from 0 to 15 868 8 ^ Math.random() * (a ^ 20 ? 16 : 4) // unless "a" is 20, in which case a random number from 8 to 11 869 : 4 // otherwise 4 870 ).toString(16) : '-' // in other cases (if "a" is 9,14,19,24) insert "-" 871 ) { 872 } 873 874 return b; 875 }; 876 var _staticEmptyObject = {}; 877 var staticEmptyObject = function staticEmptyObject() { 878 return _staticEmptyObject; 879 }; 880 var defaults = function defaults(_defaults) { 881 var keys = Object.keys(_defaults); 882 return function (opts) { 883 var filledOpts = {}; 884 885 for (var i = 0; i < keys.length; i++) { 886 var key = keys[i]; 887 var optVal = opts == null ? undefined : opts[key]; 888 filledOpts[key] = optVal === undefined ? _defaults[key] : optVal; 889 } 890 891 return filledOpts; 892 }; 893 }; 894 var removeFromArray = function removeFromArray(arr, ele, manyCopies) { 895 for (var i = arr.length; i >= 0; i--) { 896 if (arr[i] === ele) { 897 arr.splice(i, 1); 898 899 if (!manyCopies) { 900 break; 901 } 902 } 903 } 904 }; 905 var clearArray = function clearArray(arr) { 906 arr.splice(0, arr.length); 907 }; 908 var push = function push(arr, otherArr) { 909 for (var i = 0; i < otherArr.length; i++) { 910 var el = otherArr[i]; 911 arr.push(el); 912 } 913 }; 914 var getPrefixedProperty = function getPrefixedProperty(obj, propName, prefix) { 915 if (prefix) { 916 propName = prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth 917 } 918 919 return obj[propName]; 920 }; 921 var setPrefixedProperty = function setPrefixedProperty(obj, propName, prefix, value) { 922 if (prefix) { 923 propName = prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth 924 } 925 926 obj[propName] = value; 927 }; 928 929 /* global Map */ 930 var ObjectMap = 931 /*#__PURE__*/ 932 function () { 933 function ObjectMap() { 934 _classCallCheck(this, ObjectMap); 935 936 this._obj = {}; 937 } 938 939 _createClass(ObjectMap, [{ 940 key: "set", 941 value: function set(key, val) { 942 this._obj[key] = val; 943 return this; 944 } 945 }, { 946 key: "delete", 947 value: function _delete(key) { 948 this._obj[key] = undefined; 949 return this; 950 } 951 }, { 952 key: "clear", 953 value: function clear() { 954 this._obj = {}; 955 } 956 }, { 957 key: "has", 958 value: function has(key) { 959 return this._obj[key] !== undefined; 960 } 961 }, { 962 key: "get", 963 value: function get(key) { 964 return this._obj[key]; 965 } 966 }]); 967 968 return ObjectMap; 969 }(); 970 971 var Map$1 = typeof Map !== 'undefined' ? Map : ObjectMap; 972 973 /* global Set */ 974 var undef = "undefined" ; 975 976 var ObjectSet = 977 /*#__PURE__*/ 978 function () { 979 function ObjectSet(arrayOrObjectSet) { 980 _classCallCheck(this, ObjectSet); 981 982 this._obj = Object.create(null); 983 this.size = 0; 984 985 if (arrayOrObjectSet != null) { 986 var arr; 987 988 if (arrayOrObjectSet.instanceString != null && arrayOrObjectSet.instanceString() === this.instanceString()) { 989 arr = arrayOrObjectSet.toArray(); 990 } else { 991 arr = arrayOrObjectSet; 992 } 993 994 for (var i = 0; i < arr.length; i++) { 995 this.add(arr[i]); 996 } 997 } 998 } 999 1000 _createClass(ObjectSet, [{ 1001 key: "instanceString", 1002 value: function instanceString() { 1003 return 'set'; 1004 } 1005 }, { 1006 key: "add", 1007 value: function add(val) { 1008 var o = this._obj; 1009 1010 if (o[val] !== 1) { 1011 o[val] = 1; 1012 this.size++; 1013 } 1014 } 1015 }, { 1016 key: "delete", 1017 value: function _delete(val) { 1018 var o = this._obj; 1019 1020 if (o[val] === 1) { 1021 o[val] = 0; 1022 this.size--; 1023 } 1024 } 1025 }, { 1026 key: "clear", 1027 value: function clear() { 1028 this._obj = Object.create(null); 1029 } 1030 }, { 1031 key: "has", 1032 value: function has(val) { 1033 return this._obj[val] === 1; 1034 } 1035 }, { 1036 key: "toArray", 1037 value: function toArray() { 1038 var _this = this; 1039 1040 return Object.keys(this._obj).filter(function (key) { 1041 return _this.has(key); 1042 }); 1043 } 1044 }, { 1045 key: "forEach", 1046 value: function forEach(callback, thisArg) { 1047 return this.toArray().forEach(callback, thisArg); 1048 } 1049 }]); 1050 1051 return ObjectSet; 1052 }(); 1053 1054 var Set$1 = (typeof Set === "undefined" ? "undefined" : _typeof(Set)) !== undef ? Set : ObjectSet; 1055 1056 var Element = function Element(cy, params, restore) { 1057 restore = restore === undefined || restore ? true : false; 1058 1059 if (cy === undefined || params === undefined || !core(cy)) { 1060 error('An element must have a core reference and parameters set'); 1061 return; 1062 } 1063 1064 var group = params.group; // try to automatically infer the group if unspecified 1065 1066 if (group == null) { 1067 if (params.data && params.data.source != null && params.data.target != null) { 1068 group = 'edges'; 1069 } else { 1070 group = 'nodes'; 1071 } 1072 } // validate group 1073 1074 1075 if (group !== 'nodes' && group !== 'edges') { 1076 error('An element must be of type `nodes` or `edges`; you specified `' + group + '`'); 1077 return; 1078 } // make the element array-like, just like a collection 1079 1080 1081 this.length = 1; 1082 this[0] = this; // NOTE: when something is added here, add also to ele.json() 1083 1084 var _p = this._private = { 1085 cy: cy, 1086 single: true, 1087 // indicates this is an element 1088 data: params.data || {}, 1089 // data object 1090 position: params.position || { 1091 x: 0, 1092 y: 0 1093 }, 1094 // (x, y) position pair 1095 autoWidth: undefined, 1096 // width and height of nodes calculated by the renderer when set to special 'auto' value 1097 autoHeight: undefined, 1098 autoPadding: undefined, 1099 compoundBoundsClean: false, 1100 // whether the compound dimensions need to be recalculated the next time dimensions are read 1101 listeners: [], 1102 // array of bound listeners 1103 group: group, 1104 // string; 'nodes' or 'edges' 1105 style: {}, 1106 // properties as set by the style 1107 rstyle: {}, 1108 // properties for style sent from the renderer to the core 1109 styleCxts: [], 1110 // applied style contexts from the styler 1111 styleKeys: {}, 1112 // per-group keys of style property values 1113 removed: true, 1114 // whether it's inside the vis; true if removed (set true here since we call restore) 1115 selected: params.selected ? true : false, 1116 // whether it's selected 1117 selectable: params.selectable === undefined ? true : params.selectable ? true : false, 1118 // whether it's selectable 1119 locked: params.locked ? true : false, 1120 // whether the element is locked (cannot be moved) 1121 grabbed: false, 1122 // whether the element is grabbed by the mouse; renderer sets this privately 1123 grabbable: params.grabbable === undefined ? true : params.grabbable ? true : false, 1124 // whether the element can be grabbed 1125 pannable: params.pannable === undefined ? group === 'edges' ? true : false : params.pannable ? true : false, 1126 // whether the element has passthrough panning enabled 1127 active: false, 1128 // whether the element is active from user interaction 1129 classes: new Set$1(), 1130 // map ( className => true ) 1131 animation: { 1132 // object for currently-running animations 1133 current: [], 1134 queue: [] 1135 }, 1136 rscratch: {}, 1137 // object in which the renderer can store information 1138 scratch: params.scratch || {}, 1139 // scratch objects 1140 edges: [], 1141 // array of connected edges 1142 children: [], 1143 // array of children 1144 parent: null, 1145 // parent ref 1146 traversalCache: {}, 1147 // cache of output of traversal functions 1148 backgrounding: false, 1149 // whether background images are loading 1150 bbCache: null, 1151 // cache of the current bounding box 1152 bbCacheShift: { 1153 x: 0, 1154 y: 0 1155 }, 1156 // shift applied to cached bb to be applied on next get 1157 bodyBounds: null, 1158 // bounds cache of element body, w/o overlay 1159 overlayBounds: null, 1160 // bounds cache of element body, including overlay 1161 labelBounds: { 1162 // bounds cache of labels 1163 all: null, 1164 source: null, 1165 target: null, 1166 main: null 1167 }, 1168 arrowBounds: { 1169 // bounds cache of edge arrows 1170 source: null, 1171 target: null, 1172 'mid-source': null, 1173 'mid-target': null 1174 } 1175 }; 1176 1177 if (_p.position.x == null) { 1178 _p.position.x = 0; 1179 } 1180 1181 if (_p.position.y == null) { 1182 _p.position.y = 0; 1183 } // renderedPosition overrides if specified 1184 1185 1186 if (params.renderedPosition) { 1187 var rpos = params.renderedPosition; 1188 var pan = cy.pan(); 1189 var zoom = cy.zoom(); 1190 _p.position = { 1191 x: (rpos.x - pan.x) / zoom, 1192 y: (rpos.y - pan.y) / zoom 1193 }; 1194 } 1195 1196 var classes = []; 1197 1198 if (array(params.classes)) { 1199 classes = params.classes; 1200 } else if (string(params.classes)) { 1201 classes = params.classes.split(/\s+/); 1202 } 1203 1204 for (var i = 0, l = classes.length; i < l; i++) { 1205 var cls = classes[i]; 1206 1207 if (!cls || cls === '') { 1208 continue; 1209 } 1210 1211 _p.classes.add(cls); 1212 } 1213 1214 this.createEmitter(); 1215 var bypass = params.style || params.css; 1216 1217 if (bypass) { 1218 warn('Setting a `style` bypass at element creation is deprecated'); 1219 this.style(bypass); 1220 } 1221 1222 if (restore === undefined || restore) { 1223 this.restore(); 1224 } 1225 }; 1226 1227 var defineSearch = function defineSearch(params) { 1228 params = { 1229 bfs: params.bfs || !params.dfs, 1230 dfs: params.dfs || !params.bfs 1231 }; // from pseudocode on wikipedia 1232 1233 return function searchFn(roots, fn$1, directed) { 1234 var options; 1235 1236 if (plainObject(roots) && !elementOrCollection(roots)) { 1237 options = roots; 1238 roots = options.roots || options.root; 1239 fn$1 = options.visit; 1240 directed = options.directed; 1241 } 1242 1243 directed = arguments.length === 2 && !fn(fn$1) ? fn$1 : directed; 1244 fn$1 = fn(fn$1) ? fn$1 : function () {}; 1245 var cy = this._private.cy; 1246 var v = roots = string(roots) ? this.filter(roots) : roots; 1247 var Q = []; 1248 var connectedNodes = []; 1249 var connectedBy = {}; 1250 var id2depth = {}; 1251 var V = {}; 1252 var j = 0; 1253 var found; 1254 1255 var _this$byGroup = this.byGroup(), 1256 nodes = _this$byGroup.nodes, 1257 edges = _this$byGroup.edges; // enqueue v 1258 1259 1260 for (var i = 0; i < v.length; i++) { 1261 var vi = v[i]; 1262 var viId = vi.id(); 1263 1264 if (vi.isNode()) { 1265 Q.unshift(vi); 1266 1267 if (params.bfs) { 1268 V[viId] = true; 1269 connectedNodes.push(vi); 1270 } 1271 1272 id2depth[viId] = 0; 1273 } 1274 } 1275 1276 var _loop2 = function _loop2() { 1277 var v = params.bfs ? Q.shift() : Q.pop(); 1278 var vId = v.id(); 1279 1280 if (params.dfs) { 1281 if (V[vId]) { 1282 return "continue"; 1283 } 1284 1285 V[vId] = true; 1286 connectedNodes.push(v); 1287 } 1288 1289 var depth = id2depth[vId]; 1290 var prevEdge = connectedBy[vId]; 1291 var src = prevEdge != null ? prevEdge.source() : null; 1292 var tgt = prevEdge != null ? prevEdge.target() : null; 1293 var prevNode = prevEdge == null ? undefined : v.same(src) ? tgt[0] : src[0]; 1294 var ret = void 0; 1295 ret = fn$1(v, prevEdge, prevNode, j++, depth); 1296 1297 if (ret === true) { 1298 found = v; 1299 return "break"; 1300 } 1301 1302 if (ret === false) { 1303 return "break"; 1304 } 1305 1306 var vwEdges = v.connectedEdges().filter(function (e) { 1307 return (!directed || e.source().same(v)) && edges.has(e); 1308 }); 1309 1310 for (var _i2 = 0; _i2 < vwEdges.length; _i2++) { 1311 var e = vwEdges[_i2]; 1312 var w = e.connectedNodes().filter(function (n) { 1313 return !n.same(v) && nodes.has(n); 1314 }); 1315 var wId = w.id(); 1316 1317 if (w.length !== 0 && !V[wId]) { 1318 w = w[0]; 1319 Q.push(w); 1320 1321 if (params.bfs) { 1322 V[wId] = true; 1323 connectedNodes.push(w); 1324 } 1325 1326 connectedBy[wId] = e; 1327 id2depth[wId] = id2depth[vId] + 1; 1328 } 1329 } 1330 }; 1331 1332 _loop: while (Q.length !== 0) { 1333 var _ret = _loop2(); 1334 1335 switch (_ret) { 1336 case "continue": 1337 continue; 1338 1339 case "break": 1340 break _loop; 1341 } 1342 } 1343 1344 var connectedEles = cy.collection(); 1345 1346 for (var _i = 0; _i < connectedNodes.length; _i++) { 1347 var node = connectedNodes[_i]; 1348 var edge = connectedBy[node.id()]; 1349 1350 if (edge != null) { 1351 connectedEles.merge(edge); 1352 } 1353 1354 connectedEles.merge(node); 1355 } 1356 1357 return { 1358 path: cy.collection(connectedEles), 1359 found: cy.collection(found) 1360 }; 1361 }; 1362 }; // search, spanning trees, etc 1363 1364 1365 var elesfn = { 1366 breadthFirstSearch: defineSearch({ 1367 bfs: true 1368 }), 1369 depthFirstSearch: defineSearch({ 1370 dfs: true 1371 }) 1372 }; // nice, short mathemathical alias 1373 1374 elesfn.bfs = elesfn.breadthFirstSearch; 1375 elesfn.dfs = elesfn.depthFirstSearch; 1376 1377 var dijkstraDefaults = defaults({ 1378 root: null, 1379 weight: function weight(edge) { 1380 return 1; 1381 }, 1382 directed: false 1383 }); 1384 var elesfn$1 = { 1385 dijkstra: function dijkstra(options) { 1386 if (!plainObject(options)) { 1387 var args = arguments; 1388 options = { 1389 root: args[0], 1390 weight: args[1], 1391 directed: args[2] 1392 }; 1393 } 1394 1395 var _dijkstraDefaults = dijkstraDefaults(options), 1396 root = _dijkstraDefaults.root, 1397 weight = _dijkstraDefaults.weight, 1398 directed = _dijkstraDefaults.directed; 1399 1400 var eles = this; 1401 var weightFn = weight; 1402 var source = string(root) ? this.filter(root)[0] : root[0]; 1403 var dist = {}; 1404 var prev = {}; 1405 var knownDist = {}; 1406 1407 var _this$byGroup = this.byGroup(), 1408 nodes = _this$byGroup.nodes, 1409 edges = _this$byGroup.edges; 1410 1411 edges.unmergeBy(function (ele) { 1412 return ele.isLoop(); 1413 }); 1414 1415 var getDist = function getDist(node) { 1416 return dist[node.id()]; 1417 }; 1418 1419 var setDist = function setDist(node, d) { 1420 dist[node.id()] = d; 1421 Q.updateItem(node); 1422 }; 1423 1424 var Q = new Heap(function (a, b) { 1425 return getDist(a) - getDist(b); 1426 }); 1427 1428 for (var i = 0; i < nodes.length; i++) { 1429 var node = nodes[i]; 1430 dist[node.id()] = node.same(source) ? 0 : Infinity; 1431 Q.push(node); 1432 } 1433 1434 var distBetween = function distBetween(u, v) { 1435 var uvs = (directed ? u.edgesTo(v) : u.edgesWith(v)).intersect(edges); 1436 var smallestDistance = Infinity; 1437 var smallestEdge; 1438 1439 for (var _i = 0; _i < uvs.length; _i++) { 1440 var edge = uvs[_i]; 1441 1442 var _weight = weightFn(edge); 1443 1444 if (_weight < smallestDistance || !smallestEdge) { 1445 smallestDistance = _weight; 1446 smallestEdge = edge; 1447 } 1448 } 1449 1450 return { 1451 edge: smallestEdge, 1452 dist: smallestDistance 1453 }; 1454 }; 1455 1456 while (Q.size() > 0) { 1457 var u = Q.pop(); 1458 var smalletsDist = getDist(u); 1459 var uid = u.id(); 1460 knownDist[uid] = smalletsDist; 1461 1462 if (smalletsDist === Infinity) { 1463 continue; 1464 } 1465 1466 var neighbors = u.neighborhood().intersect(nodes); 1467 1468 for (var _i2 = 0; _i2 < neighbors.length; _i2++) { 1469 var v = neighbors[_i2]; 1470 var vid = v.id(); 1471 var vDist = distBetween(u, v); 1472 var alt = smalletsDist + vDist.dist; 1473 1474 if (alt < getDist(v)) { 1475 setDist(v, alt); 1476 prev[vid] = { 1477 node: u, 1478 edge: vDist.edge 1479 }; 1480 } 1481 } // for 1482 1483 } // while 1484 1485 1486 return { 1487 distanceTo: function distanceTo(node) { 1488 var target = string(node) ? nodes.filter(node)[0] : node[0]; 1489 return knownDist[target.id()]; 1490 }, 1491 pathTo: function pathTo(node) { 1492 var target = string(node) ? nodes.filter(node)[0] : node[0]; 1493 var S = []; 1494 var u = target; 1495 var uid = u.id(); 1496 1497 if (target.length > 0) { 1498 S.unshift(target); 1499 1500 while (prev[uid]) { 1501 var p = prev[uid]; 1502 S.unshift(p.edge); 1503 S.unshift(p.node); 1504 u = p.node; 1505 uid = u.id(); 1506 } 1507 } 1508 1509 return eles.spawn(S); 1510 } 1511 }; 1512 } 1513 }; 1514 1515 var elesfn$2 = { 1516 // kruskal's algorithm (finds min spanning tree, assuming undirected graph) 1517 // implemented from pseudocode from wikipedia 1518 kruskal: function kruskal(weightFn) { 1519 weightFn = weightFn || function (edge) { 1520 return 1; 1521 }; 1522 1523 var _this$byGroup = this.byGroup(), 1524 nodes = _this$byGroup.nodes, 1525 edges = _this$byGroup.edges; 1526 1527 var numNodes = nodes.length; 1528 var forest = new Array(numNodes); 1529 var A = nodes; // assumes byGroup() creates new collections that can be safely mutated 1530 1531 var findSetIndex = function findSetIndex(ele) { 1532 for (var i = 0; i < forest.length; i++) { 1533 var eles = forest[i]; 1534 1535 if (eles.has(ele)) { 1536 return i; 1537 } 1538 } 1539 }; // start with one forest per node 1540 1541 1542 for (var i = 0; i < numNodes; i++) { 1543 forest[i] = this.spawn(nodes[i]); 1544 } 1545 1546 var S = edges.sort(function (a, b) { 1547 return weightFn(a) - weightFn(b); 1548 }); 1549 1550 for (var _i = 0; _i < S.length; _i++) { 1551 var edge = S[_i]; 1552 var u = edge.source()[0]; 1553 var v = edge.target()[0]; 1554 var setUIndex = findSetIndex(u); 1555 var setVIndex = findSetIndex(v); 1556 var setU = forest[setUIndex]; 1557 var setV = forest[setVIndex]; 1558 1559 if (setUIndex !== setVIndex) { 1560 A.merge(edge); // combine forests for u and v 1561 1562 setU.merge(setV); 1563 forest.splice(setVIndex, 1); 1564 } 1565 } 1566 1567 return A; 1568 } 1569 }; 1570 1571 var aStarDefaults = defaults({ 1572 root: null, 1573 goal: null, 1574 weight: function weight(edge) { 1575 return 1; 1576 }, 1577 heuristic: function heuristic(edge) { 1578 return 0; 1579 }, 1580 directed: false 1581 }); 1582 var elesfn$3 = { 1583 // Implemented from pseudocode from wikipedia 1584 aStar: function aStar(options) { 1585 var cy = this.cy(); 1586 1587 var _aStarDefaults = aStarDefaults(options), 1588 root = _aStarDefaults.root, 1589 goal = _aStarDefaults.goal, 1590 heuristic = _aStarDefaults.heuristic, 1591 directed = _aStarDefaults.directed, 1592 weight = _aStarDefaults.weight; 1593 1594 root = cy.collection(root)[0]; 1595 goal = cy.collection(goal)[0]; 1596 var sid = root.id(); 1597 var tid = goal.id(); 1598 var gScore = {}; 1599 var fScore = {}; 1600 var closedSetIds = {}; 1601 var openSet = new Heap(function (a, b) { 1602 return fScore[a.id()] - fScore[b.id()]; 1603 }); 1604 var openSetIds = new Set$1(); 1605 var cameFrom = {}; 1606 var cameFromEdge = {}; 1607 1608 var addToOpenSet = function addToOpenSet(ele, id) { 1609 openSet.push(ele); 1610 openSetIds.add(id); 1611 }; 1612 1613 var cMin, cMinId; 1614 1615 var popFromOpenSet = function popFromOpenSet() { 1616 cMin = openSet.pop(); 1617 cMinId = cMin.id(); 1618 openSetIds["delete"](cMinId); 1619 }; 1620 1621 var isInOpenSet = function isInOpenSet(id) { 1622 return openSetIds.has(id); 1623 }; 1624 1625 addToOpenSet(root, sid); 1626 gScore[sid] = 0; 1627 fScore[sid] = heuristic(root); // Counter 1628 1629 var steps = 0; // Main loop 1630 1631 while (openSet.size() > 0) { 1632 popFromOpenSet(); 1633 steps++; // If we've found our goal, then we are done 1634 1635 if (cMinId === tid) { 1636 var path = []; 1637 var pathNode = goal; 1638 var pathNodeId = tid; 1639 var pathEdge = cameFromEdge[pathNodeId]; 1640 1641 for (;;) { 1642 path.unshift(pathNode); 1643 1644 if (pathEdge != null) { 1645 path.unshift(pathEdge); 1646 } 1647 1648 pathNode = cameFrom[pathNodeId]; 1649 1650 if (pathNode == null) { 1651 break; 1652 } 1653 1654 pathNodeId = pathNode.id(); 1655 pathEdge = cameFromEdge[pathNodeId]; 1656 } 1657 1658 return { 1659 found: true, 1660 distance: gScore[cMinId], 1661 path: this.spawn(path), 1662 steps: steps 1663 }; 1664 } // Add cMin to processed nodes 1665 1666 1667 closedSetIds[cMinId] = true; // Update scores for neighbors of cMin 1668 // Take into account if graph is directed or not 1669 1670 var vwEdges = cMin._private.edges; 1671 1672 for (var i = 0; i < vwEdges.length; i++) { 1673 var e = vwEdges[i]; // edge must be in set of calling eles 1674 1675 if (!this.hasElementWithId(e.id())) { 1676 continue; 1677 } // cMin must be the source of edge if directed 1678 1679 1680 if (directed && e.data('source') !== cMinId) { 1681 continue; 1682 } 1683 1684 var wSrc = e.source(); 1685 var wTgt = e.target(); 1686 var w = wSrc.id() !== cMinId ? wSrc : wTgt; 1687 var wid = w.id(); // node must be in set of calling eles 1688 1689 if (!this.hasElementWithId(wid)) { 1690 continue; 1691 } // if node is in closedSet, ignore it 1692 1693 1694 if (closedSetIds[wid]) { 1695 continue; 1696 } // New tentative score for node w 1697 1698 1699 var tempScore = gScore[cMinId] + weight(e); // Update gScore for node w if: 1700 // w not present in openSet 1701 // OR 1702 // tentative gScore is less than previous value 1703 // w not in openSet 1704 1705 if (!isInOpenSet(wid)) { 1706 gScore[wid] = tempScore; 1707 fScore[wid] = tempScore + heuristic(w); 1708 addToOpenSet(w, wid); 1709 cameFrom[wid] = cMin; 1710 cameFromEdge[wid] = e; 1711 continue; 1712 } // w already in openSet, but with greater gScore 1713 1714 1715 if (tempScore < gScore[wid]) { 1716 gScore[wid] = tempScore; 1717 fScore[wid] = tempScore + heuristic(w); 1718 cameFrom[wid] = cMin; 1719 } 1720 } // End of neighbors update 1721 1722 } // End of main loop 1723 // If we've reached here, then we've not reached our goal 1724 1725 1726 return { 1727 found: false, 1728 distance: undefined, 1729 path: undefined, 1730 steps: steps 1731 }; 1732 } 1733 }; // elesfn 1734 1735 var floydWarshallDefaults = defaults({ 1736 weight: function weight(edge) { 1737 return 1; 1738 }, 1739 directed: false 1740 }); 1741 var elesfn$4 = { 1742 // Implemented from pseudocode from wikipedia 1743 floydWarshall: function floydWarshall(options) { 1744 var cy = this.cy(); 1745 1746 var _floydWarshallDefault = floydWarshallDefaults(options), 1747 weight = _floydWarshallDefault.weight, 1748 directed = _floydWarshallDefault.directed; 1749 1750 var weightFn = weight; 1751 1752 var _this$byGroup = this.byGroup(), 1753 nodes = _this$byGroup.nodes, 1754 edges = _this$byGroup.edges; 1755 1756 var N = nodes.length; 1757 var Nsq = N * N; 1758 1759 var indexOf = function indexOf(node) { 1760 return nodes.indexOf(node); 1761 }; 1762 1763 var atIndex = function atIndex(i) { 1764 return nodes[i]; 1765 }; // Initialize distance matrix 1766 1767 1768 var dist = new Array(Nsq); 1769 1770 for (var n = 0; n < Nsq; n++) { 1771 var j = n % N; 1772 var i = (n - j) / N; 1773 1774 if (i === j) { 1775 dist[n] = 0; 1776 } else { 1777 dist[n] = Infinity; 1778 } 1779 } // Initialize matrix used for path reconstruction 1780 // Initialize distance matrix 1781 1782 1783 var next = new Array(Nsq); 1784 var edgeNext = new Array(Nsq); // Process edges 1785 1786 for (var _i = 0; _i < edges.length; _i++) { 1787 var edge = edges[_i]; 1788 var src = edge.source()[0]; 1789 var tgt = edge.target()[0]; 1790 1791 if (src === tgt) { 1792 continue; 1793 } // exclude loops 1794 1795 1796 var s = indexOf(src); 1797 var t = indexOf(tgt); 1798 var st = s * N + t; // source to target index 1799 1800 var _weight = weightFn(edge); // Check if already process another edge between same 2 nodes 1801 1802 1803 if (dist[st] > _weight) { 1804 dist[st] = _weight; 1805 next[st] = t; 1806 edgeNext[st] = edge; 1807 } // If undirected graph, process 'reversed' edge 1808 1809 1810 if (!directed) { 1811 var ts = t * N + s; // target to source index 1812 1813 if (!directed && dist[ts] > _weight) { 1814 dist[ts] = _weight; 1815 next[ts] = s; 1816 edgeNext[ts] = edge; 1817 } 1818 } 1819 } // Main loop 1820 1821 1822 for (var k = 0; k < N; k++) { 1823 for (var _i2 = 0; _i2 < N; _i2++) { 1824 var ik = _i2 * N + k; 1825 1826 for (var _j = 0; _j < N; _j++) { 1827 var ij = _i2 * N + _j; 1828 var kj = k * N + _j; 1829 1830 if (dist[ik] + dist[kj] < dist[ij]) { 1831 dist[ij] = dist[ik] + dist[kj]; 1832 next[ij] = next[ik]; 1833 } 1834 } 1835 } 1836 } 1837 1838 var getArgEle = function getArgEle(ele) { 1839 return (string(ele) ? cy.filter(ele) : ele)[0]; 1840 }; 1841 1842 var indexOfArgEle = function indexOfArgEle(ele) { 1843 return indexOf(getArgEle(ele)); 1844 }; 1845 1846 var res = { 1847 distance: function distance(from, to) { 1848 var i = indexOfArgEle(from); 1849 var j = indexOfArgEle(to); 1850 return dist[i * N + j]; 1851 }, 1852 path: function path(from, to) { 1853 var i = indexOfArgEle(from); 1854 var j = indexOfArgEle(to); 1855 var fromNode = atIndex(i); 1856 1857 if (i === j) { 1858 return fromNode.collection(); 1859 } 1860 1861 if (next[i * N + j] == null) { 1862 return cy.collection(); 1863 } 1864 1865 var path = cy.collection(); 1866 var prev = i; 1867 var edge; 1868 path.merge(fromNode); 1869 1870 while (i !== j) { 1871 prev = i; 1872 i = next[i * N + j]; 1873 edge = edgeNext[prev * N + i]; 1874 path.merge(edge); 1875 path.merge(atIndex(i)); 1876 } 1877 1878 return path; 1879 } 1880 }; 1881 return res; 1882 } // floydWarshall 1883 1884 }; // elesfn 1885 1886 var bellmanFordDefaults = defaults({ 1887 weight: function weight(edge) { 1888 return 1; 1889 }, 1890 directed: false, 1891 root: null 1892 }); 1893 var elesfn$5 = { 1894 // Implemented from pseudocode from wikipedia 1895 bellmanFord: function bellmanFord(options) { 1896 var _this = this; 1897 1898 var _bellmanFordDefaults = bellmanFordDefaults(options), 1899 weight = _bellmanFordDefaults.weight, 1900 directed = _bellmanFordDefaults.directed, 1901 root = _bellmanFordDefaults.root; 1902 1903 var weightFn = weight; 1904 var eles = this; 1905 var cy = this.cy(); 1906 1907 var _this$byGroup = this.byGroup(), 1908 edges = _this$byGroup.edges, 1909 nodes = _this$byGroup.nodes; 1910 1911 var numNodes = nodes.length; 1912 var infoMap = new Map$1(); 1913 var hasNegativeWeightCycle = false; 1914 var negativeWeightCycles = []; 1915 root = cy.collection(root)[0]; // in case selector passed 1916 1917 edges.unmergeBy(function (edge) { 1918 return edge.isLoop(); 1919 }); 1920 var numEdges = edges.length; 1921 1922 var getInfo = function getInfo(node) { 1923 var obj = infoMap.get(node.id()); 1924 1925 if (!obj) { 1926 obj = {}; 1927 infoMap.set(node.id(), obj); 1928 } 1929 1930 return obj; 1931 }; 1932 1933 var getNodeFromTo = function getNodeFromTo(to) { 1934 return (string(to) ? cy.$(to) : to)[0]; 1935 }; 1936 1937 var distanceTo = function distanceTo(to) { 1938 return getInfo(getNodeFromTo(to)).dist; 1939 }; 1940 1941 var pathTo = function pathTo(to) { 1942 var thisStart = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : root; 1943 var end = getNodeFromTo(to); 1944 var path = []; 1945 var node = end; 1946 1947 for (;;) { 1948 if (node == null) { 1949 return _this.spawn(); 1950 } 1951 1952 var _getInfo = getInfo(node), 1953 edge = _getInfo.edge, 1954 pred = _getInfo.pred; 1955 1956 path.unshift(node[0]); 1957 1958 if (node.same(thisStart) && path.length > 0) { 1959 break; 1960 } 1961 1962 if (edge != null) { 1963 path.unshift(edge); 1964 } 1965 1966 node = pred; 1967 } 1968 1969 return eles.spawn(path); 1970 }; // Initializations { dist, pred, edge } 1971 1972 1973 for (var i = 0; i < numNodes; i++) { 1974 var node = nodes[i]; 1975 var info = getInfo(node); 1976 1977 if (node.same(root)) { 1978 info.dist = 0; 1979 } else { 1980 info.dist = Infinity; 1981 } 1982 1983 info.pred = null; 1984 info.edge = null; 1985 } // Edges relaxation 1986 1987 1988 var replacedEdge = false; 1989 1990 var checkForEdgeReplacement = function checkForEdgeReplacement(node1, node2, edge, info1, info2, weight) { 1991 var dist = info1.dist + weight; 1992 1993 if (dist < info2.dist && !edge.same(info1.edge)) { 1994 info2.dist = dist; 1995 info2.pred = node1; 1996 info2.edge = edge; 1997 replacedEdge = true; 1998 } 1999 }; 2000 2001 for (var _i = 1; _i < numNodes; _i++) { 2002 replacedEdge = false; 2003 2004 for (var e = 0; e < numEdges; e++) { 2005 var edge = edges[e]; 2006 var src = edge.source(); 2007 var tgt = edge.target(); 2008 2009 var _weight = weightFn(edge); 2010 2011 var srcInfo = getInfo(src); 2012 var tgtInfo = getInfo(tgt); 2013 checkForEdgeReplacement(src, tgt, edge, srcInfo, tgtInfo, _weight); // If undirected graph, we need to take into account the 'reverse' edge 2014 2015 if (!directed) { 2016 checkForEdgeReplacement(tgt, src, edge, tgtInfo, srcInfo, _weight); 2017 } 2018 } 2019 2020 if (!replacedEdge) { 2021 break; 2022 } 2023 } 2024 2025 if (replacedEdge) { 2026 // Check for negative weight cycles 2027 for (var _e = 0; _e < numEdges; _e++) { 2028 var _edge = edges[_e]; 2029 2030 var _src = _edge.source(); 2031 2032 var _tgt = _edge.target(); 2033 2034 var _weight2 = weightFn(_edge); 2035 2036 var srcDist = getInfo(_src).dist; 2037 var tgtDist = getInfo(_tgt).dist; 2038 2039 if (srcDist + _weight2 < tgtDist || !directed && tgtDist + _weight2 < srcDist) { 2040 warn('Graph contains a negative weight cycle for Bellman-Ford'); 2041 hasNegativeWeightCycle = true; 2042 break; 2043 } 2044 } 2045 } 2046 2047 return { 2048 distanceTo: distanceTo, 2049 pathTo: pathTo, 2050 hasNegativeWeightCycle: hasNegativeWeightCycle, 2051 negativeWeightCycles: negativeWeightCycles 2052 }; 2053 } // bellmanFord 2054 2055 }; // elesfn 2056 2057 var sqrt2 = Math.sqrt(2); // Function which colapses 2 (meta) nodes into one 2058 // Updates the remaining edge lists 2059 // Receives as a paramater the edge which causes the collapse 2060 2061 var collapse = function collapse(edgeIndex, nodeMap, remainingEdges) { 2062 if (remainingEdges.length === 0) { 2063 error("Karger-Stein must be run on a connected (sub)graph"); 2064 } 2065 2066 var edgeInfo = remainingEdges[edgeIndex]; 2067 var sourceIn = edgeInfo[1]; 2068 var targetIn = edgeInfo[2]; 2069 var partition1 = nodeMap[sourceIn]; 2070 var partition2 = nodeMap[targetIn]; 2071 var newEdges = remainingEdges; // re-use array 2072 // Delete all edges between partition1 and partition2 2073 2074 for (var i = newEdges.length - 1; i >= 0; i--) { 2075 var edge = newEdges[i]; 2076 var src = edge[1]; 2077 var tgt = edge[2]; 2078 2079 if (nodeMap[src] === partition1 && nodeMap[tgt] === partition2 || nodeMap[src] === partition2 && nodeMap[tgt] === partition1) { 2080 newEdges.splice(i, 1); 2081 } 2082 } // All edges pointing to partition2 should now point to partition1 2083 2084 2085 for (var _i = 0; _i < newEdges.length; _i++) { 2086 var _edge = newEdges[_i]; 2087 2088 if (_edge[1] === partition2) { 2089 // Check source 2090 newEdges[_i] = _edge.slice(); // copy 2091 2092 newEdges[_i][1] = partition1; 2093 } else if (_edge[2] === partition2) { 2094 // Check target 2095 newEdges[_i] = _edge.slice(); // copy 2096 2097 newEdges[_i][2] = partition1; 2098 } 2099 } // Move all nodes from partition2 to partition1 2100 2101 2102 for (var _i2 = 0; _i2 < nodeMap.length; _i2++) { 2103 if (nodeMap[_i2] === partition2) { 2104 nodeMap[_i2] = partition1; 2105 } 2106 } 2107 2108 return newEdges; 2109 }; // Contracts a graph until we reach a certain number of meta nodes 2110 2111 2112 var contractUntil = function contractUntil(metaNodeMap, remainingEdges, size, sizeLimit) { 2113 while (size > sizeLimit) { 2114 // Choose an edge randomly 2115 var edgeIndex = Math.floor(Math.random() * remainingEdges.length); // Collapse graph based on edge 2116 2117 remainingEdges = collapse(edgeIndex, metaNodeMap, remainingEdges); 2118 size--; 2119 } 2120 2121 return remainingEdges; 2122 }; 2123 2124 var elesfn$6 = { 2125 // Computes the minimum cut of an undirected graph 2126 // Returns the correct answer with high probability 2127 kargerStein: function kargerStein() { 2128 var _this = this; 2129 2130 var _this$byGroup = this.byGroup(), 2131 nodes = _this$byGroup.nodes, 2132 edges = _this$byGroup.edges; 2133 2134 edges.unmergeBy(function (edge) { 2135 return edge.isLoop(); 2136 }); 2137 var numNodes = nodes.length; 2138 var numEdges = edges.length; 2139 var numIter = Math.ceil(Math.pow(Math.log(numNodes) / Math.LN2, 2)); 2140 var stopSize = Math.floor(numNodes / sqrt2); 2141 2142 if (numNodes < 2) { 2143 error('At least 2 nodes are required for Karger-Stein algorithm'); 2144 return undefined; 2145 } // Now store edge destination as indexes 2146 // Format for each edge (edge index, source node index, target node index) 2147 2148 2149 var edgeIndexes = []; 2150 2151 for (var i = 0; i < numEdges; i++) { 2152 var e = edges[i]; 2153 edgeIndexes.push([i, nodes.indexOf(e.source()), nodes.indexOf(e.target())]); 2154 } // We will store the best cut found here 2155 2156 2157 var minCutSize = Infinity; 2158 var minCutEdgeIndexes = []; 2159 var minCutNodeMap = new Array(numNodes); // Initial meta node partition 2160 2161 var metaNodeMap = new Array(numNodes); 2162 var metaNodeMap2 = new Array(numNodes); 2163 2164 var copyNodesMap = function copyNodesMap(from, to) { 2165 for (var _i3 = 0; _i3 < numNodes; _i3++) { 2166 to[_i3] = from[_i3]; 2167 } 2168 }; // Main loop 2169 2170 2171 for (var iter = 0; iter <= numIter; iter++) { 2172 // Reset meta node partition 2173 for (var _i4 = 0; _i4 < numNodes; _i4++) { 2174 metaNodeMap[_i4] = _i4; 2175 } // Contract until stop point (stopSize nodes) 2176 2177 2178 var edgesState = contractUntil(metaNodeMap, edgeIndexes.slice(), numNodes, stopSize); 2179 var edgesState2 = edgesState.slice(); // copy 2180 // Create a copy of the colapsed nodes state 2181 2182 copyNodesMap(metaNodeMap, metaNodeMap2); // Run 2 iterations starting in the stop state 2183 2184 var res1 = contractUntil(metaNodeMap, edgesState, stopSize, 2); 2185 var res2 = contractUntil(metaNodeMap2, edgesState2, stopSize, 2); // Is any of the 2 results the best cut so far? 2186 2187 if (res1.length <= res2.length && res1.length < minCutSize) { 2188 minCutSize = res1.length; 2189 minCutEdgeIndexes = res1; 2190 copyNodesMap(metaNodeMap, minCutNodeMap); 2191 } else if (res2.length <= res1.length && res2.length < minCutSize) { 2192 minCutSize = res2.length; 2193 minCutEdgeIndexes = res2; 2194 copyNodesMap(metaNodeMap2, minCutNodeMap); 2195 } 2196 } // end of main loop 2197 // Construct result 2198 2199 2200 var cut = this.spawn(minCutEdgeIndexes.map(function (e) { 2201 return edges[e[0]]; 2202 })); 2203 var partition1 = this.spawn(); 2204 var partition2 = this.spawn(); // traverse metaNodeMap for best cut 2205 2206 var witnessNodePartition = minCutNodeMap[0]; 2207 2208 for (var _i5 = 0; _i5 < minCutNodeMap.length; _i5++) { 2209 var partitionId = minCutNodeMap[_i5]; 2210 var node = nodes[_i5]; 2211 2212 if (partitionId === witnessNodePartition) { 2213 partition1.merge(node); 2214 } else { 2215 partition2.merge(node); 2216 } 2217 } // construct components corresponding to each disjoint subset of nodes 2218 2219 2220 var constructComponent = function constructComponent(subset) { 2221 var component = _this.spawn(); 2222 2223 subset.forEach(function (node) { 2224 component.merge(node); 2225 node.connectedEdges().forEach(function (edge) { 2226 // ensure edge is within calling collection and edge is not in cut 2227 if (_this.contains(edge) && !cut.contains(edge)) { 2228 component.merge(edge); 2229 } 2230 }); 2231 }); 2232 return component; 2233 }; 2234 2235 var components = [constructComponent(partition1), constructComponent(partition2)]; 2236 var ret = { 2237 cut: cut, 2238 components: components, 2239 // n.b. partitions are included to be compatible with the old api spec 2240 // (could be removed in a future major version) 2241 partition1: partition1, 2242 partition2: partition2 2243 }; 2244 return ret; 2245 } 2246 }; // elesfn 2247 2248 var copyPosition = function copyPosition(p) { 2249 return { 2250 x: p.x, 2251 y: p.y 2252 }; 2253 }; 2254 var modelToRenderedPosition = function modelToRenderedPosition(p, zoom, pan) { 2255 return { 2256 x: p.x * zoom + pan.x, 2257 y: p.y * zoom + pan.y 2258 }; 2259 }; 2260 var renderedToModelPosition = function renderedToModelPosition(p, zoom, pan) { 2261 return { 2262 x: (p.x - pan.x) / zoom, 2263 y: (p.y - pan.y) / zoom 2264 }; 2265 }; 2266 var array2point = function array2point(arr) { 2267 return { 2268 x: arr[0], 2269 y: arr[1] 2270 }; 2271 }; 2272 var min = function min(arr) { 2273 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2274 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2275 var min = Infinity; 2276 2277 for (var i = begin; i < end; i++) { 2278 var val = arr[i]; 2279 2280 if (isFinite(val)) { 2281 min = Math.min(val, min); 2282 } 2283 } 2284 2285 return min; 2286 }; 2287 var max = function max(arr) { 2288 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2289 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2290 var max = -Infinity; 2291 2292 for (var i = begin; i < end; i++) { 2293 var val = arr[i]; 2294 2295 if (isFinite(val)) { 2296 max = Math.max(val, max); 2297 } 2298 } 2299 2300 return max; 2301 }; 2302 var mean = function mean(arr) { 2303 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2304 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2305 var total = 0; 2306 var n = 0; 2307 2308 for (var i = begin; i < end; i++) { 2309 var val = arr[i]; 2310 2311 if (isFinite(val)) { 2312 total += val; 2313 n++; 2314 } 2315 } 2316 2317 return total / n; 2318 }; 2319 var median = function median(arr) { 2320 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2321 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 2322 var copy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 2323 var sort = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 2324 var includeHoles = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 2325 2326 if (copy) { 2327 arr = arr.slice(begin, end); 2328 } else { 2329 if (end < arr.length) { 2330 arr.splice(end, arr.length - end); 2331 } 2332 2333 if (begin > 0) { 2334 arr.splice(0, begin); 2335 } 2336 } // all non finite (e.g. Infinity, NaN) elements must be -Infinity so they go to the start 2337 2338 2339 var off = 0; // offset from non-finite values 2340 2341 for (var i = arr.length - 1; i >= 0; i--) { 2342 var v = arr[i]; 2343 2344 if (includeHoles) { 2345 if (!isFinite(v)) { 2346 arr[i] = -Infinity; 2347 off++; 2348 } 2349 } else { 2350 // just remove it if we don't want to consider holes 2351 arr.splice(i, 1); 2352 } 2353 } 2354 2355 if (sort) { 2356 arr.sort(function (a, b) { 2357 return a - b; 2358 }); // requires copy = true if you don't want to change the orig 2359 } 2360 2361 var len = arr.length; 2362 var mid = Math.floor(len / 2); 2363 2364 if (len % 2 !== 0) { 2365 return arr[mid + 1 + off]; 2366 } else { 2367 return (arr[mid - 1 + off] + arr[mid + off]) / 2; 2368 } 2369 }; 2370 var deg2rad = function deg2rad(deg) { 2371 return Math.PI * deg / 180; 2372 }; 2373 var getAngleFromDisp = function getAngleFromDisp(dispX, dispY) { 2374 return Math.atan2(dispY, dispX) - Math.PI / 2; 2375 }; 2376 var log2 = Math.log2 || function (n) { 2377 return Math.log(n) / Math.log(2); 2378 }; 2379 var signum = function signum(x) { 2380 if (x > 0) { 2381 return 1; 2382 } else if (x < 0) { 2383 return -1; 2384 } else { 2385 return 0; 2386 } 2387 }; 2388 var dist = function dist(p1, p2) { 2389 return Math.sqrt(sqdist(p1, p2)); 2390 }; 2391 var sqdist = function sqdist(p1, p2) { 2392 var dx = p2.x - p1.x; 2393 var dy = p2.y - p1.y; 2394 return dx * dx + dy * dy; 2395 }; 2396 var inPlaceSumNormalize = function inPlaceSumNormalize(v) { 2397 var length = v.length; // First, get sum of all elements 2398 2399 var total = 0; 2400 2401 for (var i = 0; i < length; i++) { 2402 total += v[i]; 2403 } // Now, divide each by the sum of all elements 2404 2405 2406 for (var _i = 0; _i < length; _i++) { 2407 v[_i] = v[_i] / total; 2408 } 2409 2410 return v; 2411 }; 2412 2413 var qbezierAt = function qbezierAt(p0, p1, p2, t) { 2414 return (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2; 2415 }; 2416 var qbezierPtAt = function qbezierPtAt(p0, p1, p2, t) { 2417 return { 2418 x: qbezierAt(p0.x, p1.x, p2.x, t), 2419 y: qbezierAt(p0.y, p1.y, p2.y, t) 2420 }; 2421 }; 2422 var lineAt = function lineAt(p0, p1, t, d) { 2423 var vec = { 2424 x: p1.x - p0.x, 2425 y: p1.y - p0.y 2426 }; 2427 var vecDist = dist(p0, p1); 2428 var normVec = { 2429 x: vec.x / vecDist, 2430 y: vec.y / vecDist 2431 }; 2432 t = t == null ? 0 : t; 2433 d = d != null ? d : t * vecDist; 2434 return { 2435 x: p0.x + normVec.x * d, 2436 y: p0.y + normVec.y * d 2437 }; 2438 }; 2439 var bound = function bound(min, val, max) { 2440 return Math.max(min, Math.min(max, val)); 2441 }; // makes a full bb (x1, y1, x2, y2, w, h) from implicit params 2442 2443 var makeBoundingBox = function makeBoundingBox(bb) { 2444 if (bb == null) { 2445 return { 2446 x1: Infinity, 2447 y1: Infinity, 2448 x2: -Infinity, 2449 y2: -Infinity, 2450 w: 0, 2451 h: 0 2452 }; 2453 } else if (bb.x1 != null && bb.y1 != null) { 2454 if (bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1) { 2455 return { 2456 x1: bb.x1, 2457 y1: bb.y1, 2458 x2: bb.x2, 2459 y2: bb.y2, 2460 w: bb.x2 - bb.x1, 2461 h: bb.y2 - bb.y1 2462 }; 2463 } else if (bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0) { 2464 return { 2465 x1: bb.x1, 2466 y1: bb.y1, 2467 x2: bb.x1 + bb.w, 2468 y2: bb.y1 + bb.h, 2469 w: bb.w, 2470 h: bb.h 2471 }; 2472 } 2473 } 2474 }; 2475 var copyBoundingBox = function copyBoundingBox(bb) { 2476 return { 2477 x1: bb.x1, 2478 x2: bb.x2, 2479 w: bb.w, 2480 y1: bb.y1, 2481 y2: bb.y2, 2482 h: bb.h 2483 }; 2484 }; 2485 var clearBoundingBox = function clearBoundingBox(bb) { 2486 bb.x1 = Infinity; 2487 bb.y1 = Infinity; 2488 bb.x2 = -Infinity; 2489 bb.y2 = -Infinity; 2490 bb.w = 0; 2491 bb.h = 0; 2492 }; 2493 var updateBoundingBox = function updateBoundingBox(bb1, bb2) { 2494 // update bb1 with bb2 bounds 2495 bb1.x1 = Math.min(bb1.x1, bb2.x1); 2496 bb1.x2 = Math.max(bb1.x2, bb2.x2); 2497 bb1.w = bb1.x2 - bb1.x1; 2498 bb1.y1 = Math.min(bb1.y1, bb2.y1); 2499 bb1.y2 = Math.max(bb1.y2, bb2.y2); 2500 bb1.h = bb1.y2 - bb1.y1; 2501 }; 2502 var expandBoundingBoxByPoint = function expandBoundingBoxByPoint(bb, x, y) { 2503 bb.x1 = Math.min(bb.x1, x); 2504 bb.x2 = Math.max(bb.x2, x); 2505 bb.w = bb.x2 - bb.x1; 2506 bb.y1 = Math.min(bb.y1, y); 2507 bb.y2 = Math.max(bb.y2, y); 2508 bb.h = bb.y2 - bb.y1; 2509 }; 2510 var expandBoundingBox = function expandBoundingBox(bb) { 2511 var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 2512 bb.x1 -= padding; 2513 bb.x2 += padding; 2514 bb.y1 -= padding; 2515 bb.y2 += padding; 2516 bb.w = bb.x2 - bb.x1; 2517 bb.h = bb.y2 - bb.y1; 2518 return bb; 2519 }; 2520 var expandBoundingBoxSides = function expandBoundingBoxSides(bb) { 2521 var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [0]; 2522 var top, right, bottom, left; 2523 2524 if (padding.length === 1) { 2525 top = right = bottom = left = padding[0]; 2526 } else if (padding.length === 2) { 2527 top = bottom = padding[0]; 2528 left = right = padding[1]; 2529 } else if (padding.length === 4) { 2530 var _padding = _slicedToArray(padding, 4); 2531 2532 top = _padding[0]; 2533 right = _padding[1]; 2534 bottom = _padding[2]; 2535 left = _padding[3]; 2536 } 2537 2538 bb.x1 -= left; 2539 bb.x2 += right; 2540 bb.y1 -= top; 2541 bb.y2 += bottom; 2542 bb.w = bb.x2 - bb.x1; 2543 bb.h = bb.y2 - bb.y1; 2544 return bb; 2545 }; 2546 2547 var assignBoundingBox = function assignBoundingBox(bb1, bb2) { 2548 bb1.x1 = bb2.x1; 2549 bb1.y1 = bb2.y1; 2550 bb1.x2 = bb2.x2; 2551 bb1.y2 = bb2.y2; 2552 bb1.w = bb1.x2 - bb1.x1; 2553 bb1.h = bb1.y2 - bb1.y1; 2554 }; 2555 var assignShiftToBoundingBox = function assignShiftToBoundingBox(bb, delta) { 2556 bb.x1 += delta.x; 2557 bb.x2 += delta.x; 2558 bb.y1 += delta.y; 2559 bb.y2 += delta.y; 2560 }; 2561 var boundingBoxesIntersect = function boundingBoxesIntersect(bb1, bb2) { 2562 // case: one bb to right of other 2563 if (bb1.x1 > bb2.x2) { 2564 return false; 2565 } 2566 2567 if (bb2.x1 > bb1.x2) { 2568 return false; 2569 } // case: one bb to left of other 2570 2571 2572 if (bb1.x2 < bb2.x1) { 2573 return false; 2574 } 2575 2576 if (bb2.x2 < bb1.x1) { 2577 return false; 2578 } // case: one bb above other 2579 2580 2581 if (bb1.y2 < bb2.y1) { 2582 return false; 2583 } 2584 2585 if (bb2.y2 < bb1.y1) { 2586 return false; 2587 } // case: one bb below other 2588 2589 2590 if (bb1.y1 > bb2.y2) { 2591 return false; 2592 } 2593 2594 if (bb2.y1 > bb1.y2) { 2595 return false; 2596 } // otherwise, must have some overlap 2597 2598 2599 return true; 2600 }; 2601 var inBoundingBox = function inBoundingBox(bb, x, y) { 2602 return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2; 2603 }; 2604 var pointInBoundingBox = function pointInBoundingBox(bb, pt) { 2605 return inBoundingBox(bb, pt.x, pt.y); 2606 }; 2607 var boundingBoxInBoundingBox = function boundingBoxInBoundingBox(bb1, bb2) { 2608 return inBoundingBox(bb1, bb2.x1, bb2.y1) && inBoundingBox(bb1, bb2.x2, bb2.y2); 2609 }; 2610 var roundRectangleIntersectLine = function roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding) { 2611 var cornerRadius = getRoundRectangleRadius(width, height); 2612 var halfWidth = width / 2; 2613 var halfHeight = height / 2; // Check intersections with straight line segments 2614 2615 var straightLineIntersections; // Top segment, left to right 2616 2617 { 2618 var topStartX = nodeX - halfWidth + cornerRadius - padding; 2619 var topStartY = nodeY - halfHeight - padding; 2620 var topEndX = nodeX + halfWidth - cornerRadius + padding; 2621 var topEndY = topStartY; 2622 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); 2623 2624 if (straightLineIntersections.length > 0) { 2625 return straightLineIntersections; 2626 } 2627 } // Right segment, top to bottom 2628 2629 { 2630 var rightStartX = nodeX + halfWidth + padding; 2631 var rightStartY = nodeY - halfHeight + cornerRadius - padding; 2632 var rightEndX = rightStartX; 2633 var rightEndY = nodeY + halfHeight - cornerRadius + padding; 2634 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false); 2635 2636 if (straightLineIntersections.length > 0) { 2637 return straightLineIntersections; 2638 } 2639 } // Bottom segment, left to right 2640 2641 { 2642 var bottomStartX = nodeX - halfWidth + cornerRadius - padding; 2643 var bottomStartY = nodeY + halfHeight + padding; 2644 var bottomEndX = nodeX + halfWidth - cornerRadius + padding; 2645 var bottomEndY = bottomStartY; 2646 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false); 2647 2648 if (straightLineIntersections.length > 0) { 2649 return straightLineIntersections; 2650 } 2651 } // Left segment, top to bottom 2652 2653 { 2654 var leftStartX = nodeX - halfWidth - padding; 2655 var leftStartY = nodeY - halfHeight + cornerRadius - padding; 2656 var leftEndX = leftStartX; 2657 var leftEndY = nodeY + halfHeight - cornerRadius + padding; 2658 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false); 2659 2660 if (straightLineIntersections.length > 0) { 2661 return straightLineIntersections; 2662 } 2663 } // Check intersections with arc segments 2664 2665 var arcIntersections; // Top Left 2666 2667 { 2668 var topLeftCenterX = nodeX - halfWidth + cornerRadius; 2669 var topLeftCenterY = nodeY - halfHeight + cornerRadius; 2670 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, topLeftCenterX, topLeftCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2671 2672 if (arcIntersections.length > 0 && arcIntersections[0] <= topLeftCenterX && arcIntersections[1] <= topLeftCenterY) { 2673 return [arcIntersections[0], arcIntersections[1]]; 2674 } 2675 } // Top Right 2676 2677 { 2678 var topRightCenterX = nodeX + halfWidth - cornerRadius; 2679 var topRightCenterY = nodeY - halfHeight + cornerRadius; 2680 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, topRightCenterX, topRightCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2681 2682 if (arcIntersections.length > 0 && arcIntersections[0] >= topRightCenterX && arcIntersections[1] <= topRightCenterY) { 2683 return [arcIntersections[0], arcIntersections[1]]; 2684 } 2685 } // Bottom Right 2686 2687 { 2688 var bottomRightCenterX = nodeX + halfWidth - cornerRadius; 2689 var bottomRightCenterY = nodeY + halfHeight - cornerRadius; 2690 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, bottomRightCenterX, bottomRightCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2691 2692 if (arcIntersections.length > 0 && arcIntersections[0] >= bottomRightCenterX && arcIntersections[1] >= bottomRightCenterY) { 2693 return [arcIntersections[0], arcIntersections[1]]; 2694 } 2695 } // Bottom Left 2696 2697 { 2698 var bottomLeftCenterX = nodeX - halfWidth + cornerRadius; 2699 var bottomLeftCenterY = nodeY + halfHeight - cornerRadius; 2700 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 2701 2702 if (arcIntersections.length > 0 && arcIntersections[0] <= bottomLeftCenterX && arcIntersections[1] >= bottomLeftCenterY) { 2703 return [arcIntersections[0], arcIntersections[1]]; 2704 } 2705 } 2706 return []; // if nothing 2707 }; 2708 var inLineVicinity = function inLineVicinity(x, y, lx1, ly1, lx2, ly2, tolerance) { 2709 var t = tolerance; 2710 var x1 = Math.min(lx1, lx2); 2711 var x2 = Math.max(lx1, lx2); 2712 var y1 = Math.min(ly1, ly2); 2713 var y2 = Math.max(ly1, ly2); 2714 return x1 - t <= x && x <= x2 + t && y1 - t <= y && y <= y2 + t; 2715 }; 2716 var inBezierVicinity = function inBezierVicinity(x, y, x1, y1, x2, y2, x3, y3, tolerance) { 2717 var bb = { 2718 x1: Math.min(x1, x3, x2) - tolerance, 2719 x2: Math.max(x1, x3, x2) + tolerance, 2720 y1: Math.min(y1, y3, y2) - tolerance, 2721 y2: Math.max(y1, y3, y2) + tolerance 2722 }; // if outside the rough bounding box for the bezier, then it can't be a hit 2723 2724 if (x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2) { 2725 // console.log('bezier out of rough bb') 2726 return false; 2727 } else { 2728 // console.log('do more expensive check'); 2729 return true; 2730 } 2731 }; 2732 var solveQuadratic = function solveQuadratic(a, b, c, val) { 2733 c -= val; 2734 var r = b * b - 4 * a * c; 2735 2736 if (r < 0) { 2737 return []; 2738 } 2739 2740 var sqrtR = Math.sqrt(r); 2741 var denom = 2 * a; 2742 var root1 = (-b + sqrtR) / denom; 2743 var root2 = (-b - sqrtR) / denom; 2744 return [root1, root2]; 2745 }; 2746 var solveCubic = function solveCubic(a, b, c, d, result) { 2747 // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where 2748 // r is the real component, i is the imaginary component 2749 // An implementation of the Cardano method from the year 1545 2750 // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots 2751 var epsilon = 0.00001; // avoid division by zero while keeping the overall expression close in value 2752 2753 if (a === 0) { 2754 a = epsilon; 2755 } 2756 2757 b /= a; 2758 c /= a; 2759 d /= a; 2760 var discriminant, q, r, dum1, s, t, term1, r13; 2761 q = (3.0 * c - b * b) / 9.0; 2762 r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b)); 2763 r /= 54.0; 2764 discriminant = q * q * q + r * r; 2765 result[1] = 0; 2766 term1 = b / 3.0; 2767 2768 if (discriminant > 0) { 2769 s = r + Math.sqrt(discriminant); 2770 s = s < 0 ? -Math.pow(-s, 1.0 / 3.0) : Math.pow(s, 1.0 / 3.0); 2771 t = r - Math.sqrt(discriminant); 2772 t = t < 0 ? -Math.pow(-t, 1.0 / 3.0) : Math.pow(t, 1.0 / 3.0); 2773 result[0] = -term1 + s + t; 2774 term1 += (s + t) / 2.0; 2775 result[4] = result[2] = -term1; 2776 term1 = Math.sqrt(3.0) * (-t + s) / 2; 2777 result[3] = term1; 2778 result[5] = -term1; 2779 return; 2780 } 2781 2782 result[5] = result[3] = 0; 2783 2784 if (discriminant === 0) { 2785 r13 = r < 0 ? -Math.pow(-r, 1.0 / 3.0) : Math.pow(r, 1.0 / 3.0); 2786 result[0] = -term1 + 2.0 * r13; 2787 result[4] = result[2] = -(r13 + term1); 2788 return; 2789 } 2790 2791 q = -q; 2792 dum1 = q * q * q; 2793 dum1 = Math.acos(r / Math.sqrt(dum1)); 2794 r13 = 2.0 * Math.sqrt(q); 2795 result[0] = -term1 + r13 * Math.cos(dum1 / 3.0); 2796 result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0); 2797 result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0); 2798 return; 2799 }; 2800 var sqdistToQuadraticBezier = function sqdistToQuadraticBezier(x, y, x1, y1, x2, y2, x3, y3) { 2801 // Find minimum distance by using the minimum of the distance 2802 // function between the given point and the curve 2803 // This gives the coefficients of the resulting cubic equation 2804 // whose roots tell us where a possible minimum is 2805 // (Coefficients are divided by 4) 2806 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; 2807 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; 2808 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; 2809 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); 2810 2811 var roots = []; // Use the cubic solving algorithm 2812 2813 solveCubic(a, b, c, d, roots); 2814 var zeroThreshold = 0.0000001; 2815 var params = []; 2816 2817 for (var index = 0; index < 6; index += 2) { 2818 if (Math.abs(roots[index + 1]) < zeroThreshold && roots[index] >= 0 && roots[index] <= 1.0) { 2819 params.push(roots[index]); 2820 } 2821 } 2822 2823 params.push(1.0); 2824 params.push(0.0); 2825 var minDistanceSquared = -1; 2826 var curX, curY, distSquared; 2827 2828 for (var i = 0; i < params.length; i++) { 2829 curX = Math.pow(1.0 - params[i], 2.0) * x1 + 2.0 * (1 - params[i]) * params[i] * x2 + params[i] * params[i] * x3; 2830 curY = Math.pow(1 - params[i], 2.0) * y1 + 2 * (1.0 - params[i]) * params[i] * y2 + params[i] * params[i] * y3; 2831 distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2); // debug('distance for param ' + params[i] + ": " + Math.sqrt(distSquared)); 2832 2833 if (minDistanceSquared >= 0) { 2834 if (distSquared < minDistanceSquared) { 2835 minDistanceSquared = distSquared; 2836 } 2837 } else { 2838 minDistanceSquared = distSquared; 2839 } 2840 } 2841 2842 return minDistanceSquared; 2843 }; 2844 var sqdistToFiniteLine = function sqdistToFiniteLine(x, y, x1, y1, x2, y2) { 2845 var offset = [x - x1, y - y1]; 2846 var line = [x2 - x1, y2 - y1]; 2847 var lineSq = line[0] * line[0] + line[1] * line[1]; 2848 var hypSq = offset[0] * offset[0] + offset[1] * offset[1]; 2849 var dotProduct = offset[0] * line[0] + offset[1] * line[1]; 2850 var adjSq = dotProduct * dotProduct / lineSq; 2851 2852 if (dotProduct < 0) { 2853 return hypSq; 2854 } 2855 2856 if (adjSq > lineSq) { 2857 return (x - x2) * (x - x2) + (y - y2) * (y - y2); 2858 } 2859 2860 return hypSq - adjSq; 2861 }; 2862 var pointInsidePolygonPoints = function pointInsidePolygonPoints(x, y, points) { 2863 var x1, y1, x2, y2; 2864 var y3; // Intersect with vertical line through (x, y) 2865 2866 var up = 0; // let down = 0; 2867 2868 for (var i = 0; i < points.length / 2; i++) { 2869 x1 = points[i * 2]; 2870 y1 = points[i * 2 + 1]; 2871 2872 if (i + 1 < points.length / 2) { 2873 x2 = points[(i + 1) * 2]; 2874 y2 = points[(i + 1) * 2 + 1]; 2875 } else { 2876 x2 = points[(i + 1 - points.length / 2) * 2]; 2877 y2 = points[(i + 1 - points.length / 2) * 2 + 1]; 2878 } 2879 2880 if (x1 == x && x2 == x) ; else if (x1 >= x && x >= x2 || x1 <= x && x <= x2) { 2881 y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1; 2882 2883 if (y3 > y) { 2884 up++; 2885 } // if( y3 < y ){ 2886 // down++; 2887 // } 2888 2889 } else { 2890 continue; 2891 } 2892 } 2893 2894 if (up % 2 === 0) { 2895 return false; 2896 } else { 2897 return true; 2898 } 2899 }; 2900 var pointInsidePolygon = function pointInsidePolygon(x, y, basePoints, centerX, centerY, width, height, direction, padding) { 2901 var transformedPoints = new Array(basePoints.length); // Gives negative angle 2902 2903 var angle; 2904 2905 if (direction[0] != null) { 2906 angle = Math.atan(direction[1] / direction[0]); 2907 2908 if (direction[0] < 0) { 2909 angle = angle + Math.PI / 2; 2910 } else { 2911 angle = -angle - Math.PI / 2; 2912 } 2913 } else { 2914 angle = direction; 2915 } 2916 2917 var cos = Math.cos(-angle); 2918 var sin = Math.sin(-angle); // console.log("base: " + basePoints); 2919 2920 for (var i = 0; i < transformedPoints.length / 2; i++) { 2921 transformedPoints[i * 2] = width / 2 * (basePoints[i * 2] * cos - basePoints[i * 2 + 1] * sin); 2922 transformedPoints[i * 2 + 1] = height / 2 * (basePoints[i * 2 + 1] * cos + basePoints[i * 2] * sin); 2923 transformedPoints[i * 2] += centerX; 2924 transformedPoints[i * 2 + 1] += centerY; 2925 } 2926 2927 var points; 2928 2929 if (padding > 0) { 2930 var expandedLineSet = expandPolygon(transformedPoints, -padding); 2931 points = joinLines(expandedLineSet); 2932 } else { 2933 points = transformedPoints; 2934 } 2935 2936 return pointInsidePolygonPoints(x, y, points); 2937 }; 2938 var pointInsideRoundPolygon = function pointInsideRoundPolygon(x, y, basePoints, centerX, centerY, width, height) { 2939 var cutPolygonPoints = new Array(basePoints.length); 2940 var halfW = width / 2; 2941 var halfH = height / 2; 2942 var cornerRadius = getRoundPolygonRadius(width, height); 2943 var squaredCornerRadius = cornerRadius * cornerRadius; 2944 2945 for (var i = 0; i < basePoints.length / 4; i++) { 2946 var sourceUv = void 0, 2947 destUv = void 0; 2948 2949 if (i === 0) { 2950 sourceUv = basePoints.length - 2; 2951 } else { 2952 sourceUv = i * 4 - 2; 2953 } 2954 2955 destUv = i * 4 + 2; 2956 var px = centerX + halfW * basePoints[i * 4]; 2957 var py = centerY + halfH * basePoints[i * 4 + 1]; 2958 var cosTheta = -basePoints[sourceUv] * basePoints[destUv] - basePoints[sourceUv + 1] * basePoints[destUv + 1]; 2959 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 2960 var cp0x = px - offset * basePoints[sourceUv]; 2961 var cp0y = py - offset * basePoints[sourceUv + 1]; 2962 var cp1x = px + offset * basePoints[destUv]; 2963 var cp1y = py + offset * basePoints[destUv + 1]; 2964 cutPolygonPoints[i * 4] = cp0x; 2965 cutPolygonPoints[i * 4 + 1] = cp0y; 2966 cutPolygonPoints[i * 4 + 2] = cp1x; 2967 cutPolygonPoints[i * 4 + 3] = cp1y; 2968 var orthx = basePoints[sourceUv + 1]; 2969 var orthy = -basePoints[sourceUv]; 2970 var cosAlpha = orthx * basePoints[destUv] + orthy * basePoints[destUv + 1]; 2971 2972 if (cosAlpha < 0) { 2973 orthx *= -1; 2974 orthy *= -1; 2975 } 2976 2977 var cx = cp0x + orthx * cornerRadius; 2978 var cy = cp0y + orthy * cornerRadius; 2979 var squaredDistance = Math.pow(cx - x, 2) + Math.pow(cy - y, 2); 2980 2981 if (squaredDistance <= squaredCornerRadius) { 2982 return true; 2983 } 2984 } 2985 2986 return pointInsidePolygonPoints(x, y, cutPolygonPoints); 2987 }; 2988 var joinLines = function joinLines(lineSet) { 2989 var vertices = new Array(lineSet.length / 2); 2990 var currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY; 2991 var nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY; 2992 2993 for (var i = 0; i < lineSet.length / 4; i++) { 2994 currentLineStartX = lineSet[i * 4]; 2995 currentLineStartY = lineSet[i * 4 + 1]; 2996 currentLineEndX = lineSet[i * 4 + 2]; 2997 currentLineEndY = lineSet[i * 4 + 3]; 2998 2999 if (i < lineSet.length / 4 - 1) { 3000 nextLineStartX = lineSet[(i + 1) * 4]; 3001 nextLineStartY = lineSet[(i + 1) * 4 + 1]; 3002 nextLineEndX = lineSet[(i + 1) * 4 + 2]; 3003 nextLineEndY = lineSet[(i + 1) * 4 + 3]; 3004 } else { 3005 nextLineStartX = lineSet[0]; 3006 nextLineStartY = lineSet[1]; 3007 nextLineEndX = lineSet[2]; 3008 nextLineEndY = lineSet[3]; 3009 } 3010 3011 var intersection = finiteLinesIntersect(currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY, nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY, true); 3012 vertices[i * 2] = intersection[0]; 3013 vertices[i * 2 + 1] = intersection[1]; 3014 } 3015 3016 return vertices; 3017 }; 3018 var expandPolygon = function expandPolygon(points, pad) { 3019 var expandedLineSet = new Array(points.length * 2); 3020 var currentPointX, currentPointY, nextPointX, nextPointY; 3021 3022 for (var i = 0; i < points.length / 2; i++) { 3023 currentPointX = points[i * 2]; 3024 currentPointY = points[i * 2 + 1]; 3025 3026 if (i < points.length / 2 - 1) { 3027 nextPointX = points[(i + 1) * 2]; 3028 nextPointY = points[(i + 1) * 2 + 1]; 3029 } else { 3030 nextPointX = points[0]; 3031 nextPointY = points[1]; 3032 } // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY] 3033 // Assume CCW polygon winding 3034 3035 3036 var offsetX = nextPointY - currentPointY; 3037 var offsetY = -(nextPointX - currentPointX); // Normalize 3038 3039 var offsetLength = Math.sqrt(offsetX * offsetX + offsetY * offsetY); 3040 var normalizedOffsetX = offsetX / offsetLength; 3041 var normalizedOffsetY = offsetY / offsetLength; 3042 expandedLineSet[i * 4] = currentPointX + normalizedOffsetX * pad; 3043 expandedLineSet[i * 4 + 1] = currentPointY + normalizedOffsetY * pad; 3044 expandedLineSet[i * 4 + 2] = nextPointX + normalizedOffsetX * pad; 3045 expandedLineSet[i * 4 + 3] = nextPointY + normalizedOffsetY * pad; 3046 } 3047 3048 return expandedLineSet; 3049 }; 3050 var intersectLineEllipse = function intersectLineEllipse(x, y, centerX, centerY, ellipseWradius, ellipseHradius) { 3051 var dispX = centerX - x; 3052 var dispY = centerY - y; 3053 dispX /= ellipseWradius; 3054 dispY /= ellipseHradius; 3055 var len = Math.sqrt(dispX * dispX + dispY * dispY); 3056 var newLength = len - 1; 3057 3058 if (newLength < 0) { 3059 return []; 3060 } 3061 3062 var lenProportion = newLength / len; 3063 return [(centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y]; 3064 }; 3065 var checkInEllipse = function checkInEllipse(x, y, width, height, centerX, centerY, padding) { 3066 x -= centerX; 3067 y -= centerY; 3068 x /= width / 2 + padding; 3069 y /= height / 2 + padding; 3070 return x * x + y * y <= 1; 3071 }; // Returns intersections of increasing distance from line's start point 3072 3073 var intersectLineCircle = function intersectLineCircle(x1, y1, x2, y2, centerX, centerY, radius) { 3074 // Calculate d, direction vector of line 3075 var d = [x2 - x1, y2 - y1]; // Direction vector of line 3076 3077 var f = [x1 - centerX, y1 - centerY]; 3078 var a = d[0] * d[0] + d[1] * d[1]; 3079 var b = 2 * (f[0] * d[0] + f[1] * d[1]); 3080 var c = f[0] * f[0] + f[1] * f[1] - radius * radius; 3081 var discriminant = b * b - 4 * a * c; 3082 3083 if (discriminant < 0) { 3084 return []; 3085 } 3086 3087 var t1 = (-b + Math.sqrt(discriminant)) / (2 * a); 3088 var t2 = (-b - Math.sqrt(discriminant)) / (2 * a); 3089 var tMin = Math.min(t1, t2); 3090 var tMax = Math.max(t1, t2); 3091 var inRangeParams = []; 3092 3093 if (tMin >= 0 && tMin <= 1) { 3094 inRangeParams.push(tMin); 3095 } 3096 3097 if (tMax >= 0 && tMax <= 1) { 3098 inRangeParams.push(tMax); 3099 } 3100 3101 if (inRangeParams.length === 0) { 3102 return []; 3103 } 3104 3105 var nearIntersectionX = inRangeParams[0] * d[0] + x1; 3106 var nearIntersectionY = inRangeParams[0] * d[1] + y1; 3107 3108 if (inRangeParams.length > 1) { 3109 if (inRangeParams[0] == inRangeParams[1]) { 3110 return [nearIntersectionX, nearIntersectionY]; 3111 } else { 3112 var farIntersectionX = inRangeParams[1] * d[0] + x1; 3113 var farIntersectionY = inRangeParams[1] * d[1] + y1; 3114 return [nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY]; 3115 } 3116 } else { 3117 return [nearIntersectionX, nearIntersectionY]; 3118 } 3119 }; 3120 var midOfThree = function midOfThree(a, b, c) { 3121 if (b <= a && a <= c || c <= a && a <= b) { 3122 return a; 3123 } else if (a <= b && b <= c || c <= b && b <= a) { 3124 return b; 3125 } else { 3126 return c; 3127 } 3128 }; // (x1,y1)=>(x2,y2) intersect with (x3,y3)=>(x4,y4) 3129 3130 var finiteLinesIntersect = function finiteLinesIntersect(x1, y1, x2, y2, x3, y3, x4, y4, infiniteLines) { 3131 var dx13 = x1 - x3; 3132 var dx21 = x2 - x1; 3133 var dx43 = x4 - x3; 3134 var dy13 = y1 - y3; 3135 var dy21 = y2 - y1; 3136 var dy43 = y4 - y3; 3137 var ua_t = dx43 * dy13 - dy43 * dx13; 3138 var ub_t = dx21 * dy13 - dy21 * dx13; 3139 var u_b = dy43 * dx21 - dx43 * dy21; 3140 3141 if (u_b !== 0) { 3142 var ua = ua_t / u_b; 3143 var ub = ub_t / u_b; 3144 var flptThreshold = 0.001; 3145 3146 var _min = 0 - flptThreshold; 3147 3148 var _max = 1 + flptThreshold; 3149 3150 if (_min <= ua && ua <= _max && _min <= ub && ub <= _max) { 3151 return [x1 + ua * dx21, y1 + ua * dy21]; 3152 } else { 3153 if (!infiniteLines) { 3154 return []; 3155 } else { 3156 return [x1 + ua * dx21, y1 + ua * dy21]; 3157 } 3158 } 3159 } else { 3160 if (ua_t === 0 || ub_t === 0) { 3161 // Parallel, coincident lines. Check if overlap 3162 // Check endpoint of second line 3163 if (midOfThree(x1, x2, x4) === x4) { 3164 return [x4, y4]; 3165 } // Check start point of second line 3166 3167 3168 if (midOfThree(x1, x2, x3) === x3) { 3169 return [x3, y3]; 3170 } // Endpoint of first line 3171 3172 3173 if (midOfThree(x3, x4, x2) === x2) { 3174 return [x2, y2]; 3175 } 3176 3177 return []; 3178 } else { 3179 // Parallel, non-coincident 3180 return []; 3181 } 3182 } 3183 }; // math.polygonIntersectLine( x, y, basePoints, centerX, centerY, width, height, padding ) 3184 // intersect a node polygon (pts transformed) 3185 // 3186 // math.polygonIntersectLine( x, y, basePoints, centerX, centerY ) 3187 // intersect the points (no transform) 3188 3189 var polygonIntersectLine = function polygonIntersectLine(x, y, basePoints, centerX, centerY, width, height, padding) { 3190 var intersections = []; 3191 var intersection; 3192 var transformedPoints = new Array(basePoints.length); 3193 var doTransform = true; 3194 3195 if (width == null) { 3196 doTransform = false; 3197 } 3198 3199 var points; 3200 3201 if (doTransform) { 3202 for (var i = 0; i < transformedPoints.length / 2; i++) { 3203 transformedPoints[i * 2] = basePoints[i * 2] * width + centerX; 3204 transformedPoints[i * 2 + 1] = basePoints[i * 2 + 1] * height + centerY; 3205 } 3206 3207 if (padding > 0) { 3208 var expandedLineSet = expandPolygon(transformedPoints, -padding); 3209 points = joinLines(expandedLineSet); 3210 } else { 3211 points = transformedPoints; 3212 } 3213 } else { 3214 points = basePoints; 3215 } 3216 3217 var currentX, currentY, nextX, nextY; 3218 3219 for (var _i2 = 0; _i2 < points.length / 2; _i2++) { 3220 currentX = points[_i2 * 2]; 3221 currentY = points[_i2 * 2 + 1]; 3222 3223 if (_i2 < points.length / 2 - 1) { 3224 nextX = points[(_i2 + 1) * 2]; 3225 nextY = points[(_i2 + 1) * 2 + 1]; 3226 } else { 3227 nextX = points[0]; 3228 nextY = points[1]; 3229 } 3230 3231 intersection = finiteLinesIntersect(x, y, centerX, centerY, currentX, currentY, nextX, nextY); 3232 3233 if (intersection.length !== 0) { 3234 intersections.push(intersection[0], intersection[1]); 3235 } 3236 } 3237 3238 return intersections; 3239 }; 3240 var roundPolygonIntersectLine = function roundPolygonIntersectLine(x, y, basePoints, centerX, centerY, width, height, padding) { 3241 var intersections = []; 3242 var intersection; 3243 var lines = new Array(basePoints.length); 3244 var halfW = width / 2; 3245 var halfH = height / 2; 3246 var cornerRadius = getRoundPolygonRadius(width, height); 3247 3248 for (var i = 0; i < basePoints.length / 4; i++) { 3249 var sourceUv = void 0, 3250 destUv = void 0; 3251 3252 if (i === 0) { 3253 sourceUv = basePoints.length - 2; 3254 } else { 3255 sourceUv = i * 4 - 2; 3256 } 3257 3258 destUv = i * 4 + 2; 3259 var px = centerX + halfW * basePoints[i * 4]; 3260 var py = centerY + halfH * basePoints[i * 4 + 1]; 3261 var cosTheta = -basePoints[sourceUv] * basePoints[destUv] - basePoints[sourceUv + 1] * basePoints[destUv + 1]; 3262 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 3263 var cp0x = px - offset * basePoints[sourceUv]; 3264 var cp0y = py - offset * basePoints[sourceUv + 1]; 3265 var cp1x = px + offset * basePoints[destUv]; 3266 var cp1y = py + offset * basePoints[destUv + 1]; 3267 3268 if (i === 0) { 3269 lines[basePoints.length - 2] = cp0x; 3270 lines[basePoints.length - 1] = cp0y; 3271 } else { 3272 lines[i * 4 - 2] = cp0x; 3273 lines[i * 4 - 1] = cp0y; 3274 } 3275 3276 lines[i * 4] = cp1x; 3277 lines[i * 4 + 1] = cp1y; 3278 var orthx = basePoints[sourceUv + 1]; 3279 var orthy = -basePoints[sourceUv]; 3280 var cosAlpha = orthx * basePoints[destUv] + orthy * basePoints[destUv + 1]; 3281 3282 if (cosAlpha < 0) { 3283 orthx *= -1; 3284 orthy *= -1; 3285 } 3286 3287 var cx = cp0x + orthx * cornerRadius; 3288 var cy = cp0y + orthy * cornerRadius; 3289 intersection = intersectLineCircle(x, y, centerX, centerY, cx, cy, cornerRadius); 3290 3291 if (intersection.length !== 0) { 3292 intersections.push(intersection[0], intersection[1]); 3293 } 3294 } 3295 3296 for (var _i3 = 0; _i3 < lines.length / 4; _i3++) { 3297 intersection = finiteLinesIntersect(x, y, centerX, centerY, lines[_i3 * 4], lines[_i3 * 4 + 1], lines[_i3 * 4 + 2], lines[_i3 * 4 + 3], false); 3298 3299 if (intersection.length !== 0) { 3300 intersections.push(intersection[0], intersection[1]); 3301 } 3302 } 3303 3304 if (intersections.length > 2) { 3305 var lowestIntersection = [intersections[0], intersections[1]]; 3306 var lowestSquaredDistance = Math.pow(lowestIntersection[0] - x, 2) + Math.pow(lowestIntersection[1] - y, 2); 3307 3308 for (var _i4 = 1; _i4 < intersections.length / 2; _i4++) { 3309 var squaredDistance = Math.pow(intersections[_i4 * 2] - x, 2) + Math.pow(intersections[_i4 * 2 + 1] - y, 2); 3310 3311 if (squaredDistance <= lowestSquaredDistance) { 3312 lowestIntersection[0] = intersections[_i4 * 2]; 3313 lowestIntersection[1] = intersections[_i4 * 2 + 1]; 3314 lowestSquaredDistance = squaredDistance; 3315 } 3316 } 3317 3318 return lowestIntersection; 3319 } 3320 3321 return intersections; 3322 }; 3323 var shortenIntersection = function shortenIntersection(intersection, offset, amount) { 3324 var disp = [intersection[0] - offset[0], intersection[1] - offset[1]]; 3325 var length = Math.sqrt(disp[0] * disp[0] + disp[1] * disp[1]); 3326 var lenRatio = (length - amount) / length; 3327 3328 if (lenRatio < 0) { 3329 lenRatio = 0.00001; 3330 } 3331 3332 return [offset[0] + lenRatio * disp[0], offset[1] + lenRatio * disp[1]]; 3333 }; 3334 var generateUnitNgonPointsFitToSquare = function generateUnitNgonPointsFitToSquare(sides, rotationRadians) { 3335 var points = generateUnitNgonPoints(sides, rotationRadians); 3336 points = fitPolygonToSquare(points); 3337 return points; 3338 }; 3339 var fitPolygonToSquare = function fitPolygonToSquare(points) { 3340 var x, y; 3341 var sides = points.length / 2; 3342 var minX = Infinity, 3343 minY = Infinity, 3344 maxX = -Infinity, 3345 maxY = -Infinity; 3346 3347 for (var i = 0; i < sides; i++) { 3348 x = points[2 * i]; 3349 y = points[2 * i + 1]; 3350 minX = Math.min(minX, x); 3351 maxX = Math.max(maxX, x); 3352 minY = Math.min(minY, y); 3353 maxY = Math.max(maxY, y); 3354 } // stretch factors 3355 3356 3357 var sx = 2 / (maxX - minX); 3358 var sy = 2 / (maxY - minY); 3359 3360 for (var _i5 = 0; _i5 < sides; _i5++) { 3361 x = points[2 * _i5] = points[2 * _i5] * sx; 3362 y = points[2 * _i5 + 1] = points[2 * _i5 + 1] * sy; 3363 minX = Math.min(minX, x); 3364 maxX = Math.max(maxX, x); 3365 minY = Math.min(minY, y); 3366 maxY = Math.max(maxY, y); 3367 } 3368 3369 if (minY < -1) { 3370 for (var _i6 = 0; _i6 < sides; _i6++) { 3371 y = points[2 * _i6 + 1] = points[2 * _i6 + 1] + (-1 - minY); 3372 } 3373 } 3374 3375 return points; 3376 }; 3377 var generateUnitNgonPoints = function generateUnitNgonPoints(sides, rotationRadians) { 3378 var increment = 1.0 / sides * 2 * Math.PI; 3379 var startAngle = sides % 2 === 0 ? Math.PI / 2.0 + increment / 2.0 : Math.PI / 2.0; 3380 startAngle += rotationRadians; 3381 var points = new Array(sides * 2); 3382 var currentAngle; 3383 3384 for (var i = 0; i < sides; i++) { 3385 currentAngle = i * increment + startAngle; 3386 points[2 * i] = Math.cos(currentAngle); // x 3387 3388 points[2 * i + 1] = Math.sin(-currentAngle); // y 3389 } 3390 3391 return points; 3392 }; // Set the default radius, unless half of width or height is smaller than default 3393 3394 var getRoundRectangleRadius = function getRoundRectangleRadius(width, height) { 3395 return Math.min(width / 4, height / 4, 8); 3396 }; // Set the default radius 3397 3398 var getRoundPolygonRadius = function getRoundPolygonRadius(width, height) { 3399 return Math.min(width / 10, height / 10, 8); 3400 }; 3401 var getCutRectangleCornerLength = function getCutRectangleCornerLength() { 3402 return 8; 3403 }; 3404 var bezierPtsToQuadCoeff = function bezierPtsToQuadCoeff(p0, p1, p2) { 3405 return [p0 - 2 * p1 + p2, 2 * (p1 - p0), p0]; 3406 }; // get curve width, height, and control point position offsets as a percentage of node height / width 3407 3408 var getBarrelCurveConstants = function getBarrelCurveConstants(width, height) { 3409 return { 3410 heightOffset: Math.min(15, 0.05 * height), 3411 widthOffset: Math.min(100, 0.25 * width), 3412 ctrlPtOffsetPct: 0.05 3413 }; 3414 }; 3415 3416 var pageRankDefaults = defaults({ 3417 dampingFactor: 0.8, 3418 precision: 0.000001, 3419 iterations: 200, 3420 weight: function weight(edge) { 3421 return 1; 3422 } 3423 }); 3424 var elesfn$7 = { 3425 pageRank: function pageRank(options) { 3426 var _pageRankDefaults = pageRankDefaults(options), 3427 dampingFactor = _pageRankDefaults.dampingFactor, 3428 precision = _pageRankDefaults.precision, 3429 iterations = _pageRankDefaults.iterations, 3430 weight = _pageRankDefaults.weight; 3431 3432 var cy = this._private.cy; 3433 3434 var _this$byGroup = this.byGroup(), 3435 nodes = _this$byGroup.nodes, 3436 edges = _this$byGroup.edges; 3437 3438 var numNodes = nodes.length; 3439 var numNodesSqd = numNodes * numNodes; 3440 var numEdges = edges.length; // Construct transposed adjacency matrix 3441 // First lets have a zeroed matrix of the right size 3442 // We'll also keep track of the sum of each column 3443 3444 var matrix = new Array(numNodesSqd); 3445 var columnSum = new Array(numNodes); 3446 var additionalProb = (1 - dampingFactor) / numNodes; // Create null matrix 3447 3448 for (var i = 0; i < numNodes; i++) { 3449 for (var j = 0; j < numNodes; j++) { 3450 var n = i * numNodes + j; 3451 matrix[n] = 0; 3452 } 3453 3454 columnSum[i] = 0; 3455 } // Now, process edges 3456 3457 3458 for (var _i = 0; _i < numEdges; _i++) { 3459 var edge = edges[_i]; 3460 var srcId = edge.data('source'); 3461 var tgtId = edge.data('target'); // Don't include loops in the matrix 3462 3463 if (srcId === tgtId) { 3464 continue; 3465 } 3466 3467 var s = nodes.indexOfId(srcId); 3468 var t = nodes.indexOfId(tgtId); 3469 var w = weight(edge); 3470 3471 var _n = t * numNodes + s; // Update matrix 3472 3473 3474 matrix[_n] += w; // Update column sum 3475 3476 columnSum[s] += w; 3477 } // Add additional probability based on damping factor 3478 // Also, take into account columns that have sum = 0 3479 3480 3481 var p = 1.0 / numNodes + additionalProb; // Shorthand 3482 // Traverse matrix, column by column 3483 3484 for (var _j = 0; _j < numNodes; _j++) { 3485 if (columnSum[_j] === 0) { 3486 // No 'links' out from node jth, assume equal probability for each possible node 3487 for (var _i2 = 0; _i2 < numNodes; _i2++) { 3488 var _n2 = _i2 * numNodes + _j; 3489 3490 matrix[_n2] = p; 3491 } 3492 } else { 3493 // Node jth has outgoing link, compute normalized probabilities 3494 for (var _i3 = 0; _i3 < numNodes; _i3++) { 3495 var _n3 = _i3 * numNodes + _j; 3496 3497 matrix[_n3] = matrix[_n3] / columnSum[_j] + additionalProb; 3498 } 3499 } 3500 } // Compute dominant eigenvector using power method 3501 3502 3503 var eigenvector = new Array(numNodes); 3504 var temp = new Array(numNodes); 3505 var previous; // Start with a vector of all 1's 3506 // Also, initialize a null vector which will be used as shorthand 3507 3508 for (var _i4 = 0; _i4 < numNodes; _i4++) { 3509 eigenvector[_i4] = 1; 3510 } 3511 3512 for (var iter = 0; iter < iterations; iter++) { 3513 // Temp array with all 0's 3514 for (var _i5 = 0; _i5 < numNodes; _i5++) { 3515 temp[_i5] = 0; 3516 } // Multiply matrix with previous result 3517 3518 3519 for (var _i6 = 0; _i6 < numNodes; _i6++) { 3520 for (var _j2 = 0; _j2 < numNodes; _j2++) { 3521 var _n4 = _i6 * numNodes + _j2; 3522 3523 temp[_i6] += matrix[_n4] * eigenvector[_j2]; 3524 } 3525 } 3526 3527 inPlaceSumNormalize(temp); 3528 previous = eigenvector; 3529 eigenvector = temp; 3530 temp = previous; 3531 var diff = 0; // Compute difference (squared module) of both vectors 3532 3533 for (var _i7 = 0; _i7 < numNodes; _i7++) { 3534 var delta = previous[_i7] - eigenvector[_i7]; 3535 diff += delta * delta; 3536 } // If difference is less than the desired threshold, stop iterating 3537 3538 3539 if (diff < precision) { 3540 break; 3541 } 3542 } // Construct result 3543 3544 3545 var res = { 3546 rank: function rank(node) { 3547 node = cy.collection(node)[0]; 3548 return eigenvector[nodes.indexOf(node)]; 3549 } 3550 }; 3551 return res; 3552 } // pageRank 3553 3554 }; // elesfn 3555 3556 var defaults$1 = defaults({ 3557 root: null, 3558 weight: function weight(edge) { 3559 return 1; 3560 }, 3561 directed: false, 3562 alpha: 0 3563 }); 3564 var elesfn$8 = { 3565 degreeCentralityNormalized: function degreeCentralityNormalized(options) { 3566 options = defaults$1(options); 3567 var cy = this.cy(); 3568 var nodes = this.nodes(); 3569 var numNodes = nodes.length; 3570 3571 if (!options.directed) { 3572 var degrees = {}; 3573 var maxDegree = 0; 3574 3575 for (var i = 0; i < numNodes; i++) { 3576 var node = nodes[i]; // add current node to the current options object and call degreeCentrality 3577 3578 options.root = node; 3579 var currDegree = this.degreeCentrality(options); 3580 3581 if (maxDegree < currDegree.degree) { 3582 maxDegree = currDegree.degree; 3583 } 3584 3585 degrees[node.id()] = currDegree.degree; 3586 } 3587 3588 return { 3589 degree: function degree(node) { 3590 if (maxDegree === 0) { 3591 return 0; 3592 } 3593 3594 if (string(node)) { 3595 // from is a selector string 3596 node = cy.filter(node); 3597 } 3598 3599 return degrees[node.id()] / maxDegree; 3600 } 3601 }; 3602 } else { 3603 var indegrees = {}; 3604 var outdegrees = {}; 3605 var maxIndegree = 0; 3606 var maxOutdegree = 0; 3607 3608 for (var _i = 0; _i < numNodes; _i++) { 3609 var _node = nodes[_i]; 3610 3611 var id = _node.id(); // add current node to the current options object and call degreeCentrality 3612 3613 3614 options.root = _node; 3615 3616 var _currDegree = this.degreeCentrality(options); 3617 3618 if (maxIndegree < _currDegree.indegree) maxIndegree = _currDegree.indegree; 3619 if (maxOutdegree < _currDegree.outdegree) maxOutdegree = _currDegree.outdegree; 3620 indegrees[id] = _currDegree.indegree; 3621 outdegrees[id] = _currDegree.outdegree; 3622 } 3623 3624 return { 3625 indegree: function indegree(node) { 3626 if (maxIndegree == 0) { 3627 return 0; 3628 } 3629 3630 if (string(node)) { 3631 // from is a selector string 3632 node = cy.filter(node); 3633 } 3634 3635 return indegrees[node.id()] / maxIndegree; 3636 }, 3637 outdegree: function outdegree(node) { 3638 if (maxOutdegree === 0) { 3639 return 0; 3640 } 3641 3642 if (string(node)) { 3643 // from is a selector string 3644 node = cy.filter(node); 3645 } 3646 3647 return outdegrees[node.id()] / maxOutdegree; 3648 } 3649 }; 3650 } 3651 }, 3652 // degreeCentralityNormalized 3653 // Implemented from the algorithm in Opsahl's paper 3654 // "Node centrality in weighted networks: Generalizing degree and shortest paths" 3655 // check the heading 2 "Degree" 3656 degreeCentrality: function degreeCentrality(options) { 3657 options = defaults$1(options); 3658 var cy = this.cy(); 3659 var callingEles = this; 3660 var _options = options, 3661 root = _options.root, 3662 weight = _options.weight, 3663 directed = _options.directed, 3664 alpha = _options.alpha; 3665 root = cy.collection(root)[0]; 3666 3667 if (!directed) { 3668 var connEdges = root.connectedEdges().intersection(callingEles); 3669 var k = connEdges.length; 3670 var s = 0; // Now, sum edge weights 3671 3672 for (var i = 0; i < connEdges.length; i++) { 3673 s += weight(connEdges[i]); 3674 } 3675 3676 return { 3677 degree: Math.pow(k, 1 - alpha) * Math.pow(s, alpha) 3678 }; 3679 } else { 3680 var edges = root.connectedEdges(); 3681 var incoming = edges.filter(function (edge) { 3682 return edge.target().same(root) && callingEles.has(edge); 3683 }); 3684 var outgoing = edges.filter(function (edge) { 3685 return edge.source().same(root) && callingEles.has(edge); 3686 }); 3687 var k_in = incoming.length; 3688 var k_out = outgoing.length; 3689 var s_in = 0; 3690 var s_out = 0; // Now, sum incoming edge weights 3691 3692 for (var _i2 = 0; _i2 < incoming.length; _i2++) { 3693 s_in += weight(incoming[_i2]); 3694 } // Now, sum outgoing edge weights 3695 3696 3697 for (var _i3 = 0; _i3 < outgoing.length; _i3++) { 3698 s_out += weight(outgoing[_i3]); 3699 } 3700 3701 return { 3702 indegree: Math.pow(k_in, 1 - alpha) * Math.pow(s_in, alpha), 3703 outdegree: Math.pow(k_out, 1 - alpha) * Math.pow(s_out, alpha) 3704 }; 3705 } 3706 } // degreeCentrality 3707 3708 }; // elesfn 3709 // nice, short mathemathical alias 3710 3711 elesfn$8.dc = elesfn$8.degreeCentrality; 3712 elesfn$8.dcn = elesfn$8.degreeCentralityNormalised = elesfn$8.degreeCentralityNormalized; 3713 3714 var defaults$2 = defaults({ 3715 harmonic: true, 3716 weight: function weight() { 3717 return 1; 3718 }, 3719 directed: false, 3720 root: null 3721 }); 3722 var elesfn$9 = { 3723 closenessCentralityNormalized: function closenessCentralityNormalized(options) { 3724 var _defaults = defaults$2(options), 3725 harmonic = _defaults.harmonic, 3726 weight = _defaults.weight, 3727 directed = _defaults.directed; 3728 3729 var cy = this.cy(); 3730 var closenesses = {}; 3731 var maxCloseness = 0; 3732 var nodes = this.nodes(); 3733 var fw = this.floydWarshall({ 3734 weight: weight, 3735 directed: directed 3736 }); // Compute closeness for every node and find the maximum closeness 3737 3738 for (var i = 0; i < nodes.length; i++) { 3739 var currCloseness = 0; 3740 var node_i = nodes[i]; 3741 3742 for (var j = 0; j < nodes.length; j++) { 3743 if (i !== j) { 3744 var d = fw.distance(node_i, nodes[j]); 3745 3746 if (harmonic) { 3747 currCloseness += 1 / d; 3748 } else { 3749 currCloseness += d; 3750 } 3751 } 3752 } 3753 3754 if (!harmonic) { 3755 currCloseness = 1 / currCloseness; 3756 } 3757 3758 if (maxCloseness < currCloseness) { 3759 maxCloseness = currCloseness; 3760 } 3761 3762 closenesses[node_i.id()] = currCloseness; 3763 } 3764 3765 return { 3766 closeness: function closeness(node) { 3767 if (maxCloseness == 0) { 3768 return 0; 3769 } 3770 3771 if (string(node)) { 3772 // from is a selector string 3773 node = cy.filter(node)[0].id(); 3774 } else { 3775 // from is a node 3776 node = node.id(); 3777 } 3778 3779 return closenesses[node] / maxCloseness; 3780 } 3781 }; 3782 }, 3783 // Implemented from pseudocode from wikipedia 3784 closenessCentrality: function closenessCentrality(options) { 3785 var _defaults2 = defaults$2(options), 3786 root = _defaults2.root, 3787 weight = _defaults2.weight, 3788 directed = _defaults2.directed, 3789 harmonic = _defaults2.harmonic; 3790 3791 root = this.filter(root)[0]; // we need distance from this node to every other node 3792 3793 var dijkstra = this.dijkstra({ 3794 root: root, 3795 weight: weight, 3796 directed: directed 3797 }); 3798 var totalDistance = 0; 3799 var nodes = this.nodes(); 3800 3801 for (var i = 0; i < nodes.length; i++) { 3802 var n = nodes[i]; 3803 3804 if (!n.same(root)) { 3805 var d = dijkstra.distanceTo(n); 3806 3807 if (harmonic) { 3808 totalDistance += 1 / d; 3809 } else { 3810 totalDistance += d; 3811 } 3812 } 3813 } 3814 3815 return harmonic ? totalDistance : 1 / totalDistance; 3816 } // closenessCentrality 3817 3818 }; // elesfn 3819 // nice, short mathemathical alias 3820 3821 elesfn$9.cc = elesfn$9.closenessCentrality; 3822 elesfn$9.ccn = elesfn$9.closenessCentralityNormalised = elesfn$9.closenessCentralityNormalized; 3823 3824 var defaults$3 = defaults({ 3825 weight: null, 3826 directed: false 3827 }); 3828 var elesfn$a = { 3829 // Implemented from the algorithm in the paper "On Variants of Shortest-Path Betweenness Centrality and their Generic Computation" by Ulrik Brandes 3830 betweennessCentrality: function betweennessCentrality(options) { 3831 var _defaults = defaults$3(options), 3832 directed = _defaults.directed, 3833 weight = _defaults.weight; 3834 3835 var weighted = weight != null; 3836 var cy = this.cy(); // starting 3837 3838 var V = this.nodes(); 3839 var A = {}; 3840 var _C = {}; 3841 var max = 0; 3842 var C = { 3843 set: function set(key, val) { 3844 _C[key] = val; 3845 3846 if (val > max) { 3847 max = val; 3848 } 3849 }, 3850 get: function get(key) { 3851 return _C[key]; 3852 } 3853 }; // A contains the neighborhoods of every node 3854 3855 for (var i = 0; i < V.length; i++) { 3856 var v = V[i]; 3857 var vid = v.id(); 3858 3859 if (directed) { 3860 A[vid] = v.outgoers().nodes(); // get outgoers of every node 3861 } else { 3862 A[vid] = v.openNeighborhood().nodes(); // get neighbors of every node 3863 } 3864 3865 C.set(vid, 0); 3866 } 3867 3868 var _loop = function _loop(s) { 3869 var sid = V[s].id(); 3870 var S = []; // stack 3871 3872 var P = {}; 3873 var g = {}; 3874 var d = {}; 3875 var Q = new Heap(function (a, b) { 3876 return d[a] - d[b]; 3877 }); // queue 3878 // init dictionaries 3879 3880 for (var _i = 0; _i < V.length; _i++) { 3881 var _vid = V[_i].id(); 3882 3883 P[_vid] = []; 3884 g[_vid] = 0; 3885 d[_vid] = Infinity; 3886 } 3887 3888 g[sid] = 1; // sigma 3889 3890 d[sid] = 0; // distance to s 3891 3892 Q.push(sid); 3893 3894 while (!Q.empty()) { 3895 var _v = Q.pop(); 3896 3897 S.push(_v); 3898 3899 if (weighted) { 3900 for (var j = 0; j < A[_v].length; j++) { 3901 var w = A[_v][j]; 3902 var vEle = cy.getElementById(_v); 3903 var edge = void 0; 3904 3905 if (vEle.edgesTo(w).length > 0) { 3906 edge = vEle.edgesTo(w)[0]; 3907 } else { 3908 edge = w.edgesTo(vEle)[0]; 3909 } 3910 3911 var edgeWeight = weight(edge); 3912 w = w.id(); 3913 3914 if (d[w] > d[_v] + edgeWeight) { 3915 d[w] = d[_v] + edgeWeight; 3916 3917 if (Q.nodes.indexOf(w) < 0) { 3918 //if w is not in Q 3919 Q.push(w); 3920 } else { 3921 // update position if w is in Q 3922 Q.updateItem(w); 3923 } 3924 3925 g[w] = 0; 3926 P[w] = []; 3927 } 3928 3929 if (d[w] == d[_v] + edgeWeight) { 3930 g[w] = g[w] + g[_v]; 3931 P[w].push(_v); 3932 } 3933 } 3934 } else { 3935 for (var _j = 0; _j < A[_v].length; _j++) { 3936 var _w = A[_v][_j].id(); 3937 3938 if (d[_w] == Infinity) { 3939 Q.push(_w); 3940 d[_w] = d[_v] + 1; 3941 } 3942 3943 if (d[_w] == d[_v] + 1) { 3944 g[_w] = g[_w] + g[_v]; 3945 3946 P[_w].push(_v); 3947 } 3948 } 3949 } 3950 } 3951 3952 var e = {}; 3953 3954 for (var _i2 = 0; _i2 < V.length; _i2++) { 3955 e[V[_i2].id()] = 0; 3956 } 3957 3958 while (S.length > 0) { 3959 var _w2 = S.pop(); 3960 3961 for (var _j2 = 0; _j2 < P[_w2].length; _j2++) { 3962 var _v2 = P[_w2][_j2]; 3963 e[_v2] = e[_v2] + g[_v2] / g[_w2] * (1 + e[_w2]); 3964 3965 if (_w2 != V[s].id()) { 3966 C.set(_w2, C.get(_w2) + e[_w2]); 3967 } 3968 } 3969 } 3970 }; 3971 3972 for (var s = 0; s < V.length; s++) { 3973 _loop(s); 3974 } 3975 3976 var ret = { 3977 betweenness: function betweenness(node) { 3978 var id = cy.collection(node).id(); 3979 return C.get(id); 3980 }, 3981 betweennessNormalized: function betweennessNormalized(node) { 3982 if (max == 0) { 3983 return 0; 3984 } 3985 3986 var id = cy.collection(node).id(); 3987 return C.get(id) / max; 3988 } 3989 }; // alias 3990 3991 ret.betweennessNormalised = ret.betweennessNormalized; 3992 return ret; 3993 } // betweennessCentrality 3994 3995 }; // elesfn 3996 // nice, short mathemathical alias 3997 3998 elesfn$a.bc = elesfn$a.betweennessCentrality; 3999 4000 // Implemented by Zoe Xi @zoexi for GSOC 2016 4001 /* eslint-disable no-unused-vars */ 4002 4003 var defaults$4 = defaults({ 4004 expandFactor: 2, 4005 // affects time of computation and cluster granularity to some extent: M * M 4006 inflateFactor: 2, 4007 // affects cluster granularity (the greater the value, the more clusters): M(i,j) / E(j) 4008 multFactor: 1, 4009 // optional self loops for each node. Use a neutral value to improve cluster computations. 4010 maxIterations: 20, 4011 // maximum number of iterations of the MCL algorithm in a single run 4012 attributes: [// attributes/features used to group nodes, ie. similarity values between nodes 4013 function (edge) { 4014 return 1; 4015 }] 4016 }); 4017 /* eslint-enable */ 4018 4019 var setOptions = function setOptions(options) { 4020 return defaults$4(options); 4021 }; 4022 /* eslint-enable */ 4023 4024 4025 var getSimilarity = function getSimilarity(edge, attributes) { 4026 var total = 0; 4027 4028 for (var i = 0; i < attributes.length; i++) { 4029 total += attributes[i](edge); 4030 } 4031 4032 return total; 4033 }; 4034 4035 var addLoops = function addLoops(M, n, val) { 4036 for (var i = 0; i < n; i++) { 4037 M[i * n + i] = val; 4038 } 4039 }; 4040 4041 var normalize = function normalize(M, n) { 4042 var sum; 4043 4044 for (var col = 0; col < n; col++) { 4045 sum = 0; 4046 4047 for (var row = 0; row < n; row++) { 4048 sum += M[row * n + col]; 4049 } 4050 4051 for (var _row = 0; _row < n; _row++) { 4052 M[_row * n + col] = M[_row * n + col] / sum; 4053 } 4054 } 4055 }; // TODO: blocked matrix multiplication? 4056 4057 4058 var mmult = function mmult(A, B, n) { 4059 var C = new Array(n * n); 4060 4061 for (var i = 0; i < n; i++) { 4062 for (var j = 0; j < n; j++) { 4063 C[i * n + j] = 0; 4064 } 4065 4066 for (var k = 0; k < n; k++) { 4067 for (var _j = 0; _j < n; _j++) { 4068 C[i * n + _j] += A[i * n + k] * B[k * n + _j]; 4069 } 4070 } 4071 } 4072 4073 return C; 4074 }; 4075 4076 var expand = function expand(M, n, expandFactor 4077 /** power **/ 4078 ) { 4079 var _M = M.slice(0); 4080 4081 for (var p = 1; p < expandFactor; p++) { 4082 M = mmult(M, _M, n); 4083 } 4084 4085 return M; 4086 }; 4087 4088 var inflate = function inflate(M, n, inflateFactor 4089 /** r **/ 4090 ) { 4091 var _M = new Array(n * n); // M(i,j) ^ inflatePower 4092 4093 4094 for (var i = 0; i < n * n; i++) { 4095 _M[i] = Math.pow(M[i], inflateFactor); 4096 } 4097 4098 normalize(_M, n); 4099 return _M; 4100 }; 4101 4102 var hasConverged = function hasConverged(M, _M, n2, roundFactor) { 4103 // Check that both matrices have the same elements (i,j) 4104 for (var i = 0; i < n2; i++) { 4105 var v1 = Math.round(M[i] * Math.pow(10, roundFactor)) / Math.pow(10, roundFactor); // truncate to 'roundFactor' decimal places 4106 4107 var v2 = Math.round(_M[i] * Math.pow(10, roundFactor)) / Math.pow(10, roundFactor); 4108 4109 if (v1 !== v2) { 4110 return false; 4111 } 4112 } 4113 4114 return true; 4115 }; 4116 4117 var assign = function assign(M, n, nodes, cy) { 4118 var clusters = []; 4119 4120 for (var i = 0; i < n; i++) { 4121 var cluster = []; 4122 4123 for (var j = 0; j < n; j++) { 4124 // Row-wise attractors and elements that they attract belong in same cluster 4125 if (Math.round(M[i * n + j] * 1000) / 1000 > 0) { 4126 cluster.push(nodes[j]); 4127 } 4128 } 4129 4130 if (cluster.length !== 0) { 4131 clusters.push(cy.collection(cluster)); 4132 } 4133 } 4134 4135 return clusters; 4136 }; 4137 4138 var isDuplicate = function isDuplicate(c1, c2) { 4139 for (var i = 0; i < c1.length; i++) { 4140 if (!c2[i] || c1[i].id() !== c2[i].id()) { 4141 return false; 4142 } 4143 } 4144 4145 return true; 4146 }; 4147 4148 var removeDuplicates = function removeDuplicates(clusters) { 4149 for (var i = 0; i < clusters.length; i++) { 4150 for (var j = 0; j < clusters.length; j++) { 4151 if (i != j && isDuplicate(clusters[i], clusters[j])) { 4152 clusters.splice(j, 1); 4153 } 4154 } 4155 } 4156 4157 return clusters; 4158 }; 4159 4160 var markovClustering = function markovClustering(options) { 4161 var nodes = this.nodes(); 4162 var edges = this.edges(); 4163 var cy = this.cy(); // Set parameters of algorithm: 4164 4165 var opts = setOptions(options); // Map each node to its position in node array 4166 4167 var id2position = {}; 4168 4169 for (var i = 0; i < nodes.length; i++) { 4170 id2position[nodes[i].id()] = i; 4171 } // Generate stochastic matrix M from input graph G (should be symmetric/undirected) 4172 4173 4174 var n = nodes.length, 4175 n2 = n * n; 4176 4177 var M = new Array(n2), 4178 _M; 4179 4180 for (var _i = 0; _i < n2; _i++) { 4181 M[_i] = 0; 4182 } 4183 4184 for (var e = 0; e < edges.length; e++) { 4185 var edge = edges[e]; 4186 var _i2 = id2position[edge.source().id()]; 4187 var j = id2position[edge.target().id()]; 4188 var sim = getSimilarity(edge, opts.attributes); 4189 M[_i2 * n + j] += sim; // G should be symmetric and undirected 4190 4191 M[j * n + _i2] += sim; 4192 } // Begin Markov cluster algorithm 4193 // Step 1: Add self loops to each node, ie. add multFactor to matrix diagonal 4194 4195 4196 addLoops(M, n, opts.multFactor); // Step 2: M = normalize( M ); 4197 4198 normalize(M, n); 4199 var isStillMoving = true; 4200 var iterations = 0; 4201 4202 while (isStillMoving && iterations < opts.maxIterations) { 4203 isStillMoving = false; // Step 3: 4204 4205 _M = expand(M, n, opts.expandFactor); // Step 4: 4206 4207 M = inflate(_M, n, opts.inflateFactor); // Step 5: check to see if ~steady state has been reached 4208 4209 if (!hasConverged(M, _M, n2, 4)) { 4210 isStillMoving = true; 4211 } 4212 4213 iterations++; 4214 } // Build clusters from matrix 4215 4216 4217 var clusters = assign(M, n, nodes, cy); // Remove duplicate clusters due to symmetry of graph and M matrix 4218 4219 clusters = removeDuplicates(clusters); 4220 return clusters; 4221 }; 4222 4223 var markovClustering$1 = { 4224 markovClustering: markovClustering, 4225 mcl: markovClustering 4226 }; 4227 4228 // Common distance metrics for clustering algorithms 4229 4230 var identity = function identity(x) { 4231 return x; 4232 }; 4233 4234 var absDiff = function absDiff(p, q) { 4235 return Math.abs(q - p); 4236 }; 4237 4238 var addAbsDiff = function addAbsDiff(total, p, q) { 4239 return total + absDiff(p, q); 4240 }; 4241 4242 var addSquaredDiff = function addSquaredDiff(total, p, q) { 4243 return total + Math.pow(q - p, 2); 4244 }; 4245 4246 var sqrt = function sqrt(x) { 4247 return Math.sqrt(x); 4248 }; 4249 4250 var maxAbsDiff = function maxAbsDiff(currentMax, p, q) { 4251 return Math.max(currentMax, absDiff(p, q)); 4252 }; 4253 4254 var getDistance = function getDistance(length, getP, getQ, init, visit) { 4255 var post = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : identity; 4256 var ret = init; 4257 var p, q; 4258 4259 for (var dim = 0; dim < length; dim++) { 4260 p = getP(dim); 4261 q = getQ(dim); 4262 ret = visit(ret, p, q); 4263 } 4264 4265 return post(ret); 4266 }; 4267 4268 var distances = { 4269 euclidean: function euclidean(length, getP, getQ) { 4270 if (length >= 2) { 4271 return getDistance(length, getP, getQ, 0, addSquaredDiff, sqrt); 4272 } else { 4273 // for single attr case, more efficient to avoid sqrt 4274 return getDistance(length, getP, getQ, 0, addAbsDiff); 4275 } 4276 }, 4277 squaredEuclidean: function squaredEuclidean(length, getP, getQ) { 4278 return getDistance(length, getP, getQ, 0, addSquaredDiff); 4279 }, 4280 manhattan: function manhattan(length, getP, getQ) { 4281 return getDistance(length, getP, getQ, 0, addAbsDiff); 4282 }, 4283 max: function max(length, getP, getQ) { 4284 return getDistance(length, getP, getQ, -Infinity, maxAbsDiff); 4285 } 4286 }; // in case the user accidentally doesn't use camel case 4287 4288 distances['squared-euclidean'] = distances['squaredEuclidean']; 4289 distances['squaredeuclidean'] = distances['squaredEuclidean']; 4290 function clusteringDistance (method, length, getP, getQ, nodeP, nodeQ) { 4291 var impl; 4292 4293 if (fn(method)) { 4294 impl = method; 4295 } else { 4296 impl = distances[method] || distances.euclidean; 4297 } 4298 4299 if (length === 0 && fn(method)) { 4300 return impl(nodeP, nodeQ); 4301 } else { 4302 return impl(length, getP, getQ, nodeP, nodeQ); 4303 } 4304 } 4305 4306 var defaults$5 = defaults({ 4307 k: 2, 4308 m: 2, 4309 sensitivityThreshold: 0.0001, 4310 distance: 'euclidean', 4311 maxIterations: 10, 4312 attributes: [], 4313 testMode: false, 4314 testCentroids: null 4315 }); 4316 4317 var setOptions$1 = function setOptions(options) { 4318 return defaults$5(options); 4319 }; 4320 /* eslint-enable */ 4321 4322 4323 var getDist = function getDist(type, node, centroid, attributes, mode) { 4324 var noNodeP = mode !== 'kMedoids'; 4325 var getP = noNodeP ? function (i) { 4326 return centroid[i]; 4327 } : function (i) { 4328 return attributes[i](centroid); 4329 }; 4330 4331 var getQ = function getQ(i) { 4332 return attributes[i](node); 4333 }; 4334 4335 var nodeP = centroid; 4336 var nodeQ = node; 4337 return clusteringDistance(type, attributes.length, getP, getQ, nodeP, nodeQ); 4338 }; 4339 4340 var randomCentroids = function randomCentroids(nodes, k, attributes) { 4341 var ndim = attributes.length; 4342 var min = new Array(ndim); 4343 var max = new Array(ndim); 4344 var centroids = new Array(k); 4345 var centroid = null; // Find min, max values for each attribute dimension 4346 4347 for (var i = 0; i < ndim; i++) { 4348 min[i] = nodes.min(attributes[i]).value; 4349 max[i] = nodes.max(attributes[i]).value; 4350 } // Build k centroids, each represented as an n-dim feature vector 4351 4352 4353 for (var c = 0; c < k; c++) { 4354 centroid = []; 4355 4356 for (var _i = 0; _i < ndim; _i++) { 4357 centroid[_i] = Math.random() * (max[_i] - min[_i]) + min[_i]; // random initial value 4358 } 4359 4360 centroids[c] = centroid; 4361 } 4362 4363 return centroids; 4364 }; 4365 4366 var classify = function classify(node, centroids, distance, attributes, type) { 4367 var min = Infinity; 4368 var index = 0; 4369 4370 for (var i = 0; i < centroids.length; i++) { 4371 var dist = getDist(distance, node, centroids[i], attributes, type); 4372 4373 if (dist < min) { 4374 min = dist; 4375 index = i; 4376 } 4377 } 4378 4379 return index; 4380 }; 4381 4382 var buildCluster = function buildCluster(centroid, nodes, assignment) { 4383 var cluster = []; 4384 var node = null; 4385 4386 for (var n = 0; n < nodes.length; n++) { 4387 node = nodes[n]; 4388 4389 if (assignment[node.id()] === centroid) { 4390 //console.log("Node " + node.id() + " is associated with medoid #: " + m); 4391 cluster.push(node); 4392 } 4393 } 4394 4395 return cluster; 4396 }; 4397 4398 var haveValuesConverged = function haveValuesConverged(v1, v2, sensitivityThreshold) { 4399 return Math.abs(v2 - v1) <= sensitivityThreshold; 4400 }; 4401 4402 var haveMatricesConverged = function haveMatricesConverged(v1, v2, sensitivityThreshold) { 4403 for (var i = 0; i < v1.length; i++) { 4404 for (var j = 0; j < v1[i].length; j++) { 4405 var diff = Math.abs(v1[i][j] - v2[i][j]); 4406 4407 if (diff > sensitivityThreshold) { 4408 return false; 4409 } 4410 } 4411 } 4412 4413 return true; 4414 }; 4415 4416 var seenBefore = function seenBefore(node, medoids, n) { 4417 for (var i = 0; i < n; i++) { 4418 if (node === medoids[i]) return true; 4419 } 4420 4421 return false; 4422 }; 4423 4424 var randomMedoids = function randomMedoids(nodes, k) { 4425 var medoids = new Array(k); // For small data sets, the probability of medoid conflict is greater, 4426 // so we need to check to see if we've already seen or chose this node before. 4427 4428 if (nodes.length < 50) { 4429 // Randomly select k medoids from the n nodes 4430 for (var i = 0; i < k; i++) { 4431 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). 4432 // Instead choose a different random node. 4433 4434 while (seenBefore(node, medoids, i)) { 4435 node = nodes[Math.floor(Math.random() * nodes.length)]; 4436 } 4437 4438 medoids[i] = node; 4439 } 4440 } else { 4441 // Relatively large data set, so pretty safe to not check and just select random nodes 4442 for (var _i2 = 0; _i2 < k; _i2++) { 4443 medoids[_i2] = nodes[Math.floor(Math.random() * nodes.length)]; 4444 } 4445 } 4446 4447 return medoids; 4448 }; 4449 4450 var findCost = function findCost(potentialNewMedoid, cluster, attributes) { 4451 var cost = 0; 4452 4453 for (var n = 0; n < cluster.length; n++) { 4454 cost += getDist('manhattan', cluster[n], potentialNewMedoid, attributes, 'kMedoids'); 4455 } 4456 4457 return cost; 4458 }; 4459 4460 var kMeans = function kMeans(options) { 4461 var cy = this.cy(); 4462 var nodes = this.nodes(); 4463 var node = null; // Set parameters of algorithm: # of clusters, distance metric, etc. 4464 4465 var opts = setOptions$1(options); // Begin k-means algorithm 4466 4467 var clusters = new Array(opts.k); 4468 var assignment = {}; 4469 var centroids; // Step 1: Initialize centroid positions 4470 4471 if (opts.testMode) { 4472 if (typeof opts.testCentroids === 'number') { 4473 centroids = randomCentroids(nodes, opts.k, opts.attributes); 4474 } else if (_typeof(opts.testCentroids) === 'object') { 4475 centroids = opts.testCentroids; 4476 } else { 4477 centroids = randomCentroids(nodes, opts.k, opts.attributes); 4478 } 4479 } else { 4480 centroids = randomCentroids(nodes, opts.k, opts.attributes); 4481 } 4482 4483 var isStillMoving = true; 4484 var iterations = 0; 4485 4486 while (isStillMoving && iterations < opts.maxIterations) { 4487 // Step 2: Assign nodes to the nearest centroid 4488 for (var n = 0; n < nodes.length; n++) { 4489 node = nodes[n]; // Determine which cluster this node belongs to: node id => cluster # 4490 4491 assignment[node.id()] = classify(node, centroids, opts.distance, opts.attributes, 'kMeans'); 4492 } // Step 3: For each of the k clusters, update its centroid 4493 4494 4495 isStillMoving = false; 4496 4497 for (var c = 0; c < opts.k; c++) { 4498 // Get all nodes that belong to this cluster 4499 var cluster = buildCluster(c, nodes, assignment); 4500 4501 if (cluster.length === 0) { 4502 // If cluster is empty, break out early & move to next cluster 4503 continue; 4504 } // Update centroids by calculating avg of all nodes within the cluster. 4505 4506 4507 var ndim = opts.attributes.length; 4508 var centroid = centroids[c]; // [ dim_1, dim_2, dim_3, ... , dim_n ] 4509 4510 var newCentroid = new Array(ndim); 4511 var sum = new Array(ndim); 4512 4513 for (var d = 0; d < ndim; d++) { 4514 sum[d] = 0.0; 4515 4516 for (var i = 0; i < cluster.length; i++) { 4517 node = cluster[i]; 4518 sum[d] += opts.attributes[d](node); 4519 } 4520 4521 newCentroid[d] = sum[d] / cluster.length; // Check to see if algorithm has converged, i.e. when centroids no longer change 4522 4523 if (!haveValuesConverged(newCentroid[d], centroid[d], opts.sensitivityThreshold)) { 4524 isStillMoving = true; 4525 } 4526 } 4527 4528 centroids[c] = newCentroid; 4529 clusters[c] = cy.collection(cluster); 4530 } 4531 4532 iterations++; 4533 } 4534 4535 return clusters; 4536 }; 4537 4538 var kMedoids = function kMedoids(options) { 4539 var cy = this.cy(); 4540 var nodes = this.nodes(); 4541 var node = null; 4542 var opts = setOptions$1(options); // Begin k-medoids algorithm 4543 4544 var clusters = new Array(opts.k); 4545 var medoids; 4546 var assignment = {}; 4547 var curCost; 4548 var minCosts = new Array(opts.k); // minimum cost configuration for each cluster 4549 // Step 1: Initialize k medoids 4550 4551 if (opts.testMode) { 4552 if (typeof opts.testCentroids === 'number') ; else if (_typeof(opts.testCentroids) === 'object') { 4553 medoids = opts.testCentroids; 4554 } else { 4555 medoids = randomMedoids(nodes, opts.k); 4556 } 4557 } else { 4558 medoids = randomMedoids(nodes, opts.k); 4559 } 4560 4561 var isStillMoving = true; 4562 var iterations = 0; 4563 4564 while (isStillMoving && iterations < opts.maxIterations) { 4565 // Step 2: Assign nodes to the nearest medoid 4566 for (var n = 0; n < nodes.length; n++) { 4567 node = nodes[n]; // Determine which cluster this node belongs to: node id => cluster # 4568 4569 assignment[node.id()] = classify(node, medoids, opts.distance, opts.attributes, 'kMedoids'); 4570 } 4571 4572 isStillMoving = false; // Step 3: For each medoid m, and for each node assciated with mediod m, 4573 // select the node with the lowest configuration cost as new medoid. 4574 4575 for (var m = 0; m < medoids.length; m++) { 4576 // Get all nodes that belong to this medoid 4577 var cluster = buildCluster(m, nodes, assignment); 4578 4579 if (cluster.length === 0) { 4580 // If cluster is empty, break out early & move to next cluster 4581 continue; 4582 } 4583 4584 minCosts[m] = findCost(medoids[m], cluster, opts.attributes); // original cost 4585 // Select different medoid if its configuration has the lowest cost 4586 4587 for (var _n = 0; _n < cluster.length; _n++) { 4588 curCost = findCost(cluster[_n], cluster, opts.attributes); 4589 4590 if (curCost < minCosts[m]) { 4591 minCosts[m] = curCost; 4592 medoids[m] = cluster[_n]; 4593 isStillMoving = true; 4594 } 4595 } 4596 4597 clusters[m] = cy.collection(cluster); 4598 } 4599 4600 iterations++; 4601 } 4602 4603 return clusters; 4604 }; 4605 4606 var updateCentroids = function updateCentroids(centroids, nodes, U, weight, opts) { 4607 var numerator, denominator; 4608 4609 for (var n = 0; n < nodes.length; n++) { 4610 for (var c = 0; c < centroids.length; c++) { 4611 weight[n][c] = Math.pow(U[n][c], opts.m); 4612 } 4613 } 4614 4615 for (var _c = 0; _c < centroids.length; _c++) { 4616 for (var dim = 0; dim < opts.attributes.length; dim++) { 4617 numerator = 0; 4618 denominator = 0; 4619 4620 for (var _n2 = 0; _n2 < nodes.length; _n2++) { 4621 numerator += weight[_n2][_c] * opts.attributes[dim](nodes[_n2]); 4622 denominator += weight[_n2][_c]; 4623 } 4624 4625 centroids[_c][dim] = numerator / denominator; 4626 } 4627 } 4628 }; 4629 4630 var updateMembership = function updateMembership(U, _U, centroids, nodes, opts) { 4631 // Save previous step 4632 for (var i = 0; i < U.length; i++) { 4633 _U[i] = U[i].slice(); 4634 } 4635 4636 var sum, numerator, denominator; 4637 var pow = 2 / (opts.m - 1); 4638 4639 for (var c = 0; c < centroids.length; c++) { 4640 for (var n = 0; n < nodes.length; n++) { 4641 sum = 0; 4642 4643 for (var k = 0; k < centroids.length; k++) { 4644 // against all other centroids 4645 numerator = getDist(opts.distance, nodes[n], centroids[c], opts.attributes, 'cmeans'); 4646 denominator = getDist(opts.distance, nodes[n], centroids[k], opts.attributes, 'cmeans'); 4647 sum += Math.pow(numerator / denominator, pow); 4648 } 4649 4650 U[n][c] = 1 / sum; 4651 } 4652 } 4653 }; 4654 4655 var assign$1 = function assign(nodes, U, opts, cy) { 4656 var clusters = new Array(opts.k); 4657 4658 for (var c = 0; c < clusters.length; c++) { 4659 clusters[c] = []; 4660 } 4661 4662 var max; 4663 var index; 4664 4665 for (var n = 0; n < U.length; n++) { 4666 // for each node (U is N x C matrix) 4667 max = -Infinity; 4668 index = -1; // Determine which cluster the node is most likely to belong in 4669 4670 for (var _c2 = 0; _c2 < U[0].length; _c2++) { 4671 if (U[n][_c2] > max) { 4672 max = U[n][_c2]; 4673 index = _c2; 4674 } 4675 } 4676 4677 clusters[index].push(nodes[n]); 4678 } // Turn every array into a collection of nodes 4679 4680 4681 for (var _c3 = 0; _c3 < clusters.length; _c3++) { 4682 clusters[_c3] = cy.collection(clusters[_c3]); 4683 } 4684 4685 return clusters; 4686 }; 4687 4688 var fuzzyCMeans = function fuzzyCMeans(options) { 4689 var cy = this.cy(); 4690 var nodes = this.nodes(); 4691 var opts = setOptions$1(options); // Begin fuzzy c-means algorithm 4692 4693 var clusters; 4694 var centroids; 4695 var U; 4696 4697 var _U; 4698 4699 var weight; // Step 1: Initialize letiables. 4700 4701 _U = new Array(nodes.length); 4702 4703 for (var i = 0; i < nodes.length; i++) { 4704 // N x C matrix 4705 _U[i] = new Array(opts.k); 4706 } 4707 4708 U = new Array(nodes.length); 4709 4710 for (var _i3 = 0; _i3 < nodes.length; _i3++) { 4711 // N x C matrix 4712 U[_i3] = new Array(opts.k); 4713 } 4714 4715 for (var _i4 = 0; _i4 < nodes.length; _i4++) { 4716 var total = 0; 4717 4718 for (var j = 0; j < opts.k; j++) { 4719 U[_i4][j] = Math.random(); 4720 total += U[_i4][j]; 4721 } 4722 4723 for (var _j = 0; _j < opts.k; _j++) { 4724 U[_i4][_j] = U[_i4][_j] / total; 4725 } 4726 } 4727 4728 centroids = new Array(opts.k); 4729 4730 for (var _i5 = 0; _i5 < opts.k; _i5++) { 4731 centroids[_i5] = new Array(opts.attributes.length); 4732 } 4733 4734 weight = new Array(nodes.length); 4735 4736 for (var _i6 = 0; _i6 < nodes.length; _i6++) { 4737 // N x C matrix 4738 weight[_i6] = new Array(opts.k); 4739 } // end init FCM 4740 4741 4742 var isStillMoving = true; 4743 var iterations = 0; 4744 4745 while (isStillMoving && iterations < opts.maxIterations) { 4746 isStillMoving = false; // Step 2: Calculate the centroids for each step. 4747 4748 updateCentroids(centroids, nodes, U, weight, opts); // Step 3: Update the partition matrix U. 4749 4750 updateMembership(U, _U, centroids, nodes, opts); // Step 4: Check for convergence. 4751 4752 if (!haveMatricesConverged(U, _U, opts.sensitivityThreshold)) { 4753 isStillMoving = true; 4754 } 4755 4756 iterations++; 4757 } // Assign nodes to clusters with highest probability. 4758 4759 4760 clusters = assign$1(nodes, U, opts, cy); 4761 return { 4762 clusters: clusters, 4763 degreeOfMembership: U 4764 }; 4765 }; 4766 4767 var kClustering = { 4768 kMeans: kMeans, 4769 kMedoids: kMedoids, 4770 fuzzyCMeans: fuzzyCMeans, 4771 fcm: fuzzyCMeans 4772 }; 4773 4774 // Implemented by Zoe Xi @zoexi for GSOC 2016 4775 var defaults$6 = defaults({ 4776 distance: 'euclidean', 4777 // distance metric to compare nodes 4778 linkage: 'min', 4779 // linkage criterion : how to determine the distance between clusters of nodes 4780 mode: 'threshold', 4781 // mode:'threshold' => clusters must be threshold distance apart 4782 threshold: Infinity, 4783 // the distance threshold 4784 // mode:'dendrogram' => the nodes are organised as leaves in a tree (siblings are close), merging makes clusters 4785 addDendrogram: false, 4786 // whether to add the dendrogram to the graph for viz 4787 dendrogramDepth: 0, 4788 // depth at which dendrogram branches are merged into the returned clusters 4789 attributes: [] // array of attr functions 4790 4791 }); 4792 var linkageAliases = { 4793 'single': 'min', 4794 'complete': 'max' 4795 }; 4796 4797 var setOptions$2 = function setOptions(options) { 4798 var opts = defaults$6(options); 4799 var preferredAlias = linkageAliases[opts.linkage]; 4800 4801 if (preferredAlias != null) { 4802 opts.linkage = preferredAlias; 4803 } 4804 4805 return opts; 4806 }; 4807 4808 var mergeClosest = function mergeClosest(clusters, index, dists, mins, opts) { 4809 // Find two closest clusters from cached mins 4810 var minKey = 0; 4811 var min = Infinity; 4812 var dist; 4813 var attrs = opts.attributes; 4814 4815 var getDist = function getDist(n1, n2) { 4816 return clusteringDistance(opts.distance, attrs.length, function (i) { 4817 return attrs[i](n1); 4818 }, function (i) { 4819 return attrs[i](n2); 4820 }, n1, n2); 4821 }; 4822 4823 for (var i = 0; i < clusters.length; i++) { 4824 var key = clusters[i].key; 4825 var _dist = dists[key][mins[key]]; 4826 4827 if (_dist < min) { 4828 minKey = key; 4829 min = _dist; 4830 } 4831 } 4832 4833 if (opts.mode === 'threshold' && min >= opts.threshold || opts.mode === 'dendrogram' && clusters.length === 1) { 4834 return false; 4835 } 4836 4837 var c1 = index[minKey]; 4838 var c2 = index[mins[minKey]]; 4839 var merged; // Merge two closest clusters 4840 4841 if (opts.mode === 'dendrogram') { 4842 merged = { 4843 left: c1, 4844 right: c2, 4845 key: c1.key 4846 }; 4847 } else { 4848 merged = { 4849 value: c1.value.concat(c2.value), 4850 key: c1.key 4851 }; 4852 } 4853 4854 clusters[c1.index] = merged; 4855 clusters.splice(c2.index, 1); 4856 index[c1.key] = merged; // Update distances with new merged cluster 4857 4858 for (var _i = 0; _i < clusters.length; _i++) { 4859 var cur = clusters[_i]; 4860 4861 if (c1.key === cur.key) { 4862 dist = Infinity; 4863 } else if (opts.linkage === 'min') { 4864 dist = dists[c1.key][cur.key]; 4865 4866 if (dists[c1.key][cur.key] > dists[c2.key][cur.key]) { 4867 dist = dists[c2.key][cur.key]; 4868 } 4869 } else if (opts.linkage === 'max') { 4870 dist = dists[c1.key][cur.key]; 4871 4872 if (dists[c1.key][cur.key] < dists[c2.key][cur.key]) { 4873 dist = dists[c2.key][cur.key]; 4874 } 4875 } else if (opts.linkage === 'mean') { 4876 dist = (dists[c1.key][cur.key] * c1.size + dists[c2.key][cur.key] * c2.size) / (c1.size + c2.size); 4877 } else { 4878 if (opts.mode === 'dendrogram') dist = getDist(cur.value, c1.value);else dist = getDist(cur.value[0], c1.value[0]); 4879 } 4880 4881 dists[c1.key][cur.key] = dists[cur.key][c1.key] = dist; // distance matrix is symmetric 4882 } // Update cached mins 4883 4884 4885 for (var _i2 = 0; _i2 < clusters.length; _i2++) { 4886 var key1 = clusters[_i2].key; 4887 4888 if (mins[key1] === c1.key || mins[key1] === c2.key) { 4889 var _min = key1; 4890 4891 for (var j = 0; j < clusters.length; j++) { 4892 var key2 = clusters[j].key; 4893 4894 if (dists[key1][key2] < dists[key1][_min]) { 4895 _min = key2; 4896 } 4897 } 4898 4899 mins[key1] = _min; 4900 } 4901 4902 clusters[_i2].index = _i2; 4903 } // Clean up meta data used for clustering 4904 4905 4906 c1.key = c2.key = c1.index = c2.index = null; 4907 return true; 4908 }; 4909 4910 var getAllChildren = function getAllChildren(root, arr, cy) { 4911 if (!root) return; 4912 4913 if (root.value) { 4914 arr.push(root.value); 4915 } else { 4916 if (root.left) getAllChildren(root.left, arr); 4917 if (root.right) getAllChildren(root.right, arr); 4918 } 4919 }; 4920 4921 var buildDendrogram = function buildDendrogram(root, cy) { 4922 if (!root) return ''; 4923 4924 if (root.left && root.right) { 4925 var leftStr = buildDendrogram(root.left, cy); 4926 var rightStr = buildDendrogram(root.right, cy); 4927 var node = cy.add({ 4928 group: 'nodes', 4929 data: { 4930 id: leftStr + ',' + rightStr 4931 } 4932 }); 4933 cy.add({ 4934 group: 'edges', 4935 data: { 4936 source: leftStr, 4937 target: node.id() 4938 } 4939 }); 4940 cy.add({ 4941 group: 'edges', 4942 data: { 4943 source: rightStr, 4944 target: node.id() 4945 } 4946 }); 4947 return node.id(); 4948 } else if (root.value) { 4949 return root.value.id(); 4950 } 4951 }; 4952 4953 var buildClustersFromTree = function buildClustersFromTree(root, k, cy) { 4954 if (!root) return []; 4955 var left = [], 4956 right = [], 4957 leaves = []; 4958 4959 if (k === 0) { 4960 // don't cut tree, simply return all nodes as 1 single cluster 4961 if (root.left) getAllChildren(root.left, left); 4962 if (root.right) getAllChildren(root.right, right); 4963 leaves = left.concat(right); 4964 return [cy.collection(leaves)]; 4965 } else if (k === 1) { 4966 // cut at root 4967 if (root.value) { 4968 // leaf node 4969 return [cy.collection(root.value)]; 4970 } else { 4971 if (root.left) getAllChildren(root.left, left); 4972 if (root.right) getAllChildren(root.right, right); 4973 return [cy.collection(left), cy.collection(right)]; 4974 } 4975 } else { 4976 if (root.value) { 4977 return [cy.collection(root.value)]; 4978 } else { 4979 if (root.left) left = buildClustersFromTree(root.left, k - 1, cy); 4980 if (root.right) right = buildClustersFromTree(root.right, k - 1, cy); 4981 return left.concat(right); 4982 } 4983 } 4984 }; 4985 /* eslint-enable */ 4986 4987 4988 var hierarchicalClustering = function hierarchicalClustering(options) { 4989 var cy = this.cy(); 4990 var nodes = this.nodes(); // Set parameters of algorithm: linkage type, distance metric, etc. 4991 4992 var opts = setOptions$2(options); 4993 var attrs = opts.attributes; 4994 4995 var getDist = function getDist(n1, n2) { 4996 return clusteringDistance(opts.distance, attrs.length, function (i) { 4997 return attrs[i](n1); 4998 }, function (i) { 4999 return attrs[i](n2); 5000 }, n1, n2); 5001 }; // Begin hierarchical algorithm 5002 5003 5004 var clusters = []; 5005 var dists = []; // distances between each pair of clusters 5006 5007 var mins = []; // closest cluster for each cluster 5008 5009 var index = []; // hash of all clusters by key 5010 // In agglomerative (bottom-up) clustering, each node starts as its own cluster 5011 5012 for (var n = 0; n < nodes.length; n++) { 5013 var cluster = { 5014 value: opts.mode === 'dendrogram' ? nodes[n] : [nodes[n]], 5015 key: n, 5016 index: n 5017 }; 5018 clusters[n] = cluster; 5019 index[n] = cluster; 5020 dists[n] = []; 5021 mins[n] = 0; 5022 } // Calculate the distance between each pair of clusters 5023 5024 5025 for (var i = 0; i < clusters.length; i++) { 5026 for (var j = 0; j <= i; j++) { 5027 var dist = void 0; 5028 5029 if (opts.mode === 'dendrogram') { 5030 // modes store cluster values differently 5031 dist = i === j ? Infinity : getDist(clusters[i].value, clusters[j].value); 5032 } else { 5033 dist = i === j ? Infinity : getDist(clusters[i].value[0], clusters[j].value[0]); 5034 } 5035 5036 dists[i][j] = dist; 5037 dists[j][i] = dist; 5038 5039 if (dist < dists[i][mins[i]]) { 5040 mins[i] = j; // Cache mins: closest cluster to cluster i is cluster j 5041 } 5042 } 5043 } // Find the closest pair of clusters and merge them into a single cluster. 5044 // Update distances between new cluster and each of the old clusters, and loop until threshold reached. 5045 5046 5047 var merged = mergeClosest(clusters, index, dists, mins, opts); 5048 5049 while (merged) { 5050 merged = mergeClosest(clusters, index, dists, mins, opts); 5051 } 5052 5053 var retClusters; // Dendrogram mode builds the hierarchy and adds intermediary nodes + edges 5054 // in addition to returning the clusters. 5055 5056 if (opts.mode === 'dendrogram') { 5057 retClusters = buildClustersFromTree(clusters[0], opts.dendrogramDepth, cy); 5058 if (opts.addDendrogram) buildDendrogram(clusters[0], cy); 5059 } else { 5060 // Regular mode simply returns the clusters 5061 retClusters = new Array(clusters.length); 5062 clusters.forEach(function (cluster, i) { 5063 // Clean up meta data used for clustering 5064 cluster.key = cluster.index = null; 5065 retClusters[i] = cy.collection(cluster.value); 5066 }); 5067 } 5068 5069 return retClusters; 5070 }; 5071 5072 var hierarchicalClustering$1 = { 5073 hierarchicalClustering: hierarchicalClustering, 5074 hca: hierarchicalClustering 5075 }; 5076 5077 // Implemented by Zoe Xi @zoexi for GSOC 2016 5078 var defaults$7 = defaults({ 5079 distance: 'euclidean', 5080 // distance metric to compare attributes between two nodes 5081 preference: 'median', 5082 // suitability of a data point to serve as an exemplar 5083 damping: 0.8, 5084 // damping factor between [0.5, 1) 5085 maxIterations: 1000, 5086 // max number of iterations to run 5087 minIterations: 100, 5088 // min number of iterations to run in order for clustering to stop 5089 attributes: [// functions to quantify the similarity between any two points 5090 // e.g. node => node.data('weight') 5091 ] 5092 }); 5093 5094 var setOptions$3 = function setOptions(options) { 5095 var dmp = options.damping; 5096 var pref = options.preference; 5097 5098 if (!(0.5 <= dmp && dmp < 1)) { 5099 error("Damping must range on [0.5, 1). Got: ".concat(dmp)); 5100 } 5101 5102 var validPrefs = ['median', 'mean', 'min', 'max']; 5103 5104 if (!(validPrefs.some(function (v) { 5105 return v === pref; 5106 }) || number(pref))) { 5107 error("Preference must be one of [".concat(validPrefs.map(function (p) { 5108 return "'".concat(p, "'"); 5109 }).join(', '), "] or a number. Got: ").concat(pref)); 5110 } 5111 5112 return defaults$7(options); 5113 }; 5114 /* eslint-enable */ 5115 5116 5117 var getSimilarity$1 = function getSimilarity(type, n1, n2, attributes) { 5118 var attr = function attr(n, i) { 5119 return attributes[i](n); 5120 }; // nb negative because similarity should have an inverse relationship to distance 5121 5122 5123 return -clusteringDistance(type, attributes.length, function (i) { 5124 return attr(n1, i); 5125 }, function (i) { 5126 return attr(n2, i); 5127 }, n1, n2); 5128 }; 5129 5130 var getPreference = function getPreference(S, preference) { 5131 // larger preference = greater # of clusters 5132 var p = null; 5133 5134 if (preference === 'median') { 5135 p = median(S); 5136 } else if (preference === 'mean') { 5137 p = mean(S); 5138 } else if (preference === 'min') { 5139 p = min(S); 5140 } else if (preference === 'max') { 5141 p = max(S); 5142 } else { 5143 // Custom preference number, as set by user 5144 p = preference; 5145 } 5146 5147 return p; 5148 }; 5149 5150 var findExemplars = function findExemplars(n, R, A) { 5151 var indices = []; 5152 5153 for (var i = 0; i < n; i++) { 5154 if (R[i * n + i] + A[i * n + i] > 0) { 5155 indices.push(i); 5156 } 5157 } 5158 5159 return indices; 5160 }; 5161 5162 var assignClusters = function assignClusters(n, S, exemplars) { 5163 var clusters = []; 5164 5165 for (var i = 0; i < n; i++) { 5166 var index = -1; 5167 var max = -Infinity; 5168 5169 for (var ei = 0; ei < exemplars.length; ei++) { 5170 var e = exemplars[ei]; 5171 5172 if (S[i * n + e] > max) { 5173 index = e; 5174 max = S[i * n + e]; 5175 } 5176 } 5177 5178 if (index > 0) { 5179 clusters.push(index); 5180 } 5181 } 5182 5183 for (var _ei = 0; _ei < exemplars.length; _ei++) { 5184 clusters[exemplars[_ei]] = exemplars[_ei]; 5185 } 5186 5187 return clusters; 5188 }; 5189 5190 var assign$2 = function assign(n, S, exemplars) { 5191 var clusters = assignClusters(n, S, exemplars); 5192 5193 for (var ei = 0; ei < exemplars.length; ei++) { 5194 var ii = []; 5195 5196 for (var c = 0; c < clusters.length; c++) { 5197 if (clusters[c] === exemplars[ei]) { 5198 ii.push(c); 5199 } 5200 } 5201 5202 var maxI = -1; 5203 var maxSum = -Infinity; 5204 5205 for (var i = 0; i < ii.length; i++) { 5206 var sum = 0; 5207 5208 for (var j = 0; j < ii.length; j++) { 5209 sum += S[ii[j] * n + ii[i]]; 5210 } 5211 5212 if (sum > maxSum) { 5213 maxI = i; 5214 maxSum = sum; 5215 } 5216 } 5217 5218 exemplars[ei] = ii[maxI]; 5219 } 5220 5221 clusters = assignClusters(n, S, exemplars); 5222 return clusters; 5223 }; 5224 5225 var affinityPropagation = function affinityPropagation(options) { 5226 var cy = this.cy(); 5227 var nodes = this.nodes(); 5228 var opts = setOptions$3(options); // Map each node to its position in node array 5229 5230 var id2position = {}; 5231 5232 for (var i = 0; i < nodes.length; i++) { 5233 id2position[nodes[i].id()] = i; 5234 } // Begin affinity propagation algorithm 5235 5236 5237 var n; // number of data points 5238 5239 var n2; // size of matrices 5240 5241 var S; // similarity matrix (1D array) 5242 5243 var p; // preference/suitability of a data point to serve as an exemplar 5244 5245 var R; // responsibility matrix (1D array) 5246 5247 var A; // availability matrix (1D array) 5248 5249 n = nodes.length; 5250 n2 = n * n; // Initialize and build S similarity matrix 5251 5252 S = new Array(n2); 5253 5254 for (var _i = 0; _i < n2; _i++) { 5255 S[_i] = -Infinity; // for cases where two data points shouldn't be linked together 5256 } 5257 5258 for (var _i2 = 0; _i2 < n; _i2++) { 5259 for (var j = 0; j < n; j++) { 5260 if (_i2 !== j) { 5261 S[_i2 * n + j] = getSimilarity$1(opts.distance, nodes[_i2], nodes[j], opts.attributes); 5262 } 5263 } 5264 } // Place preferences on the diagonal of S 5265 5266 5267 p = getPreference(S, opts.preference); 5268 5269 for (var _i3 = 0; _i3 < n; _i3++) { 5270 S[_i3 * n + _i3] = p; 5271 } // Initialize R responsibility matrix 5272 5273 5274 R = new Array(n2); 5275 5276 for (var _i4 = 0; _i4 < n2; _i4++) { 5277 R[_i4] = 0.0; 5278 } // Initialize A availability matrix 5279 5280 5281 A = new Array(n2); 5282 5283 for (var _i5 = 0; _i5 < n2; _i5++) { 5284 A[_i5] = 0.0; 5285 } 5286 5287 var old = new Array(n); 5288 var Rp = new Array(n); 5289 var se = new Array(n); 5290 5291 for (var _i6 = 0; _i6 < n; _i6++) { 5292 old[_i6] = 0.0; 5293 Rp[_i6] = 0.0; 5294 se[_i6] = 0; 5295 } 5296 5297 var e = new Array(n * opts.minIterations); 5298 5299 for (var _i7 = 0; _i7 < e.length; _i7++) { 5300 e[_i7] = 0; 5301 } 5302 5303 var iter; 5304 5305 for (iter = 0; iter < opts.maxIterations; iter++) { 5306 // main algorithmic loop 5307 // Update R responsibility matrix 5308 for (var _i8 = 0; _i8 < n; _i8++) { 5309 var max = -Infinity, 5310 max2 = -Infinity, 5311 maxI = -1, 5312 AS = 0.0; 5313 5314 for (var _j = 0; _j < n; _j++) { 5315 old[_j] = R[_i8 * n + _j]; 5316 AS = A[_i8 * n + _j] + S[_i8 * n + _j]; 5317 5318 if (AS >= max) { 5319 max2 = max; 5320 max = AS; 5321 maxI = _j; 5322 } else if (AS > max2) { 5323 max2 = AS; 5324 } 5325 } 5326 5327 for (var _j2 = 0; _j2 < n; _j2++) { 5328 R[_i8 * n + _j2] = (1 - opts.damping) * (S[_i8 * n + _j2] - max) + opts.damping * old[_j2]; 5329 } 5330 5331 R[_i8 * n + maxI] = (1 - opts.damping) * (S[_i8 * n + maxI] - max2) + opts.damping * old[maxI]; 5332 } // Update A availability matrix 5333 5334 5335 for (var _i9 = 0; _i9 < n; _i9++) { 5336 var sum = 0; 5337 5338 for (var _j3 = 0; _j3 < n; _j3++) { 5339 old[_j3] = A[_j3 * n + _i9]; 5340 Rp[_j3] = Math.max(0, R[_j3 * n + _i9]); 5341 sum += Rp[_j3]; 5342 } 5343 5344 sum -= Rp[_i9]; 5345 Rp[_i9] = R[_i9 * n + _i9]; 5346 sum += Rp[_i9]; 5347 5348 for (var _j4 = 0; _j4 < n; _j4++) { 5349 A[_j4 * n + _i9] = (1 - opts.damping) * Math.min(0, sum - Rp[_j4]) + opts.damping * old[_j4]; 5350 } 5351 5352 A[_i9 * n + _i9] = (1 - opts.damping) * (sum - Rp[_i9]) + opts.damping * old[_i9]; 5353 } // Check for convergence 5354 5355 5356 var K = 0; 5357 5358 for (var _i10 = 0; _i10 < n; _i10++) { 5359 var E = A[_i10 * n + _i10] + R[_i10 * n + _i10] > 0 ? 1 : 0; 5360 e[iter % opts.minIterations * n + _i10] = E; 5361 K += E; 5362 } 5363 5364 if (K > 0 && (iter >= opts.minIterations - 1 || iter == opts.maxIterations - 1)) { 5365 var _sum = 0; 5366 5367 for (var _i11 = 0; _i11 < n; _i11++) { 5368 se[_i11] = 0; 5369 5370 for (var _j5 = 0; _j5 < opts.minIterations; _j5++) { 5371 se[_i11] += e[_j5 * n + _i11]; 5372 } 5373 5374 if (se[_i11] === 0 || se[_i11] === opts.minIterations) { 5375 _sum++; 5376 } 5377 } 5378 5379 if (_sum === n) { 5380 // then we have convergence 5381 break; 5382 } 5383 } 5384 } // Identify exemplars (cluster centers) 5385 5386 5387 var exemplarsIndices = findExemplars(n, R, A); // Assign nodes to clusters 5388 5389 var clusterIndices = assign$2(n, S, exemplarsIndices); 5390 var clusters = {}; 5391 5392 for (var c = 0; c < exemplarsIndices.length; c++) { 5393 clusters[exemplarsIndices[c]] = []; 5394 } 5395 5396 for (var _i12 = 0; _i12 < nodes.length; _i12++) { 5397 var pos = id2position[nodes[_i12].id()]; 5398 5399 var clusterIndex = clusterIndices[pos]; 5400 5401 if (clusterIndex != null) { 5402 // the node may have not been assigned a cluster if no valid attributes were specified 5403 clusters[clusterIndex].push(nodes[_i12]); 5404 } 5405 } 5406 5407 var retClusters = new Array(exemplarsIndices.length); 5408 5409 for (var _c = 0; _c < exemplarsIndices.length; _c++) { 5410 retClusters[_c] = cy.collection(clusters[exemplarsIndices[_c]]); 5411 } 5412 5413 return retClusters; 5414 }; 5415 5416 var affinityPropagation$1 = { 5417 affinityPropagation: affinityPropagation, 5418 ap: affinityPropagation 5419 }; 5420 5421 var hierholzerDefaults = defaults({ 5422 root: undefined, 5423 directed: false 5424 }); 5425 var elesfn$b = { 5426 hierholzer: function hierholzer(options) { 5427 if (!plainObject(options)) { 5428 var args = arguments; 5429 options = { 5430 root: args[0], 5431 directed: args[1] 5432 }; 5433 } 5434 5435 var _hierholzerDefaults = hierholzerDefaults(options), 5436 root = _hierholzerDefaults.root, 5437 directed = _hierholzerDefaults.directed; 5438 5439 var eles = this; 5440 var dflag = false; 5441 var oddIn; 5442 var oddOut; 5443 var startVertex; 5444 if (root) startVertex = string(root) ? this.filter(root)[0].id() : root[0].id(); 5445 var nodes = {}; 5446 var edges = {}; 5447 5448 if (directed) { 5449 eles.forEach(function (ele) { 5450 var id = ele.id(); 5451 5452 if (ele.isNode()) { 5453 var ind = ele.indegree(true); 5454 var outd = ele.outdegree(true); 5455 var d1 = ind - outd; 5456 var d2 = outd - ind; 5457 5458 if (d1 == 1) { 5459 if (oddIn) dflag = true;else oddIn = id; 5460 } else if (d2 == 1) { 5461 if (oddOut) dflag = true;else oddOut = id; 5462 } else if (d2 > 1 || d1 > 1) { 5463 dflag = true; 5464 } 5465 5466 nodes[id] = []; 5467 ele.outgoers().forEach(function (e) { 5468 if (e.isEdge()) nodes[id].push(e.id()); 5469 }); 5470 } else { 5471 edges[id] = [undefined, ele.target().id()]; 5472 } 5473 }); 5474 } else { 5475 eles.forEach(function (ele) { 5476 var id = ele.id(); 5477 5478 if (ele.isNode()) { 5479 var d = ele.degree(true); 5480 5481 if (d % 2) { 5482 if (!oddIn) oddIn = id;else if (!oddOut) oddOut = id;else dflag = true; 5483 } 5484 5485 nodes[id] = []; 5486 ele.connectedEdges().forEach(function (e) { 5487 return nodes[id].push(e.id()); 5488 }); 5489 } else { 5490 edges[id] = [ele.source().id(), ele.target().id()]; 5491 } 5492 }); 5493 } 5494 5495 var result = { 5496 found: false, 5497 trail: undefined 5498 }; 5499 if (dflag) return result;else if (oddOut && oddIn) { 5500 if (directed) { 5501 if (startVertex && oddOut != startVertex) { 5502 return result; 5503 } 5504 5505 startVertex = oddOut; 5506 } else { 5507 if (startVertex && oddOut != startVertex && oddIn != startVertex) { 5508 return result; 5509 } else if (!startVertex) { 5510 startVertex = oddOut; 5511 } 5512 } 5513 } else { 5514 if (!startVertex) startVertex = eles[0].id(); 5515 } 5516 5517 var walk = function walk(v) { 5518 var currentNode = v; 5519 var subtour = [v]; 5520 var adj, adjTail, adjHead; 5521 5522 while (nodes[currentNode].length) { 5523 adj = nodes[currentNode].shift(); 5524 adjTail = edges[adj][0]; 5525 adjHead = edges[adj][1]; 5526 5527 if (currentNode != adjHead) { 5528 nodes[adjHead] = nodes[adjHead].filter(function (e) { 5529 return e != adj; 5530 }); 5531 currentNode = adjHead; 5532 } else if (!directed && currentNode != adjTail) { 5533 nodes[adjTail] = nodes[adjTail].filter(function (e) { 5534 return e != adj; 5535 }); 5536 currentNode = adjTail; 5537 } 5538 5539 subtour.unshift(adj); 5540 subtour.unshift(currentNode); 5541 } 5542 5543 return subtour; 5544 }; 5545 5546 var trail = []; 5547 var subtour = []; 5548 subtour = walk(startVertex); 5549 5550 while (subtour.length != 1) { 5551 if (nodes[subtour[0]].length == 0) { 5552 trail.unshift(eles.getElementById(subtour.shift())); 5553 trail.unshift(eles.getElementById(subtour.shift())); 5554 } else { 5555 subtour = walk(subtour.shift()).concat(subtour); 5556 } 5557 } 5558 5559 trail.unshift(eles.getElementById(subtour.shift())); // final node 5560 5561 for (var d in nodes) { 5562 if (nodes[d].length) { 5563 return result; 5564 } 5565 } 5566 5567 result.found = true; 5568 result.trail = this.spawn(trail); 5569 return result; 5570 } 5571 }; 5572 5573 var hopcroftTarjanBiconnected = function hopcroftTarjanBiconnected() { 5574 var eles = this; 5575 var nodes = {}; 5576 var id = 0; 5577 var edgeCount = 0; 5578 var components = []; 5579 var stack = []; 5580 var visitedEdges = {}; 5581 5582 var buildComponent = function buildComponent(x, y) { 5583 var i = stack.length - 1; 5584 var cutset = []; 5585 var component = eles.spawn(); 5586 5587 while (stack[i].x != x || stack[i].y != y) { 5588 cutset.push(stack.pop().edge); 5589 i--; 5590 } 5591 5592 cutset.push(stack.pop().edge); 5593 cutset.forEach(function (edge) { 5594 var connectedNodes = edge.connectedNodes().intersection(eles); 5595 component.merge(edge); 5596 connectedNodes.forEach(function (node) { 5597 var nodeId = node.id(); 5598 var connectedEdges = node.connectedEdges().intersection(eles); 5599 component.merge(node); 5600 5601 if (!nodes[nodeId].cutVertex) { 5602 component.merge(connectedEdges); 5603 } else { 5604 component.merge(connectedEdges.filter(function (edge) { 5605 return edge.isLoop(); 5606 })); 5607 } 5608 }); 5609 }); 5610 components.push(component); 5611 }; 5612 5613 var biconnectedSearch = function biconnectedSearch(root, currentNode, parent) { 5614 if (root === parent) edgeCount += 1; 5615 nodes[currentNode] = { 5616 id: id, 5617 low: id++, 5618 cutVertex: false 5619 }; 5620 var edges = eles.getElementById(currentNode).connectedEdges().intersection(eles); 5621 5622 if (edges.size() === 0) { 5623 components.push(eles.spawn(eles.getElementById(currentNode))); 5624 } else { 5625 var sourceId, targetId, otherNodeId, edgeId; 5626 edges.forEach(function (edge) { 5627 sourceId = edge.source().id(); 5628 targetId = edge.target().id(); 5629 otherNodeId = sourceId === currentNode ? targetId : sourceId; 5630 5631 if (otherNodeId !== parent) { 5632 edgeId = edge.id(); 5633 5634 if (!visitedEdges[edgeId]) { 5635 visitedEdges[edgeId] = true; 5636 stack.push({ 5637 x: currentNode, 5638 y: otherNodeId, 5639 edge: edge 5640 }); 5641 } 5642 5643 if (!(otherNodeId in nodes)) { 5644 biconnectedSearch(root, otherNodeId, currentNode); 5645 nodes[currentNode].low = Math.min(nodes[currentNode].low, nodes[otherNodeId].low); 5646 5647 if (nodes[currentNode].id <= nodes[otherNodeId].low) { 5648 nodes[currentNode].cutVertex = true; 5649 buildComponent(currentNode, otherNodeId); 5650 } 5651 } else { 5652 nodes[currentNode].low = Math.min(nodes[currentNode].low, nodes[otherNodeId].id); 5653 } 5654 } 5655 }); 5656 } 5657 }; 5658 5659 eles.forEach(function (ele) { 5660 if (ele.isNode()) { 5661 var nodeId = ele.id(); 5662 5663 if (!(nodeId in nodes)) { 5664 edgeCount = 0; 5665 biconnectedSearch(nodeId, nodeId); 5666 nodes[nodeId].cutVertex = edgeCount > 1; 5667 } 5668 } 5669 }); 5670 var cutVertices = Object.keys(nodes).filter(function (id) { 5671 return nodes[id].cutVertex; 5672 }).map(function (id) { 5673 return eles.getElementById(id); 5674 }); 5675 return { 5676 cut: eles.spawn(cutVertices), 5677 components: components 5678 }; 5679 }; 5680 5681 var hopcroftTarjanBiconnected$1 = { 5682 hopcroftTarjanBiconnected: hopcroftTarjanBiconnected, 5683 htbc: hopcroftTarjanBiconnected, 5684 htb: hopcroftTarjanBiconnected, 5685 hopcroftTarjanBiconnectedComponents: hopcroftTarjanBiconnected 5686 }; 5687 5688 var elesfn$c = {}; 5689 [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) { 5690 extend(elesfn$c, props); 5691 }); 5692 5693 /*! 5694 Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable 5695 Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) 5696 Licensed under The MIT License (http://opensource.org/licenses/MIT) 5697 */ 5698 5699 /* promise states [Promises/A+ 2.1] */ 5700 var STATE_PENDING = 0; 5701 /* [Promises/A+ 2.1.1] */ 5702 5703 var STATE_FULFILLED = 1; 5704 /* [Promises/A+ 2.1.2] */ 5705 5706 var STATE_REJECTED = 2; 5707 /* [Promises/A+ 2.1.3] */ 5708 5709 /* promise object constructor */ 5710 5711 var api = function api(executor) { 5712 /* optionally support non-constructor/plain-function call */ 5713 if (!(this instanceof api)) return new api(executor); 5714 /* initialize object */ 5715 5716 this.id = 'Thenable/1.0.7'; 5717 this.state = STATE_PENDING; 5718 /* initial state */ 5719 5720 this.fulfillValue = undefined; 5721 /* initial value */ 5722 5723 /* [Promises/A+ 1.3, 2.1.2.2] */ 5724 5725 this.rejectReason = undefined; 5726 /* initial reason */ 5727 5728 /* [Promises/A+ 1.5, 2.1.3.2] */ 5729 5730 this.onFulfilled = []; 5731 /* initial handlers */ 5732 5733 this.onRejected = []; 5734 /* initial handlers */ 5735 5736 /* provide optional information-hiding proxy */ 5737 5738 this.proxy = { 5739 then: this.then.bind(this) 5740 }; 5741 /* support optional executor function */ 5742 5743 if (typeof executor === 'function') executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); 5744 }; 5745 /* promise API methods */ 5746 5747 5748 api.prototype = { 5749 /* promise resolving methods */ 5750 fulfill: function fulfill(value) { 5751 return deliver(this, STATE_FULFILLED, 'fulfillValue', value); 5752 }, 5753 reject: function reject(value) { 5754 return deliver(this, STATE_REJECTED, 'rejectReason', value); 5755 }, 5756 5757 /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ 5758 then: function then(onFulfilled, onRejected) { 5759 var curr = this; 5760 var next = new api(); 5761 /* [Promises/A+ 2.2.7] */ 5762 5763 curr.onFulfilled.push(resolver(onFulfilled, next, 'fulfill')); 5764 /* [Promises/A+ 2.2.2/2.2.6] */ 5765 5766 curr.onRejected.push(resolver(onRejected, next, 'reject')); 5767 /* [Promises/A+ 2.2.3/2.2.6] */ 5768 5769 execute(curr); 5770 return next.proxy; 5771 /* [Promises/A+ 2.2.7, 3.3] */ 5772 } 5773 }; 5774 /* deliver an action */ 5775 5776 var deliver = function deliver(curr, state, name, value) { 5777 if (curr.state === STATE_PENDING) { 5778 curr.state = state; 5779 /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ 5780 5781 curr[name] = value; 5782 /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ 5783 5784 execute(curr); 5785 } 5786 5787 return curr; 5788 }; 5789 /* execute all handlers */ 5790 5791 5792 var execute = function execute(curr) { 5793 if (curr.state === STATE_FULFILLED) execute_handlers(curr, 'onFulfilled', curr.fulfillValue);else if (curr.state === STATE_REJECTED) execute_handlers(curr, 'onRejected', curr.rejectReason); 5794 }; 5795 /* execute particular set of handlers */ 5796 5797 5798 var execute_handlers = function execute_handlers(curr, name, value) { 5799 /* global setImmediate: true */ 5800 5801 /* global setTimeout: true */ 5802 5803 /* short-circuit processing */ 5804 if (curr[name].length === 0) return; 5805 /* iterate over all handlers, exactly once */ 5806 5807 var handlers = curr[name]; 5808 curr[name] = []; 5809 /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ 5810 5811 var func = function func() { 5812 for (var i = 0; i < handlers.length; i++) { 5813 handlers[i](value); 5814 } 5815 /* [Promises/A+ 2.2.5] */ 5816 5817 }; 5818 /* execute procedure asynchronously */ 5819 5820 /* [Promises/A+ 2.2.4, 3.1] */ 5821 5822 5823 if (typeof setImmediate === 'function') setImmediate(func);else setTimeout(func, 0); 5824 }; 5825 /* generate a resolver function */ 5826 5827 5828 var resolver = function resolver(cb, next, method) { 5829 return function (value) { 5830 if (typeof cb !== 'function') 5831 /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ 5832 next[method].call(next, value); 5833 /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ 5834 else { 5835 var result; 5836 5837 try { 5838 result = cb(value); 5839 } 5840 /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ 5841 catch (e) { 5842 next.reject(e); 5843 /* [Promises/A+ 2.2.7.2] */ 5844 5845 return; 5846 } 5847 5848 resolve(next, result); 5849 /* [Promises/A+ 2.2.7.1] */ 5850 } 5851 }; 5852 }; 5853 /* "Promise Resolution Procedure" */ 5854 5855 /* [Promises/A+ 2.3] */ 5856 5857 5858 var resolve = function resolve(promise, x) { 5859 /* sanity check arguments */ 5860 5861 /* [Promises/A+ 2.3.1] */ 5862 if (promise === x || promise.proxy === x) { 5863 promise.reject(new TypeError('cannot resolve promise with itself')); 5864 return; 5865 } 5866 /* surgically check for a "then" method 5867 (mainly to just call the "getter" of "then" only once) */ 5868 5869 5870 var then; 5871 5872 if (_typeof(x) === 'object' && x !== null || typeof x === 'function') { 5873 try { 5874 then = x.then; 5875 } 5876 /* [Promises/A+ 2.3.3.1, 3.5] */ 5877 catch (e) { 5878 promise.reject(e); 5879 /* [Promises/A+ 2.3.3.2] */ 5880 5881 return; 5882 } 5883 } 5884 /* handle own Thenables [Promises/A+ 2.3.2] 5885 and similar "thenables" [Promises/A+ 2.3.3] */ 5886 5887 5888 if (typeof then === 'function') { 5889 var resolved = false; 5890 5891 try { 5892 /* call retrieved "then" method */ 5893 5894 /* [Promises/A+ 2.3.3.3] */ 5895 then.call(x, 5896 /* resolvePromise */ 5897 5898 /* [Promises/A+ 2.3.3.3.1] */ 5899 function (y) { 5900 if (resolved) return; 5901 resolved = true; 5902 /* [Promises/A+ 2.3.3.3.3] */ 5903 5904 if (y === x) 5905 /* [Promises/A+ 3.6] */ 5906 promise.reject(new TypeError('circular thenable chain'));else resolve(promise, y); 5907 }, 5908 /* rejectPromise */ 5909 5910 /* [Promises/A+ 2.3.3.3.2] */ 5911 function (r) { 5912 if (resolved) return; 5913 resolved = true; 5914 /* [Promises/A+ 2.3.3.3.3] */ 5915 5916 promise.reject(r); 5917 }); 5918 } catch (e) { 5919 if (!resolved) 5920 /* [Promises/A+ 2.3.3.3.3] */ 5921 promise.reject(e); 5922 /* [Promises/A+ 2.3.3.3.4] */ 5923 } 5924 5925 return; 5926 } 5927 /* handle other values */ 5928 5929 5930 promise.fulfill(x); 5931 /* [Promises/A+ 2.3.4, 2.3.3.4] */ 5932 }; // so we always have Promise.all() 5933 5934 5935 api.all = function (ps) { 5936 return new api(function (resolveAll, rejectAll) { 5937 var vals = new Array(ps.length); 5938 var doneCount = 0; 5939 5940 var fulfill = function fulfill(i, val) { 5941 vals[i] = val; 5942 doneCount++; 5943 5944 if (doneCount === ps.length) { 5945 resolveAll(vals); 5946 } 5947 }; 5948 5949 for (var i = 0; i < ps.length; i++) { 5950 (function (i) { 5951 var p = ps[i]; 5952 var isPromise = p != null && p.then != null; 5953 5954 if (isPromise) { 5955 p.then(function (val) { 5956 fulfill(i, val); 5957 }, function (err) { 5958 rejectAll(err); 5959 }); 5960 } else { 5961 var val = p; 5962 fulfill(i, val); 5963 } 5964 })(i); 5965 } 5966 }); 5967 }; 5968 5969 api.resolve = function (val) { 5970 return new api(function (resolve, reject) { 5971 resolve(val); 5972 }); 5973 }; 5974 5975 api.reject = function (val) { 5976 return new api(function (resolve, reject) { 5977 reject(val); 5978 }); 5979 }; 5980 5981 var Promise$1 = typeof Promise !== 'undefined' ? Promise : api; // eslint-disable-line no-undef 5982 5983 var Animation = function Animation(target, opts, opts2) { 5984 var isCore = core(target); 5985 var isEle = !isCore; 5986 5987 var _p = this._private = extend({ 5988 duration: 1000 5989 }, opts, opts2); 5990 5991 _p.target = target; 5992 _p.style = _p.style || _p.css; 5993 _p.started = false; 5994 _p.playing = false; 5995 _p.hooked = false; 5996 _p.applying = false; 5997 _p.progress = 0; 5998 _p.completes = []; 5999 _p.frames = []; 6000 6001 if (_p.complete && fn(_p.complete)) { 6002 _p.completes.push(_p.complete); 6003 } 6004 6005 if (isEle) { 6006 var pos = target.position(); 6007 _p.startPosition = _p.startPosition || { 6008 x: pos.x, 6009 y: pos.y 6010 }; 6011 _p.startStyle = _p.startStyle || target.cy().style().getAnimationStartStyle(target, _p.style); 6012 } 6013 6014 if (isCore) { 6015 var pan = target.pan(); 6016 _p.startPan = { 6017 x: pan.x, 6018 y: pan.y 6019 }; 6020 _p.startZoom = target.zoom(); 6021 } // for future timeline/animations impl 6022 6023 6024 this.length = 1; 6025 this[0] = this; 6026 }; 6027 6028 var anifn = Animation.prototype; 6029 extend(anifn, { 6030 instanceString: function instanceString() { 6031 return 'animation'; 6032 }, 6033 hook: function hook() { 6034 var _p = this._private; 6035 6036 if (!_p.hooked) { 6037 // add to target's animation queue 6038 var q; 6039 var tAni = _p.target._private.animation; 6040 6041 if (_p.queue) { 6042 q = tAni.queue; 6043 } else { 6044 q = tAni.current; 6045 } 6046 6047 q.push(this); // add to the animation loop pool 6048 6049 if (elementOrCollection(_p.target)) { 6050 _p.target.cy().addToAnimationPool(_p.target); 6051 } 6052 6053 _p.hooked = true; 6054 } 6055 6056 return this; 6057 }, 6058 play: function play() { 6059 var _p = this._private; // autorewind 6060 6061 if (_p.progress === 1) { 6062 _p.progress = 0; 6063 } 6064 6065 _p.playing = true; 6066 _p.started = false; // needs to be started by animation loop 6067 6068 _p.stopped = false; 6069 this.hook(); // the animation loop will start the animation... 6070 6071 return this; 6072 }, 6073 playing: function playing() { 6074 return this._private.playing; 6075 }, 6076 apply: function apply() { 6077 var _p = this._private; 6078 _p.applying = true; 6079 _p.started = false; // needs to be started by animation loop 6080 6081 _p.stopped = false; 6082 this.hook(); // the animation loop will apply the animation at this progress 6083 6084 return this; 6085 }, 6086 applying: function applying() { 6087 return this._private.applying; 6088 }, 6089 pause: function pause() { 6090 var _p = this._private; 6091 _p.playing = false; 6092 _p.started = false; 6093 return this; 6094 }, 6095 stop: function stop() { 6096 var _p = this._private; 6097 _p.playing = false; 6098 _p.started = false; 6099 _p.stopped = true; // to be removed from animation queues 6100 6101 return this; 6102 }, 6103 rewind: function rewind() { 6104 return this.progress(0); 6105 }, 6106 fastforward: function fastforward() { 6107 return this.progress(1); 6108 }, 6109 time: function time(t) { 6110 var _p = this._private; 6111 6112 if (t === undefined) { 6113 return _p.progress * _p.duration; 6114 } else { 6115 return this.progress(t / _p.duration); 6116 } 6117 }, 6118 progress: function progress(p) { 6119 var _p = this._private; 6120 var wasPlaying = _p.playing; 6121 6122 if (p === undefined) { 6123 return _p.progress; 6124 } else { 6125 if (wasPlaying) { 6126 this.pause(); 6127 } 6128 6129 _p.progress = p; 6130 _p.started = false; 6131 6132 if (wasPlaying) { 6133 this.play(); 6134 } 6135 } 6136 6137 return this; 6138 }, 6139 completed: function completed() { 6140 return this._private.progress === 1; 6141 }, 6142 reverse: function reverse() { 6143 var _p = this._private; 6144 var wasPlaying = _p.playing; 6145 6146 if (wasPlaying) { 6147 this.pause(); 6148 } 6149 6150 _p.progress = 1 - _p.progress; 6151 _p.started = false; 6152 6153 var swap = function swap(a, b) { 6154 var _pa = _p[a]; 6155 6156 if (_pa == null) { 6157 return; 6158 } 6159 6160 _p[a] = _p[b]; 6161 _p[b] = _pa; 6162 }; 6163 6164 swap('zoom', 'startZoom'); 6165 swap('pan', 'startPan'); 6166 swap('position', 'startPosition'); // swap styles 6167 6168 if (_p.style) { 6169 for (var i = 0; i < _p.style.length; i++) { 6170 var prop = _p.style[i]; 6171 var name = prop.name; 6172 var startStyleProp = _p.startStyle[name]; 6173 _p.startStyle[name] = prop; 6174 _p.style[i] = startStyleProp; 6175 } 6176 } 6177 6178 if (wasPlaying) { 6179 this.play(); 6180 } 6181 6182 return this; 6183 }, 6184 promise: function promise(type) { 6185 var _p = this._private; 6186 var arr; 6187 6188 switch (type) { 6189 case 'frame': 6190 arr = _p.frames; 6191 break; 6192 6193 default: 6194 case 'complete': 6195 case 'completed': 6196 arr = _p.completes; 6197 } 6198 6199 return new Promise$1(function (resolve, reject) { 6200 arr.push(function () { 6201 resolve(); 6202 }); 6203 }); 6204 } 6205 }); 6206 anifn.complete = anifn.completed; 6207 anifn.run = anifn.play; 6208 anifn.running = anifn.playing; 6209 6210 var define = { 6211 animated: function animated() { 6212 return function animatedImpl() { 6213 var self = this; 6214 var selfIsArrayLike = self.length !== undefined; 6215 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6216 6217 var cy = this._private.cy || this; 6218 6219 if (!cy.styleEnabled()) { 6220 return false; 6221 } 6222 6223 var ele = all[0]; 6224 6225 if (ele) { 6226 return ele._private.animation.current.length > 0; 6227 } 6228 }; 6229 }, 6230 // animated 6231 clearQueue: function clearQueue() { 6232 return function clearQueueImpl() { 6233 var self = this; 6234 var selfIsArrayLike = self.length !== undefined; 6235 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6236 6237 var cy = this._private.cy || this; 6238 6239 if (!cy.styleEnabled()) { 6240 return this; 6241 } 6242 6243 for (var i = 0; i < all.length; i++) { 6244 var ele = all[i]; 6245 ele._private.animation.queue = []; 6246 } 6247 6248 return this; 6249 }; 6250 }, 6251 // clearQueue 6252 delay: function delay() { 6253 return function delayImpl(time, complete) { 6254 var cy = this._private.cy || this; 6255 6256 if (!cy.styleEnabled()) { 6257 return this; 6258 } 6259 6260 return this.animate({ 6261 delay: time, 6262 duration: time, 6263 complete: complete 6264 }); 6265 }; 6266 }, 6267 // delay 6268 delayAnimation: function delayAnimation() { 6269 return function delayAnimationImpl(time, complete) { 6270 var cy = this._private.cy || this; 6271 6272 if (!cy.styleEnabled()) { 6273 return this; 6274 } 6275 6276 return this.animation({ 6277 delay: time, 6278 duration: time, 6279 complete: complete 6280 }); 6281 }; 6282 }, 6283 // delay 6284 animation: function animation() { 6285 return function animationImpl(properties, params) { 6286 var self = this; 6287 var selfIsArrayLike = self.length !== undefined; 6288 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6289 6290 var cy = this._private.cy || this; 6291 var isCore = !selfIsArrayLike; 6292 var isEles = !isCore; 6293 6294 if (!cy.styleEnabled()) { 6295 return this; 6296 } 6297 6298 var style = cy.style(); 6299 properties = extend({}, properties, params); 6300 var propertiesEmpty = Object.keys(properties).length === 0; 6301 6302 if (propertiesEmpty) { 6303 return new Animation(all[0], properties); // nothing to animate 6304 } 6305 6306 if (properties.duration === undefined) { 6307 properties.duration = 400; 6308 } 6309 6310 switch (properties.duration) { 6311 case 'slow': 6312 properties.duration = 600; 6313 break; 6314 6315 case 'fast': 6316 properties.duration = 200; 6317 break; 6318 } 6319 6320 if (isEles) { 6321 properties.style = style.getPropsList(properties.style || properties.css); 6322 properties.css = undefined; 6323 } 6324 6325 if (isEles && properties.renderedPosition != null) { 6326 var rpos = properties.renderedPosition; 6327 var pan = cy.pan(); 6328 var zoom = cy.zoom(); 6329 properties.position = renderedToModelPosition(rpos, zoom, pan); 6330 } // override pan w/ panBy if set 6331 6332 6333 if (isCore && properties.panBy != null) { 6334 var panBy = properties.panBy; 6335 var cyPan = cy.pan(); 6336 properties.pan = { 6337 x: cyPan.x + panBy.x, 6338 y: cyPan.y + panBy.y 6339 }; 6340 } // override pan w/ center if set 6341 6342 6343 var center = properties.center || properties.centre; 6344 6345 if (isCore && center != null) { 6346 var centerPan = cy.getCenterPan(center.eles, properties.zoom); 6347 6348 if (centerPan != null) { 6349 properties.pan = centerPan; 6350 } 6351 } // override pan & zoom w/ fit if set 6352 6353 6354 if (isCore && properties.fit != null) { 6355 var fit = properties.fit; 6356 var fitVp = cy.getFitViewport(fit.eles || fit.boundingBox, fit.padding); 6357 6358 if (fitVp != null) { 6359 properties.pan = fitVp.pan; 6360 properties.zoom = fitVp.zoom; 6361 } 6362 } // override zoom (& potentially pan) w/ zoom obj if set 6363 6364 6365 if (isCore && plainObject(properties.zoom)) { 6366 var vp = cy.getZoomedViewport(properties.zoom); 6367 6368 if (vp != null) { 6369 if (vp.zoomed) { 6370 properties.zoom = vp.zoom; 6371 } 6372 6373 if (vp.panned) { 6374 properties.pan = vp.pan; 6375 } 6376 } else { 6377 properties.zoom = null; // an inavalid zoom (e.g. no delta) gets automatically destroyed 6378 } 6379 } 6380 6381 return new Animation(all[0], properties); 6382 }; 6383 }, 6384 // animate 6385 animate: function animate() { 6386 return function animateImpl(properties, params) { 6387 var self = this; 6388 var selfIsArrayLike = self.length !== undefined; 6389 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6390 6391 var cy = this._private.cy || this; 6392 6393 if (!cy.styleEnabled()) { 6394 return this; 6395 } 6396 6397 if (params) { 6398 properties = extend({}, properties, params); 6399 } // manually hook and run the animation 6400 6401 6402 for (var i = 0; i < all.length; i++) { 6403 var ele = all[i]; 6404 var queue = ele.animated() && (properties.queue === undefined || properties.queue); 6405 var ani = ele.animation(properties, queue ? { 6406 queue: true 6407 } : undefined); 6408 ani.play(); 6409 } 6410 6411 return this; // chaining 6412 }; 6413 }, 6414 // animate 6415 stop: function stop() { 6416 return function stopImpl(clearQueue, jumpToEnd) { 6417 var self = this; 6418 var selfIsArrayLike = self.length !== undefined; 6419 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6420 6421 var cy = this._private.cy || this; 6422 6423 if (!cy.styleEnabled()) { 6424 return this; 6425 } 6426 6427 for (var i = 0; i < all.length; i++) { 6428 var ele = all[i]; 6429 var _p = ele._private; 6430 var anis = _p.animation.current; 6431 6432 for (var j = 0; j < anis.length; j++) { 6433 var ani = anis[j]; 6434 var ani_p = ani._private; 6435 6436 if (jumpToEnd) { 6437 // next iteration of the animation loop, the animation 6438 // will go straight to the end and be removed 6439 ani_p.duration = 0; 6440 } 6441 } // clear the queue of future animations 6442 6443 6444 if (clearQueue) { 6445 _p.animation.queue = []; 6446 } 6447 6448 if (!jumpToEnd) { 6449 _p.animation.current = []; 6450 } 6451 } // we have to notify (the animation loop doesn't do it for us on `stop`) 6452 6453 6454 cy.notify('draw'); 6455 return this; 6456 }; 6457 } // stop 6458 6459 }; // define 6460 6461 var define$1 = { 6462 // access data field 6463 data: function data(params) { 6464 var defaults = { 6465 field: 'data', 6466 bindingEvent: 'data', 6467 allowBinding: false, 6468 allowSetting: false, 6469 allowGetting: false, 6470 settingEvent: 'data', 6471 settingTriggersEvent: false, 6472 triggerFnName: 'trigger', 6473 immutableKeys: {}, 6474 // key => true if immutable 6475 updateStyle: false, 6476 beforeGet: function beforeGet(self) {}, 6477 beforeSet: function beforeSet(self, obj) {}, 6478 onSet: function onSet(self) {}, 6479 canSet: function canSet(self) { 6480 return true; 6481 } 6482 }; 6483 params = extend({}, defaults, params); 6484 return function dataImpl(name, value) { 6485 var p = params; 6486 var self = this; 6487 var selfIsArrayLike = self.length !== undefined; 6488 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6489 6490 var single = selfIsArrayLike ? self[0] : self; // .data('foo', ...) 6491 6492 if (string(name)) { 6493 // set or get property 6494 // .data('foo') 6495 if (p.allowGetting && value === undefined) { 6496 // get 6497 var ret; 6498 6499 if (single) { 6500 p.beforeGet(single); 6501 ret = single._private[p.field][name]; 6502 } 6503 6504 return ret; // .data('foo', 'bar') 6505 } else if (p.allowSetting && value !== undefined) { 6506 // set 6507 var valid = !p.immutableKeys[name]; 6508 6509 if (valid) { 6510 var change = _defineProperty({}, name, value); 6511 6512 p.beforeSet(self, change); 6513 6514 for (var i = 0, l = all.length; i < l; i++) { 6515 var ele = all[i]; 6516 6517 if (p.canSet(ele)) { 6518 ele._private[p.field][name] = value; 6519 } 6520 } // update mappers if asked 6521 6522 6523 if (p.updateStyle) { 6524 self.updateStyle(); 6525 } // call onSet callback 6526 6527 6528 p.onSet(self); 6529 6530 if (p.settingTriggersEvent) { 6531 self[p.triggerFnName](p.settingEvent); 6532 } 6533 } 6534 } // .data({ 'foo': 'bar' }) 6535 6536 } else if (p.allowSetting && plainObject(name)) { 6537 // extend 6538 var obj = name; 6539 var k, v; 6540 var keys = Object.keys(obj); 6541 p.beforeSet(self, obj); 6542 6543 for (var _i = 0; _i < keys.length; _i++) { 6544 k = keys[_i]; 6545 v = obj[k]; 6546 6547 var _valid = !p.immutableKeys[k]; 6548 6549 if (_valid) { 6550 for (var j = 0; j < all.length; j++) { 6551 var _ele = all[j]; 6552 6553 if (p.canSet(_ele)) { 6554 _ele._private[p.field][k] = v; 6555 } 6556 } 6557 } 6558 } // update mappers if asked 6559 6560 6561 if (p.updateStyle) { 6562 self.updateStyle(); 6563 } // call onSet callback 6564 6565 6566 p.onSet(self); 6567 6568 if (p.settingTriggersEvent) { 6569 self[p.triggerFnName](p.settingEvent); 6570 } // .data(function(){ ... }) 6571 6572 } else if (p.allowBinding && fn(name)) { 6573 // bind to event 6574 var fn$1 = name; 6575 self.on(p.bindingEvent, fn$1); // .data() 6576 } else if (p.allowGetting && name === undefined) { 6577 // get whole object 6578 var _ret; 6579 6580 if (single) { 6581 p.beforeGet(single); 6582 _ret = single._private[p.field]; 6583 } 6584 6585 return _ret; 6586 } 6587 6588 return self; // maintain chainability 6589 }; // function 6590 }, 6591 // data 6592 // remove data field 6593 removeData: function removeData(params) { 6594 var defaults = { 6595 field: 'data', 6596 event: 'data', 6597 triggerFnName: 'trigger', 6598 triggerEvent: false, 6599 immutableKeys: {} // key => true if immutable 6600 6601 }; 6602 params = extend({}, defaults, params); 6603 return function removeDataImpl(names) { 6604 var p = params; 6605 var self = this; 6606 var selfIsArrayLike = self.length !== undefined; 6607 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6608 // .removeData('foo bar') 6609 6610 if (string(names)) { 6611 // then get the list of keys, and delete them 6612 var keys = names.split(/\s+/); 6613 var l = keys.length; 6614 6615 for (var i = 0; i < l; i++) { 6616 // delete each non-empty key 6617 var key = keys[i]; 6618 6619 if (emptyString(key)) { 6620 continue; 6621 } 6622 6623 var valid = !p.immutableKeys[key]; // not valid if immutable 6624 6625 if (valid) { 6626 for (var i_a = 0, l_a = all.length; i_a < l_a; i_a++) { 6627 all[i_a]._private[p.field][key] = undefined; 6628 } 6629 } 6630 } 6631 6632 if (p.triggerEvent) { 6633 self[p.triggerFnName](p.event); 6634 } // .removeData() 6635 6636 } else if (names === undefined) { 6637 // then delete all keys 6638 for (var _i_a = 0, _l_a = all.length; _i_a < _l_a; _i_a++) { 6639 var _privateFields = all[_i_a]._private[p.field]; 6640 6641 var _keys = Object.keys(_privateFields); 6642 6643 for (var _i2 = 0; _i2 < _keys.length; _i2++) { 6644 var _key = _keys[_i2]; 6645 var validKeyToDelete = !p.immutableKeys[_key]; 6646 6647 if (validKeyToDelete) { 6648 _privateFields[_key] = undefined; 6649 } 6650 } 6651 } 6652 6653 if (p.triggerEvent) { 6654 self[p.triggerFnName](p.event); 6655 } 6656 } 6657 6658 return self; // maintain chaining 6659 }; // function 6660 } // removeData 6661 6662 }; // define 6663 6664 var define$2 = { 6665 eventAliasesOn: function eventAliasesOn(proto) { 6666 var p = proto; 6667 p.addListener = p.listen = p.bind = p.on; 6668 p.unlisten = p.unbind = p.off = p.removeListener; 6669 p.trigger = p.emit; // this is just a wrapper alias of .on() 6670 6671 p.pon = p.promiseOn = function (events, selector) { 6672 var self = this; 6673 var args = Array.prototype.slice.call(arguments, 0); 6674 return new Promise$1(function (resolve, reject) { 6675 var callback = function callback(e) { 6676 self.off.apply(self, offArgs); 6677 resolve(e); 6678 }; 6679 6680 var onArgs = args.concat([callback]); 6681 var offArgs = onArgs.concat([]); 6682 self.on.apply(self, onArgs); 6683 }); 6684 }; 6685 } 6686 }; // define 6687 6688 // use this module to cherry pick functions into your prototype 6689 var define$3 = {}; 6690 [define, define$1, define$2].forEach(function (m) { 6691 extend(define$3, m); 6692 }); 6693 6694 var elesfn$d = { 6695 animate: define$3.animate(), 6696 animation: define$3.animation(), 6697 animated: define$3.animated(), 6698 clearQueue: define$3.clearQueue(), 6699 delay: define$3.delay(), 6700 delayAnimation: define$3.delayAnimation(), 6701 stop: define$3.stop() 6702 }; 6703 6704 var elesfn$e = { 6705 classes: function classes(_classes) { 6706 var self = this; 6707 6708 if (_classes === undefined) { 6709 var ret = []; 6710 6711 self[0]._private.classes.forEach(function (cls) { 6712 return ret.push(cls); 6713 }); 6714 6715 return ret; 6716 } else if (!array(_classes)) { 6717 // extract classes from string 6718 _classes = (_classes || '').match(/\S+/g) || []; 6719 } 6720 6721 var changed = []; 6722 var classesSet = new Set$1(_classes); // check and update each ele 6723 6724 for (var j = 0; j < self.length; j++) { 6725 var ele = self[j]; 6726 var _p = ele._private; 6727 var eleClasses = _p.classes; 6728 var changedEle = false; // check if ele has all of the passed classes 6729 6730 for (var i = 0; i < _classes.length; i++) { 6731 var cls = _classes[i]; 6732 var eleHasClass = eleClasses.has(cls); 6733 6734 if (!eleHasClass) { 6735 changedEle = true; 6736 break; 6737 } 6738 } // check if ele has classes outside of those passed 6739 6740 6741 if (!changedEle) { 6742 changedEle = eleClasses.size !== _classes.length; 6743 } 6744 6745 if (changedEle) { 6746 _p.classes = classesSet; 6747 changed.push(ele); 6748 } 6749 } // trigger update style on those eles that had class changes 6750 6751 6752 if (changed.length > 0) { 6753 this.spawn(changed).updateStyle().emit('class'); 6754 } 6755 6756 return self; 6757 }, 6758 addClass: function addClass(classes) { 6759 return this.toggleClass(classes, true); 6760 }, 6761 hasClass: function hasClass(className) { 6762 var ele = this[0]; 6763 return ele != null && ele._private.classes.has(className); 6764 }, 6765 toggleClass: function toggleClass(classes, toggle) { 6766 if (!array(classes)) { 6767 // extract classes from string 6768 classes = classes.match(/\S+/g) || []; 6769 } 6770 6771 var self = this; 6772 var toggleUndefd = toggle === undefined; 6773 var changed = []; // eles who had classes changed 6774 6775 for (var i = 0, il = self.length; i < il; i++) { 6776 var ele = self[i]; 6777 var eleClasses = ele._private.classes; 6778 var changedEle = false; 6779 6780 for (var j = 0; j < classes.length; j++) { 6781 var cls = classes[j]; 6782 var hasClass = eleClasses.has(cls); 6783 var changedNow = false; 6784 6785 if (toggle || toggleUndefd && !hasClass) { 6786 eleClasses.add(cls); 6787 changedNow = true; 6788 } else if (!toggle || toggleUndefd && hasClass) { 6789 eleClasses["delete"](cls); 6790 changedNow = true; 6791 } 6792 6793 if (!changedEle && changedNow) { 6794 changed.push(ele); 6795 changedEle = true; 6796 } 6797 } // for j classes 6798 6799 } // for i eles 6800 // trigger update style on those eles that had class changes 6801 6802 6803 if (changed.length > 0) { 6804 this.spawn(changed).updateStyle().emit('class'); 6805 } 6806 6807 return self; 6808 }, 6809 removeClass: function removeClass(classes) { 6810 return this.toggleClass(classes, false); 6811 }, 6812 flashClass: function flashClass(classes, duration) { 6813 var self = this; 6814 6815 if (duration == null) { 6816 duration = 250; 6817 } else if (duration === 0) { 6818 return self; // nothing to do really 6819 } 6820 6821 self.addClass(classes); 6822 setTimeout(function () { 6823 self.removeClass(classes); 6824 }, duration); 6825 return self; 6826 } 6827 }; 6828 elesfn$e.className = elesfn$e.classNames = elesfn$e.classes; 6829 6830 var tokens = { 6831 metaChar: '[\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]', 6832 // chars we need to escape in let names, etc 6833 comparatorOp: '=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=', 6834 // binary comparison op (used in data selectors) 6835 boolOp: '\\?|\\!|\\^', 6836 // boolean (unary) operators (used in data selectors) 6837 string: '"(?:\\\\"|[^"])*"' + '|' + "'(?:\\\\'|[^'])*'", 6838 // string literals (used in data selectors) -- doublequotes | singlequotes 6839 number: number$1, 6840 // number literal (used in data selectors) --- e.g. 0.1234, 1234, 12e123 6841 meta: 'degree|indegree|outdegree', 6842 // allowed metadata fields (i.e. allowed functions to use from Collection) 6843 separator: '\\s*,\\s*', 6844 // queries are separated by commas, e.g. edge[foo = 'bar'], node.someClass 6845 descendant: '\\s+', 6846 child: '\\s+>\\s+', 6847 subject: '\\$', 6848 group: 'node|edge|\\*', 6849 directedEdge: '\\s+->\\s+', 6850 undirectedEdge: '\\s+<->\\s+' 6851 }; 6852 tokens.variable = '(?:[\\w-]|(?:\\\\' + tokens.metaChar + '))+'; // a variable name 6853 6854 tokens.value = tokens.string + '|' + tokens.number; // a value literal, either a string or number 6855 6856 tokens.className = tokens.variable; // a class name (follows variable conventions) 6857 6858 tokens.id = tokens.variable; // an element id (follows variable conventions) 6859 6860 (function () { 6861 var ops, op, i; // add @ variants to comparatorOp 6862 6863 ops = tokens.comparatorOp.split('|'); 6864 6865 for (i = 0; i < ops.length; i++) { 6866 op = ops[i]; 6867 tokens.comparatorOp += '|@' + op; 6868 } // add ! variants to comparatorOp 6869 6870 6871 ops = tokens.comparatorOp.split('|'); 6872 6873 for (i = 0; i < ops.length; i++) { 6874 op = ops[i]; 6875 6876 if (op.indexOf('!') >= 0) { 6877 continue; 6878 } // skip ops that explicitly contain ! 6879 6880 6881 if (op === '=') { 6882 continue; 6883 } // skip = b/c != is explicitly defined 6884 6885 6886 tokens.comparatorOp += '|\\!' + op; 6887 } 6888 })(); 6889 6890 /** 6891 * Make a new query object 6892 * 6893 * @prop type {Type} The type enum (int) of the query 6894 * @prop checks List of checks to make against an ele to test for a match 6895 */ 6896 var newQuery = function newQuery() { 6897 return { 6898 checks: [] 6899 }; 6900 }; 6901 6902 /** 6903 * A check type enum-like object. Uses integer values for fast match() lookup. 6904 * The ordering does not matter as long as the ints are unique. 6905 */ 6906 var Type = { 6907 /** E.g. node */ 6908 GROUP: 0, 6909 6910 /** A collection of elements */ 6911 COLLECTION: 1, 6912 6913 /** A filter(ele) function */ 6914 FILTER: 2, 6915 6916 /** E.g. [foo > 1] */ 6917 DATA_COMPARE: 3, 6918 6919 /** E.g. [foo] */ 6920 DATA_EXIST: 4, 6921 6922 /** E.g. [?foo] */ 6923 DATA_BOOL: 5, 6924 6925 /** E.g. [[degree > 2]] */ 6926 META_COMPARE: 6, 6927 6928 /** E.g. :selected */ 6929 STATE: 7, 6930 6931 /** E.g. #foo */ 6932 ID: 8, 6933 6934 /** E.g. .foo */ 6935 CLASS: 9, 6936 6937 /** E.g. #foo <-> #bar */ 6938 UNDIRECTED_EDGE: 10, 6939 6940 /** E.g. #foo -> #bar */ 6941 DIRECTED_EDGE: 11, 6942 6943 /** E.g. $#foo -> #bar */ 6944 NODE_SOURCE: 12, 6945 6946 /** E.g. #foo -> $#bar */ 6947 NODE_TARGET: 13, 6948 6949 /** E.g. $#foo <-> #bar */ 6950 NODE_NEIGHBOR: 14, 6951 6952 /** E.g. #foo > #bar */ 6953 CHILD: 15, 6954 6955 /** E.g. #foo #bar */ 6956 DESCENDANT: 16, 6957 6958 /** E.g. $#foo > #bar */ 6959 PARENT: 17, 6960 6961 /** E.g. $#foo #bar */ 6962 ANCESTOR: 18, 6963 6964 /** E.g. #foo > $bar > #baz */ 6965 COMPOUND_SPLIT: 19, 6966 6967 /** Always matches, useful placeholder for subject in `COMPOUND_SPLIT` */ 6968 TRUE: 20 6969 }; 6970 6971 var stateSelectors = [{ 6972 selector: ':selected', 6973 matches: function matches(ele) { 6974 return ele.selected(); 6975 } 6976 }, { 6977 selector: ':unselected', 6978 matches: function matches(ele) { 6979 return !ele.selected(); 6980 } 6981 }, { 6982 selector: ':selectable', 6983 matches: function matches(ele) { 6984 return ele.selectable(); 6985 } 6986 }, { 6987 selector: ':unselectable', 6988 matches: function matches(ele) { 6989 return !ele.selectable(); 6990 } 6991 }, { 6992 selector: ':locked', 6993 matches: function matches(ele) { 6994 return ele.locked(); 6995 } 6996 }, { 6997 selector: ':unlocked', 6998 matches: function matches(ele) { 6999 return !ele.locked(); 7000 } 7001 }, { 7002 selector: ':visible', 7003 matches: function matches(ele) { 7004 return ele.visible(); 7005 } 7006 }, { 7007 selector: ':hidden', 7008 matches: function matches(ele) { 7009 return !ele.visible(); 7010 } 7011 }, { 7012 selector: ':transparent', 7013 matches: function matches(ele) { 7014 return ele.transparent(); 7015 } 7016 }, { 7017 selector: ':grabbed', 7018 matches: function matches(ele) { 7019 return ele.grabbed(); 7020 } 7021 }, { 7022 selector: ':free', 7023 matches: function matches(ele) { 7024 return !ele.grabbed(); 7025 } 7026 }, { 7027 selector: ':removed', 7028 matches: function matches(ele) { 7029 return ele.removed(); 7030 } 7031 }, { 7032 selector: ':inside', 7033 matches: function matches(ele) { 7034 return !ele.removed(); 7035 } 7036 }, { 7037 selector: ':grabbable', 7038 matches: function matches(ele) { 7039 return ele.grabbable(); 7040 } 7041 }, { 7042 selector: ':ungrabbable', 7043 matches: function matches(ele) { 7044 return !ele.grabbable(); 7045 } 7046 }, { 7047 selector: ':animated', 7048 matches: function matches(ele) { 7049 return ele.animated(); 7050 } 7051 }, { 7052 selector: ':unanimated', 7053 matches: function matches(ele) { 7054 return !ele.animated(); 7055 } 7056 }, { 7057 selector: ':parent', 7058 matches: function matches(ele) { 7059 return ele.isParent(); 7060 } 7061 }, { 7062 selector: ':childless', 7063 matches: function matches(ele) { 7064 return ele.isChildless(); 7065 } 7066 }, { 7067 selector: ':child', 7068 matches: function matches(ele) { 7069 return ele.isChild(); 7070 } 7071 }, { 7072 selector: ':orphan', 7073 matches: function matches(ele) { 7074 return ele.isOrphan(); 7075 } 7076 }, { 7077 selector: ':nonorphan', 7078 matches: function matches(ele) { 7079 return ele.isChild(); 7080 } 7081 }, { 7082 selector: ':compound', 7083 matches: function matches(ele) { 7084 if (ele.isNode()) { 7085 return ele.isParent(); 7086 } else { 7087 return ele.source().isParent() || ele.target().isParent(); 7088 } 7089 } 7090 }, { 7091 selector: ':loop', 7092 matches: function matches(ele) { 7093 return ele.isLoop(); 7094 } 7095 }, { 7096 selector: ':simple', 7097 matches: function matches(ele) { 7098 return ele.isSimple(); 7099 } 7100 }, { 7101 selector: ':active', 7102 matches: function matches(ele) { 7103 return ele.active(); 7104 } 7105 }, { 7106 selector: ':inactive', 7107 matches: function matches(ele) { 7108 return !ele.active(); 7109 } 7110 }, { 7111 selector: ':backgrounding', 7112 matches: function matches(ele) { 7113 return ele.backgrounding(); 7114 } 7115 }, { 7116 selector: ':nonbackgrounding', 7117 matches: function matches(ele) { 7118 return !ele.backgrounding(); 7119 } 7120 }].sort(function (a, b) { 7121 // n.b. selectors that are starting substrings of others must have the longer ones first 7122 return descending(a.selector, b.selector); 7123 }); 7124 7125 var lookup = function () { 7126 var selToFn = {}; 7127 var s; 7128 7129 for (var i = 0; i < stateSelectors.length; i++) { 7130 s = stateSelectors[i]; 7131 selToFn[s.selector] = s.matches; 7132 } 7133 7134 return selToFn; 7135 }(); 7136 7137 var stateSelectorMatches = function stateSelectorMatches(sel, ele) { 7138 return lookup[sel](ele); 7139 }; 7140 var stateSelectorRegex = '(' + stateSelectors.map(function (s) { 7141 return s.selector; 7142 }).join('|') + ')'; 7143 7144 // so that values get compared properly in Selector.filter() 7145 7146 var cleanMetaChars = function cleanMetaChars(str) { 7147 return str.replace(new RegExp('\\\\(' + tokens.metaChar + ')', 'g'), function (match, $1) { 7148 return $1; 7149 }); 7150 }; 7151 7152 var replaceLastQuery = function replaceLastQuery(selector, examiningQuery, replacementQuery) { 7153 selector[selector.length - 1] = replacementQuery; 7154 }; // NOTE: add new expression syntax here to have it recognised by the parser; 7155 // - a query contains all adjacent (i.e. no separator in between) expressions; 7156 // - the current query is stored in selector[i] 7157 // - you need to check the query objects in match() for it actually filter properly, but that's pretty straight forward 7158 7159 7160 var exprs = [{ 7161 name: 'group', 7162 // just used for identifying when debugging 7163 query: true, 7164 regex: '(' + tokens.group + ')', 7165 populate: function populate(selector, query, _ref) { 7166 var _ref2 = _slicedToArray(_ref, 1), 7167 group = _ref2[0]; 7168 7169 query.checks.push({ 7170 type: Type.GROUP, 7171 value: group === '*' ? group : group + 's' 7172 }); 7173 } 7174 }, { 7175 name: 'state', 7176 query: true, 7177 regex: stateSelectorRegex, 7178 populate: function populate(selector, query, _ref3) { 7179 var _ref4 = _slicedToArray(_ref3, 1), 7180 state = _ref4[0]; 7181 7182 query.checks.push({ 7183 type: Type.STATE, 7184 value: state 7185 }); 7186 } 7187 }, { 7188 name: 'id', 7189 query: true, 7190 regex: '\\#(' + tokens.id + ')', 7191 populate: function populate(selector, query, _ref5) { 7192 var _ref6 = _slicedToArray(_ref5, 1), 7193 id = _ref6[0]; 7194 7195 query.checks.push({ 7196 type: Type.ID, 7197 value: cleanMetaChars(id) 7198 }); 7199 } 7200 }, { 7201 name: 'className', 7202 query: true, 7203 regex: '\\.(' + tokens.className + ')', 7204 populate: function populate(selector, query, _ref7) { 7205 var _ref8 = _slicedToArray(_ref7, 1), 7206 className = _ref8[0]; 7207 7208 query.checks.push({ 7209 type: Type.CLASS, 7210 value: cleanMetaChars(className) 7211 }); 7212 } 7213 }, { 7214 name: 'dataExists', 7215 query: true, 7216 regex: '\\[\\s*(' + tokens.variable + ')\\s*\\]', 7217 populate: function populate(selector, query, _ref9) { 7218 var _ref10 = _slicedToArray(_ref9, 1), 7219 variable = _ref10[0]; 7220 7221 query.checks.push({ 7222 type: Type.DATA_EXIST, 7223 field: cleanMetaChars(variable) 7224 }); 7225 } 7226 }, { 7227 name: 'dataCompare', 7228 query: true, 7229 regex: '\\[\\s*(' + tokens.variable + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.value + ')\\s*\\]', 7230 populate: function populate(selector, query, _ref11) { 7231 var _ref12 = _slicedToArray(_ref11, 3), 7232 variable = _ref12[0], 7233 comparatorOp = _ref12[1], 7234 value = _ref12[2]; 7235 7236 var valueIsString = new RegExp('^' + tokens.string + '$').exec(value) != null; 7237 7238 if (valueIsString) { 7239 value = value.substring(1, value.length - 1); 7240 } else { 7241 value = parseFloat(value); 7242 } 7243 7244 query.checks.push({ 7245 type: Type.DATA_COMPARE, 7246 field: cleanMetaChars(variable), 7247 operator: comparatorOp, 7248 value: value 7249 }); 7250 } 7251 }, { 7252 name: 'dataBool', 7253 query: true, 7254 regex: '\\[\\s*(' + tokens.boolOp + ')\\s*(' + tokens.variable + ')\\s*\\]', 7255 populate: function populate(selector, query, _ref13) { 7256 var _ref14 = _slicedToArray(_ref13, 2), 7257 boolOp = _ref14[0], 7258 variable = _ref14[1]; 7259 7260 query.checks.push({ 7261 type: Type.DATA_BOOL, 7262 field: cleanMetaChars(variable), 7263 operator: boolOp 7264 }); 7265 } 7266 }, { 7267 name: 'metaCompare', 7268 query: true, 7269 regex: '\\[\\[\\s*(' + tokens.meta + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.number + ')\\s*\\]\\]', 7270 populate: function populate(selector, query, _ref15) { 7271 var _ref16 = _slicedToArray(_ref15, 3), 7272 meta = _ref16[0], 7273 comparatorOp = _ref16[1], 7274 number = _ref16[2]; 7275 7276 query.checks.push({ 7277 type: Type.META_COMPARE, 7278 field: cleanMetaChars(meta), 7279 operator: comparatorOp, 7280 value: parseFloat(number) 7281 }); 7282 } 7283 }, { 7284 name: 'nextQuery', 7285 separator: true, 7286 regex: tokens.separator, 7287 populate: function populate(selector, query) { 7288 var currentSubject = selector.currentSubject; 7289 var edgeCount = selector.edgeCount; 7290 var compoundCount = selector.compoundCount; 7291 var lastQ = selector[selector.length - 1]; 7292 7293 if (currentSubject != null) { 7294 lastQ.subject = currentSubject; 7295 selector.currentSubject = null; 7296 } 7297 7298 lastQ.edgeCount = edgeCount; 7299 lastQ.compoundCount = compoundCount; 7300 selector.edgeCount = 0; 7301 selector.compoundCount = 0; // go on to next query 7302 7303 var nextQuery = selector[selector.length++] = newQuery(); 7304 return nextQuery; // this is the new query to be filled by the following exprs 7305 } 7306 }, { 7307 name: 'directedEdge', 7308 separator: true, 7309 regex: tokens.directedEdge, 7310 populate: function populate(selector, query) { 7311 if (selector.currentSubject == null) { 7312 // undirected edge 7313 var edgeQuery = newQuery(); 7314 var source = query; 7315 var target = newQuery(); 7316 edgeQuery.checks.push({ 7317 type: Type.DIRECTED_EDGE, 7318 source: source, 7319 target: target 7320 }); // the query in the selector should be the edge rather than the source 7321 7322 replaceLastQuery(selector, query, edgeQuery); 7323 selector.edgeCount++; // we're now populating the target query with expressions that follow 7324 7325 return target; 7326 } else { 7327 // source/target 7328 var srcTgtQ = newQuery(); 7329 var _source = query; 7330 7331 var _target = newQuery(); 7332 7333 srcTgtQ.checks.push({ 7334 type: Type.NODE_SOURCE, 7335 source: _source, 7336 target: _target 7337 }); // the query in the selector should be the neighbourhood rather than the node 7338 7339 replaceLastQuery(selector, query, srcTgtQ); 7340 selector.edgeCount++; 7341 return _target; // now populating the target with the following expressions 7342 } 7343 } 7344 }, { 7345 name: 'undirectedEdge', 7346 separator: true, 7347 regex: tokens.undirectedEdge, 7348 populate: function populate(selector, query) { 7349 if (selector.currentSubject == null) { 7350 // undirected edge 7351 var edgeQuery = newQuery(); 7352 var source = query; 7353 var target = newQuery(); 7354 edgeQuery.checks.push({ 7355 type: Type.UNDIRECTED_EDGE, 7356 nodes: [source, target] 7357 }); // the query in the selector should be the edge rather than the source 7358 7359 replaceLastQuery(selector, query, edgeQuery); 7360 selector.edgeCount++; // we're now populating the target query with expressions that follow 7361 7362 return target; 7363 } else { 7364 // neighbourhood 7365 var nhoodQ = newQuery(); 7366 var node = query; 7367 var neighbor = newQuery(); 7368 nhoodQ.checks.push({ 7369 type: Type.NODE_NEIGHBOR, 7370 node: node, 7371 neighbor: neighbor 7372 }); // the query in the selector should be the neighbourhood rather than the node 7373 7374 replaceLastQuery(selector, query, nhoodQ); 7375 return neighbor; // now populating the neighbor with following expressions 7376 } 7377 } 7378 }, { 7379 name: 'child', 7380 separator: true, 7381 regex: tokens.child, 7382 populate: function populate(selector, query) { 7383 if (selector.currentSubject == null) { 7384 // default: child query 7385 var parentChildQuery = newQuery(); 7386 var child = newQuery(); 7387 var parent = selector[selector.length - 1]; 7388 parentChildQuery.checks.push({ 7389 type: Type.CHILD, 7390 parent: parent, 7391 child: child 7392 }); // the query in the selector should be the '>' itself 7393 7394 replaceLastQuery(selector, query, parentChildQuery); 7395 selector.compoundCount++; // we're now populating the child query with expressions that follow 7396 7397 return child; 7398 } else if (selector.currentSubject === query) { 7399 // compound split query 7400 var compound = newQuery(); 7401 var left = selector[selector.length - 1]; 7402 var right = newQuery(); 7403 var subject = newQuery(); 7404 7405 var _child = newQuery(); 7406 7407 var _parent = newQuery(); // set up the root compound q 7408 7409 7410 compound.checks.push({ 7411 type: Type.COMPOUND_SPLIT, 7412 left: left, 7413 right: right, 7414 subject: subject 7415 }); // populate the subject and replace the q at the old spot (within left) with TRUE 7416 7417 subject.checks = query.checks; // take the checks from the left 7418 7419 query.checks = [{ 7420 type: Type.TRUE 7421 }]; // checks under left refs the subject implicitly 7422 // set up the right q 7423 7424 _parent.checks.push({ 7425 type: Type.TRUE 7426 }); // parent implicitly refs the subject 7427 7428 7429 right.checks.push({ 7430 type: Type.PARENT, 7431 // type is swapped on right side queries 7432 parent: _parent, 7433 child: _child // empty for now 7434 7435 }); 7436 replaceLastQuery(selector, left, compound); // update the ref since we moved things around for `query` 7437 7438 selector.currentSubject = subject; 7439 selector.compoundCount++; 7440 return _child; // now populating the right side's child 7441 } else { 7442 // parent query 7443 // info for parent query 7444 var _parent2 = newQuery(); 7445 7446 var _child2 = newQuery(); 7447 7448 var pcQChecks = [{ 7449 type: Type.PARENT, 7450 parent: _parent2, 7451 child: _child2 7452 }]; // the parent-child query takes the place of the query previously being populated 7453 7454 _parent2.checks = query.checks; // the previous query contains the checks for the parent 7455 7456 query.checks = pcQChecks; // pc query takes over 7457 7458 selector.compoundCount++; 7459 return _child2; // we're now populating the child 7460 } 7461 } 7462 }, { 7463 name: 'descendant', 7464 separator: true, 7465 regex: tokens.descendant, 7466 populate: function populate(selector, query) { 7467 if (selector.currentSubject == null) { 7468 // default: descendant query 7469 var ancChQuery = newQuery(); 7470 var descendant = newQuery(); 7471 var ancestor = selector[selector.length - 1]; 7472 ancChQuery.checks.push({ 7473 type: Type.DESCENDANT, 7474 ancestor: ancestor, 7475 descendant: descendant 7476 }); // the query in the selector should be the '>' itself 7477 7478 replaceLastQuery(selector, query, ancChQuery); 7479 selector.compoundCount++; // we're now populating the descendant query with expressions that follow 7480 7481 return descendant; 7482 } else if (selector.currentSubject === query) { 7483 // compound split query 7484 var compound = newQuery(); 7485 var left = selector[selector.length - 1]; 7486 var right = newQuery(); 7487 var subject = newQuery(); 7488 7489 var _descendant = newQuery(); 7490 7491 var _ancestor = newQuery(); // set up the root compound q 7492 7493 7494 compound.checks.push({ 7495 type: Type.COMPOUND_SPLIT, 7496 left: left, 7497 right: right, 7498 subject: subject 7499 }); // populate the subject and replace the q at the old spot (within left) with TRUE 7500 7501 subject.checks = query.checks; // take the checks from the left 7502 7503 query.checks = [{ 7504 type: Type.TRUE 7505 }]; // checks under left refs the subject implicitly 7506 // set up the right q 7507 7508 _ancestor.checks.push({ 7509 type: Type.TRUE 7510 }); // ancestor implicitly refs the subject 7511 7512 7513 right.checks.push({ 7514 type: Type.ANCESTOR, 7515 // type is swapped on right side queries 7516 ancestor: _ancestor, 7517 descendant: _descendant // empty for now 7518 7519 }); 7520 replaceLastQuery(selector, left, compound); // update the ref since we moved things around for `query` 7521 7522 selector.currentSubject = subject; 7523 selector.compoundCount++; 7524 return _descendant; // now populating the right side's descendant 7525 } else { 7526 // ancestor query 7527 // info for parent query 7528 var _ancestor2 = newQuery(); 7529 7530 var _descendant2 = newQuery(); 7531 7532 var adQChecks = [{ 7533 type: Type.ANCESTOR, 7534 ancestor: _ancestor2, 7535 descendant: _descendant2 7536 }]; // the parent-child query takes the place of the query previously being populated 7537 7538 _ancestor2.checks = query.checks; // the previous query contains the checks for the parent 7539 7540 query.checks = adQChecks; // pc query takes over 7541 7542 selector.compoundCount++; 7543 return _descendant2; // we're now populating the child 7544 } 7545 } 7546 }, { 7547 name: 'subject', 7548 modifier: true, 7549 regex: tokens.subject, 7550 populate: function populate(selector, query) { 7551 if (selector.currentSubject != null && selector.currentSubject !== query) { 7552 warn('Redefinition of subject in selector `' + selector.toString() + '`'); 7553 return false; 7554 } 7555 7556 selector.currentSubject = query; 7557 var topQ = selector[selector.length - 1]; 7558 var topChk = topQ.checks[0]; 7559 var topType = topChk == null ? null : topChk.type; 7560 7561 if (topType === Type.DIRECTED_EDGE) { 7562 // directed edge with subject on the target 7563 // change to target node check 7564 topChk.type = Type.NODE_TARGET; 7565 } else if (topType === Type.UNDIRECTED_EDGE) { 7566 // undirected edge with subject on the second node 7567 // change to neighbor check 7568 topChk.type = Type.NODE_NEIGHBOR; 7569 topChk.node = topChk.nodes[1]; // second node is subject 7570 7571 topChk.neighbor = topChk.nodes[0]; // clean up unused fields for new type 7572 7573 topChk.nodes = null; 7574 } 7575 } 7576 }]; 7577 exprs.forEach(function (e) { 7578 return e.regexObj = new RegExp('^' + e.regex); 7579 }); 7580 7581 /** 7582 * Of all the expressions, find the first match in the remaining text. 7583 * @param {string} remaining The remaining text to parse 7584 * @returns The matched expression and the newly remaining text `{ expr, match, name, remaining }` 7585 */ 7586 7587 var consumeExpr = function consumeExpr(remaining) { 7588 var expr; 7589 var match; 7590 var name; 7591 7592 for (var j = 0; j < exprs.length; j++) { 7593 var e = exprs[j]; 7594 var n = e.name; 7595 var m = remaining.match(e.regexObj); 7596 7597 if (m != null) { 7598 match = m; 7599 expr = e; 7600 name = n; 7601 var consumed = m[0]; 7602 remaining = remaining.substring(consumed.length); 7603 break; // we've consumed one expr, so we can return now 7604 } 7605 } 7606 7607 return { 7608 expr: expr, 7609 match: match, 7610 name: name, 7611 remaining: remaining 7612 }; 7613 }; 7614 /** 7615 * Consume all the leading whitespace 7616 * @param {string} remaining The text to consume 7617 * @returns The text with the leading whitespace removed 7618 */ 7619 7620 7621 var consumeWhitespace = function consumeWhitespace(remaining) { 7622 var match = remaining.match(/^\s+/); 7623 7624 if (match) { 7625 var consumed = match[0]; 7626 remaining = remaining.substring(consumed.length); 7627 } 7628 7629 return remaining; 7630 }; 7631 /** 7632 * Parse the string and store the parsed representation in the Selector. 7633 * @param {string} selector The selector string 7634 * @returns `true` if the selector was successfully parsed, `false` otherwise 7635 */ 7636 7637 7638 var parse = function parse(selector) { 7639 var self = this; 7640 var remaining = self.inputText = selector; 7641 var currentQuery = self[0] = newQuery(); 7642 self.length = 1; 7643 remaining = consumeWhitespace(remaining); // get rid of leading whitespace 7644 7645 for (;;) { 7646 var exprInfo = consumeExpr(remaining); 7647 7648 if (exprInfo.expr == null) { 7649 warn('The selector `' + selector + '`is invalid'); 7650 return false; 7651 } else { 7652 var args = exprInfo.match.slice(1); // let the token populate the selector object in currentQuery 7653 7654 var ret = exprInfo.expr.populate(self, currentQuery, args); 7655 7656 if (ret === false) { 7657 return false; // exit if population failed 7658 } else if (ret != null) { 7659 currentQuery = ret; // change the current query to be filled if the expr specifies 7660 } 7661 } 7662 7663 remaining = exprInfo.remaining; // we're done when there's nothing left to parse 7664 7665 if (remaining.match(/^\s*$/)) { 7666 break; 7667 } 7668 } 7669 7670 var lastQ = self[self.length - 1]; 7671 7672 if (self.currentSubject != null) { 7673 lastQ.subject = self.currentSubject; 7674 } 7675 7676 lastQ.edgeCount = self.edgeCount; 7677 lastQ.compoundCount = self.compoundCount; 7678 7679 for (var i = 0; i < self.length; i++) { 7680 var q = self[i]; // in future, this could potentially be allowed if there were operator precedence and detection of invalid combinations 7681 7682 if (q.compoundCount > 0 && q.edgeCount > 0) { 7683 warn('The selector `' + selector + '` is invalid because it uses both a compound selector and an edge selector'); 7684 return false; 7685 } 7686 7687 if (q.edgeCount > 1) { 7688 warn('The selector `' + selector + '` is invalid because it uses multiple edge selectors'); 7689 return false; 7690 } else if (q.edgeCount === 1) { 7691 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.'); 7692 } 7693 } 7694 7695 return true; // success 7696 }; 7697 /** 7698 * Get the selector represented as a string. This value uses default formatting, 7699 * so things like spacing may differ from the input text passed to the constructor. 7700 * @returns {string} The selector string 7701 */ 7702 7703 7704 var toString = function toString() { 7705 if (this.toStringCache != null) { 7706 return this.toStringCache; 7707 } 7708 7709 var clean = function clean(obj) { 7710 if (obj == null) { 7711 return ''; 7712 } else { 7713 return obj; 7714 } 7715 }; 7716 7717 var cleanVal = function cleanVal(val) { 7718 if (string(val)) { 7719 return '"' + val + '"'; 7720 } else { 7721 return clean(val); 7722 } 7723 }; 7724 7725 var space = function space(val) { 7726 return ' ' + val + ' '; 7727 }; 7728 7729 var checkToString = function checkToString(check, subject) { 7730 var type = check.type, 7731 value = check.value; 7732 7733 switch (type) { 7734 case Type.GROUP: 7735 { 7736 var group = clean(value); 7737 return group.substring(0, group.length - 1); 7738 } 7739 7740 case Type.DATA_COMPARE: 7741 { 7742 var field = check.field, 7743 operator = check.operator; 7744 return '[' + field + space(clean(operator)) + cleanVal(value) + ']'; 7745 } 7746 7747 case Type.DATA_BOOL: 7748 { 7749 var _operator = check.operator, 7750 _field = check.field; 7751 return '[' + clean(_operator) + _field + ']'; 7752 } 7753 7754 case Type.DATA_EXIST: 7755 { 7756 var _field2 = check.field; 7757 return '[' + _field2 + ']'; 7758 } 7759 7760 case Type.META_COMPARE: 7761 { 7762 var _operator2 = check.operator, 7763 _field3 = check.field; 7764 return '[[' + _field3 + space(clean(_operator2)) + cleanVal(value) + ']]'; 7765 } 7766 7767 case Type.STATE: 7768 { 7769 return value; 7770 } 7771 7772 case Type.ID: 7773 { 7774 return '#' + value; 7775 } 7776 7777 case Type.CLASS: 7778 { 7779 return '.' + value; 7780 } 7781 7782 case Type.PARENT: 7783 case Type.CHILD: 7784 { 7785 return queryToString(check.parent, subject) + space('>') + queryToString(check.child, subject); 7786 } 7787 7788 case Type.ANCESTOR: 7789 case Type.DESCENDANT: 7790 { 7791 return queryToString(check.ancestor, subject) + ' ' + queryToString(check.descendant, subject); 7792 } 7793 7794 case Type.COMPOUND_SPLIT: 7795 { 7796 var lhs = queryToString(check.left, subject); 7797 var sub = queryToString(check.subject, subject); 7798 var rhs = queryToString(check.right, subject); 7799 return lhs + (lhs.length > 0 ? ' ' : '') + sub + rhs; 7800 } 7801 7802 case Type.TRUE: 7803 { 7804 return ''; 7805 } 7806 } 7807 }; 7808 7809 var queryToString = function queryToString(query, subject) { 7810 return query.checks.reduce(function (str, chk, i) { 7811 return str + (subject === query && i === 0 ? '$' : '') + checkToString(chk, subject); 7812 }, ''); 7813 }; 7814 7815 var str = ''; 7816 7817 for (var i = 0; i < this.length; i++) { 7818 var query = this[i]; 7819 str += queryToString(query, query.subject); 7820 7821 if (this.length > 1 && i < this.length - 1) { 7822 str += ', '; 7823 } 7824 } 7825 7826 this.toStringCache = str; 7827 return str; 7828 }; 7829 var parse$1 = { 7830 parse: parse, 7831 toString: toString 7832 }; 7833 7834 var valCmp = function valCmp(fieldVal, operator, value) { 7835 var matches; 7836 var isFieldStr = string(fieldVal); 7837 var isFieldNum = number(fieldVal); 7838 var isValStr = string(value); 7839 var fieldStr, valStr; 7840 var caseInsensitive = false; 7841 var notExpr = false; 7842 var isIneqCmp = false; 7843 7844 if (operator.indexOf('!') >= 0) { 7845 operator = operator.replace('!', ''); 7846 notExpr = true; 7847 } 7848 7849 if (operator.indexOf('@') >= 0) { 7850 operator = operator.replace('@', ''); 7851 caseInsensitive = true; 7852 } 7853 7854 if (isFieldStr || isValStr || caseInsensitive) { 7855 fieldStr = !isFieldStr && !isFieldNum ? '' : '' + fieldVal; 7856 valStr = '' + value; 7857 } // if we're doing a case insensitive comparison, then we're using a STRING comparison 7858 // even if we're comparing numbers 7859 7860 7861 if (caseInsensitive) { 7862 fieldVal = fieldStr = fieldStr.toLowerCase(); 7863 value = valStr = valStr.toLowerCase(); 7864 } 7865 7866 switch (operator) { 7867 case '*=': 7868 matches = fieldStr.indexOf(valStr) >= 0; 7869 break; 7870 7871 case '$=': 7872 matches = fieldStr.indexOf(valStr, fieldStr.length - valStr.length) >= 0; 7873 break; 7874 7875 case '^=': 7876 matches = fieldStr.indexOf(valStr) === 0; 7877 break; 7878 7879 case '=': 7880 matches = fieldVal === value; 7881 break; 7882 7883 case '>': 7884 isIneqCmp = true; 7885 matches = fieldVal > value; 7886 break; 7887 7888 case '>=': 7889 isIneqCmp = true; 7890 matches = fieldVal >= value; 7891 break; 7892 7893 case '<': 7894 isIneqCmp = true; 7895 matches = fieldVal < value; 7896 break; 7897 7898 case '<=': 7899 isIneqCmp = true; 7900 matches = fieldVal <= value; 7901 break; 7902 7903 default: 7904 matches = false; 7905 break; 7906 } // apply the not op, but null vals for inequalities should always stay non-matching 7907 7908 7909 if (notExpr && (fieldVal != null || !isIneqCmp)) { 7910 matches = !matches; 7911 } 7912 7913 return matches; 7914 }; 7915 var boolCmp = function boolCmp(fieldVal, operator) { 7916 switch (operator) { 7917 case '?': 7918 return fieldVal ? true : false; 7919 7920 case '!': 7921 return fieldVal ? false : true; 7922 7923 case '^': 7924 return fieldVal === undefined; 7925 } 7926 }; 7927 var existCmp = function existCmp(fieldVal) { 7928 return fieldVal !== undefined; 7929 }; 7930 var data = function data(ele, field) { 7931 return ele.data(field); 7932 }; 7933 var meta = function meta(ele, field) { 7934 return ele[field](); 7935 }; 7936 7937 /** A lookup of `match(check, ele)` functions by `Type` int */ 7938 7939 var match = []; 7940 /** 7941 * Returns whether the query matches for the element 7942 * @param query The `{ type, value, ... }` query object 7943 * @param ele The element to compare against 7944 */ 7945 7946 var matches = function matches(query, ele) { 7947 return query.checks.every(function (chk) { 7948 return match[chk.type](chk, ele); 7949 }); 7950 }; 7951 7952 match[Type.GROUP] = function (check, ele) { 7953 var group = check.value; 7954 return group === '*' || group === ele.group(); 7955 }; 7956 7957 match[Type.STATE] = function (check, ele) { 7958 var stateSelector = check.value; 7959 return stateSelectorMatches(stateSelector, ele); 7960 }; 7961 7962 match[Type.ID] = function (check, ele) { 7963 var id = check.value; 7964 return ele.id() === id; 7965 }; 7966 7967 match[Type.CLASS] = function (check, ele) { 7968 var cls = check.value; 7969 return ele.hasClass(cls); 7970 }; 7971 7972 match[Type.META_COMPARE] = function (check, ele) { 7973 var field = check.field, 7974 operator = check.operator, 7975 value = check.value; 7976 return valCmp(meta(ele, field), operator, value); 7977 }; 7978 7979 match[Type.DATA_COMPARE] = function (check, ele) { 7980 var field = check.field, 7981 operator = check.operator, 7982 value = check.value; 7983 return valCmp(data(ele, field), operator, value); 7984 }; 7985 7986 match[Type.DATA_BOOL] = function (check, ele) { 7987 var field = check.field, 7988 operator = check.operator; 7989 return boolCmp(data(ele, field), operator); 7990 }; 7991 7992 match[Type.DATA_EXIST] = function (check, ele) { 7993 var field = check.field, 7994 operator = check.operator; 7995 return existCmp(data(ele, field)); 7996 }; 7997 7998 match[Type.UNDIRECTED_EDGE] = function (check, ele) { 7999 var qA = check.nodes[0]; 8000 var qB = check.nodes[1]; 8001 var src = ele.source(); 8002 var tgt = ele.target(); 8003 return matches(qA, src) && matches(qB, tgt) || matches(qB, src) && matches(qA, tgt); 8004 }; 8005 8006 match[Type.NODE_NEIGHBOR] = function (check, ele) { 8007 return matches(check.node, ele) && ele.neighborhood().some(function (n) { 8008 return n.isNode() && matches(check.neighbor, n); 8009 }); 8010 }; 8011 8012 match[Type.DIRECTED_EDGE] = function (check, ele) { 8013 return matches(check.source, ele.source()) && matches(check.target, ele.target()); 8014 }; 8015 8016 match[Type.NODE_SOURCE] = function (check, ele) { 8017 return matches(check.source, ele) && ele.outgoers().some(function (n) { 8018 return n.isNode() && matches(check.target, n); 8019 }); 8020 }; 8021 8022 match[Type.NODE_TARGET] = function (check, ele) { 8023 return matches(check.target, ele) && ele.incomers().some(function (n) { 8024 return n.isNode() && matches(check.source, n); 8025 }); 8026 }; 8027 8028 match[Type.CHILD] = function (check, ele) { 8029 return matches(check.child, ele) && matches(check.parent, ele.parent()); 8030 }; 8031 8032 match[Type.PARENT] = function (check, ele) { 8033 return matches(check.parent, ele) && ele.children().some(function (c) { 8034 return matches(check.child, c); 8035 }); 8036 }; 8037 8038 match[Type.DESCENDANT] = function (check, ele) { 8039 return matches(check.descendant, ele) && ele.ancestors().some(function (a) { 8040 return matches(check.ancestor, a); 8041 }); 8042 }; 8043 8044 match[Type.ANCESTOR] = function (check, ele) { 8045 return matches(check.ancestor, ele) && ele.descendants().some(function (d) { 8046 return matches(check.descendant, d); 8047 }); 8048 }; 8049 8050 match[Type.COMPOUND_SPLIT] = function (check, ele) { 8051 return matches(check.subject, ele) && matches(check.left, ele) && matches(check.right, ele); 8052 }; 8053 8054 match[Type.TRUE] = function () { 8055 return true; 8056 }; 8057 8058 match[Type.COLLECTION] = function (check, ele) { 8059 var collection = check.value; 8060 return collection.has(ele); 8061 }; 8062 8063 match[Type.FILTER] = function (check, ele) { 8064 var filter = check.value; 8065 return filter(ele); 8066 }; 8067 8068 var filter = function filter(collection) { 8069 var self = this; // for 1 id #foo queries, just get the element 8070 8071 if (self.length === 1 && self[0].checks.length === 1 && self[0].checks[0].type === Type.ID) { 8072 return collection.getElementById(self[0].checks[0].value).collection(); 8073 } 8074 8075 var selectorFunction = function selectorFunction(element) { 8076 for (var j = 0; j < self.length; j++) { 8077 var query = self[j]; 8078 8079 if (matches(query, element)) { 8080 return true; 8081 } 8082 } 8083 8084 return false; 8085 }; 8086 8087 if (self.text() == null) { 8088 selectorFunction = function selectorFunction() { 8089 return true; 8090 }; 8091 } 8092 8093 return collection.filter(selectorFunction); 8094 }; // filter 8095 // does selector match a single element? 8096 8097 8098 var matches$1 = function matches$1(ele) { 8099 var self = this; 8100 8101 for (var j = 0; j < self.length; j++) { 8102 var query = self[j]; 8103 8104 if (matches(query, ele)) { 8105 return true; 8106 } 8107 } 8108 8109 return false; 8110 }; // matches 8111 8112 8113 var matching = { 8114 matches: matches$1, 8115 filter: filter 8116 }; 8117 8118 var Selector = function Selector(selector) { 8119 this.inputText = selector; 8120 this.currentSubject = null; 8121 this.compoundCount = 0; 8122 this.edgeCount = 0; 8123 this.length = 0; 8124 8125 if (selector == null || string(selector) && selector.match(/^\s*$/)) ; else if (elementOrCollection(selector)) { 8126 this.addQuery({ 8127 checks: [{ 8128 type: Type.COLLECTION, 8129 value: selector.collection() 8130 }] 8131 }); 8132 } else if (fn(selector)) { 8133 this.addQuery({ 8134 checks: [{ 8135 type: Type.FILTER, 8136 value: selector 8137 }] 8138 }); 8139 } else if (string(selector)) { 8140 if (!this.parse(selector)) { 8141 this.invalid = true; 8142 } 8143 } else { 8144 error('A selector must be created from a string; found '); 8145 } 8146 }; 8147 8148 var selfn = Selector.prototype; 8149 [parse$1, matching].forEach(function (p) { 8150 return extend(selfn, p); 8151 }); 8152 8153 selfn.text = function () { 8154 return this.inputText; 8155 }; 8156 8157 selfn.size = function () { 8158 return this.length; 8159 }; 8160 8161 selfn.eq = function (i) { 8162 return this[i]; 8163 }; 8164 8165 selfn.sameText = function (otherSel) { 8166 return !this.invalid && !otherSel.invalid && this.text() === otherSel.text(); 8167 }; 8168 8169 selfn.addQuery = function (q) { 8170 this[this.length++] = q; 8171 }; 8172 8173 selfn.selector = selfn.toString; 8174 8175 var elesfn$f = { 8176 allAre: function allAre(selector) { 8177 var selObj = new Selector(selector); 8178 return this.every(function (ele) { 8179 return selObj.matches(ele); 8180 }); 8181 }, 8182 is: function is(selector) { 8183 var selObj = new Selector(selector); 8184 return this.some(function (ele) { 8185 return selObj.matches(ele); 8186 }); 8187 }, 8188 some: function some(fn, thisArg) { 8189 for (var i = 0; i < this.length; i++) { 8190 var ret = !thisArg ? fn(this[i], i, this) : fn.apply(thisArg, [this[i], i, this]); 8191 8192 if (ret) { 8193 return true; 8194 } 8195 } 8196 8197 return false; 8198 }, 8199 every: function every(fn, thisArg) { 8200 for (var i = 0; i < this.length; i++) { 8201 var ret = !thisArg ? fn(this[i], i, this) : fn.apply(thisArg, [this[i], i, this]); 8202 8203 if (!ret) { 8204 return false; 8205 } 8206 } 8207 8208 return true; 8209 }, 8210 same: function same(collection) { 8211 // cheap collection ref check 8212 if (this === collection) { 8213 return true; 8214 } 8215 8216 collection = this.cy().collection(collection); 8217 var thisLength = this.length; 8218 var collectionLength = collection.length; // cheap length check 8219 8220 if (thisLength !== collectionLength) { 8221 return false; 8222 } // cheap element ref check 8223 8224 8225 if (thisLength === 1) { 8226 return this[0] === collection[0]; 8227 } 8228 8229 return this.every(function (ele) { 8230 return collection.hasElementWithId(ele.id()); 8231 }); 8232 }, 8233 anySame: function anySame(collection) { 8234 collection = this.cy().collection(collection); 8235 return this.some(function (ele) { 8236 return collection.hasElementWithId(ele.id()); 8237 }); 8238 }, 8239 allAreNeighbors: function allAreNeighbors(collection) { 8240 collection = this.cy().collection(collection); 8241 var nhood = this.neighborhood(); 8242 return collection.every(function (ele) { 8243 return nhood.hasElementWithId(ele.id()); 8244 }); 8245 }, 8246 contains: function contains(collection) { 8247 collection = this.cy().collection(collection); 8248 var self = this; 8249 return collection.every(function (ele) { 8250 return self.hasElementWithId(ele.id()); 8251 }); 8252 } 8253 }; 8254 elesfn$f.allAreNeighbours = elesfn$f.allAreNeighbors; 8255 elesfn$f.has = elesfn$f.contains; 8256 elesfn$f.equal = elesfn$f.equals = elesfn$f.same; 8257 8258 var cache = function cache(fn, name) { 8259 return function traversalCache(arg1, arg2, arg3, arg4) { 8260 var selectorOrEles = arg1; 8261 var eles = this; 8262 var key; 8263 8264 if (selectorOrEles == null) { 8265 key = ''; 8266 } else if (elementOrCollection(selectorOrEles) && selectorOrEles.length === 1) { 8267 key = selectorOrEles.id(); 8268 } 8269 8270 if (eles.length === 1 && key) { 8271 var _p = eles[0]._private; 8272 var tch = _p.traversalCache = _p.traversalCache || {}; 8273 var ch = tch[name] = tch[name] || []; 8274 var hash = hashString(key); 8275 var cacheHit = ch[hash]; 8276 8277 if (cacheHit) { 8278 return cacheHit; 8279 } else { 8280 return ch[hash] = fn.call(eles, arg1, arg2, arg3, arg4); 8281 } 8282 } else { 8283 return fn.call(eles, arg1, arg2, arg3, arg4); 8284 } 8285 }; 8286 }; 8287 8288 var elesfn$g = { 8289 parent: function parent(selector) { 8290 var parents = []; // optimisation for single ele call 8291 8292 if (this.length === 1) { 8293 var parent = this[0]._private.parent; 8294 8295 if (parent) { 8296 return parent; 8297 } 8298 } 8299 8300 for (var i = 0; i < this.length; i++) { 8301 var ele = this[i]; 8302 var _parent = ele._private.parent; 8303 8304 if (_parent) { 8305 parents.push(_parent); 8306 } 8307 } 8308 8309 return this.spawn(parents, { 8310 unique: true 8311 }).filter(selector); 8312 }, 8313 parents: function parents(selector) { 8314 var parents = []; 8315 var eles = this.parent(); 8316 8317 while (eles.nonempty()) { 8318 for (var i = 0; i < eles.length; i++) { 8319 var ele = eles[i]; 8320 parents.push(ele); 8321 } 8322 8323 eles = eles.parent(); 8324 } 8325 8326 return this.spawn(parents, { 8327 unique: true 8328 }).filter(selector); 8329 }, 8330 commonAncestors: function commonAncestors(selector) { 8331 var ancestors; 8332 8333 for (var i = 0; i < this.length; i++) { 8334 var ele = this[i]; 8335 var parents = ele.parents(); 8336 ancestors = ancestors || parents; 8337 ancestors = ancestors.intersect(parents); // current list must be common with current ele parents set 8338 } 8339 8340 return ancestors.filter(selector); 8341 }, 8342 orphans: function orphans(selector) { 8343 return this.stdFilter(function (ele) { 8344 return ele.isOrphan(); 8345 }).filter(selector); 8346 }, 8347 nonorphans: function nonorphans(selector) { 8348 return this.stdFilter(function (ele) { 8349 return ele.isChild(); 8350 }).filter(selector); 8351 }, 8352 children: cache(function (selector) { 8353 var children = []; 8354 8355 for (var i = 0; i < this.length; i++) { 8356 var ele = this[i]; 8357 var eleChildren = ele._private.children; 8358 8359 for (var j = 0; j < eleChildren.length; j++) { 8360 children.push(eleChildren[j]); 8361 } 8362 } 8363 8364 return this.spawn(children, { 8365 unique: true 8366 }).filter(selector); 8367 }, 'children'), 8368 siblings: function siblings(selector) { 8369 return this.parent().children().not(this).filter(selector); 8370 }, 8371 isParent: function isParent() { 8372 var ele = this[0]; 8373 8374 if (ele) { 8375 return ele.isNode() && ele._private.children.length !== 0; 8376 } 8377 }, 8378 isChildless: function isChildless() { 8379 var ele = this[0]; 8380 8381 if (ele) { 8382 return ele.isNode() && ele._private.children.length === 0; 8383 } 8384 }, 8385 isChild: function isChild() { 8386 var ele = this[0]; 8387 8388 if (ele) { 8389 return ele.isNode() && ele._private.parent != null; 8390 } 8391 }, 8392 isOrphan: function isOrphan() { 8393 var ele = this[0]; 8394 8395 if (ele) { 8396 return ele.isNode() && ele._private.parent == null; 8397 } 8398 }, 8399 descendants: function descendants(selector) { 8400 var elements = []; 8401 8402 function add(eles) { 8403 for (var i = 0; i < eles.length; i++) { 8404 var ele = eles[i]; 8405 elements.push(ele); 8406 8407 if (ele.children().nonempty()) { 8408 add(ele.children()); 8409 } 8410 } 8411 } 8412 8413 add(this.children()); 8414 return this.spawn(elements, { 8415 unique: true 8416 }).filter(selector); 8417 } 8418 }; 8419 8420 function forEachCompound(eles, fn, includeSelf, recursiveStep) { 8421 var q = []; 8422 var did = new Set$1(); 8423 var cy = eles.cy(); 8424 var hasCompounds = cy.hasCompoundNodes(); 8425 8426 for (var i = 0; i < eles.length; i++) { 8427 var ele = eles[i]; 8428 8429 if (includeSelf) { 8430 q.push(ele); 8431 } else if (hasCompounds) { 8432 recursiveStep(q, did, ele); 8433 } 8434 } 8435 8436 while (q.length > 0) { 8437 var _ele = q.shift(); 8438 8439 fn(_ele); 8440 did.add(_ele.id()); 8441 8442 if (hasCompounds) { 8443 recursiveStep(q, did, _ele); 8444 } 8445 } 8446 8447 return eles; 8448 } 8449 8450 function addChildren(q, did, ele) { 8451 if (ele.isParent()) { 8452 var children = ele._private.children; 8453 8454 for (var i = 0; i < children.length; i++) { 8455 var child = children[i]; 8456 8457 if (!did.has(child.id())) { 8458 q.push(child); 8459 } 8460 } 8461 } 8462 } // very efficient version of eles.add( eles.descendants() ).forEach() 8463 // for internal use 8464 8465 8466 elesfn$g.forEachDown = function (fn) { 8467 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 8468 return forEachCompound(this, fn, includeSelf, addChildren); 8469 }; 8470 8471 function addParent(q, did, ele) { 8472 if (ele.isChild()) { 8473 var parent = ele._private.parent; 8474 8475 if (!did.has(parent.id())) { 8476 q.push(parent); 8477 } 8478 } 8479 } 8480 8481 elesfn$g.forEachUp = function (fn) { 8482 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 8483 return forEachCompound(this, fn, includeSelf, addParent); 8484 }; 8485 8486 function addParentAndChildren(q, did, ele) { 8487 addParent(q, did, ele); 8488 addChildren(q, did, ele); 8489 } 8490 8491 elesfn$g.forEachUpAndDown = function (fn) { 8492 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 8493 return forEachCompound(this, fn, includeSelf, addParentAndChildren); 8494 }; // aliases 8495 8496 8497 elesfn$g.ancestors = elesfn$g.parents; 8498 8499 var fn$1, elesfn$h; 8500 fn$1 = elesfn$h = { 8501 data: define$3.data({ 8502 field: 'data', 8503 bindingEvent: 'data', 8504 allowBinding: true, 8505 allowSetting: true, 8506 settingEvent: 'data', 8507 settingTriggersEvent: true, 8508 triggerFnName: 'trigger', 8509 allowGetting: true, 8510 immutableKeys: { 8511 'id': true, 8512 'source': true, 8513 'target': true, 8514 'parent': true 8515 }, 8516 updateStyle: true 8517 }), 8518 removeData: define$3.removeData({ 8519 field: 'data', 8520 event: 'data', 8521 triggerFnName: 'trigger', 8522 triggerEvent: true, 8523 immutableKeys: { 8524 'id': true, 8525 'source': true, 8526 'target': true, 8527 'parent': true 8528 }, 8529 updateStyle: true 8530 }), 8531 scratch: define$3.data({ 8532 field: 'scratch', 8533 bindingEvent: 'scratch', 8534 allowBinding: true, 8535 allowSetting: true, 8536 settingEvent: 'scratch', 8537 settingTriggersEvent: true, 8538 triggerFnName: 'trigger', 8539 allowGetting: true, 8540 updateStyle: true 8541 }), 8542 removeScratch: define$3.removeData({ 8543 field: 'scratch', 8544 event: 'scratch', 8545 triggerFnName: 'trigger', 8546 triggerEvent: true, 8547 updateStyle: true 8548 }), 8549 rscratch: define$3.data({ 8550 field: 'rscratch', 8551 allowBinding: false, 8552 allowSetting: true, 8553 settingTriggersEvent: false, 8554 allowGetting: true 8555 }), 8556 removeRscratch: define$3.removeData({ 8557 field: 'rscratch', 8558 triggerEvent: false 8559 }), 8560 id: function id() { 8561 var ele = this[0]; 8562 8563 if (ele) { 8564 return ele._private.data.id; 8565 } 8566 } 8567 }; // aliases 8568 8569 fn$1.attr = fn$1.data; 8570 fn$1.removeAttr = fn$1.removeData; 8571 var data$1 = elesfn$h; 8572 8573 var elesfn$i = {}; 8574 8575 function defineDegreeFunction(callback) { 8576 return function (includeLoops) { 8577 var self = this; 8578 8579 if (includeLoops === undefined) { 8580 includeLoops = true; 8581 } 8582 8583 if (self.length === 0) { 8584 return; 8585 } 8586 8587 if (self.isNode() && !self.removed()) { 8588 var degree = 0; 8589 var node = self[0]; 8590 var connectedEdges = node._private.edges; 8591 8592 for (var i = 0; i < connectedEdges.length; i++) { 8593 var edge = connectedEdges[i]; 8594 8595 if (!includeLoops && edge.isLoop()) { 8596 continue; 8597 } 8598 8599 degree += callback(node, edge); 8600 } 8601 8602 return degree; 8603 } else { 8604 return; 8605 } 8606 }; 8607 } 8608 8609 extend(elesfn$i, { 8610 degree: defineDegreeFunction(function (node, edge) { 8611 if (edge.source().same(edge.target())) { 8612 return 2; 8613 } else { 8614 return 1; 8615 } 8616 }), 8617 indegree: defineDegreeFunction(function (node, edge) { 8618 if (edge.target().same(node)) { 8619 return 1; 8620 } else { 8621 return 0; 8622 } 8623 }), 8624 outdegree: defineDegreeFunction(function (node, edge) { 8625 if (edge.source().same(node)) { 8626 return 1; 8627 } else { 8628 return 0; 8629 } 8630 }) 8631 }); 8632 8633 function defineDegreeBoundsFunction(degreeFn, callback) { 8634 return function (includeLoops) { 8635 var ret; 8636 var nodes = this.nodes(); 8637 8638 for (var i = 0; i < nodes.length; i++) { 8639 var ele = nodes[i]; 8640 var degree = ele[degreeFn](includeLoops); 8641 8642 if (degree !== undefined && (ret === undefined || callback(degree, ret))) { 8643 ret = degree; 8644 } 8645 } 8646 8647 return ret; 8648 }; 8649 } 8650 8651 extend(elesfn$i, { 8652 minDegree: defineDegreeBoundsFunction('degree', function (degree, min) { 8653 return degree < min; 8654 }), 8655 maxDegree: defineDegreeBoundsFunction('degree', function (degree, max) { 8656 return degree > max; 8657 }), 8658 minIndegree: defineDegreeBoundsFunction('indegree', function (degree, min) { 8659 return degree < min; 8660 }), 8661 maxIndegree: defineDegreeBoundsFunction('indegree', function (degree, max) { 8662 return degree > max; 8663 }), 8664 minOutdegree: defineDegreeBoundsFunction('outdegree', function (degree, min) { 8665 return degree < min; 8666 }), 8667 maxOutdegree: defineDegreeBoundsFunction('outdegree', function (degree, max) { 8668 return degree > max; 8669 }) 8670 }); 8671 extend(elesfn$i, { 8672 totalDegree: function totalDegree(includeLoops) { 8673 var total = 0; 8674 var nodes = this.nodes(); 8675 8676 for (var i = 0; i < nodes.length; i++) { 8677 total += nodes[i].degree(includeLoops); 8678 } 8679 8680 return total; 8681 } 8682 }); 8683 8684 var fn$2, elesfn$j; 8685 8686 var beforePositionSet = function beforePositionSet(eles, newPos, silent) { 8687 for (var i = 0; i < eles.length; i++) { 8688 var ele = eles[i]; 8689 8690 if (!ele.locked()) { 8691 var oldPos = ele._private.position; 8692 var delta = { 8693 x: newPos.x != null ? newPos.x - oldPos.x : 0, 8694 y: newPos.y != null ? newPos.y - oldPos.y : 0 8695 }; 8696 8697 if (ele.isParent() && !(delta.x === 0 && delta.y === 0)) { 8698 ele.children().shift(delta, silent); 8699 } 8700 8701 ele.shiftCachedBoundingBox(delta); 8702 } 8703 } 8704 }; 8705 8706 var positionDef = { 8707 field: 'position', 8708 bindingEvent: 'position', 8709 allowBinding: true, 8710 allowSetting: true, 8711 settingEvent: 'position', 8712 settingTriggersEvent: true, 8713 triggerFnName: 'emitAndNotify', 8714 allowGetting: true, 8715 validKeys: ['x', 'y'], 8716 beforeGet: function beforeGet(ele) { 8717 ele.updateCompoundBounds(); 8718 }, 8719 beforeSet: function beforeSet(eles, newPos) { 8720 beforePositionSet(eles, newPos, false); 8721 }, 8722 onSet: function onSet(eles) { 8723 eles.dirtyCompoundBoundsCache(); 8724 }, 8725 canSet: function canSet(ele) { 8726 return !ele.locked(); 8727 } 8728 }; 8729 fn$2 = elesfn$j = { 8730 position: define$3.data(positionDef), 8731 // position but no notification to renderer 8732 silentPosition: define$3.data(extend({}, positionDef, { 8733 allowBinding: false, 8734 allowSetting: true, 8735 settingTriggersEvent: false, 8736 allowGetting: false, 8737 beforeSet: function beforeSet(eles, newPos) { 8738 beforePositionSet(eles, newPos, true); 8739 } 8740 })), 8741 positions: function positions(pos, silent) { 8742 if (plainObject(pos)) { 8743 if (silent) { 8744 this.silentPosition(pos); 8745 } else { 8746 this.position(pos); 8747 } 8748 } else if (fn(pos)) { 8749 var _fn = pos; 8750 var cy = this.cy(); 8751 cy.startBatch(); 8752 8753 for (var i = 0; i < this.length; i++) { 8754 var ele = this[i]; 8755 8756 var _pos = void 0; 8757 8758 if (_pos = _fn(ele, i)) { 8759 if (silent) { 8760 ele.silentPosition(_pos); 8761 } else { 8762 ele.position(_pos); 8763 } 8764 } 8765 } 8766 8767 cy.endBatch(); 8768 } 8769 8770 return this; // chaining 8771 }, 8772 silentPositions: function silentPositions(pos) { 8773 return this.positions(pos, true); 8774 }, 8775 shift: function shift(dim, val, silent) { 8776 var delta; 8777 8778 if (plainObject(dim)) { 8779 delta = { 8780 x: number(dim.x) ? dim.x : 0, 8781 y: number(dim.y) ? dim.y : 0 8782 }; 8783 silent = val; 8784 } else if (string(dim) && number(val)) { 8785 delta = { 8786 x: 0, 8787 y: 0 8788 }; 8789 delta[dim] = val; 8790 } 8791 8792 if (delta != null) { 8793 var cy = this.cy(); 8794 cy.startBatch(); 8795 8796 for (var i = 0; i < this.length; i++) { 8797 var ele = this[i]; 8798 var pos = ele.position(); 8799 var newPos = { 8800 x: pos.x + delta.x, 8801 y: pos.y + delta.y 8802 }; 8803 8804 if (silent) { 8805 ele.silentPosition(newPos); 8806 } else { 8807 ele.position(newPos); 8808 } 8809 } 8810 8811 cy.endBatch(); 8812 } 8813 8814 return this; 8815 }, 8816 silentShift: function silentShift(dim, val) { 8817 if (plainObject(dim)) { 8818 this.shift(dim, true); 8819 } else if (string(dim) && number(val)) { 8820 this.shift(dim, val, true); 8821 } 8822 8823 return this; 8824 }, 8825 // get/set the rendered (i.e. on screen) positon of the element 8826 renderedPosition: function renderedPosition(dim, val) { 8827 var ele = this[0]; 8828 var cy = this.cy(); 8829 var zoom = cy.zoom(); 8830 var pan = cy.pan(); 8831 var rpos = plainObject(dim) ? dim : undefined; 8832 var setting = rpos !== undefined || val !== undefined && string(dim); 8833 8834 if (ele && ele.isNode()) { 8835 // must have an element and must be a node to return position 8836 if (setting) { 8837 for (var i = 0; i < this.length; i++) { 8838 var _ele = this[i]; 8839 8840 if (val !== undefined) { 8841 // set one dimension 8842 _ele.position(dim, (val - pan[dim]) / zoom); 8843 } else if (rpos !== undefined) { 8844 // set whole position 8845 _ele.position(renderedToModelPosition(rpos, zoom, pan)); 8846 } 8847 } 8848 } else { 8849 // getting 8850 var pos = ele.position(); 8851 rpos = modelToRenderedPosition(pos, zoom, pan); 8852 8853 if (dim === undefined) { 8854 // then return the whole rendered position 8855 return rpos; 8856 } else { 8857 // then return the specified dimension 8858 return rpos[dim]; 8859 } 8860 } 8861 } else if (!setting) { 8862 return undefined; // for empty collection case 8863 } 8864 8865 return this; // chaining 8866 }, 8867 // get/set the position relative to the parent 8868 relativePosition: function relativePosition(dim, val) { 8869 var ele = this[0]; 8870 var cy = this.cy(); 8871 var ppos = plainObject(dim) ? dim : undefined; 8872 var setting = ppos !== undefined || val !== undefined && string(dim); 8873 var hasCompoundNodes = cy.hasCompoundNodes(); 8874 8875 if (ele && ele.isNode()) { 8876 // must have an element and must be a node to return position 8877 if (setting) { 8878 for (var i = 0; i < this.length; i++) { 8879 var _ele2 = this[i]; 8880 var parent = hasCompoundNodes ? _ele2.parent() : null; 8881 var hasParent = parent && parent.length > 0; 8882 var relativeToParent = hasParent; 8883 8884 if (hasParent) { 8885 parent = parent[0]; 8886 } 8887 8888 var origin = relativeToParent ? parent.position() : { 8889 x: 0, 8890 y: 0 8891 }; 8892 8893 if (val !== undefined) { 8894 // set one dimension 8895 _ele2.position(dim, val + origin[dim]); 8896 } else if (ppos !== undefined) { 8897 // set whole position 8898 _ele2.position({ 8899 x: ppos.x + origin.x, 8900 y: ppos.y + origin.y 8901 }); 8902 } 8903 } 8904 } else { 8905 // getting 8906 var pos = ele.position(); 8907 8908 var _parent = hasCompoundNodes ? ele.parent() : null; 8909 8910 var _hasParent = _parent && _parent.length > 0; 8911 8912 var _relativeToParent = _hasParent; 8913 8914 if (_hasParent) { 8915 _parent = _parent[0]; 8916 } 8917 8918 var _origin = _relativeToParent ? _parent.position() : { 8919 x: 0, 8920 y: 0 8921 }; 8922 8923 ppos = { 8924 x: pos.x - _origin.x, 8925 y: pos.y - _origin.y 8926 }; 8927 8928 if (dim === undefined) { 8929 // then return the whole rendered position 8930 return ppos; 8931 } else { 8932 // then return the specified dimension 8933 return ppos[dim]; 8934 } 8935 } 8936 } else if (!setting) { 8937 return undefined; // for empty collection case 8938 } 8939 8940 return this; // chaining 8941 } 8942 }; // aliases 8943 8944 fn$2.modelPosition = fn$2.point = fn$2.position; 8945 fn$2.modelPositions = fn$2.points = fn$2.positions; 8946 fn$2.renderedPoint = fn$2.renderedPosition; 8947 fn$2.relativePoint = fn$2.relativePosition; 8948 var position = elesfn$j; 8949 8950 var fn$3, elesfn$k; 8951 fn$3 = elesfn$k = {}; 8952 8953 elesfn$k.renderedBoundingBox = function (options) { 8954 var bb = this.boundingBox(options); 8955 var cy = this.cy(); 8956 var zoom = cy.zoom(); 8957 var pan = cy.pan(); 8958 var x1 = bb.x1 * zoom + pan.x; 8959 var x2 = bb.x2 * zoom + pan.x; 8960 var y1 = bb.y1 * zoom + pan.y; 8961 var y2 = bb.y2 * zoom + pan.y; 8962 return { 8963 x1: x1, 8964 x2: x2, 8965 y1: y1, 8966 y2: y2, 8967 w: x2 - x1, 8968 h: y2 - y1 8969 }; 8970 }; 8971 8972 elesfn$k.dirtyCompoundBoundsCache = function () { 8973 var cy = this.cy(); 8974 8975 if (!cy.styleEnabled() || !cy.hasCompoundNodes()) { 8976 return this; 8977 } 8978 8979 this.forEachUp(function (ele) { 8980 if (ele.isParent()) { 8981 var _p = ele._private; 8982 _p.compoundBoundsClean = false; 8983 _p.bbCache = null; 8984 ele.emitAndNotify('bounds'); 8985 } 8986 }); 8987 return this; 8988 }; 8989 8990 elesfn$k.updateCompoundBounds = function () { 8991 var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 8992 var cy = this.cy(); // not possible to do on non-compound graphs or with the style disabled 8993 8994 if (!cy.styleEnabled() || !cy.hasCompoundNodes()) { 8995 return this; 8996 } // save cycles when batching -- but bounds will be stale (or not exist yet) 8997 8998 8999 if (!force && cy.batching()) { 9000 return this; 9001 } 9002 9003 function update(parent) { 9004 if (!parent.isParent()) { 9005 return; 9006 } 9007 9008 var _p = parent._private; 9009 var children = parent.children(); 9010 var includeLabels = parent.pstyle('compound-sizing-wrt-labels').value === 'include'; 9011 var min = { 9012 width: { 9013 val: parent.pstyle('min-width').pfValue, 9014 left: parent.pstyle('min-width-bias-left'), 9015 right: parent.pstyle('min-width-bias-right') 9016 }, 9017 height: { 9018 val: parent.pstyle('min-height').pfValue, 9019 top: parent.pstyle('min-height-bias-top'), 9020 bottom: parent.pstyle('min-height-bias-bottom') 9021 } 9022 }; 9023 var bb = children.boundingBox({ 9024 includeLabels: includeLabels, 9025 includeOverlays: false, 9026 // updating the compound bounds happens outside of the regular 9027 // cache cycle (i.e. before fired events) 9028 useCache: false 9029 }); 9030 var pos = _p.position; // if children take up zero area then keep position and fall back on stylesheet w/h 9031 9032 if (bb.w === 0 || bb.h === 0) { 9033 bb = { 9034 w: parent.pstyle('width').pfValue, 9035 h: parent.pstyle('height').pfValue 9036 }; 9037 bb.x1 = pos.x - bb.w / 2; 9038 bb.x2 = pos.x + bb.w / 2; 9039 bb.y1 = pos.y - bb.h / 2; 9040 bb.y2 = pos.y + bb.h / 2; 9041 } 9042 9043 function computeBiasValues(propDiff, propBias, propBiasComplement) { 9044 var biasDiff = 0; 9045 var biasComplementDiff = 0; 9046 var biasTotal = propBias + propBiasComplement; 9047 9048 if (propDiff > 0 && biasTotal > 0) { 9049 biasDiff = propBias / biasTotal * propDiff; 9050 biasComplementDiff = propBiasComplement / biasTotal * propDiff; 9051 } 9052 9053 return { 9054 biasDiff: biasDiff, 9055 biasComplementDiff: biasComplementDiff 9056 }; 9057 } 9058 9059 function computePaddingValues(width, height, paddingObject, relativeTo) { 9060 // Assuming percentage is number from 0 to 1 9061 if (paddingObject.units === '%') { 9062 switch (relativeTo) { 9063 case 'width': 9064 return width > 0 ? paddingObject.pfValue * width : 0; 9065 9066 case 'height': 9067 return height > 0 ? paddingObject.pfValue * height : 0; 9068 9069 case 'average': 9070 return width > 0 && height > 0 ? paddingObject.pfValue * (width + height) / 2 : 0; 9071 9072 case 'min': 9073 return width > 0 && height > 0 ? width > height ? paddingObject.pfValue * height : paddingObject.pfValue * width : 0; 9074 9075 case 'max': 9076 return width > 0 && height > 0 ? width > height ? paddingObject.pfValue * width : paddingObject.pfValue * height : 0; 9077 9078 default: 9079 return 0; 9080 } 9081 } else if (paddingObject.units === 'px') { 9082 return paddingObject.pfValue; 9083 } else { 9084 return 0; 9085 } 9086 } 9087 9088 var leftVal = min.width.left.value; 9089 9090 if (min.width.left.units === 'px' && min.width.val > 0) { 9091 leftVal = leftVal * 100 / min.width.val; 9092 } 9093 9094 var rightVal = min.width.right.value; 9095 9096 if (min.width.right.units === 'px' && min.width.val > 0) { 9097 rightVal = rightVal * 100 / min.width.val; 9098 } 9099 9100 var topVal = min.height.top.value; 9101 9102 if (min.height.top.units === 'px' && min.height.val > 0) { 9103 topVal = topVal * 100 / min.height.val; 9104 } 9105 9106 var bottomVal = min.height.bottom.value; 9107 9108 if (min.height.bottom.units === 'px' && min.height.val > 0) { 9109 bottomVal = bottomVal * 100 / min.height.val; 9110 } 9111 9112 var widthBiasDiffs = computeBiasValues(min.width.val - bb.w, leftVal, rightVal); 9113 var diffLeft = widthBiasDiffs.biasDiff; 9114 var diffRight = widthBiasDiffs.biasComplementDiff; 9115 var heightBiasDiffs = computeBiasValues(min.height.val - bb.h, topVal, bottomVal); 9116 var diffTop = heightBiasDiffs.biasDiff; 9117 var diffBottom = heightBiasDiffs.biasComplementDiff; 9118 _p.autoPadding = computePaddingValues(bb.w, bb.h, parent.pstyle('padding'), parent.pstyle('padding-relative-to').value); 9119 _p.autoWidth = Math.max(bb.w, min.width.val); 9120 pos.x = (-diffLeft + bb.x1 + bb.x2 + diffRight) / 2; 9121 _p.autoHeight = Math.max(bb.h, min.height.val); 9122 pos.y = (-diffTop + bb.y1 + bb.y2 + diffBottom) / 2; 9123 } 9124 9125 for (var i = 0; i < this.length; i++) { 9126 var ele = this[i]; 9127 var _p = ele._private; 9128 9129 if (!_p.compoundBoundsClean) { 9130 update(ele); 9131 9132 if (!cy.batching()) { 9133 _p.compoundBoundsClean = true; 9134 } 9135 } 9136 } 9137 9138 return this; 9139 }; 9140 9141 var noninf = function noninf(x) { 9142 if (x === Infinity || x === -Infinity) { 9143 return 0; 9144 } 9145 9146 return x; 9147 }; 9148 9149 var updateBounds = function updateBounds(b, x1, y1, x2, y2) { 9150 // don't update with zero area boxes 9151 if (x2 - x1 === 0 || y2 - y1 === 0) { 9152 return; 9153 } // don't update with null dim 9154 9155 9156 if (x1 == null || y1 == null || x2 == null || y2 == null) { 9157 return; 9158 } 9159 9160 b.x1 = x1 < b.x1 ? x1 : b.x1; 9161 b.x2 = x2 > b.x2 ? x2 : b.x2; 9162 b.y1 = y1 < b.y1 ? y1 : b.y1; 9163 b.y2 = y2 > b.y2 ? y2 : b.y2; 9164 b.w = b.x2 - b.x1; 9165 b.h = b.y2 - b.y1; 9166 }; 9167 9168 var updateBoundsFromBox = function updateBoundsFromBox(b, b2) { 9169 if (b2 == null) { 9170 return b; 9171 } 9172 9173 return updateBounds(b, b2.x1, b2.y1, b2.x2, b2.y2); 9174 }; 9175 9176 var prefixedProperty = function prefixedProperty(obj, field, prefix) { 9177 return getPrefixedProperty(obj, field, prefix); 9178 }; 9179 9180 var updateBoundsFromArrow = function updateBoundsFromArrow(bounds, ele, prefix) { 9181 if (ele.cy().headless()) { 9182 return; 9183 } 9184 9185 var _p = ele._private; 9186 var rstyle = _p.rstyle; 9187 var halfArW = rstyle.arrowWidth / 2; 9188 var arrowType = ele.pstyle(prefix + '-arrow-shape').value; 9189 var x; 9190 var y; 9191 9192 if (arrowType !== 'none') { 9193 if (prefix === 'source') { 9194 x = rstyle.srcX; 9195 y = rstyle.srcY; 9196 } else if (prefix === 'target') { 9197 x = rstyle.tgtX; 9198 y = rstyle.tgtY; 9199 } else { 9200 x = rstyle.midX; 9201 y = rstyle.midY; 9202 } // always store the individual arrow bounds 9203 9204 9205 var bbs = _p.arrowBounds = _p.arrowBounds || {}; 9206 var bb = bbs[prefix] = bbs[prefix] || {}; 9207 bb.x1 = x - halfArW; 9208 bb.y1 = y - halfArW; 9209 bb.x2 = x + halfArW; 9210 bb.y2 = y + halfArW; 9211 bb.w = bb.x2 - bb.x1; 9212 bb.h = bb.y2 - bb.y1; 9213 expandBoundingBox(bb, 1); 9214 updateBounds(bounds, bb.x1, bb.y1, bb.x2, bb.y2); 9215 } 9216 }; 9217 9218 var updateBoundsFromLabel = function updateBoundsFromLabel(bounds, ele, prefix) { 9219 if (ele.cy().headless()) { 9220 return; 9221 } 9222 9223 var prefixDash; 9224 9225 if (prefix) { 9226 prefixDash = prefix + '-'; 9227 } else { 9228 prefixDash = ''; 9229 } 9230 9231 var _p = ele._private; 9232 var rstyle = _p.rstyle; 9233 var label = ele.pstyle(prefixDash + 'label').strValue; 9234 9235 if (label) { 9236 var halign = ele.pstyle('text-halign'); 9237 var valign = ele.pstyle('text-valign'); 9238 var labelWidth = prefixedProperty(rstyle, 'labelWidth', prefix); 9239 var labelHeight = prefixedProperty(rstyle, 'labelHeight', prefix); 9240 var labelX = prefixedProperty(rstyle, 'labelX', prefix); 9241 var labelY = prefixedProperty(rstyle, 'labelY', prefix); 9242 var marginX = ele.pstyle(prefixDash + 'text-margin-x').pfValue; 9243 var marginY = ele.pstyle(prefixDash + 'text-margin-y').pfValue; 9244 var isEdge = ele.isEdge(); 9245 var rotation = ele.pstyle(prefixDash + 'text-rotation'); 9246 var outlineWidth = ele.pstyle('text-outline-width').pfValue; 9247 var borderWidth = ele.pstyle('text-border-width').pfValue; 9248 var halfBorderWidth = borderWidth / 2; 9249 var padding = ele.pstyle('text-background-padding').pfValue; 9250 var lh = labelHeight; 9251 var lw = labelWidth; 9252 var lw_2 = lw / 2; 9253 var lh_2 = lh / 2; 9254 var lx1, lx2, ly1, ly2; 9255 9256 if (isEdge) { 9257 lx1 = labelX - lw_2; 9258 lx2 = labelX + lw_2; 9259 ly1 = labelY - lh_2; 9260 ly2 = labelY + lh_2; 9261 } else { 9262 switch (halign.value) { 9263 case 'left': 9264 lx1 = labelX - lw; 9265 lx2 = labelX; 9266 break; 9267 9268 case 'center': 9269 lx1 = labelX - lw_2; 9270 lx2 = labelX + lw_2; 9271 break; 9272 9273 case 'right': 9274 lx1 = labelX; 9275 lx2 = labelX + lw; 9276 break; 9277 } 9278 9279 switch (valign.value) { 9280 case 'top': 9281 ly1 = labelY - lh; 9282 ly2 = labelY; 9283 break; 9284 9285 case 'center': 9286 ly1 = labelY - lh_2; 9287 ly2 = labelY + lh_2; 9288 break; 9289 9290 case 'bottom': 9291 ly1 = labelY; 9292 ly2 = labelY + lh; 9293 break; 9294 } 9295 } // shift by margin and expand by outline and border 9296 9297 9298 lx1 += marginX - Math.max(outlineWidth, halfBorderWidth) - padding; 9299 lx2 += marginX + Math.max(outlineWidth, halfBorderWidth) + padding; 9300 ly1 += marginY - Math.max(outlineWidth, halfBorderWidth) - padding; 9301 ly2 += marginY + Math.max(outlineWidth, halfBorderWidth) + padding; // always store the unrotated label bounds separately 9302 9303 var bbPrefix = prefix || 'main'; 9304 var bbs = _p.labelBounds; 9305 var bb = bbs[bbPrefix] = bbs[bbPrefix] || {}; 9306 bb.x1 = lx1; 9307 bb.y1 = ly1; 9308 bb.x2 = lx2; 9309 bb.y2 = ly2; 9310 bb.w = lx2 - lx1; 9311 bb.h = ly2 - ly1; 9312 expandBoundingBox(bb, 1); // expand to work around browser dimension inaccuracies 9313 9314 var isAutorotate = isEdge && rotation.strValue === 'autorotate'; 9315 var isPfValue = rotation.pfValue != null && rotation.pfValue !== 0; 9316 9317 if (isAutorotate || isPfValue) { 9318 var theta = isAutorotate ? prefixedProperty(_p.rstyle, 'labelAngle', prefix) : rotation.pfValue; 9319 var cos = Math.cos(theta); 9320 var sin = Math.sin(theta); // rotation point (default value for center-center) 9321 9322 var xo = (lx1 + lx2) / 2; 9323 var yo = (ly1 + ly2) / 2; 9324 9325 if (!isEdge) { 9326 switch (halign.value) { 9327 case 'left': 9328 xo = lx2; 9329 break; 9330 9331 case 'right': 9332 xo = lx1; 9333 break; 9334 } 9335 9336 switch (valign.value) { 9337 case 'top': 9338 yo = ly2; 9339 break; 9340 9341 case 'bottom': 9342 yo = ly1; 9343 break; 9344 } 9345 } 9346 9347 var rotate = function rotate(x, y) { 9348 x = x - xo; 9349 y = y - yo; 9350 return { 9351 x: x * cos - y * sin + xo, 9352 y: x * sin + y * cos + yo 9353 }; 9354 }; 9355 9356 var px1y1 = rotate(lx1, ly1); 9357 var px1y2 = rotate(lx1, ly2); 9358 var px2y1 = rotate(lx2, ly1); 9359 var px2y2 = rotate(lx2, ly2); 9360 lx1 = Math.min(px1y1.x, px1y2.x, px2y1.x, px2y2.x); 9361 lx2 = Math.max(px1y1.x, px1y2.x, px2y1.x, px2y2.x); 9362 ly1 = Math.min(px1y1.y, px1y2.y, px2y1.y, px2y2.y); 9363 ly2 = Math.max(px1y1.y, px1y2.y, px2y1.y, px2y2.y); 9364 } 9365 9366 var bbPrefixRot = bbPrefix + 'Rot'; 9367 var bbRot = bbs[bbPrefixRot] = bbs[bbPrefixRot] || {}; 9368 bbRot.x1 = lx1; 9369 bbRot.y1 = ly1; 9370 bbRot.x2 = lx2; 9371 bbRot.y2 = ly2; 9372 bbRot.w = lx2 - lx1; 9373 bbRot.h = ly2 - ly1; 9374 updateBounds(bounds, lx1, ly1, lx2, ly2); 9375 updateBounds(_p.labelBounds.all, lx1, ly1, lx2, ly2); 9376 } 9377 9378 return bounds; 9379 }; // get the bounding box of the elements (in raw model position) 9380 9381 9382 var boundingBoxImpl = function boundingBoxImpl(ele, options) { 9383 var cy = ele._private.cy; 9384 var styleEnabled = cy.styleEnabled(); 9385 var headless = cy.headless(); 9386 var bounds = makeBoundingBox(); 9387 var _p = ele._private; 9388 var isNode = ele.isNode(); 9389 var isEdge = ele.isEdge(); 9390 var ex1, ex2, ey1, ey2; // extrema of body / lines 9391 9392 var x, y; // node pos 9393 9394 var rstyle = _p.rstyle; 9395 var manualExpansion = isNode && styleEnabled ? ele.pstyle('bounds-expansion').pfValue : [0]; // must use `display` prop only, as reading `compound.width()` causes recursion 9396 // (other factors like width values will be considered later in this function anyway) 9397 9398 var isDisplayed = function isDisplayed(ele) { 9399 return ele.pstyle('display').value !== 'none'; 9400 }; 9401 9402 var displayed = !styleEnabled || isDisplayed(ele) // must take into account connected nodes b/c of implicit edge hiding on display:none node 9403 && (!isEdge || isDisplayed(ele.source()) && isDisplayed(ele.target())); 9404 9405 if (displayed) { 9406 // displayed suffices, since we will find zero area eles anyway 9407 var overlayOpacity = 0; 9408 var overlayPadding = 0; 9409 9410 if (styleEnabled && options.includeOverlays) { 9411 overlayOpacity = ele.pstyle('overlay-opacity').value; 9412 9413 if (overlayOpacity !== 0) { 9414 overlayPadding = ele.pstyle('overlay-padding').value; 9415 } 9416 } 9417 9418 var w = 0; 9419 var wHalf = 0; 9420 9421 if (styleEnabled) { 9422 w = ele.pstyle('width').pfValue; 9423 wHalf = w / 2; 9424 } 9425 9426 if (isNode && options.includeNodes) { 9427 var pos = ele.position(); 9428 x = pos.x; 9429 y = pos.y; 9430 9431 var _w = ele.outerWidth(); 9432 9433 var halfW = _w / 2; 9434 var h = ele.outerHeight(); 9435 var halfH = h / 2; // handle node dimensions 9436 ///////////////////////// 9437 9438 ex1 = x - halfW; 9439 ex2 = x + halfW; 9440 ey1 = y - halfH; 9441 ey2 = y + halfH; 9442 updateBounds(bounds, ex1, ey1, ex2, ey2); 9443 } else if (isEdge && options.includeEdges) { 9444 if (styleEnabled && !headless) { 9445 var curveStyle = ele.pstyle('curve-style').strValue; // handle edge dimensions (rough box estimate) 9446 ////////////////////////////////////////////// 9447 9448 ex1 = Math.min(rstyle.srcX, rstyle.midX, rstyle.tgtX); 9449 ex2 = Math.max(rstyle.srcX, rstyle.midX, rstyle.tgtX); 9450 ey1 = Math.min(rstyle.srcY, rstyle.midY, rstyle.tgtY); 9451 ey2 = Math.max(rstyle.srcY, rstyle.midY, rstyle.tgtY); // take into account edge width 9452 9453 ex1 -= wHalf; 9454 ex2 += wHalf; 9455 ey1 -= wHalf; 9456 ey2 += wHalf; 9457 updateBounds(bounds, ex1, ey1, ex2, ey2); // precise edges 9458 //////////////// 9459 9460 if (curveStyle === 'haystack') { 9461 var hpts = rstyle.haystackPts; 9462 9463 if (hpts && hpts.length === 2) { 9464 ex1 = hpts[0].x; 9465 ey1 = hpts[0].y; 9466 ex2 = hpts[1].x; 9467 ey2 = hpts[1].y; 9468 9469 if (ex1 > ex2) { 9470 var temp = ex1; 9471 ex1 = ex2; 9472 ex2 = temp; 9473 } 9474 9475 if (ey1 > ey2) { 9476 var _temp = ey1; 9477 ey1 = ey2; 9478 ey2 = _temp; 9479 } 9480 9481 updateBounds(bounds, ex1 - wHalf, ey1 - wHalf, ex2 + wHalf, ey2 + wHalf); 9482 } 9483 } else if (curveStyle === 'bezier' || curveStyle === 'unbundled-bezier' || curveStyle === 'segments' || curveStyle === 'taxi') { 9484 var pts; 9485 9486 switch (curveStyle) { 9487 case 'bezier': 9488 case 'unbundled-bezier': 9489 pts = rstyle.bezierPts; 9490 break; 9491 9492 case 'segments': 9493 case 'taxi': 9494 pts = rstyle.linePts; 9495 break; 9496 } 9497 9498 if (pts != null) { 9499 for (var j = 0; j < pts.length; j++) { 9500 var pt = pts[j]; 9501 ex1 = pt.x - wHalf; 9502 ex2 = pt.x + wHalf; 9503 ey1 = pt.y - wHalf; 9504 ey2 = pt.y + wHalf; 9505 updateBounds(bounds, ex1, ey1, ex2, ey2); 9506 } 9507 } 9508 } // bezier-like or segment-like edge 9509 9510 } else { 9511 // headless or style disabled 9512 // fallback on source and target positions 9513 ////////////////////////////////////////// 9514 var n1 = ele.source(); 9515 var n1pos = n1.position(); 9516 var n2 = ele.target(); 9517 var n2pos = n2.position(); 9518 ex1 = n1pos.x; 9519 ex2 = n2pos.x; 9520 ey1 = n1pos.y; 9521 ey2 = n2pos.y; 9522 9523 if (ex1 > ex2) { 9524 var _temp2 = ex1; 9525 ex1 = ex2; 9526 ex2 = _temp2; 9527 } 9528 9529 if (ey1 > ey2) { 9530 var _temp3 = ey1; 9531 ey1 = ey2; 9532 ey2 = _temp3; 9533 } // take into account edge width 9534 9535 9536 ex1 -= wHalf; 9537 ex2 += wHalf; 9538 ey1 -= wHalf; 9539 ey2 += wHalf; 9540 updateBounds(bounds, ex1, ey1, ex2, ey2); 9541 } // headless or style disabled 9542 9543 } // edges 9544 // handle edge arrow size 9545 ///////////////////////// 9546 9547 9548 if (styleEnabled && options.includeEdges && isEdge) { 9549 updateBoundsFromArrow(bounds, ele, 'mid-source'); 9550 updateBoundsFromArrow(bounds, ele, 'mid-target'); 9551 updateBoundsFromArrow(bounds, ele, 'source'); 9552 updateBoundsFromArrow(bounds, ele, 'target'); 9553 } // ghost 9554 //////// 9555 9556 9557 if (styleEnabled) { 9558 var ghost = ele.pstyle('ghost').value === 'yes'; 9559 9560 if (ghost) { 9561 var gx = ele.pstyle('ghost-offset-x').pfValue; 9562 var gy = ele.pstyle('ghost-offset-y').pfValue; 9563 updateBounds(bounds, bounds.x1 + gx, bounds.y1 + gy, bounds.x2 + gx, bounds.y2 + gy); 9564 } 9565 } // always store the body bounds separately from the labels 9566 9567 9568 var bbBody = _p.bodyBounds = _p.bodyBounds || {}; 9569 assignBoundingBox(bbBody, bounds); 9570 expandBoundingBoxSides(bbBody, manualExpansion); 9571 expandBoundingBox(bbBody, 1); // expand to work around browser dimension inaccuracies 9572 // overlay 9573 ////////// 9574 9575 if (styleEnabled) { 9576 ex1 = bounds.x1; 9577 ex2 = bounds.x2; 9578 ey1 = bounds.y1; 9579 ey2 = bounds.y2; 9580 updateBounds(bounds, ex1 - overlayPadding, ey1 - overlayPadding, ex2 + overlayPadding, ey2 + overlayPadding); 9581 } // always store the body bounds separately from the labels 9582 9583 9584 var bbOverlay = _p.overlayBounds = _p.overlayBounds || {}; 9585 assignBoundingBox(bbOverlay, bounds); 9586 expandBoundingBoxSides(bbOverlay, manualExpansion); 9587 expandBoundingBox(bbOverlay, 1); // expand to work around browser dimension inaccuracies 9588 // handle label dimensions 9589 ////////////////////////// 9590 9591 var bbLabels = _p.labelBounds = _p.labelBounds || {}; 9592 9593 if (bbLabels.all != null) { 9594 clearBoundingBox(bbLabels.all); 9595 } else { 9596 bbLabels.all = makeBoundingBox(); 9597 } 9598 9599 if (styleEnabled && options.includeLabels) { 9600 if (options.includeMainLabels) { 9601 updateBoundsFromLabel(bounds, ele, null); 9602 } 9603 9604 if (isEdge) { 9605 if (options.includeSourceLabels) { 9606 updateBoundsFromLabel(bounds, ele, 'source'); 9607 } 9608 9609 if (options.includeTargetLabels) { 9610 updateBoundsFromLabel(bounds, ele, 'target'); 9611 } 9612 } 9613 } // style enabled for labels 9614 9615 } // if displayed 9616 9617 9618 bounds.x1 = noninf(bounds.x1); 9619 bounds.y1 = noninf(bounds.y1); 9620 bounds.x2 = noninf(bounds.x2); 9621 bounds.y2 = noninf(bounds.y2); 9622 bounds.w = noninf(bounds.x2 - bounds.x1); 9623 bounds.h = noninf(bounds.y2 - bounds.y1); 9624 9625 if (bounds.w > 0 && bounds.h > 0 && displayed) { 9626 expandBoundingBoxSides(bounds, manualExpansion); // expand bounds by 1 because antialiasing can increase the visual/effective size by 1 on all sides 9627 9628 expandBoundingBox(bounds, 1); 9629 } 9630 9631 return bounds; 9632 }; 9633 9634 var getKey = function getKey(opts) { 9635 var i = 0; 9636 9637 var tf = function tf(val) { 9638 return (val ? 1 : 0) << i++; 9639 }; 9640 9641 var key = 0; 9642 key += tf(opts.incudeNodes); 9643 key += tf(opts.includeEdges); 9644 key += tf(opts.includeLabels); 9645 key += tf(opts.includeMainLabels); 9646 key += tf(opts.includeSourceLabels); 9647 key += tf(opts.includeTargetLabels); 9648 key += tf(opts.includeOverlays); 9649 return key; 9650 }; 9651 9652 var getBoundingBoxPosKey = function getBoundingBoxPosKey(ele) { 9653 if (ele.isEdge()) { 9654 var p1 = ele.source().position(); 9655 var p2 = ele.target().position(); 9656 9657 var r = function r(x) { 9658 return Math.round(x); 9659 }; 9660 9661 return hashIntsArray([r(p1.x), r(p1.y), r(p2.x), r(p2.y)]); 9662 } else { 9663 return 0; 9664 } 9665 }; 9666 9667 var cachedBoundingBoxImpl = function cachedBoundingBoxImpl(ele, opts) { 9668 var _p = ele._private; 9669 var bb; 9670 var isEdge = ele.isEdge(); 9671 var key = opts == null ? defBbOptsKey : getKey(opts); 9672 var usingDefOpts = key === defBbOptsKey; 9673 var currPosKey = getBoundingBoxPosKey(ele); 9674 var isPosKeySame = _p.bbCachePosKey === currPosKey; 9675 var useCache = opts.useCache && isPosKeySame; 9676 9677 var isDirty = function isDirty(ele) { 9678 return ele._private.bbCache == null; 9679 }; 9680 9681 var needRecalc = !useCache || isDirty(ele) || isEdge && isDirty(ele.source()) || isDirty(ele.target()); 9682 9683 if (needRecalc) { 9684 if (!isPosKeySame) { 9685 ele.recalculateRenderedStyle(); 9686 } 9687 9688 bb = boundingBoxImpl(ele, defBbOpts); 9689 _p.bbCache = bb; 9690 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; 9691 _p.bbCachePosKey = currPosKey; 9692 } else { 9693 bb = _p.bbCache; 9694 } 9695 9696 if (!needRecalc && (_p.bbCacheShift.x !== 0 || _p.bbCacheShift.y !== 0)) { 9697 var shift = assignShiftToBoundingBox; 9698 var delta = _p.bbCacheShift; 9699 9700 var safeShift = function safeShift(bb, delta) { 9701 if (bb != null) { 9702 shift(bb, delta); 9703 } 9704 }; 9705 9706 shift(bb, delta); 9707 var bodyBounds = _p.bodyBounds, 9708 overlayBounds = _p.overlayBounds, 9709 labelBounds = _p.labelBounds, 9710 arrowBounds = _p.arrowBounds; 9711 safeShift(bodyBounds, delta); 9712 safeShift(overlayBounds, delta); 9713 9714 if (arrowBounds != null) { 9715 safeShift(arrowBounds.source, delta); 9716 safeShift(arrowBounds.target, delta); 9717 safeShift(arrowBounds['mid-source'], delta); 9718 safeShift(arrowBounds['mid-target'], delta); 9719 } 9720 9721 if (labelBounds != null) { 9722 safeShift(labelBounds.main, delta); 9723 safeShift(labelBounds.all, delta); 9724 safeShift(labelBounds.source, delta); 9725 safeShift(labelBounds.target, delta); 9726 } 9727 } // always reset the shift, because we either applied the shift or cleared it by doing a fresh recalc 9728 9729 9730 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; // not using def opts => need to build up bb from combination of sub bbs 9731 9732 if (!usingDefOpts) { 9733 var isNode = ele.isNode(); 9734 bb = makeBoundingBox(); 9735 9736 if (opts.includeNodes && isNode || opts.includeEdges && !isNode) { 9737 if (opts.includeOverlays) { 9738 updateBoundsFromBox(bb, _p.overlayBounds); 9739 } else { 9740 updateBoundsFromBox(bb, _p.bodyBounds); 9741 } 9742 } 9743 9744 if (opts.includeLabels) { 9745 if (opts.includeMainLabels && (!isEdge || opts.includeSourceLabels && opts.includeTargetLabels)) { 9746 updateBoundsFromBox(bb, _p.labelBounds.all); 9747 } else { 9748 if (opts.includeMainLabels) { 9749 updateBoundsFromBox(bb, _p.labelBounds.mainRot); 9750 } 9751 9752 if (opts.includeSourceLabels) { 9753 updateBoundsFromBox(bb, _p.labelBounds.sourceRot); 9754 } 9755 9756 if (opts.includeTargetLabels) { 9757 updateBoundsFromBox(bb, _p.labelBounds.targetRot); 9758 } 9759 } 9760 } 9761 9762 bb.w = bb.x2 - bb.x1; 9763 bb.h = bb.y2 - bb.y1; 9764 } 9765 9766 return bb; 9767 }; 9768 9769 var defBbOpts = { 9770 includeNodes: true, 9771 includeEdges: true, 9772 includeLabels: true, 9773 includeMainLabels: true, 9774 includeSourceLabels: true, 9775 includeTargetLabels: true, 9776 includeOverlays: true, 9777 useCache: true 9778 }; 9779 var defBbOptsKey = getKey(defBbOpts); 9780 var filledBbOpts = defaults(defBbOpts); 9781 9782 elesfn$k.boundingBox = function (options) { 9783 var bounds; // the main usecase is ele.boundingBox() for a single element with no/def options 9784 // specified s.t. the cache is used, so check for this case to make it faster by 9785 // avoiding the overhead of the rest of the function 9786 9787 if (this.length === 1 && this[0]._private.bbCache != null && (options === undefined || options.useCache === undefined || options.useCache === true)) { 9788 if (options === undefined) { 9789 options = defBbOpts; 9790 } else { 9791 options = filledBbOpts(options); 9792 } 9793 9794 bounds = cachedBoundingBoxImpl(this[0], options); 9795 } else { 9796 bounds = makeBoundingBox(); 9797 options = options || defBbOpts; 9798 var opts = filledBbOpts(options); 9799 var eles = this; 9800 var cy = eles.cy(); 9801 var styleEnabled = cy.styleEnabled(); 9802 9803 if (styleEnabled) { 9804 for (var i = 0; i < eles.length; i++) { 9805 var ele = eles[i]; 9806 var _p = ele._private; 9807 var currPosKey = getBoundingBoxPosKey(ele); 9808 var isPosKeySame = _p.bbCachePosKey === currPosKey; 9809 var useCache = opts.useCache && isPosKeySame; 9810 ele.recalculateRenderedStyle(useCache); 9811 } 9812 } 9813 9814 this.updateCompoundBounds(); 9815 9816 for (var _i = 0; _i < eles.length; _i++) { 9817 var _ele = eles[_i]; 9818 updateBoundsFromBox(bounds, cachedBoundingBoxImpl(_ele, opts)); 9819 } 9820 } 9821 9822 bounds.x1 = noninf(bounds.x1); 9823 bounds.y1 = noninf(bounds.y1); 9824 bounds.x2 = noninf(bounds.x2); 9825 bounds.y2 = noninf(bounds.y2); 9826 bounds.w = noninf(bounds.x2 - bounds.x1); 9827 bounds.h = noninf(bounds.y2 - bounds.y1); 9828 return bounds; 9829 }; 9830 9831 elesfn$k.dirtyBoundingBoxCache = function () { 9832 for (var i = 0; i < this.length; i++) { 9833 var _p = this[i]._private; 9834 _p.bbCache = null; 9835 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; 9836 _p.bbCachePosKey = null; 9837 _p.bodyBounds = null; 9838 _p.overlayBounds = null; 9839 _p.labelBounds.all = null; 9840 _p.labelBounds.source = null; 9841 _p.labelBounds.target = null; 9842 _p.labelBounds.main = null; 9843 _p.labelBounds.sourceRot = null; 9844 _p.labelBounds.targetRot = null; 9845 _p.labelBounds.mainRot = null; 9846 _p.arrowBounds.source = null; 9847 _p.arrowBounds.target = null; 9848 _p.arrowBounds['mid-source'] = null; 9849 _p.arrowBounds['mid-target'] = null; 9850 } 9851 9852 this.emitAndNotify('bounds'); 9853 return this; 9854 }; 9855 9856 elesfn$k.shiftCachedBoundingBox = function (delta) { 9857 for (var i = 0; i < this.length; i++) { 9858 var ele = this[i]; 9859 var _p = ele._private; 9860 var bb = _p.bbCache; 9861 9862 if (bb != null) { 9863 _p.bbCacheShift.x += delta.x; 9864 _p.bbCacheShift.y += delta.y; 9865 } 9866 } 9867 9868 this.emitAndNotify('bounds'); 9869 return this; 9870 }; // private helper to get bounding box for custom node positions 9871 // - good for perf in certain cases but currently requires dirtying the rendered style 9872 // - would be better to not modify the nodes but the nodes are read directly everywhere in the renderer... 9873 // - try to use for only things like discrete layouts where the node position would change anyway 9874 9875 9876 elesfn$k.boundingBoxAt = function (fn) { 9877 var nodes = this.nodes(); 9878 var cy = this.cy(); 9879 var hasCompoundNodes = cy.hasCompoundNodes(); 9880 9881 if (hasCompoundNodes) { 9882 nodes = nodes.filter(function (node) { 9883 return !node.isParent(); 9884 }); 9885 } 9886 9887 if (plainObject(fn)) { 9888 var obj = fn; 9889 9890 fn = function fn() { 9891 return obj; 9892 }; 9893 } 9894 9895 var storeOldPos = function storeOldPos(node, i) { 9896 return node._private.bbAtOldPos = fn(node, i); 9897 }; 9898 9899 var getOldPos = function getOldPos(node) { 9900 return node._private.bbAtOldPos; 9901 }; 9902 9903 cy.startBatch(); 9904 nodes.forEach(storeOldPos).silentPositions(fn); 9905 9906 if (hasCompoundNodes) { 9907 this.updateCompoundBounds(true); // force update b/c we're inside a batch cycle 9908 } 9909 9910 var bb = copyBoundingBox(this.boundingBox({ 9911 useCache: false 9912 })); 9913 nodes.silentPositions(getOldPos); 9914 cy.endBatch(); 9915 return bb; 9916 }; 9917 9918 fn$3.boundingbox = fn$3.bb = fn$3.boundingBox; 9919 fn$3.renderedBoundingbox = fn$3.renderedBoundingBox; 9920 var bounds = elesfn$k; 9921 9922 var fn$4, elesfn$l; 9923 fn$4 = elesfn$l = {}; 9924 9925 var defineDimFns = function defineDimFns(opts) { 9926 opts.uppercaseName = capitalize(opts.name); 9927 opts.autoName = 'auto' + opts.uppercaseName; 9928 opts.labelName = 'label' + opts.uppercaseName; 9929 opts.outerName = 'outer' + opts.uppercaseName; 9930 opts.uppercaseOuterName = capitalize(opts.outerName); 9931 9932 fn$4[opts.name] = function dimImpl() { 9933 var ele = this[0]; 9934 var _p = ele._private; 9935 var cy = _p.cy; 9936 var styleEnabled = cy._private.styleEnabled; 9937 9938 if (ele) { 9939 if (styleEnabled) { 9940 if (ele.isParent()) { 9941 ele.updateCompoundBounds(); 9942 return _p[opts.autoName] || 0; 9943 } 9944 9945 var d = ele.pstyle(opts.name); 9946 9947 switch (d.strValue) { 9948 case 'label': 9949 ele.recalculateRenderedStyle(); 9950 return _p.rstyle[opts.labelName] || 0; 9951 9952 default: 9953 return d.pfValue; 9954 } 9955 } else { 9956 return 1; 9957 } 9958 } 9959 }; 9960 9961 fn$4['outer' + opts.uppercaseName] = function outerDimImpl() { 9962 var ele = this[0]; 9963 var _p = ele._private; 9964 var cy = _p.cy; 9965 var styleEnabled = cy._private.styleEnabled; 9966 9967 if (ele) { 9968 if (styleEnabled) { 9969 var dim = ele[opts.name](); 9970 var border = ele.pstyle('border-width').pfValue; // n.b. 1/2 each side 9971 9972 var padding = 2 * ele.padding(); 9973 return dim + border + padding; 9974 } else { 9975 return 1; 9976 } 9977 } 9978 }; 9979 9980 fn$4['rendered' + opts.uppercaseName] = function renderedDimImpl() { 9981 var ele = this[0]; 9982 9983 if (ele) { 9984 var d = ele[opts.name](); 9985 return d * this.cy().zoom(); 9986 } 9987 }; 9988 9989 fn$4['rendered' + opts.uppercaseOuterName] = function renderedOuterDimImpl() { 9990 var ele = this[0]; 9991 9992 if (ele) { 9993 var od = ele[opts.outerName](); 9994 return od * this.cy().zoom(); 9995 } 9996 }; 9997 }; 9998 9999 defineDimFns({ 10000 name: 'width' 10001 }); 10002 defineDimFns({ 10003 name: 'height' 10004 }); 10005 10006 elesfn$l.padding = function () { 10007 var ele = this[0]; 10008 var _p = ele._private; 10009 10010 if (ele.isParent()) { 10011 ele.updateCompoundBounds(); 10012 10013 if (_p.autoPadding !== undefined) { 10014 return _p.autoPadding; 10015 } else { 10016 return ele.pstyle('padding').pfValue; 10017 } 10018 } else { 10019 return ele.pstyle('padding').pfValue; 10020 } 10021 }; 10022 10023 elesfn$l.paddedHeight = function () { 10024 var ele = this[0]; 10025 return ele.height() + 2 * ele.padding(); 10026 }; 10027 10028 elesfn$l.paddedWidth = function () { 10029 var ele = this[0]; 10030 return ele.width() + 2 * ele.padding(); 10031 }; 10032 10033 var widthHeight = elesfn$l; 10034 10035 var ifEdge = function ifEdge(ele, getValue) { 10036 if (ele.isEdge()) { 10037 return getValue(ele); 10038 } 10039 }; 10040 10041 var ifEdgeRenderedPosition = function ifEdgeRenderedPosition(ele, getPoint) { 10042 if (ele.isEdge()) { 10043 var cy = ele.cy(); 10044 return modelToRenderedPosition(getPoint(ele), cy.zoom(), cy.pan()); 10045 } 10046 }; 10047 10048 var ifEdgeRenderedPositions = function ifEdgeRenderedPositions(ele, getPoints) { 10049 if (ele.isEdge()) { 10050 var cy = ele.cy(); 10051 var pan = cy.pan(); 10052 var zoom = cy.zoom(); 10053 return getPoints(ele).map(function (p) { 10054 return modelToRenderedPosition(p, zoom, pan); 10055 }); 10056 } 10057 }; 10058 10059 var controlPoints = function controlPoints(ele) { 10060 return ele.renderer().getControlPoints(ele); 10061 }; 10062 10063 var segmentPoints = function segmentPoints(ele) { 10064 return ele.renderer().getSegmentPoints(ele); 10065 }; 10066 10067 var sourceEndpoint = function sourceEndpoint(ele) { 10068 return ele.renderer().getSourceEndpoint(ele); 10069 }; 10070 10071 var targetEndpoint = function targetEndpoint(ele) { 10072 return ele.renderer().getTargetEndpoint(ele); 10073 }; 10074 10075 var midpoint = function midpoint(ele) { 10076 return ele.renderer().getEdgeMidpoint(ele); 10077 }; 10078 10079 var pts = { 10080 controlPoints: { 10081 get: controlPoints, 10082 mult: true 10083 }, 10084 segmentPoints: { 10085 get: segmentPoints, 10086 mult: true 10087 }, 10088 sourceEndpoint: { 10089 get: sourceEndpoint 10090 }, 10091 targetEndpoint: { 10092 get: targetEndpoint 10093 }, 10094 midpoint: { 10095 get: midpoint 10096 } 10097 }; 10098 10099 var renderedName = function renderedName(name) { 10100 return 'rendered' + name[0].toUpperCase() + name.substr(1); 10101 }; 10102 10103 var edgePoints = Object.keys(pts).reduce(function (obj, name) { 10104 var spec = pts[name]; 10105 var rName = renderedName(name); 10106 10107 obj[name] = function () { 10108 return ifEdge(this, spec.get); 10109 }; 10110 10111 if (spec.mult) { 10112 obj[rName] = function () { 10113 return ifEdgeRenderedPositions(this, spec.get); 10114 }; 10115 } else { 10116 obj[rName] = function () { 10117 return ifEdgeRenderedPosition(this, spec.get); 10118 }; 10119 } 10120 10121 return obj; 10122 }, {}); 10123 10124 var dimensions = extend({}, position, bounds, widthHeight, edgePoints); 10125 10126 /*! 10127 Event object based on jQuery events, MIT license 10128 10129 https://jquery.org/license/ 10130 https://tldrlegal.com/license/mit-license 10131 https://github.com/jquery/jquery/blob/master/src/event.js 10132 */ 10133 var Event = function Event(src, props) { 10134 this.recycle(src, props); 10135 }; 10136 10137 function returnFalse() { 10138 return false; 10139 } 10140 10141 function returnTrue() { 10142 return true; 10143 } // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 10144 10145 10146 Event.prototype = { 10147 instanceString: function instanceString() { 10148 return 'event'; 10149 }, 10150 recycle: function recycle(src, props) { 10151 this.isImmediatePropagationStopped = this.isPropagationStopped = this.isDefaultPrevented = returnFalse; 10152 10153 if (src != null && src.preventDefault) { 10154 // Browser Event object 10155 this.type = src.type; // Events bubbling up the document may have been marked as prevented 10156 // by a handler lower down the tree; reflect the correct value. 10157 10158 this.isDefaultPrevented = src.defaultPrevented ? returnTrue : returnFalse; 10159 } else if (src != null && src.type) { 10160 // Plain object containing all event details 10161 props = src; 10162 } else { 10163 // Event string 10164 this.type = src; 10165 } // Put explicitly provided properties onto the event object 10166 10167 10168 if (props != null) { 10169 // more efficient to manually copy fields we use 10170 this.originalEvent = props.originalEvent; 10171 this.type = props.type != null ? props.type : this.type; 10172 this.cy = props.cy; 10173 this.target = props.target; 10174 this.position = props.position; 10175 this.renderedPosition = props.renderedPosition; 10176 this.namespace = props.namespace; 10177 this.layout = props.layout; 10178 } 10179 10180 if (this.cy != null && this.position != null && this.renderedPosition == null) { 10181 // create a rendered position based on the passed position 10182 var pos = this.position; 10183 var zoom = this.cy.zoom(); 10184 var pan = this.cy.pan(); 10185 this.renderedPosition = { 10186 x: pos.x * zoom + pan.x, 10187 y: pos.y * zoom + pan.y 10188 }; 10189 } // Create a timestamp if incoming event doesn't have one 10190 10191 10192 this.timeStamp = src && src.timeStamp || Date.now(); 10193 }, 10194 preventDefault: function preventDefault() { 10195 this.isDefaultPrevented = returnTrue; 10196 var e = this.originalEvent; 10197 10198 if (!e) { 10199 return; 10200 } // if preventDefault exists run it on the original event 10201 10202 10203 if (e.preventDefault) { 10204 e.preventDefault(); 10205 } 10206 }, 10207 stopPropagation: function stopPropagation() { 10208 this.isPropagationStopped = returnTrue; 10209 var e = this.originalEvent; 10210 10211 if (!e) { 10212 return; 10213 } // if stopPropagation exists run it on the original event 10214 10215 10216 if (e.stopPropagation) { 10217 e.stopPropagation(); 10218 } 10219 }, 10220 stopImmediatePropagation: function stopImmediatePropagation() { 10221 this.isImmediatePropagationStopped = returnTrue; 10222 this.stopPropagation(); 10223 }, 10224 isDefaultPrevented: returnFalse, 10225 isPropagationStopped: returnFalse, 10226 isImmediatePropagationStopped: returnFalse 10227 }; 10228 10229 var eventRegex = /^([^.]+)(\.(?:[^.]+))?$/; // regex for matching event strings (e.g. "click.namespace") 10230 10231 var universalNamespace = '.*'; // matches as if no namespace specified and prevents users from unbinding accidentally 10232 10233 var defaults$8 = { 10234 qualifierCompare: function qualifierCompare(q1, q2) { 10235 return q1 === q2; 10236 }, 10237 eventMatches: function eventMatches() 10238 /*context, listener, eventObj*/ 10239 { 10240 return true; 10241 }, 10242 addEventFields: function addEventFields() 10243 /*context, evt*/ 10244 {}, 10245 callbackContext: function callbackContext(context 10246 /*, listener, eventObj*/ 10247 ) { 10248 return context; 10249 }, 10250 beforeEmit: function beforeEmit() 10251 /* context, listener, eventObj */ 10252 {}, 10253 afterEmit: function afterEmit() 10254 /* context, listener, eventObj */ 10255 {}, 10256 bubble: function bubble() 10257 /*context*/ 10258 { 10259 return false; 10260 }, 10261 parent: function parent() 10262 /*context*/ 10263 { 10264 return null; 10265 }, 10266 context: null 10267 }; 10268 var defaultsKeys = Object.keys(defaults$8); 10269 var emptyOpts = {}; 10270 10271 function Emitter() { 10272 var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : emptyOpts; 10273 var context = arguments.length > 1 ? arguments[1] : undefined; 10274 10275 // micro-optimisation vs Object.assign() -- reduces Element instantiation time 10276 for (var i = 0; i < defaultsKeys.length; i++) { 10277 var key = defaultsKeys[i]; 10278 this[key] = opts[key] || defaults$8[key]; 10279 } 10280 10281 this.context = context || this.context; 10282 this.listeners = []; 10283 this.emitting = 0; 10284 } 10285 10286 var p = Emitter.prototype; 10287 10288 var forEachEvent = function forEachEvent(self, handler, events, qualifier, callback, conf, confOverrides) { 10289 if (fn(qualifier)) { 10290 callback = qualifier; 10291 qualifier = null; 10292 } 10293 10294 if (confOverrides) { 10295 if (conf == null) { 10296 conf = confOverrides; 10297 } else { 10298 conf = extend({}, conf, confOverrides); 10299 } 10300 } 10301 10302 var eventList = array(events) ? events : events.split(/\s+/); 10303 10304 for (var i = 0; i < eventList.length; i++) { 10305 var evt = eventList[i]; 10306 10307 if (emptyString(evt)) { 10308 continue; 10309 } 10310 10311 var match = evt.match(eventRegex); // type[.namespace] 10312 10313 if (match) { 10314 var type = match[1]; 10315 var namespace = match[2] ? match[2] : null; 10316 var ret = handler(self, evt, type, namespace, qualifier, callback, conf); 10317 10318 if (ret === false) { 10319 break; 10320 } // allow exiting early 10321 10322 } 10323 } 10324 }; 10325 10326 var makeEventObj = function makeEventObj(self, obj) { 10327 self.addEventFields(self.context, obj); 10328 return new Event(obj.type, obj); 10329 }; 10330 10331 var forEachEventObj = function forEachEventObj(self, handler, events) { 10332 if (event(events)) { 10333 handler(self, events); 10334 return; 10335 } else if (plainObject(events)) { 10336 handler(self, makeEventObj(self, events)); 10337 return; 10338 } 10339 10340 var eventList = array(events) ? events : events.split(/\s+/); 10341 10342 for (var i = 0; i < eventList.length; i++) { 10343 var evt = eventList[i]; 10344 10345 if (emptyString(evt)) { 10346 continue; 10347 } 10348 10349 var match = evt.match(eventRegex); // type[.namespace] 10350 10351 if (match) { 10352 var type = match[1]; 10353 var namespace = match[2] ? match[2] : null; 10354 var eventObj = makeEventObj(self, { 10355 type: type, 10356 namespace: namespace, 10357 target: self.context 10358 }); 10359 handler(self, eventObj); 10360 } 10361 } 10362 }; 10363 10364 p.on = p.addListener = function (events, qualifier, callback, conf, confOverrides) { 10365 forEachEvent(this, function (self, event, type, namespace, qualifier, callback, conf) { 10366 if (fn(callback)) { 10367 self.listeners.push({ 10368 event: event, 10369 // full event string 10370 callback: callback, 10371 // callback to run 10372 type: type, 10373 // the event type (e.g. 'click') 10374 namespace: namespace, 10375 // the event namespace (e.g. ".foo") 10376 qualifier: qualifier, 10377 // a restriction on whether to match this emitter 10378 conf: conf // additional configuration 10379 10380 }); 10381 } 10382 }, events, qualifier, callback, conf, confOverrides); 10383 return this; 10384 }; 10385 10386 p.one = function (events, qualifier, callback, conf) { 10387 return this.on(events, qualifier, callback, conf, { 10388 one: true 10389 }); 10390 }; 10391 10392 p.removeListener = p.off = function (events, qualifier, callback, conf) { 10393 var _this = this; 10394 10395 if (this.emitting !== 0) { 10396 this.listeners = copyArray(this.listeners); 10397 } 10398 10399 var listeners = this.listeners; 10400 10401 var _loop = function _loop(i) { 10402 var listener = listeners[i]; 10403 forEachEvent(_this, function (self, event, type, namespace, qualifier, callback 10404 /*, conf*/ 10405 ) { 10406 if ((listener.type === type || events === '*') && (!namespace && listener.namespace !== '.*' || listener.namespace === namespace) && (!qualifier || self.qualifierCompare(listener.qualifier, qualifier)) && (!callback || listener.callback === callback)) { 10407 listeners.splice(i, 1); 10408 return false; 10409 } 10410 }, events, qualifier, callback, conf); 10411 }; 10412 10413 for (var i = listeners.length - 1; i >= 0; i--) { 10414 _loop(i); 10415 } 10416 10417 return this; 10418 }; 10419 10420 p.removeAllListeners = function () { 10421 return this.removeListener('*'); 10422 }; 10423 10424 p.emit = p.trigger = function (events, extraParams, manualCallback) { 10425 var listeners = this.listeners; 10426 var numListenersBeforeEmit = listeners.length; 10427 this.emitting++; 10428 10429 if (!array(extraParams)) { 10430 extraParams = [extraParams]; 10431 } 10432 10433 forEachEventObj(this, function (self, eventObj) { 10434 if (manualCallback != null) { 10435 listeners = [{ 10436 event: eventObj.event, 10437 type: eventObj.type, 10438 namespace: eventObj.namespace, 10439 callback: manualCallback 10440 }]; 10441 numListenersBeforeEmit = listeners.length; 10442 } 10443 10444 var _loop2 = function _loop2(i) { 10445 var listener = listeners[i]; 10446 10447 if (listener.type === eventObj.type && (!listener.namespace || listener.namespace === eventObj.namespace || listener.namespace === universalNamespace) && self.eventMatches(self.context, listener, eventObj)) { 10448 var args = [eventObj]; 10449 10450 if (extraParams != null) { 10451 push(args, extraParams); 10452 } 10453 10454 self.beforeEmit(self.context, listener, eventObj); 10455 10456 if (listener.conf && listener.conf.one) { 10457 self.listeners = self.listeners.filter(function (l) { 10458 return l !== listener; 10459 }); 10460 } 10461 10462 var context = self.callbackContext(self.context, listener, eventObj); 10463 var ret = listener.callback.apply(context, args); 10464 self.afterEmit(self.context, listener, eventObj); 10465 10466 if (ret === false) { 10467 eventObj.stopPropagation(); 10468 eventObj.preventDefault(); 10469 } 10470 } // if listener matches 10471 10472 }; 10473 10474 for (var i = 0; i < numListenersBeforeEmit; i++) { 10475 _loop2(i); 10476 } // for listener 10477 10478 10479 if (self.bubble(self.context) && !eventObj.isPropagationStopped()) { 10480 self.parent(self.context).emit(eventObj, extraParams); 10481 } 10482 }, events); 10483 this.emitting--; 10484 return this; 10485 }; 10486 10487 var emitterOptions = { 10488 qualifierCompare: function qualifierCompare(selector1, selector2) { 10489 if (selector1 == null || selector2 == null) { 10490 return selector1 == null && selector2 == null; 10491 } else { 10492 return selector1.sameText(selector2); 10493 } 10494 }, 10495 eventMatches: function eventMatches(ele, listener, eventObj) { 10496 var selector = listener.qualifier; 10497 10498 if (selector != null) { 10499 return ele !== eventObj.target && element(eventObj.target) && selector.matches(eventObj.target); 10500 } 10501 10502 return true; 10503 }, 10504 addEventFields: function addEventFields(ele, evt) { 10505 evt.cy = ele.cy(); 10506 evt.target = ele; 10507 }, 10508 callbackContext: function callbackContext(ele, listener, eventObj) { 10509 return listener.qualifier != null ? eventObj.target : ele; 10510 }, 10511 beforeEmit: function beforeEmit(context, listener 10512 /*, eventObj*/ 10513 ) { 10514 if (listener.conf && listener.conf.once) { 10515 listener.conf.onceCollection.removeListener(listener.event, listener.qualifier, listener.callback); 10516 } 10517 }, 10518 bubble: function bubble() { 10519 return true; 10520 }, 10521 parent: function parent(ele) { 10522 return ele.isChild() ? ele.parent() : ele.cy(); 10523 } 10524 }; 10525 10526 var argSelector = function argSelector(arg) { 10527 if (string(arg)) { 10528 return new Selector(arg); 10529 } else { 10530 return arg; 10531 } 10532 }; 10533 10534 var elesfn$m = { 10535 createEmitter: function createEmitter() { 10536 for (var i = 0; i < this.length; i++) { 10537 var ele = this[i]; 10538 var _p = ele._private; 10539 10540 if (!_p.emitter) { 10541 _p.emitter = new Emitter(emitterOptions, ele); 10542 } 10543 } 10544 10545 return this; 10546 }, 10547 emitter: function emitter() { 10548 return this._private.emitter; 10549 }, 10550 on: function on(events, selector, callback) { 10551 var argSel = argSelector(selector); 10552 10553 for (var i = 0; i < this.length; i++) { 10554 var ele = this[i]; 10555 ele.emitter().on(events, argSel, callback); 10556 } 10557 10558 return this; 10559 }, 10560 removeListener: function removeListener(events, selector, callback) { 10561 var argSel = argSelector(selector); 10562 10563 for (var i = 0; i < this.length; i++) { 10564 var ele = this[i]; 10565 ele.emitter().removeListener(events, argSel, callback); 10566 } 10567 10568 return this; 10569 }, 10570 removeAllListeners: function removeAllListeners() { 10571 for (var i = 0; i < this.length; i++) { 10572 var ele = this[i]; 10573 ele.emitter().removeAllListeners(); 10574 } 10575 10576 return this; 10577 }, 10578 one: function one(events, selector, callback) { 10579 var argSel = argSelector(selector); 10580 10581 for (var i = 0; i < this.length; i++) { 10582 var ele = this[i]; 10583 ele.emitter().one(events, argSel, callback); 10584 } 10585 10586 return this; 10587 }, 10588 once: function once(events, selector, callback) { 10589 var argSel = argSelector(selector); 10590 10591 for (var i = 0; i < this.length; i++) { 10592 var ele = this[i]; 10593 ele.emitter().on(events, argSel, callback, { 10594 once: true, 10595 onceCollection: this 10596 }); 10597 } 10598 }, 10599 emit: function emit(events, extraParams) { 10600 for (var i = 0; i < this.length; i++) { 10601 var ele = this[i]; 10602 ele.emitter().emit(events, extraParams); 10603 } 10604 10605 return this; 10606 }, 10607 emitAndNotify: function emitAndNotify(event, extraParams) { 10608 // for internal use only 10609 if (this.length === 0) { 10610 return; 10611 } // empty collections don't need to notify anything 10612 // notify renderer 10613 10614 10615 this.cy().notify(event, this); 10616 this.emit(event, extraParams); 10617 return this; 10618 } 10619 }; 10620 define$3.eventAliasesOn(elesfn$m); 10621 10622 var elesfn$n = { 10623 nodes: function nodes(selector) { 10624 return this.filter(function (ele) { 10625 return ele.isNode(); 10626 }).filter(selector); 10627 }, 10628 edges: function edges(selector) { 10629 return this.filter(function (ele) { 10630 return ele.isEdge(); 10631 }).filter(selector); 10632 }, 10633 // internal helper to get nodes and edges as separate collections with single iteration over elements 10634 byGroup: function byGroup() { 10635 var nodes = this.spawn(); 10636 var edges = this.spawn(); 10637 10638 for (var i = 0; i < this.length; i++) { 10639 var ele = this[i]; 10640 10641 if (ele.isNode()) { 10642 nodes.merge(ele); 10643 } else { 10644 edges.merge(ele); 10645 } 10646 } 10647 10648 return { 10649 nodes: nodes, 10650 edges: edges 10651 }; 10652 }, 10653 filter: function filter(_filter, thisArg) { 10654 if (_filter === undefined) { 10655 // check this first b/c it's the most common/performant case 10656 return this; 10657 } else if (string(_filter) || elementOrCollection(_filter)) { 10658 return new Selector(_filter).filter(this); 10659 } else if (fn(_filter)) { 10660 var filterEles = this.spawn(); 10661 var eles = this; 10662 10663 for (var i = 0; i < eles.length; i++) { 10664 var ele = eles[i]; 10665 var include = thisArg ? _filter.apply(thisArg, [ele, i, eles]) : _filter(ele, i, eles); 10666 10667 if (include) { 10668 filterEles.merge(ele); 10669 } 10670 } 10671 10672 return filterEles; 10673 } 10674 10675 return this.spawn(); // if not handled by above, give 'em an empty collection 10676 }, 10677 not: function not(toRemove) { 10678 if (!toRemove) { 10679 return this; 10680 } else { 10681 if (string(toRemove)) { 10682 toRemove = this.filter(toRemove); 10683 } 10684 10685 var elements = []; 10686 var rMap = toRemove._private.map; 10687 10688 for (var i = 0; i < this.length; i++) { 10689 var element = this[i]; 10690 var remove = rMap.has(element.id()); 10691 10692 if (!remove) { 10693 elements.push(element); 10694 } 10695 } 10696 10697 return this.spawn(elements); 10698 } 10699 }, 10700 absoluteComplement: function absoluteComplement() { 10701 var cy = this.cy(); 10702 return cy.mutableElements().not(this); 10703 }, 10704 intersect: function intersect(other) { 10705 // if a selector is specified, then filter by it instead 10706 if (string(other)) { 10707 var selector = other; 10708 return this.filter(selector); 10709 } 10710 10711 var elements = []; 10712 var col1 = this; 10713 var col2 = other; 10714 var col1Smaller = this.length < other.length; 10715 var map2 = col1Smaller ? col2._private.map : col1._private.map; 10716 var col = col1Smaller ? col1 : col2; 10717 10718 for (var i = 0; i < col.length; i++) { 10719 var id = col[i]._private.data.id; 10720 var entry = map2.get(id); 10721 10722 if (entry) { 10723 elements.push(entry.ele); 10724 } 10725 } 10726 10727 return this.spawn(elements); 10728 }, 10729 xor: function xor(other) { 10730 var cy = this._private.cy; 10731 10732 if (string(other)) { 10733 other = cy.$(other); 10734 } 10735 10736 var elements = []; 10737 var col1 = this; 10738 var col2 = other; 10739 10740 var add = function add(col, other) { 10741 for (var i = 0; i < col.length; i++) { 10742 var ele = col[i]; 10743 var id = ele._private.data.id; 10744 var inOther = other.hasElementWithId(id); 10745 10746 if (!inOther) { 10747 elements.push(ele); 10748 } 10749 } 10750 }; 10751 10752 add(col1, col2); 10753 add(col2, col1); 10754 return this.spawn(elements); 10755 }, 10756 diff: function diff(other) { 10757 var cy = this._private.cy; 10758 10759 if (string(other)) { 10760 other = cy.$(other); 10761 } 10762 10763 var left = []; 10764 var right = []; 10765 var both = []; 10766 var col1 = this; 10767 var col2 = other; 10768 10769 var add = function add(col, other, retEles) { 10770 for (var i = 0; i < col.length; i++) { 10771 var ele = col[i]; 10772 var id = ele._private.data.id; 10773 var inOther = other.hasElementWithId(id); 10774 10775 if (inOther) { 10776 both.push(ele); 10777 } else { 10778 retEles.push(ele); 10779 } 10780 } 10781 }; 10782 10783 add(col1, col2, left); 10784 add(col2, col1, right); 10785 return { 10786 left: this.spawn(left, { 10787 unique: true 10788 }), 10789 right: this.spawn(right, { 10790 unique: true 10791 }), 10792 both: this.spawn(both, { 10793 unique: true 10794 }) 10795 }; 10796 }, 10797 add: function add(toAdd) { 10798 var cy = this._private.cy; 10799 10800 if (!toAdd) { 10801 return this; 10802 } 10803 10804 if (string(toAdd)) { 10805 var selector = toAdd; 10806 toAdd = cy.mutableElements().filter(selector); 10807 } 10808 10809 var elements = []; 10810 10811 for (var i = 0; i < this.length; i++) { 10812 elements.push(this[i]); 10813 } 10814 10815 var map = this._private.map; 10816 10817 for (var _i = 0; _i < toAdd.length; _i++) { 10818 var add = !map.has(toAdd[_i].id()); 10819 10820 if (add) { 10821 elements.push(toAdd[_i]); 10822 } 10823 } 10824 10825 return this.spawn(elements); 10826 }, 10827 // in place merge on calling collection 10828 merge: function merge(toAdd) { 10829 var _p = this._private; 10830 var cy = _p.cy; 10831 10832 if (!toAdd) { 10833 return this; 10834 } 10835 10836 if (toAdd && string(toAdd)) { 10837 var selector = toAdd; 10838 toAdd = cy.mutableElements().filter(selector); 10839 } 10840 10841 var map = _p.map; 10842 10843 for (var i = 0; i < toAdd.length; i++) { 10844 var toAddEle = toAdd[i]; 10845 var id = toAddEle._private.data.id; 10846 var add = !map.has(id); 10847 10848 if (add) { 10849 var index = this.length++; 10850 this[index] = toAddEle; 10851 map.set(id, { 10852 ele: toAddEle, 10853 index: index 10854 }); 10855 } else { 10856 // replace 10857 var _index = map.get(id).index; 10858 this[_index] = toAddEle; 10859 map.set(id, { 10860 ele: toAddEle, 10861 index: _index 10862 }); 10863 } 10864 } 10865 10866 return this; // chaining 10867 }, 10868 unmergeAt: function unmergeAt(i) { 10869 var ele = this[i]; 10870 var id = ele.id(); 10871 var _p = this._private; 10872 var map = _p.map; // remove ele 10873 10874 this[i] = undefined; 10875 map["delete"](id); 10876 var unmergedLastEle = i === this.length - 1; // replace empty spot with last ele in collection 10877 10878 if (this.length > 1 && !unmergedLastEle) { 10879 var lastEleI = this.length - 1; 10880 var lastEle = this[lastEleI]; 10881 var lastEleId = lastEle._private.data.id; 10882 this[lastEleI] = undefined; 10883 this[i] = lastEle; 10884 map.set(lastEleId, { 10885 ele: lastEle, 10886 index: i 10887 }); 10888 } // the collection is now 1 ele smaller 10889 10890 10891 this.length--; 10892 return this; 10893 }, 10894 // remove single ele in place in calling collection 10895 unmergeOne: function unmergeOne(ele) { 10896 ele = ele[0]; 10897 var _p = this._private; 10898 var id = ele._private.data.id; 10899 var map = _p.map; 10900 var entry = map.get(id); 10901 10902 if (!entry) { 10903 return this; // no need to remove 10904 } 10905 10906 var i = entry.index; 10907 this.unmergeAt(i); 10908 return this; 10909 }, 10910 // remove eles in place on calling collection 10911 unmerge: function unmerge(toRemove) { 10912 var cy = this._private.cy; 10913 10914 if (!toRemove) { 10915 return this; 10916 } 10917 10918 if (toRemove && string(toRemove)) { 10919 var selector = toRemove; 10920 toRemove = cy.mutableElements().filter(selector); 10921 } 10922 10923 for (var i = 0; i < toRemove.length; i++) { 10924 this.unmergeOne(toRemove[i]); 10925 } 10926 10927 return this; // chaining 10928 }, 10929 unmergeBy: function unmergeBy(toRmFn) { 10930 for (var i = this.length - 1; i >= 0; i--) { 10931 var ele = this[i]; 10932 10933 if (toRmFn(ele)) { 10934 this.unmergeAt(i); 10935 } 10936 } 10937 10938 return this; 10939 }, 10940 map: function map(mapFn, thisArg) { 10941 var arr = []; 10942 var eles = this; 10943 10944 for (var i = 0; i < eles.length; i++) { 10945 var ele = eles[i]; 10946 var ret = thisArg ? mapFn.apply(thisArg, [ele, i, eles]) : mapFn(ele, i, eles); 10947 arr.push(ret); 10948 } 10949 10950 return arr; 10951 }, 10952 reduce: function reduce(fn, initialValue) { 10953 var val = initialValue; 10954 var eles = this; 10955 10956 for (var i = 0; i < eles.length; i++) { 10957 val = fn(val, eles[i], i, eles); 10958 } 10959 10960 return val; 10961 }, 10962 max: function max(valFn, thisArg) { 10963 var max = -Infinity; 10964 var maxEle; 10965 var eles = this; 10966 10967 for (var i = 0; i < eles.length; i++) { 10968 var ele = eles[i]; 10969 var val = thisArg ? valFn.apply(thisArg, [ele, i, eles]) : valFn(ele, i, eles); 10970 10971 if (val > max) { 10972 max = val; 10973 maxEle = ele; 10974 } 10975 } 10976 10977 return { 10978 value: max, 10979 ele: maxEle 10980 }; 10981 }, 10982 min: function min(valFn, thisArg) { 10983 var min = Infinity; 10984 var minEle; 10985 var eles = this; 10986 10987 for (var i = 0; i < eles.length; i++) { 10988 var ele = eles[i]; 10989 var val = thisArg ? valFn.apply(thisArg, [ele, i, eles]) : valFn(ele, i, eles); 10990 10991 if (val < min) { 10992 min = val; 10993 minEle = ele; 10994 } 10995 } 10996 10997 return { 10998 value: min, 10999 ele: minEle 11000 }; 11001 } 11002 }; // aliases 11003 11004 var fn$5 = elesfn$n; 11005 fn$5['u'] = fn$5['|'] = fn$5['+'] = fn$5.union = fn$5.or = fn$5.add; 11006 fn$5['\\'] = fn$5['!'] = fn$5['-'] = fn$5.difference = fn$5.relativeComplement = fn$5.subtract = fn$5.not; 11007 fn$5['n'] = fn$5['&'] = fn$5['.'] = fn$5.and = fn$5.intersection = fn$5.intersect; 11008 fn$5['^'] = fn$5['(+)'] = fn$5['(-)'] = fn$5.symmetricDifference = fn$5.symdiff = fn$5.xor; 11009 fn$5.fnFilter = fn$5.filterFn = fn$5.stdFilter = fn$5.filter; 11010 fn$5.complement = fn$5.abscomp = fn$5.absoluteComplement; 11011 11012 var elesfn$o = { 11013 isNode: function isNode() { 11014 return this.group() === 'nodes'; 11015 }, 11016 isEdge: function isEdge() { 11017 return this.group() === 'edges'; 11018 }, 11019 isLoop: function isLoop() { 11020 return this.isEdge() && this.source()[0] === this.target()[0]; 11021 }, 11022 isSimple: function isSimple() { 11023 return this.isEdge() && this.source()[0] !== this.target()[0]; 11024 }, 11025 group: function group() { 11026 var ele = this[0]; 11027 11028 if (ele) { 11029 return ele._private.group; 11030 } 11031 } 11032 }; 11033 11034 /** 11035 * Elements are drawn in a specific order based on compound depth (low to high), the element type (nodes above edges), 11036 * and z-index (low to high). These styles affect how this applies: 11037 * 11038 * z-compound-depth: May be `bottom | orphan | auto | top`. The first drawn is `bottom`, then `orphan` which is the 11039 * same depth as the root of the compound graph, followed by the default value `auto` which draws in order from 11040 * root to leaves of the compound graph. The last drawn is `top`. 11041 * z-index-compare: May be `auto | manual`. The default value is `auto` which always draws edges under nodes. 11042 * `manual` ignores this convention and draws based on the `z-index` value setting. 11043 * z-index: An integer value that affects the relative draw order of elements. In general, an element with a higher 11044 * `z-index` will be drawn on top of an element with a lower `z-index`. 11045 */ 11046 11047 var zIndexSort = function zIndexSort(a, b) { 11048 var cy = a.cy(); 11049 var hasCompoundNodes = cy.hasCompoundNodes(); 11050 11051 function getDepth(ele) { 11052 var style = ele.pstyle('z-compound-depth'); 11053 11054 if (style.value === 'auto') { 11055 return hasCompoundNodes ? ele.zDepth() : 0; 11056 } else if (style.value === 'bottom') { 11057 return -1; 11058 } else if (style.value === 'top') { 11059 return MAX_INT; 11060 } // 'orphan' 11061 11062 11063 return 0; 11064 } 11065 11066 var depthDiff = getDepth(a) - getDepth(b); 11067 11068 if (depthDiff !== 0) { 11069 return depthDiff; 11070 } 11071 11072 function getEleDepth(ele) { 11073 var style = ele.pstyle('z-index-compare'); 11074 11075 if (style.value === 'auto') { 11076 return ele.isNode() ? 1 : 0; 11077 } // 'manual' 11078 11079 11080 return 0; 11081 } 11082 11083 var eleDiff = getEleDepth(a) - getEleDepth(b); 11084 11085 if (eleDiff !== 0) { 11086 return eleDiff; 11087 } 11088 11089 var zDiff = a.pstyle('z-index').value - b.pstyle('z-index').value; 11090 11091 if (zDiff !== 0) { 11092 return zDiff; 11093 } // compare indices in the core (order added to graph w/ last on top) 11094 11095 11096 return a.poolIndex() - b.poolIndex(); 11097 }; 11098 11099 var elesfn$p = { 11100 forEach: function forEach(fn$1, thisArg) { 11101 if (fn(fn$1)) { 11102 var N = this.length; 11103 11104 for (var i = 0; i < N; i++) { 11105 var ele = this[i]; 11106 var ret = thisArg ? fn$1.apply(thisArg, [ele, i, this]) : fn$1(ele, i, this); 11107 11108 if (ret === false) { 11109 break; 11110 } // exit each early on return false 11111 11112 } 11113 } 11114 11115 return this; 11116 }, 11117 toArray: function toArray() { 11118 var array = []; 11119 11120 for (var i = 0; i < this.length; i++) { 11121 array.push(this[i]); 11122 } 11123 11124 return array; 11125 }, 11126 slice: function slice(start, end) { 11127 var array = []; 11128 var thisSize = this.length; 11129 11130 if (end == null) { 11131 end = thisSize; 11132 } 11133 11134 if (start == null) { 11135 start = 0; 11136 } 11137 11138 if (start < 0) { 11139 start = thisSize + start; 11140 } 11141 11142 if (end < 0) { 11143 end = thisSize + end; 11144 } 11145 11146 for (var i = start; i >= 0 && i < end && i < thisSize; i++) { 11147 array.push(this[i]); 11148 } 11149 11150 return this.spawn(array); 11151 }, 11152 size: function size() { 11153 return this.length; 11154 }, 11155 eq: function eq(i) { 11156 return this[i] || this.spawn(); 11157 }, 11158 first: function first() { 11159 return this[0] || this.spawn(); 11160 }, 11161 last: function last() { 11162 return this[this.length - 1] || this.spawn(); 11163 }, 11164 empty: function empty() { 11165 return this.length === 0; 11166 }, 11167 nonempty: function nonempty() { 11168 return !this.empty(); 11169 }, 11170 sort: function sort(sortFn) { 11171 if (!fn(sortFn)) { 11172 return this; 11173 } 11174 11175 var sorted = this.toArray().sort(sortFn); 11176 return this.spawn(sorted); 11177 }, 11178 sortByZIndex: function sortByZIndex() { 11179 return this.sort(zIndexSort); 11180 }, 11181 zDepth: function zDepth() { 11182 var ele = this[0]; 11183 11184 if (!ele) { 11185 return undefined; 11186 } // let cy = ele.cy(); 11187 11188 11189 var _p = ele._private; 11190 var group = _p.group; 11191 11192 if (group === 'nodes') { 11193 var depth = _p.data.parent ? ele.parents().size() : 0; 11194 11195 if (!ele.isParent()) { 11196 return MAX_INT - 1; // childless nodes always on top 11197 } 11198 11199 return depth; 11200 } else { 11201 var src = _p.source; 11202 var tgt = _p.target; 11203 var srcDepth = src.zDepth(); 11204 var tgtDepth = tgt.zDepth(); 11205 return Math.max(srcDepth, tgtDepth, 0); // depth of deepest parent 11206 } 11207 } 11208 }; 11209 elesfn$p.each = elesfn$p.forEach; 11210 11211 var defineSymbolIterator = function defineSymbolIterator() { 11212 var typeofUndef = "undefined" ; 11213 var isIteratorSupported = (typeof Symbol === "undefined" ? "undefined" : _typeof(Symbol)) != typeofUndef && _typeof(Symbol.iterator) != typeofUndef; // eslint-disable-line no-undef 11214 11215 if (isIteratorSupported) { 11216 elesfn$p[Symbol.iterator] = function () { 11217 var _this = this; 11218 11219 // eslint-disable-line no-undef 11220 var entry = { 11221 value: undefined, 11222 done: false 11223 }; 11224 var i = 0; 11225 var length = this.length; 11226 return _defineProperty({ 11227 next: function next() { 11228 if (i < length) { 11229 entry.value = _this[i++]; 11230 } else { 11231 entry.value = undefined; 11232 entry.done = true; 11233 } 11234 11235 return entry; 11236 } 11237 }, Symbol.iterator, function () { 11238 // eslint-disable-line no-undef 11239 return this; 11240 }); 11241 }; 11242 } 11243 }; 11244 11245 defineSymbolIterator(); 11246 11247 var getLayoutDimensionOptions = defaults({ 11248 nodeDimensionsIncludeLabels: false 11249 }); 11250 var elesfn$q = { 11251 // Calculates and returns node dimensions { x, y } based on options given 11252 layoutDimensions: function layoutDimensions(options) { 11253 options = getLayoutDimensionOptions(options); 11254 var dims; 11255 11256 if (!this.takesUpSpace()) { 11257 dims = { 11258 w: 0, 11259 h: 0 11260 }; 11261 } else if (options.nodeDimensionsIncludeLabels) { 11262 var bbDim = this.boundingBox(); 11263 dims = { 11264 w: bbDim.w, 11265 h: bbDim.h 11266 }; 11267 } else { 11268 dims = { 11269 w: this.outerWidth(), 11270 h: this.outerHeight() 11271 }; 11272 } // sanitise the dimensions for external layouts (avoid division by zero) 11273 11274 11275 if (dims.w === 0 || dims.h === 0) { 11276 dims.w = dims.h = 1; 11277 } 11278 11279 return dims; 11280 }, 11281 // using standard layout options, apply position function (w/ or w/o animation) 11282 layoutPositions: function layoutPositions(layout, options, fn) { 11283 var nodes = this.nodes(); 11284 var cy = this.cy(); 11285 var layoutEles = options.eles; // nodes & edges 11286 11287 var getMemoizeKey = function getMemoizeKey(node) { 11288 return node.id(); 11289 }; 11290 11291 var fnMem = memoize(fn, getMemoizeKey); // memoized version of position function 11292 11293 layout.emit({ 11294 type: 'layoutstart', 11295 layout: layout 11296 }); 11297 layout.animations = []; 11298 11299 var calculateSpacing = function calculateSpacing(spacing, nodesBb, pos) { 11300 var center = { 11301 x: nodesBb.x1 + nodesBb.w / 2, 11302 y: nodesBb.y1 + nodesBb.h / 2 11303 }; 11304 var spacingVector = { 11305 // scale from center of bounding box (not necessarily 0,0) 11306 x: (pos.x - center.x) * spacing, 11307 y: (pos.y - center.y) * spacing 11308 }; 11309 return { 11310 x: center.x + spacingVector.x, 11311 y: center.y + spacingVector.y 11312 }; 11313 }; 11314 11315 var useSpacingFactor = options.spacingFactor && options.spacingFactor !== 1; 11316 11317 var spacingBb = function spacingBb() { 11318 if (!useSpacingFactor) { 11319 return null; 11320 } 11321 11322 var bb = makeBoundingBox(); 11323 11324 for (var i = 0; i < nodes.length; i++) { 11325 var node = nodes[i]; 11326 var pos = fnMem(node, i); 11327 expandBoundingBoxByPoint(bb, pos.x, pos.y); 11328 } 11329 11330 return bb; 11331 }; 11332 11333 var bb = spacingBb(); 11334 var getFinalPos = memoize(function (node, i) { 11335 var newPos = fnMem(node, i); 11336 11337 if (useSpacingFactor) { 11338 var spacing = Math.abs(options.spacingFactor); 11339 newPos = calculateSpacing(spacing, bb, newPos); 11340 } 11341 11342 if (options.transform != null) { 11343 newPos = options.transform(node, newPos); 11344 } 11345 11346 return newPos; 11347 }, getMemoizeKey); 11348 11349 if (options.animate) { 11350 for (var i = 0; i < nodes.length; i++) { 11351 var node = nodes[i]; 11352 var newPos = getFinalPos(node, i); 11353 var animateNode = options.animateFilter == null || options.animateFilter(node, i); 11354 11355 if (animateNode) { 11356 var ani = node.animation({ 11357 position: newPos, 11358 duration: options.animationDuration, 11359 easing: options.animationEasing 11360 }); 11361 layout.animations.push(ani); 11362 } else { 11363 node.position(newPos); 11364 } 11365 } 11366 11367 if (options.fit) { 11368 var fitAni = cy.animation({ 11369 fit: { 11370 boundingBox: layoutEles.boundingBoxAt(getFinalPos), 11371 padding: options.padding 11372 }, 11373 duration: options.animationDuration, 11374 easing: options.animationEasing 11375 }); 11376 layout.animations.push(fitAni); 11377 } else if (options.zoom !== undefined && options.pan !== undefined) { 11378 var zoomPanAni = cy.animation({ 11379 zoom: options.zoom, 11380 pan: options.pan, 11381 duration: options.animationDuration, 11382 easing: options.animationEasing 11383 }); 11384 layout.animations.push(zoomPanAni); 11385 } 11386 11387 layout.animations.forEach(function (ani) { 11388 return ani.play(); 11389 }); 11390 layout.one('layoutready', options.ready); 11391 layout.emit({ 11392 type: 'layoutready', 11393 layout: layout 11394 }); 11395 Promise$1.all(layout.animations.map(function (ani) { 11396 return ani.promise(); 11397 })).then(function () { 11398 layout.one('layoutstop', options.stop); 11399 layout.emit({ 11400 type: 'layoutstop', 11401 layout: layout 11402 }); 11403 }); 11404 } else { 11405 nodes.positions(getFinalPos); 11406 11407 if (options.fit) { 11408 cy.fit(options.eles, options.padding); 11409 } 11410 11411 if (options.zoom != null) { 11412 cy.zoom(options.zoom); 11413 } 11414 11415 if (options.pan) { 11416 cy.pan(options.pan); 11417 } 11418 11419 layout.one('layoutready', options.ready); 11420 layout.emit({ 11421 type: 'layoutready', 11422 layout: layout 11423 }); 11424 layout.one('layoutstop', options.stop); 11425 layout.emit({ 11426 type: 'layoutstop', 11427 layout: layout 11428 }); 11429 } 11430 11431 return this; // chaining 11432 }, 11433 layout: function layout(options) { 11434 var cy = this.cy(); 11435 return cy.makeLayout(extend({}, options, { 11436 eles: this 11437 })); 11438 } 11439 }; // aliases: 11440 11441 elesfn$q.createLayout = elesfn$q.makeLayout = elesfn$q.layout; 11442 11443 function styleCache(key, fn, ele) { 11444 var _p = ele._private; 11445 var cache = _p.styleCache = _p.styleCache || []; 11446 var val; 11447 11448 if ((val = cache[key]) != null) { 11449 return val; 11450 } else { 11451 val = cache[key] = fn(ele); 11452 return val; 11453 } 11454 } 11455 11456 function cacheStyleFunction(key, fn) { 11457 key = hashString(key); 11458 return function cachedStyleFunction(ele) { 11459 return styleCache(key, fn, ele); 11460 }; 11461 } 11462 11463 function cachePrototypeStyleFunction(key, fn) { 11464 key = hashString(key); 11465 11466 var selfFn = function selfFn(ele) { 11467 return fn.call(ele); 11468 }; 11469 11470 return function cachedPrototypeStyleFunction() { 11471 var ele = this[0]; 11472 11473 if (ele) { 11474 return styleCache(key, selfFn, ele); 11475 } 11476 }; 11477 } 11478 11479 var elesfn$r = { 11480 recalculateRenderedStyle: function recalculateRenderedStyle(useCache) { 11481 var cy = this.cy(); 11482 var renderer = cy.renderer(); 11483 var styleEnabled = cy.styleEnabled(); 11484 11485 if (renderer && styleEnabled) { 11486 renderer.recalculateRenderedStyle(this, useCache); 11487 } 11488 11489 return this; 11490 }, 11491 dirtyStyleCache: function dirtyStyleCache() { 11492 var cy = this.cy(); 11493 11494 var dirty = function dirty(ele) { 11495 return ele._private.styleCache = null; 11496 }; 11497 11498 if (cy.hasCompoundNodes()) { 11499 var eles; 11500 eles = this.spawnSelf().merge(this.descendants()).merge(this.parents()); 11501 eles.merge(eles.connectedEdges()); 11502 eles.forEach(dirty); 11503 } else { 11504 this.forEach(function (ele) { 11505 dirty(ele); 11506 ele.connectedEdges().forEach(dirty); 11507 }); 11508 } 11509 11510 return this; 11511 }, 11512 // fully updates (recalculates) the style for the elements 11513 updateStyle: function updateStyle(notifyRenderer) { 11514 var cy = this._private.cy; 11515 11516 if (!cy.styleEnabled()) { 11517 return this; 11518 } 11519 11520 if (cy.batching()) { 11521 var bEles = cy._private.batchStyleEles; 11522 bEles.merge(this); 11523 return this; // chaining and exit early when batching 11524 } 11525 11526 var hasCompounds = cy.hasCompoundNodes(); 11527 var style = cy.style(); 11528 var updatedEles = this; 11529 notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; 11530 11531 if (hasCompounds) { 11532 // then add everything up and down for compound selector checks 11533 updatedEles = this.spawnSelf().merge(this.descendants()).merge(this.parents()); 11534 } 11535 11536 var changedEles = style.apply(updatedEles); 11537 11538 if (notifyRenderer) { 11539 changedEles.emitAndNotify('style'); // let renderer know we changed style 11540 } else { 11541 changedEles.emit('style'); // just fire the event 11542 } 11543 11544 return this; // chaining 11545 }, 11546 // get the internal parsed style object for the specified property 11547 parsedStyle: function parsedStyle(property) { 11548 var includeNonDefault = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 11549 var ele = this[0]; 11550 var cy = ele.cy(); 11551 11552 if (!cy.styleEnabled()) { 11553 return; 11554 } 11555 11556 if (ele) { 11557 var overriddenStyle = ele._private.style[property]; 11558 11559 if (overriddenStyle != null) { 11560 return overriddenStyle; 11561 } else if (includeNonDefault) { 11562 return cy.style().getDefaultProperty(property); 11563 } else { 11564 return null; 11565 } 11566 } 11567 }, 11568 numericStyle: function numericStyle(property) { 11569 var ele = this[0]; 11570 11571 if (!ele.cy().styleEnabled()) { 11572 return; 11573 } 11574 11575 if (ele) { 11576 var pstyle = ele.pstyle(property); 11577 return pstyle.pfValue !== undefined ? pstyle.pfValue : pstyle.value; 11578 } 11579 }, 11580 numericStyleUnits: function numericStyleUnits(property) { 11581 var ele = this[0]; 11582 11583 if (!ele.cy().styleEnabled()) { 11584 return; 11585 } 11586 11587 if (ele) { 11588 return ele.pstyle(property).units; 11589 } 11590 }, 11591 // get the specified css property as a rendered value (i.e. on-screen value) 11592 // or get the whole rendered style if no property specified (NB doesn't allow setting) 11593 renderedStyle: function renderedStyle(property) { 11594 var cy = this.cy(); 11595 11596 if (!cy.styleEnabled()) { 11597 return this; 11598 } 11599 11600 var ele = this[0]; 11601 11602 if (ele) { 11603 return cy.style().getRenderedStyle(ele, property); 11604 } 11605 }, 11606 // read the calculated css style of the element or override the style (via a bypass) 11607 style: function style(name, value) { 11608 var cy = this.cy(); 11609 11610 if (!cy.styleEnabled()) { 11611 return this; 11612 } 11613 11614 var updateTransitions = false; 11615 var style = cy.style(); 11616 11617 if (plainObject(name)) { 11618 // then extend the bypass 11619 var props = name; 11620 style.applyBypass(this, props, updateTransitions); 11621 this.emitAndNotify('style'); // let the renderer know we've updated style 11622 } else if (string(name)) { 11623 if (value === undefined) { 11624 // then get the property from the style 11625 var ele = this[0]; 11626 11627 if (ele) { 11628 return style.getStylePropertyValue(ele, name); 11629 } else { 11630 // empty collection => can't get any value 11631 return; 11632 } 11633 } else { 11634 // then set the bypass with the property value 11635 style.applyBypass(this, name, value, updateTransitions); 11636 this.emitAndNotify('style'); // let the renderer know we've updated style 11637 } 11638 } else if (name === undefined) { 11639 var _ele = this[0]; 11640 11641 if (_ele) { 11642 return style.getRawStyle(_ele); 11643 } else { 11644 // empty collection => can't get any value 11645 return; 11646 } 11647 } 11648 11649 return this; // chaining 11650 }, 11651 removeStyle: function removeStyle(names) { 11652 var cy = this.cy(); 11653 11654 if (!cy.styleEnabled()) { 11655 return this; 11656 } 11657 11658 var updateTransitions = false; 11659 var style = cy.style(); 11660 var eles = this; 11661 11662 if (names === undefined) { 11663 for (var i = 0; i < eles.length; i++) { 11664 var ele = eles[i]; 11665 style.removeAllBypasses(ele, updateTransitions); 11666 } 11667 } else { 11668 names = names.split(/\s+/); 11669 11670 for (var _i = 0; _i < eles.length; _i++) { 11671 var _ele2 = eles[_i]; 11672 style.removeBypasses(_ele2, names, updateTransitions); 11673 } 11674 } 11675 11676 this.emitAndNotify('style'); // let the renderer know we've updated style 11677 11678 return this; // chaining 11679 }, 11680 show: function show() { 11681 this.css('display', 'element'); 11682 return this; // chaining 11683 }, 11684 hide: function hide() { 11685 this.css('display', 'none'); 11686 return this; // chaining 11687 }, 11688 effectiveOpacity: function effectiveOpacity() { 11689 var cy = this.cy(); 11690 11691 if (!cy.styleEnabled()) { 11692 return 1; 11693 } 11694 11695 var hasCompoundNodes = cy.hasCompoundNodes(); 11696 var ele = this[0]; 11697 11698 if (ele) { 11699 var _p = ele._private; 11700 var parentOpacity = ele.pstyle('opacity').value; 11701 11702 if (!hasCompoundNodes) { 11703 return parentOpacity; 11704 } 11705 11706 var parents = !_p.data.parent ? null : ele.parents(); 11707 11708 if (parents) { 11709 for (var i = 0; i < parents.length; i++) { 11710 var parent = parents[i]; 11711 var opacity = parent.pstyle('opacity').value; 11712 parentOpacity = opacity * parentOpacity; 11713 } 11714 } 11715 11716 return parentOpacity; 11717 } 11718 }, 11719 transparent: function transparent() { 11720 var cy = this.cy(); 11721 11722 if (!cy.styleEnabled()) { 11723 return false; 11724 } 11725 11726 var ele = this[0]; 11727 var hasCompoundNodes = ele.cy().hasCompoundNodes(); 11728 11729 if (ele) { 11730 if (!hasCompoundNodes) { 11731 return ele.pstyle('opacity').value === 0; 11732 } else { 11733 return ele.effectiveOpacity() === 0; 11734 } 11735 } 11736 }, 11737 backgrounding: function backgrounding() { 11738 var cy = this.cy(); 11739 11740 if (!cy.styleEnabled()) { 11741 return false; 11742 } 11743 11744 var ele = this[0]; 11745 return ele._private.backgrounding ? true : false; 11746 } 11747 }; 11748 11749 function checkCompound(ele, parentOk) { 11750 var _p = ele._private; 11751 var parents = _p.data.parent ? ele.parents() : null; 11752 11753 if (parents) { 11754 for (var i = 0; i < parents.length; i++) { 11755 var parent = parents[i]; 11756 11757 if (!parentOk(parent)) { 11758 return false; 11759 } 11760 } 11761 } 11762 11763 return true; 11764 } 11765 11766 function defineDerivedStateFunction(specs) { 11767 var ok = specs.ok; 11768 var edgeOkViaNode = specs.edgeOkViaNode || specs.ok; 11769 var parentOk = specs.parentOk || specs.ok; 11770 return function () { 11771 var cy = this.cy(); 11772 11773 if (!cy.styleEnabled()) { 11774 return true; 11775 } 11776 11777 var ele = this[0]; 11778 var hasCompoundNodes = cy.hasCompoundNodes(); 11779 11780 if (ele) { 11781 var _p = ele._private; 11782 11783 if (!ok(ele)) { 11784 return false; 11785 } 11786 11787 if (ele.isNode()) { 11788 return !hasCompoundNodes || checkCompound(ele, parentOk); 11789 } else { 11790 var src = _p.source; 11791 var tgt = _p.target; 11792 return edgeOkViaNode(src) && (!hasCompoundNodes || checkCompound(src, edgeOkViaNode)) && (src === tgt || edgeOkViaNode(tgt) && (!hasCompoundNodes || checkCompound(tgt, edgeOkViaNode))); 11793 } 11794 } 11795 }; 11796 } 11797 11798 var eleTakesUpSpace = cacheStyleFunction('eleTakesUpSpace', function (ele) { 11799 return ele.pstyle('display').value === 'element' && ele.width() !== 0 && (ele.isNode() ? ele.height() !== 0 : true); 11800 }); 11801 elesfn$r.takesUpSpace = cachePrototypeStyleFunction('takesUpSpace', defineDerivedStateFunction({ 11802 ok: eleTakesUpSpace 11803 })); 11804 var eleInteractive = cacheStyleFunction('eleInteractive', function (ele) { 11805 return ele.pstyle('events').value === 'yes' && ele.pstyle('visibility').value === 'visible' && eleTakesUpSpace(ele); 11806 }); 11807 var parentInteractive = cacheStyleFunction('parentInteractive', function (parent) { 11808 return parent.pstyle('visibility').value === 'visible' && eleTakesUpSpace(parent); 11809 }); 11810 elesfn$r.interactive = cachePrototypeStyleFunction('interactive', defineDerivedStateFunction({ 11811 ok: eleInteractive, 11812 parentOk: parentInteractive, 11813 edgeOkViaNode: eleTakesUpSpace 11814 })); 11815 11816 elesfn$r.noninteractive = function () { 11817 var ele = this[0]; 11818 11819 if (ele) { 11820 return !ele.interactive(); 11821 } 11822 }; 11823 11824 var eleVisible = cacheStyleFunction('eleVisible', function (ele) { 11825 return ele.pstyle('visibility').value === 'visible' && ele.pstyle('opacity').pfValue !== 0 && eleTakesUpSpace(ele); 11826 }); 11827 var edgeVisibleViaNode = eleTakesUpSpace; 11828 elesfn$r.visible = cachePrototypeStyleFunction('visible', defineDerivedStateFunction({ 11829 ok: eleVisible, 11830 edgeOkViaNode: edgeVisibleViaNode 11831 })); 11832 11833 elesfn$r.hidden = function () { 11834 var ele = this[0]; 11835 11836 if (ele) { 11837 return !ele.visible(); 11838 } 11839 }; 11840 11841 elesfn$r.isBundledBezier = cachePrototypeStyleFunction('isBundledBezier', function () { 11842 if (!this.cy().styleEnabled()) { 11843 return false; 11844 } 11845 11846 return !this.removed() && this.pstyle('curve-style').value === 'bezier' && this.takesUpSpace(); 11847 }); 11848 elesfn$r.bypass = elesfn$r.css = elesfn$r.style; 11849 elesfn$r.renderedCss = elesfn$r.renderedStyle; 11850 elesfn$r.removeBypass = elesfn$r.removeCss = elesfn$r.removeStyle; 11851 elesfn$r.pstyle = elesfn$r.parsedStyle; 11852 11853 var elesfn$s = {}; 11854 11855 function defineSwitchFunction(params) { 11856 return function () { 11857 var args = arguments; 11858 var changedEles = []; // e.g. cy.nodes().select( data, handler ) 11859 11860 if (args.length === 2) { 11861 var data = args[0]; 11862 var handler = args[1]; 11863 this.on(params.event, data, handler); 11864 } // e.g. cy.nodes().select( handler ) 11865 else if (args.length === 1 && fn(args[0])) { 11866 var _handler = args[0]; 11867 this.on(params.event, _handler); 11868 } // e.g. cy.nodes().select() 11869 // e.g. (private) cy.nodes().select(['tapselect']) 11870 else if (args.length === 0 || args.length === 1 && array(args[0])) { 11871 var addlEvents = args.length === 1 ? args[0] : null; 11872 11873 for (var i = 0; i < this.length; i++) { 11874 var ele = this[i]; 11875 var able = !params.ableField || ele._private[params.ableField]; 11876 var changed = ele._private[params.field] != params.value; 11877 11878 if (params.overrideAble) { 11879 var overrideAble = params.overrideAble(ele); 11880 11881 if (overrideAble !== undefined) { 11882 able = overrideAble; 11883 11884 if (!overrideAble) { 11885 return this; 11886 } // to save cycles assume not able for all on override 11887 11888 } 11889 } 11890 11891 if (able) { 11892 ele._private[params.field] = params.value; 11893 11894 if (changed) { 11895 changedEles.push(ele); 11896 } 11897 } 11898 } 11899 11900 var changedColl = this.spawn(changedEles); 11901 changedColl.updateStyle(); // change of state => possible change of style 11902 11903 changedColl.emit(params.event); 11904 11905 if (addlEvents) { 11906 changedColl.emit(addlEvents); 11907 } 11908 } 11909 11910 return this; 11911 }; 11912 } 11913 11914 function defineSwitchSet(params) { 11915 elesfn$s[params.field] = function () { 11916 var ele = this[0]; 11917 11918 if (ele) { 11919 if (params.overrideField) { 11920 var val = params.overrideField(ele); 11921 11922 if (val !== undefined) { 11923 return val; 11924 } 11925 } 11926 11927 return ele._private[params.field]; 11928 } 11929 }; 11930 11931 elesfn$s[params.on] = defineSwitchFunction({ 11932 event: params.on, 11933 field: params.field, 11934 ableField: params.ableField, 11935 overrideAble: params.overrideAble, 11936 value: true 11937 }); 11938 elesfn$s[params.off] = defineSwitchFunction({ 11939 event: params.off, 11940 field: params.field, 11941 ableField: params.ableField, 11942 overrideAble: params.overrideAble, 11943 value: false 11944 }); 11945 } 11946 11947 defineSwitchSet({ 11948 field: 'locked', 11949 overrideField: function overrideField(ele) { 11950 return ele.cy().autolock() ? true : undefined; 11951 }, 11952 on: 'lock', 11953 off: 'unlock' 11954 }); 11955 defineSwitchSet({ 11956 field: 'grabbable', 11957 overrideField: function overrideField(ele) { 11958 return ele.cy().autoungrabify() || ele.pannable() ? false : undefined; 11959 }, 11960 on: 'grabify', 11961 off: 'ungrabify' 11962 }); 11963 defineSwitchSet({ 11964 field: 'selected', 11965 ableField: 'selectable', 11966 overrideAble: function overrideAble(ele) { 11967 return ele.cy().autounselectify() ? false : undefined; 11968 }, 11969 on: 'select', 11970 off: 'unselect' 11971 }); 11972 defineSwitchSet({ 11973 field: 'selectable', 11974 overrideField: function overrideField(ele) { 11975 return ele.cy().autounselectify() ? false : undefined; 11976 }, 11977 on: 'selectify', 11978 off: 'unselectify' 11979 }); 11980 elesfn$s.deselect = elesfn$s.unselect; 11981 11982 elesfn$s.grabbed = function () { 11983 var ele = this[0]; 11984 11985 if (ele) { 11986 return ele._private.grabbed; 11987 } 11988 }; 11989 11990 defineSwitchSet({ 11991 field: 'active', 11992 on: 'activate', 11993 off: 'unactivate' 11994 }); 11995 defineSwitchSet({ 11996 field: 'pannable', 11997 on: 'panify', 11998 off: 'unpanify' 11999 }); 12000 12001 elesfn$s.inactive = function () { 12002 var ele = this[0]; 12003 12004 if (ele) { 12005 return !ele._private.active; 12006 } 12007 }; 12008 12009 var elesfn$t = {}; // DAG functions 12010 //////////////// 12011 12012 var defineDagExtremity = function defineDagExtremity(params) { 12013 return function dagExtremityImpl(selector) { 12014 var eles = this; 12015 var ret = []; 12016 12017 for (var i = 0; i < eles.length; i++) { 12018 var ele = eles[i]; 12019 12020 if (!ele.isNode()) { 12021 continue; 12022 } 12023 12024 var disqualified = false; 12025 var edges = ele.connectedEdges(); 12026 12027 for (var j = 0; j < edges.length; j++) { 12028 var edge = edges[j]; 12029 var src = edge.source(); 12030 var tgt = edge.target(); 12031 12032 if (params.noIncomingEdges && tgt === ele && src !== ele || params.noOutgoingEdges && src === ele && tgt !== ele) { 12033 disqualified = true; 12034 break; 12035 } 12036 } 12037 12038 if (!disqualified) { 12039 ret.push(ele); 12040 } 12041 } 12042 12043 return this.spawn(ret, { 12044 unique: true 12045 }).filter(selector); 12046 }; 12047 }; 12048 12049 var defineDagOneHop = function defineDagOneHop(params) { 12050 return function (selector) { 12051 var eles = this; 12052 var oEles = []; 12053 12054 for (var i = 0; i < eles.length; i++) { 12055 var ele = eles[i]; 12056 12057 if (!ele.isNode()) { 12058 continue; 12059 } 12060 12061 var edges = ele.connectedEdges(); 12062 12063 for (var j = 0; j < edges.length; j++) { 12064 var edge = edges[j]; 12065 var src = edge.source(); 12066 var tgt = edge.target(); 12067 12068 if (params.outgoing && src === ele) { 12069 oEles.push(edge); 12070 oEles.push(tgt); 12071 } else if (params.incoming && tgt === ele) { 12072 oEles.push(edge); 12073 oEles.push(src); 12074 } 12075 } 12076 } 12077 12078 return this.spawn(oEles, { 12079 unique: true 12080 }).filter(selector); 12081 }; 12082 }; 12083 12084 var defineDagAllHops = function defineDagAllHops(params) { 12085 return function (selector) { 12086 var eles = this; 12087 var sEles = []; 12088 var sElesIds = {}; 12089 12090 for (;;) { 12091 var next = params.outgoing ? eles.outgoers() : eles.incomers(); 12092 12093 if (next.length === 0) { 12094 break; 12095 } // done if none left 12096 12097 12098 var newNext = false; 12099 12100 for (var i = 0; i < next.length; i++) { 12101 var n = next[i]; 12102 var nid = n.id(); 12103 12104 if (!sElesIds[nid]) { 12105 sElesIds[nid] = true; 12106 sEles.push(n); 12107 newNext = true; 12108 } 12109 } 12110 12111 if (!newNext) { 12112 break; 12113 } // done if touched all outgoers already 12114 12115 12116 eles = next; 12117 } 12118 12119 return this.spawn(sEles, { 12120 unique: true 12121 }).filter(selector); 12122 }; 12123 }; 12124 12125 elesfn$t.clearTraversalCache = function () { 12126 for (var i = 0; i < this.length; i++) { 12127 this[i]._private.traversalCache = null; 12128 } 12129 }; 12130 12131 extend(elesfn$t, { 12132 // get the root nodes in the DAG 12133 roots: defineDagExtremity({ 12134 noIncomingEdges: true 12135 }), 12136 // get the leaf nodes in the DAG 12137 leaves: defineDagExtremity({ 12138 noOutgoingEdges: true 12139 }), 12140 // normally called children in graph theory 12141 // these nodes =edges=> outgoing nodes 12142 outgoers: cache(defineDagOneHop({ 12143 outgoing: true 12144 }), 'outgoers'), 12145 // aka DAG descendants 12146 successors: defineDagAllHops({ 12147 outgoing: true 12148 }), 12149 // normally called parents in graph theory 12150 // these nodes <=edges= incoming nodes 12151 incomers: cache(defineDagOneHop({ 12152 incoming: true 12153 }), 'incomers'), 12154 // aka DAG ancestors 12155 predecessors: defineDagAllHops({ 12156 incoming: true 12157 }) 12158 }); // Neighbourhood functions 12159 ////////////////////////// 12160 12161 extend(elesfn$t, { 12162 neighborhood: cache(function (selector) { 12163 var elements = []; 12164 var nodes = this.nodes(); 12165 12166 for (var i = 0; i < nodes.length; i++) { 12167 // for all nodes 12168 var node = nodes[i]; 12169 var connectedEdges = node.connectedEdges(); // for each connected edge, add the edge and the other node 12170 12171 for (var j = 0; j < connectedEdges.length; j++) { 12172 var edge = connectedEdges[j]; 12173 var src = edge.source(); 12174 var tgt = edge.target(); 12175 var otherNode = node === src ? tgt : src; // need check in case of loop 12176 12177 if (otherNode.length > 0) { 12178 elements.push(otherNode[0]); // add node 1 hop away 12179 } // add connected edge 12180 12181 12182 elements.push(edge[0]); 12183 } 12184 } 12185 12186 return this.spawn(elements, { 12187 unique: true 12188 }).filter(selector); 12189 }, 'neighborhood'), 12190 closedNeighborhood: function closedNeighborhood(selector) { 12191 return this.neighborhood().add(this).filter(selector); 12192 }, 12193 openNeighborhood: function openNeighborhood(selector) { 12194 return this.neighborhood(selector); 12195 } 12196 }); // aliases 12197 12198 elesfn$t.neighbourhood = elesfn$t.neighborhood; 12199 elesfn$t.closedNeighbourhood = elesfn$t.closedNeighborhood; 12200 elesfn$t.openNeighbourhood = elesfn$t.openNeighborhood; // Edge functions 12201 ///////////////// 12202 12203 extend(elesfn$t, { 12204 source: cache(function sourceImpl(selector) { 12205 var ele = this[0]; 12206 var src; 12207 12208 if (ele) { 12209 src = ele._private.source || ele.cy().collection(); 12210 } 12211 12212 return src && selector ? src.filter(selector) : src; 12213 }, 'source'), 12214 target: cache(function targetImpl(selector) { 12215 var ele = this[0]; 12216 var tgt; 12217 12218 if (ele) { 12219 tgt = ele._private.target || ele.cy().collection(); 12220 } 12221 12222 return tgt && selector ? tgt.filter(selector) : tgt; 12223 }, 'target'), 12224 sources: defineSourceFunction({ 12225 attr: 'source' 12226 }), 12227 targets: defineSourceFunction({ 12228 attr: 'target' 12229 }) 12230 }); 12231 12232 function defineSourceFunction(params) { 12233 return function sourceImpl(selector) { 12234 var sources = []; 12235 12236 for (var i = 0; i < this.length; i++) { 12237 var ele = this[i]; 12238 var src = ele._private[params.attr]; 12239 12240 if (src) { 12241 sources.push(src); 12242 } 12243 } 12244 12245 return this.spawn(sources, { 12246 unique: true 12247 }).filter(selector); 12248 }; 12249 } 12250 12251 extend(elesfn$t, { 12252 edgesWith: cache(defineEdgesWithFunction(), 'edgesWith'), 12253 edgesTo: cache(defineEdgesWithFunction({ 12254 thisIsSrc: true 12255 }), 'edgesTo') 12256 }); 12257 12258 function defineEdgesWithFunction(params) { 12259 return function edgesWithImpl(otherNodes) { 12260 var elements = []; 12261 var cy = this._private.cy; 12262 var p = params || {}; // get elements if a selector is specified 12263 12264 if (string(otherNodes)) { 12265 otherNodes = cy.$(otherNodes); 12266 } 12267 12268 for (var h = 0; h < otherNodes.length; h++) { 12269 var edges = otherNodes[h]._private.edges; 12270 12271 for (var i = 0; i < edges.length; i++) { 12272 var edge = edges[i]; 12273 var edgeData = edge._private.data; 12274 var thisToOther = this.hasElementWithId(edgeData.source) && otherNodes.hasElementWithId(edgeData.target); 12275 var otherToThis = otherNodes.hasElementWithId(edgeData.source) && this.hasElementWithId(edgeData.target); 12276 var edgeConnectsThisAndOther = thisToOther || otherToThis; 12277 12278 if (!edgeConnectsThisAndOther) { 12279 continue; 12280 } 12281 12282 if (p.thisIsSrc || p.thisIsTgt) { 12283 if (p.thisIsSrc && !thisToOther) { 12284 continue; 12285 } 12286 12287 if (p.thisIsTgt && !otherToThis) { 12288 continue; 12289 } 12290 } 12291 12292 elements.push(edge); 12293 } 12294 } 12295 12296 return this.spawn(elements, { 12297 unique: true 12298 }); 12299 }; 12300 } 12301 12302 extend(elesfn$t, { 12303 connectedEdges: cache(function (selector) { 12304 var retEles = []; 12305 var eles = this; 12306 12307 for (var i = 0; i < eles.length; i++) { 12308 var node = eles[i]; 12309 12310 if (!node.isNode()) { 12311 continue; 12312 } 12313 12314 var edges = node._private.edges; 12315 12316 for (var j = 0; j < edges.length; j++) { 12317 var edge = edges[j]; 12318 retEles.push(edge); 12319 } 12320 } 12321 12322 return this.spawn(retEles, { 12323 unique: true 12324 }).filter(selector); 12325 }, 'connectedEdges'), 12326 connectedNodes: cache(function (selector) { 12327 var retEles = []; 12328 var eles = this; 12329 12330 for (var i = 0; i < eles.length; i++) { 12331 var edge = eles[i]; 12332 12333 if (!edge.isEdge()) { 12334 continue; 12335 } 12336 12337 retEles.push(edge.source()[0]); 12338 retEles.push(edge.target()[0]); 12339 } 12340 12341 return this.spawn(retEles, { 12342 unique: true 12343 }).filter(selector); 12344 }, 'connectedNodes'), 12345 parallelEdges: cache(defineParallelEdgesFunction(), 'parallelEdges'), 12346 codirectedEdges: cache(defineParallelEdgesFunction({ 12347 codirected: true 12348 }), 'codirectedEdges') 12349 }); 12350 12351 function defineParallelEdgesFunction(params) { 12352 var defaults = { 12353 codirected: false 12354 }; 12355 params = extend({}, defaults, params); 12356 return function parallelEdgesImpl(selector) { 12357 // micro-optimised for renderer 12358 var elements = []; 12359 var edges = this.edges(); 12360 var p = params; // look at all the edges in the collection 12361 12362 for (var i = 0; i < edges.length; i++) { 12363 var edge1 = edges[i]; 12364 var edge1_p = edge1._private; 12365 var src1 = edge1_p.source; 12366 var srcid1 = src1._private.data.id; 12367 var tgtid1 = edge1_p.data.target; 12368 var srcEdges1 = src1._private.edges; // look at edges connected to the src node of this edge 12369 12370 for (var j = 0; j < srcEdges1.length; j++) { 12371 var edge2 = srcEdges1[j]; 12372 var edge2data = edge2._private.data; 12373 var tgtid2 = edge2data.target; 12374 var srcid2 = edge2data.source; 12375 var codirected = tgtid2 === tgtid1 && srcid2 === srcid1; 12376 var oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2; 12377 12378 if (p.codirected && codirected || !p.codirected && (codirected || oppdirected)) { 12379 elements.push(edge2); 12380 } 12381 } 12382 } 12383 12384 return this.spawn(elements, { 12385 unique: true 12386 }).filter(selector); 12387 }; 12388 } // Misc functions 12389 ///////////////// 12390 12391 12392 extend(elesfn$t, { 12393 components: function components(root) { 12394 var self = this; 12395 var cy = self.cy(); 12396 var visited = cy.collection(); 12397 var unvisited = root == null ? self.nodes() : root.nodes(); 12398 var components = []; 12399 12400 if (root != null && unvisited.empty()) { 12401 // root may contain only edges 12402 unvisited = root.sources(); // doesn't matter which node to use (undirected), so just use the source sides 12403 } 12404 12405 var visitInComponent = function visitInComponent(node, component) { 12406 visited.merge(node); 12407 unvisited.unmerge(node); 12408 component.merge(node); 12409 }; 12410 12411 if (unvisited.empty()) { 12412 return self.spawn(); 12413 } 12414 12415 var _loop = function _loop() { 12416 // each iteration yields a component 12417 var cmpt = cy.collection(); 12418 components.push(cmpt); 12419 var root = unvisited[0]; 12420 visitInComponent(root, cmpt); 12421 self.bfs({ 12422 directed: false, 12423 roots: root, 12424 visit: function visit(v) { 12425 return visitInComponent(v, cmpt); 12426 } 12427 }); 12428 cmpt.forEach(function (node) { 12429 node.connectedEdges().forEach(function (e) { 12430 // connectedEdges() usually cached 12431 if (self.has(e) && cmpt.has(e.source()) && cmpt.has(e.target())) { 12432 // has() is cheap 12433 cmpt.merge(e); // forEach() only considers nodes -- sets N at call time 12434 } 12435 }); 12436 }); 12437 }; 12438 12439 do { 12440 _loop(); 12441 } while (unvisited.length > 0); 12442 12443 return components; 12444 }, 12445 component: function component() { 12446 var ele = this[0]; 12447 return ele.cy().mutableElements().components(ele)[0]; 12448 } 12449 }); 12450 elesfn$t.componentsOf = elesfn$t.components; 12451 12452 var idFactory = { 12453 generate: function generate(cy, element, tryThisId) { 12454 var id = tryThisId != null ? tryThisId : uuid(); 12455 12456 while (cy.hasElementWithId(id)) { 12457 id = uuid(); 12458 } 12459 12460 return id; 12461 } 12462 }; // represents a set of nodes, edges, or both together 12463 12464 var Collection = function Collection(cy, elements, options) { 12465 if (cy === undefined || !core(cy)) { 12466 error('A collection must have a reference to the core'); 12467 return; 12468 } 12469 12470 var map = new Map$1(); 12471 var createdElements = false; 12472 12473 if (!elements) { 12474 elements = []; 12475 } else if (elements.length > 0 && plainObject(elements[0]) && !element(elements[0])) { 12476 createdElements = true; // make elements from json and restore all at once later 12477 12478 var eles = []; 12479 var elesIds = new Set$1(); 12480 12481 for (var i = 0, l = elements.length; i < l; i++) { 12482 var json = elements[i]; 12483 12484 if (json.data == null) { 12485 json.data = {}; 12486 } 12487 12488 var _data = json.data; // make sure newly created elements have valid ids 12489 12490 if (_data.id == null) { 12491 _data.id = idFactory.generate(cy, json); 12492 } else if (cy.hasElementWithId(_data.id) || elesIds.has(_data.id)) { 12493 continue; // can't create element if prior id already exists 12494 } 12495 12496 var ele = new Element(cy, json, false); 12497 eles.push(ele); 12498 elesIds.add(_data.id); 12499 } 12500 12501 elements = eles; 12502 } 12503 12504 this.length = 0; 12505 12506 for (var _i = 0, _l = elements.length; _i < _l; _i++) { 12507 var element$1 = elements[_i][0]; // [0] in case elements is an array of collections, rather than array of elements 12508 12509 if (element$1 == null) { 12510 continue; 12511 } 12512 12513 var id = element$1._private.data.id; 12514 12515 if (options == null || options.unique && !map.has(id)) { 12516 map.set(id, { 12517 index: this.length, 12518 ele: element$1 12519 }); 12520 this[this.length] = element$1; 12521 this.length++; 12522 } 12523 } 12524 12525 this._private = { 12526 cy: cy, 12527 map: map 12528 }; // restore the elements if we created them from json 12529 12530 if (createdElements) { 12531 this.restore(); 12532 } 12533 }; // Functions 12534 //////////////////////////////////////////////////////////////////////////////////////////////////// 12535 // keep the prototypes in sync (an element has the same functions as a collection) 12536 // and use elefn and elesfn as shorthands to the prototypes 12537 12538 12539 var elesfn$u = Element.prototype = Collection.prototype; 12540 12541 elesfn$u.instanceString = function () { 12542 return 'collection'; 12543 }; 12544 12545 elesfn$u.spawn = function (cy, eles, opts) { 12546 if (!core(cy)) { 12547 // cy is optional 12548 opts = eles; 12549 eles = cy; 12550 cy = this.cy(); 12551 } 12552 12553 return new Collection(cy, eles, opts); 12554 }; 12555 12556 elesfn$u.spawnSelf = function () { 12557 return this.spawn(this); 12558 }; 12559 12560 elesfn$u.cy = function () { 12561 return this._private.cy; 12562 }; 12563 12564 elesfn$u.renderer = function () { 12565 return this._private.cy.renderer(); 12566 }; 12567 12568 elesfn$u.element = function () { 12569 return this[0]; 12570 }; 12571 12572 elesfn$u.collection = function () { 12573 if (collection(this)) { 12574 return this; 12575 } else { 12576 // an element 12577 return new Collection(this._private.cy, [this]); 12578 } 12579 }; 12580 12581 elesfn$u.unique = function () { 12582 return new Collection(this._private.cy, this, { 12583 unique: true 12584 }); 12585 }; 12586 12587 elesfn$u.hasElementWithId = function (id) { 12588 id = '' + id; // id must be string 12589 12590 return this._private.map.has(id); 12591 }; 12592 12593 elesfn$u.getElementById = function (id) { 12594 id = '' + id; // id must be string 12595 12596 var cy = this._private.cy; 12597 12598 var entry = this._private.map.get(id); 12599 12600 return entry ? entry.ele : new Collection(cy); // get ele or empty collection 12601 }; 12602 12603 elesfn$u.$id = elesfn$u.getElementById; 12604 12605 elesfn$u.poolIndex = function () { 12606 var cy = this._private.cy; 12607 var eles = cy._private.elements; 12608 var id = this[0]._private.data.id; 12609 return eles._private.map.get(id).index; 12610 }; 12611 12612 elesfn$u.indexOf = function (ele) { 12613 var id = ele[0]._private.data.id; 12614 return this._private.map.get(id).index; 12615 }; 12616 12617 elesfn$u.indexOfId = function (id) { 12618 id = '' + id; // id must be string 12619 12620 return this._private.map.get(id).index; 12621 }; 12622 12623 elesfn$u.json = function (obj) { 12624 var ele = this.element(); 12625 var cy = this.cy(); 12626 12627 if (ele == null && obj) { 12628 return this; 12629 } // can't set to no eles 12630 12631 12632 if (ele == null) { 12633 return undefined; 12634 } // can't get from no eles 12635 12636 12637 var p = ele._private; 12638 12639 if (plainObject(obj)) { 12640 // set 12641 cy.startBatch(); 12642 12643 if (obj.data) { 12644 ele.data(obj.data); 12645 var _data2 = p.data; 12646 12647 if (ele.isEdge()) { 12648 // source and target are immutable via data() 12649 var move = false; 12650 var spec = {}; 12651 var src = obj.data.source; 12652 var tgt = obj.data.target; 12653 12654 if (src != null && src != _data2.source) { 12655 spec.source = '' + src; // id must be string 12656 12657 move = true; 12658 } 12659 12660 if (tgt != null && tgt != _data2.target) { 12661 spec.target = '' + tgt; // id must be string 12662 12663 move = true; 12664 } 12665 12666 if (move) { 12667 ele = ele.move(spec); 12668 } 12669 } else { 12670 // parent is immutable via data() 12671 var newParentValSpecd = 'parent' in obj.data; 12672 var parent = obj.data.parent; 12673 12674 if (newParentValSpecd && (parent != null || _data2.parent != null) && parent != _data2.parent) { 12675 if (parent === undefined) { 12676 // can't set undefined imperatively, so use null 12677 parent = null; 12678 } 12679 12680 if (parent != null) { 12681 parent = '' + parent; // id must be string 12682 } 12683 12684 ele = ele.move({ 12685 parent: parent 12686 }); 12687 } 12688 } 12689 } 12690 12691 if (obj.position) { 12692 ele.position(obj.position); 12693 } // ignore group -- immutable 12694 12695 12696 var checkSwitch = function checkSwitch(k, trueFnName, falseFnName) { 12697 var obj_k = obj[k]; 12698 12699 if (obj_k != null && obj_k !== p[k]) { 12700 if (obj_k) { 12701 ele[trueFnName](); 12702 } else { 12703 ele[falseFnName](); 12704 } 12705 } 12706 }; 12707 12708 checkSwitch('removed', 'remove', 'restore'); 12709 checkSwitch('selected', 'select', 'unselect'); 12710 checkSwitch('selectable', 'selectify', 'unselectify'); 12711 checkSwitch('locked', 'lock', 'unlock'); 12712 checkSwitch('grabbable', 'grabify', 'ungrabify'); 12713 checkSwitch('pannable', 'panify', 'unpanify'); 12714 12715 if (obj.classes != null) { 12716 ele.classes(obj.classes); 12717 } 12718 12719 cy.endBatch(); 12720 return this; 12721 } else if (obj === undefined) { 12722 // get 12723 var json = { 12724 data: copy(p.data), 12725 position: copy(p.position), 12726 group: p.group, 12727 removed: p.removed, 12728 selected: p.selected, 12729 selectable: p.selectable, 12730 locked: p.locked, 12731 grabbable: p.grabbable, 12732 pannable: p.pannable, 12733 classes: null 12734 }; 12735 json.classes = ''; 12736 var i = 0; 12737 p.classes.forEach(function (cls) { 12738 return json.classes += i++ === 0 ? cls : ' ' + cls; 12739 }); 12740 return json; 12741 } 12742 }; 12743 12744 elesfn$u.jsons = function () { 12745 var jsons = []; 12746 12747 for (var i = 0; i < this.length; i++) { 12748 var ele = this[i]; 12749 var json = ele.json(); 12750 jsons.push(json); 12751 } 12752 12753 return jsons; 12754 }; 12755 12756 elesfn$u.clone = function () { 12757 var cy = this.cy(); 12758 var elesArr = []; 12759 12760 for (var i = 0; i < this.length; i++) { 12761 var ele = this[i]; 12762 var json = ele.json(); 12763 var clone = new Element(cy, json, false); // NB no restore 12764 12765 elesArr.push(clone); 12766 } 12767 12768 return new Collection(cy, elesArr); 12769 }; 12770 12771 elesfn$u.copy = elesfn$u.clone; 12772 12773 elesfn$u.restore = function () { 12774 var notifyRenderer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 12775 var addToPool = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 12776 var self = this; 12777 var cy = self.cy(); 12778 var cy_p = cy._private; // create arrays of nodes and edges, since we need to 12779 // restore the nodes first 12780 12781 var nodes = []; 12782 var edges = []; 12783 var elements; 12784 12785 for (var _i2 = 0, l = self.length; _i2 < l; _i2++) { 12786 var ele = self[_i2]; 12787 12788 if (addToPool && !ele.removed()) { 12789 // don't need to handle this ele 12790 continue; 12791 } // keep nodes first in the array and edges after 12792 12793 12794 if (ele.isNode()) { 12795 // put to front of array if node 12796 nodes.push(ele); 12797 } else { 12798 // put to end of array if edge 12799 edges.push(ele); 12800 } 12801 } 12802 12803 elements = nodes.concat(edges); 12804 var i; 12805 12806 var removeFromElements = function removeFromElements() { 12807 elements.splice(i, 1); 12808 i--; 12809 }; // now, restore each element 12810 12811 12812 for (i = 0; i < elements.length; i++) { 12813 var _ele = elements[i]; 12814 var _private = _ele._private; 12815 var _data3 = _private.data; // the traversal cache should start fresh when ele is added 12816 12817 _ele.clearTraversalCache(); // set id and validate 12818 12819 12820 if (!addToPool && !_private.removed) ; else if (_data3.id === undefined) { 12821 _data3.id = idFactory.generate(cy, _ele); 12822 } else if (number(_data3.id)) { 12823 _data3.id = '' + _data3.id; // now it's a string 12824 } else if (emptyString(_data3.id) || !string(_data3.id)) { 12825 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 12826 12827 removeFromElements(); 12828 continue; 12829 } else if (cy.hasElementWithId(_data3.id)) { 12830 error('Can not create second element with ID `' + _data3.id + '`'); // can't create element if one already has that id 12831 12832 removeFromElements(); 12833 continue; 12834 } 12835 12836 var id = _data3.id; // id is finalised, now let's keep a ref 12837 12838 if (_ele.isNode()) { 12839 // extra checks for nodes 12840 var pos = _private.position; // make sure the nodes have a defined position 12841 12842 if (pos.x == null) { 12843 pos.x = 0; 12844 } 12845 12846 if (pos.y == null) { 12847 pos.y = 0; 12848 } 12849 } 12850 12851 if (_ele.isEdge()) { 12852 // extra checks for edges 12853 var edge = _ele; 12854 var fields = ['source', 'target']; 12855 var fieldsLength = fields.length; 12856 var badSourceOrTarget = false; 12857 12858 for (var j = 0; j < fieldsLength; j++) { 12859 var field = fields[j]; 12860 var val = _data3[field]; 12861 12862 if (number(val)) { 12863 val = _data3[field] = '' + _data3[field]; // now string 12864 } 12865 12866 if (val == null || val === '') { 12867 // can't create if source or target is not defined properly 12868 error('Can not create edge `' + id + '` with unspecified ' + field); 12869 badSourceOrTarget = true; 12870 } else if (!cy.hasElementWithId(val)) { 12871 // can't create edge if one of its nodes doesn't exist 12872 error('Can not create edge `' + id + '` with nonexistant ' + field + ' `' + val + '`'); 12873 badSourceOrTarget = true; 12874 } 12875 } 12876 12877 if (badSourceOrTarget) { 12878 removeFromElements(); 12879 continue; 12880 } // can't create this 12881 12882 12883 var src = cy.getElementById(_data3.source); 12884 var tgt = cy.getElementById(_data3.target); // only one edge in node if loop 12885 12886 if (src.same(tgt)) { 12887 src._private.edges.push(edge); 12888 } else { 12889 src._private.edges.push(edge); 12890 12891 tgt._private.edges.push(edge); 12892 } 12893 12894 edge._private.source = src; 12895 edge._private.target = tgt; 12896 } // if is edge 12897 // create mock ids / indexes maps for element so it can be used like collections 12898 12899 12900 _private.map = new Map$1(); 12901 12902 _private.map.set(id, { 12903 ele: _ele, 12904 index: 0 12905 }); 12906 12907 _private.removed = false; 12908 12909 if (addToPool) { 12910 cy.addToPool(_ele); 12911 } 12912 } // for each element 12913 // do compound node sanity checks 12914 12915 12916 for (var _i3 = 0; _i3 < nodes.length; _i3++) { 12917 // each node 12918 var node = nodes[_i3]; 12919 var _data4 = node._private.data; 12920 12921 if (number(_data4.parent)) { 12922 // then automake string 12923 _data4.parent = '' + _data4.parent; 12924 } 12925 12926 var parentId = _data4.parent; 12927 var specifiedParent = parentId != null; 12928 12929 if (specifiedParent) { 12930 var parent = cy.getElementById(parentId); 12931 12932 if (parent.empty()) { 12933 // non-existant parent; just remove it 12934 _data4.parent = undefined; 12935 } else { 12936 var selfAsParent = false; 12937 var ancestor = parent; 12938 12939 while (!ancestor.empty()) { 12940 if (node.same(ancestor)) { 12941 // mark self as parent and remove from data 12942 selfAsParent = true; 12943 _data4.parent = undefined; // remove parent reference 12944 // exit or we loop forever 12945 12946 break; 12947 } 12948 12949 ancestor = ancestor.parent(); 12950 } 12951 12952 if (!selfAsParent) { 12953 // connect with children 12954 parent[0]._private.children.push(node); 12955 12956 node._private.parent = parent[0]; // let the core know we have a compound graph 12957 12958 cy_p.hasCompoundNodes = true; 12959 } 12960 } // else 12961 12962 } // if specified parent 12963 12964 } // for each node 12965 12966 12967 if (elements.length > 0) { 12968 var restored = new Collection(cy, elements); 12969 12970 for (var _i4 = 0; _i4 < restored.length; _i4++) { 12971 var _ele2 = restored[_i4]; 12972 12973 if (_ele2.isNode()) { 12974 continue; 12975 } // adding an edge invalidates the traversal caches for the parallel edges 12976 12977 12978 _ele2.parallelEdges().clearTraversalCache(); // adding an edge invalidates the traversal cache for the connected nodes 12979 12980 12981 _ele2.source().clearTraversalCache(); 12982 12983 _ele2.target().clearTraversalCache(); 12984 } 12985 12986 var toUpdateStyle; 12987 12988 if (cy_p.hasCompoundNodes) { 12989 toUpdateStyle = cy.collection().merge(restored).merge(restored.connectedNodes()).merge(restored.parent()); 12990 } else { 12991 toUpdateStyle = restored; 12992 } 12993 12994 toUpdateStyle.dirtyCompoundBoundsCache().dirtyBoundingBoxCache().updateStyle(notifyRenderer); 12995 12996 if (notifyRenderer) { 12997 restored.emitAndNotify('add'); 12998 } else if (addToPool) { 12999 restored.emit('add'); 13000 } 13001 } 13002 13003 return self; // chainability 13004 }; 13005 13006 elesfn$u.removed = function () { 13007 var ele = this[0]; 13008 return ele && ele._private.removed; 13009 }; 13010 13011 elesfn$u.inside = function () { 13012 var ele = this[0]; 13013 return ele && !ele._private.removed; 13014 }; 13015 13016 elesfn$u.remove = function () { 13017 var notifyRenderer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 13018 var removeFromPool = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 13019 var self = this; 13020 var elesToRemove = []; 13021 var elesToRemoveIds = {}; 13022 var cy = self._private.cy; // add connected edges 13023 13024 function addConnectedEdges(node) { 13025 var edges = node._private.edges; 13026 13027 for (var i = 0; i < edges.length; i++) { 13028 add(edges[i]); 13029 } 13030 } // add descendant nodes 13031 13032 13033 function addChildren(node) { 13034 var children = node._private.children; 13035 13036 for (var i = 0; i < children.length; i++) { 13037 add(children[i]); 13038 } 13039 } 13040 13041 function add(ele) { 13042 var alreadyAdded = elesToRemoveIds[ele.id()]; 13043 13044 if (removeFromPool && ele.removed() || alreadyAdded) { 13045 return; 13046 } else { 13047 elesToRemoveIds[ele.id()] = true; 13048 } 13049 13050 if (ele.isNode()) { 13051 elesToRemove.push(ele); // nodes are removed last 13052 13053 addConnectedEdges(ele); 13054 addChildren(ele); 13055 } else { 13056 elesToRemove.unshift(ele); // edges are removed first 13057 } 13058 } // make the list of elements to remove 13059 // (may be removing more than specified due to connected edges etc) 13060 13061 13062 for (var i = 0, l = self.length; i < l; i++) { 13063 var ele = self[i]; 13064 add(ele); 13065 } 13066 13067 function removeEdgeRef(node, edge) { 13068 var connectedEdges = node._private.edges; 13069 removeFromArray(connectedEdges, edge); // removing an edges invalidates the traversal cache for its nodes 13070 13071 node.clearTraversalCache(); 13072 } 13073 13074 function removeParallelRef(pllEdge) { 13075 // removing an edge invalidates the traversal caches for the parallel edges 13076 pllEdge.clearTraversalCache(); 13077 } 13078 13079 var alteredParents = []; 13080 alteredParents.ids = {}; 13081 13082 function removeChildRef(parent, ele) { 13083 ele = ele[0]; 13084 parent = parent[0]; 13085 var children = parent._private.children; 13086 var pid = parent.id(); 13087 removeFromArray(children, ele); // remove parent => child ref 13088 13089 ele._private.parent = null; // remove child => parent ref 13090 13091 if (!alteredParents.ids[pid]) { 13092 alteredParents.ids[pid] = true; 13093 alteredParents.push(parent); 13094 } 13095 } 13096 13097 self.dirtyCompoundBoundsCache(); 13098 13099 if (removeFromPool) { 13100 cy.removeFromPool(elesToRemove); // remove from core pool 13101 } 13102 13103 for (var _i5 = 0; _i5 < elesToRemove.length; _i5++) { 13104 var _ele3 = elesToRemove[_i5]; 13105 13106 if (_ele3.isEdge()) { 13107 // remove references to this edge in its connected nodes 13108 var src = _ele3.source()[0]; 13109 13110 var tgt = _ele3.target()[0]; 13111 13112 removeEdgeRef(src, _ele3); 13113 removeEdgeRef(tgt, _ele3); 13114 13115 var pllEdges = _ele3.parallelEdges(); 13116 13117 for (var j = 0; j < pllEdges.length; j++) { 13118 var pllEdge = pllEdges[j]; 13119 removeParallelRef(pllEdge); 13120 13121 if (pllEdge.isBundledBezier()) { 13122 pllEdge.dirtyBoundingBoxCache(); 13123 } 13124 } 13125 } else { 13126 // remove reference to parent 13127 var parent = _ele3.parent(); 13128 13129 if (parent.length !== 0) { 13130 removeChildRef(parent, _ele3); 13131 } 13132 } 13133 13134 if (removeFromPool) { 13135 // mark as removed 13136 _ele3._private.removed = true; 13137 } 13138 } // check to see if we have a compound graph or not 13139 13140 13141 var elesStillInside = cy._private.elements; 13142 cy._private.hasCompoundNodes = false; 13143 13144 for (var _i6 = 0; _i6 < elesStillInside.length; _i6++) { 13145 var _ele4 = elesStillInside[_i6]; 13146 13147 if (_ele4.isParent()) { 13148 cy._private.hasCompoundNodes = true; 13149 break; 13150 } 13151 } 13152 13153 var removedElements = new Collection(this.cy(), elesToRemove); 13154 13155 if (removedElements.size() > 0) { 13156 // must manually notify since trigger won't do this automatically once removed 13157 if (notifyRenderer) { 13158 removedElements.emitAndNotify('remove'); 13159 } else if (removeFromPool) { 13160 removedElements.emit('remove'); 13161 } 13162 } // the parents who were modified by the removal need their style updated 13163 13164 13165 for (var _i7 = 0; _i7 < alteredParents.length; _i7++) { 13166 var _ele5 = alteredParents[_i7]; 13167 13168 if (!removeFromPool || !_ele5.removed()) { 13169 _ele5.updateStyle(); 13170 } 13171 } 13172 13173 return removedElements; 13174 }; 13175 13176 elesfn$u.move = function (struct) { 13177 var cy = this._private.cy; 13178 var eles = this; // just clean up refs, caches, etc. in the same way as when removing and then restoring 13179 // (our calls to remove/restore do not remove from the graph or make events) 13180 13181 var notifyRenderer = false; 13182 var modifyPool = false; 13183 13184 var toString = function toString(id) { 13185 return id == null ? id : '' + id; 13186 }; // id must be string 13187 13188 13189 if (struct.source !== undefined || struct.target !== undefined) { 13190 var srcId = toString(struct.source); 13191 var tgtId = toString(struct.target); 13192 var srcExists = srcId != null && cy.hasElementWithId(srcId); 13193 var tgtExists = tgtId != null && cy.hasElementWithId(tgtId); 13194 13195 if (srcExists || tgtExists) { 13196 cy.batch(function () { 13197 // avoid duplicate style updates 13198 eles.remove(notifyRenderer, modifyPool); // clean up refs etc. 13199 13200 eles.emitAndNotify('moveout'); 13201 13202 for (var i = 0; i < eles.length; i++) { 13203 var ele = eles[i]; 13204 var _data5 = ele._private.data; 13205 13206 if (ele.isEdge()) { 13207 if (srcExists) { 13208 _data5.source = srcId; 13209 } 13210 13211 if (tgtExists) { 13212 _data5.target = tgtId; 13213 } 13214 } 13215 } 13216 13217 eles.restore(notifyRenderer, modifyPool); // make new refs, style, etc. 13218 }); 13219 eles.emitAndNotify('move'); 13220 } 13221 } else if (struct.parent !== undefined) { 13222 // move node to new parent 13223 var parentId = toString(struct.parent); 13224 var parentExists = parentId === null || cy.hasElementWithId(parentId); 13225 13226 if (parentExists) { 13227 var pidToAssign = parentId === null ? undefined : parentId; 13228 cy.batch(function () { 13229 // avoid duplicate style updates 13230 var updated = eles.remove(notifyRenderer, modifyPool); // clean up refs etc. 13231 13232 updated.emitAndNotify('moveout'); 13233 13234 for (var i = 0; i < eles.length; i++) { 13235 var ele = eles[i]; 13236 var _data6 = ele._private.data; 13237 13238 if (ele.isNode()) { 13239 _data6.parent = pidToAssign; 13240 } 13241 } 13242 13243 updated.restore(notifyRenderer, modifyPool); // make new refs, style, etc. 13244 }); 13245 eles.emitAndNotify('move'); 13246 } 13247 } 13248 13249 return this; 13250 }; 13251 13252 [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) { 13253 extend(elesfn$u, props); 13254 }); 13255 13256 var corefn = { 13257 add: function add(opts) { 13258 var elements; 13259 var cy = this; // add the elements 13260 13261 if (elementOrCollection(opts)) { 13262 var eles = opts; 13263 13264 if (eles._private.cy === cy) { 13265 // same instance => just restore 13266 elements = eles.restore(); 13267 } else { 13268 // otherwise, copy from json 13269 var jsons = []; 13270 13271 for (var i = 0; i < eles.length; i++) { 13272 var ele = eles[i]; 13273 jsons.push(ele.json()); 13274 } 13275 13276 elements = new Collection(cy, jsons); 13277 } 13278 } // specify an array of options 13279 else if (array(opts)) { 13280 var _jsons = opts; 13281 elements = new Collection(cy, _jsons); 13282 } // specify via opts.nodes and opts.edges 13283 else if (plainObject(opts) && (array(opts.nodes) || array(opts.edges))) { 13284 var elesByGroup = opts; 13285 var _jsons2 = []; 13286 var grs = ['nodes', 'edges']; 13287 13288 for (var _i = 0, il = grs.length; _i < il; _i++) { 13289 var group = grs[_i]; 13290 var elesArray = elesByGroup[group]; 13291 13292 if (array(elesArray)) { 13293 for (var j = 0, jl = elesArray.length; j < jl; j++) { 13294 var json = extend({ 13295 group: group 13296 }, elesArray[j]); 13297 13298 _jsons2.push(json); 13299 } 13300 } 13301 } 13302 13303 elements = new Collection(cy, _jsons2); 13304 } // specify options for one element 13305 else { 13306 var _json = opts; 13307 elements = new Element(cy, _json).collection(); 13308 } 13309 13310 return elements; 13311 }, 13312 remove: function remove(collection) { 13313 if (elementOrCollection(collection)) ; else if (string(collection)) { 13314 var selector = collection; 13315 collection = this.$(selector); 13316 } 13317 13318 return collection.remove(); 13319 } 13320 }; 13321 13322 /* global Float32Array */ 13323 13324 /*! Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 13325 function generateCubicBezier(mX1, mY1, mX2, mY2) { 13326 var NEWTON_ITERATIONS = 4, 13327 NEWTON_MIN_SLOPE = 0.001, 13328 SUBDIVISION_PRECISION = 0.0000001, 13329 SUBDIVISION_MAX_ITERATIONS = 10, 13330 kSplineTableSize = 11, 13331 kSampleStepSize = 1.0 / (kSplineTableSize - 1.0), 13332 float32ArraySupported = typeof Float32Array !== 'undefined'; 13333 /* Must contain four arguments. */ 13334 13335 if (arguments.length !== 4) { 13336 return false; 13337 } 13338 /* Arguments must be numbers. */ 13339 13340 13341 for (var i = 0; i < 4; ++i) { 13342 if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) { 13343 return false; 13344 } 13345 } 13346 /* X values must be in the [0, 1] range. */ 13347 13348 13349 mX1 = Math.min(mX1, 1); 13350 mX2 = Math.min(mX2, 1); 13351 mX1 = Math.max(mX1, 0); 13352 mX2 = Math.max(mX2, 0); 13353 var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); 13354 13355 function A(aA1, aA2) { 13356 return 1.0 - 3.0 * aA2 + 3.0 * aA1; 13357 } 13358 13359 function B(aA1, aA2) { 13360 return 3.0 * aA2 - 6.0 * aA1; 13361 } 13362 13363 function C(aA1) { 13364 return 3.0 * aA1; 13365 } 13366 13367 function calcBezier(aT, aA1, aA2) { 13368 return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; 13369 } 13370 13371 function getSlope(aT, aA1, aA2) { 13372 return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); 13373 } 13374 13375 function newtonRaphsonIterate(aX, aGuessT) { 13376 for (var _i = 0; _i < NEWTON_ITERATIONS; ++_i) { 13377 var currentSlope = getSlope(aGuessT, mX1, mX2); 13378 13379 if (currentSlope === 0.0) { 13380 return aGuessT; 13381 } 13382 13383 var currentX = calcBezier(aGuessT, mX1, mX2) - aX; 13384 aGuessT -= currentX / currentSlope; 13385 } 13386 13387 return aGuessT; 13388 } 13389 13390 function calcSampleValues() { 13391 for (var _i2 = 0; _i2 < kSplineTableSize; ++_i2) { 13392 mSampleValues[_i2] = calcBezier(_i2 * kSampleStepSize, mX1, mX2); 13393 } 13394 } 13395 13396 function binarySubdivide(aX, aA, aB) { 13397 var currentX, 13398 currentT, 13399 i = 0; 13400 13401 do { 13402 currentT = aA + (aB - aA) / 2.0; 13403 currentX = calcBezier(currentT, mX1, mX2) - aX; 13404 13405 if (currentX > 0.0) { 13406 aB = currentT; 13407 } else { 13408 aA = currentT; 13409 } 13410 } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); 13411 13412 return currentT; 13413 } 13414 13415 function getTForX(aX) { 13416 var intervalStart = 0.0, 13417 currentSample = 1, 13418 lastSample = kSplineTableSize - 1; 13419 13420 for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { 13421 intervalStart += kSampleStepSize; 13422 } 13423 13424 --currentSample; 13425 var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]), 13426 guessForT = intervalStart + dist * kSampleStepSize, 13427 initialSlope = getSlope(guessForT, mX1, mX2); 13428 13429 if (initialSlope >= NEWTON_MIN_SLOPE) { 13430 return newtonRaphsonIterate(aX, guessForT); 13431 } else if (initialSlope === 0.0) { 13432 return guessForT; 13433 } else { 13434 return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize); 13435 } 13436 } 13437 13438 var _precomputed = false; 13439 13440 function precompute() { 13441 _precomputed = true; 13442 13443 if (mX1 !== mY1 || mX2 !== mY2) { 13444 calcSampleValues(); 13445 } 13446 } 13447 13448 var f = function f(aX) { 13449 if (!_precomputed) { 13450 precompute(); 13451 } 13452 13453 if (mX1 === mY1 && mX2 === mY2) { 13454 return aX; 13455 } 13456 13457 if (aX === 0) { 13458 return 0; 13459 } 13460 13461 if (aX === 1) { 13462 return 1; 13463 } 13464 13465 return calcBezier(getTForX(aX), mY1, mY2); 13466 }; 13467 13468 f.getControlPoints = function () { 13469 return [{ 13470 x: mX1, 13471 y: mY1 13472 }, { 13473 x: mX2, 13474 y: mY2 13475 }]; 13476 }; 13477 13478 var str = "generateBezier(" + [mX1, mY1, mX2, mY2] + ")"; 13479 13480 f.toString = function () { 13481 return str; 13482 }; 13483 13484 return f; 13485 } 13486 13487 /*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 13488 13489 /* 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 13490 then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */ 13491 var generateSpringRK4 = function () { 13492 function springAccelerationForState(state) { 13493 return -state.tension * state.x - state.friction * state.v; 13494 } 13495 13496 function springEvaluateStateWithDerivative(initialState, dt, derivative) { 13497 var state = { 13498 x: initialState.x + derivative.dx * dt, 13499 v: initialState.v + derivative.dv * dt, 13500 tension: initialState.tension, 13501 friction: initialState.friction 13502 }; 13503 return { 13504 dx: state.v, 13505 dv: springAccelerationForState(state) 13506 }; 13507 } 13508 13509 function springIntegrateState(state, dt) { 13510 var a = { 13511 dx: state.v, 13512 dv: springAccelerationForState(state) 13513 }, 13514 b = springEvaluateStateWithDerivative(state, dt * 0.5, a), 13515 c = springEvaluateStateWithDerivative(state, dt * 0.5, b), 13516 d = springEvaluateStateWithDerivative(state, dt, c), 13517 dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx), 13518 dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv); 13519 state.x = state.x + dxdt * dt; 13520 state.v = state.v + dvdt * dt; 13521 return state; 13522 } 13523 13524 return function springRK4Factory(tension, friction, duration) { 13525 var initState = { 13526 x: -1, 13527 v: 0, 13528 tension: null, 13529 friction: null 13530 }, 13531 path = [0], 13532 time_lapsed = 0, 13533 tolerance = 1 / 10000, 13534 DT = 16 / 1000, 13535 have_duration, 13536 dt, 13537 last_state; 13538 tension = parseFloat(tension) || 500; 13539 friction = parseFloat(friction) || 20; 13540 duration = duration || null; 13541 initState.tension = tension; 13542 initState.friction = friction; 13543 have_duration = duration !== null; 13544 /* Calculate the actual time it takes for this animation to complete with the provided conditions. */ 13545 13546 if (have_duration) { 13547 /* Run the simulation without a duration. */ 13548 time_lapsed = springRK4Factory(tension, friction); 13549 /* Compute the adjusted time delta. */ 13550 13551 dt = time_lapsed / duration * DT; 13552 } else { 13553 dt = DT; 13554 } 13555 13556 for (;;) { 13557 /* Next/step function .*/ 13558 last_state = springIntegrateState(last_state || initState, dt); 13559 /* Store the position. */ 13560 13561 path.push(1 + last_state.x); 13562 time_lapsed += 16; 13563 /* If the change threshold is reached, break. */ 13564 13565 if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) { 13566 break; 13567 } 13568 } 13569 /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the 13570 computed path and returns a snapshot of the position according to a given percentComplete. */ 13571 13572 13573 return !have_duration ? time_lapsed : function (percentComplete) { 13574 return path[percentComplete * (path.length - 1) | 0]; 13575 }; 13576 }; 13577 }(); 13578 13579 var cubicBezier = function cubicBezier(t1, p1, t2, p2) { 13580 var bezier = generateCubicBezier(t1, p1, t2, p2); 13581 return function (start, end, percent) { 13582 return start + (end - start) * bezier(percent); 13583 }; 13584 }; 13585 13586 var easings = { 13587 'linear': function linear(start, end, percent) { 13588 return start + (end - start) * percent; 13589 }, 13590 // default easings 13591 'ease': cubicBezier(0.25, 0.1, 0.25, 1), 13592 'ease-in': cubicBezier(0.42, 0, 1, 1), 13593 'ease-out': cubicBezier(0, 0, 0.58, 1), 13594 'ease-in-out': cubicBezier(0.42, 0, 0.58, 1), 13595 // sine 13596 'ease-in-sine': cubicBezier(0.47, 0, 0.745, 0.715), 13597 'ease-out-sine': cubicBezier(0.39, 0.575, 0.565, 1), 13598 'ease-in-out-sine': cubicBezier(0.445, 0.05, 0.55, 0.95), 13599 // quad 13600 'ease-in-quad': cubicBezier(0.55, 0.085, 0.68, 0.53), 13601 'ease-out-quad': cubicBezier(0.25, 0.46, 0.45, 0.94), 13602 'ease-in-out-quad': cubicBezier(0.455, 0.03, 0.515, 0.955), 13603 // cubic 13604 'ease-in-cubic': cubicBezier(0.55, 0.055, 0.675, 0.19), 13605 'ease-out-cubic': cubicBezier(0.215, 0.61, 0.355, 1), 13606 'ease-in-out-cubic': cubicBezier(0.645, 0.045, 0.355, 1), 13607 // quart 13608 'ease-in-quart': cubicBezier(0.895, 0.03, 0.685, 0.22), 13609 'ease-out-quart': cubicBezier(0.165, 0.84, 0.44, 1), 13610 'ease-in-out-quart': cubicBezier(0.77, 0, 0.175, 1), 13611 // quint 13612 'ease-in-quint': cubicBezier(0.755, 0.05, 0.855, 0.06), 13613 'ease-out-quint': cubicBezier(0.23, 1, 0.32, 1), 13614 'ease-in-out-quint': cubicBezier(0.86, 0, 0.07, 1), 13615 // expo 13616 'ease-in-expo': cubicBezier(0.95, 0.05, 0.795, 0.035), 13617 'ease-out-expo': cubicBezier(0.19, 1, 0.22, 1), 13618 'ease-in-out-expo': cubicBezier(1, 0, 0, 1), 13619 // circ 13620 'ease-in-circ': cubicBezier(0.6, 0.04, 0.98, 0.335), 13621 'ease-out-circ': cubicBezier(0.075, 0.82, 0.165, 1), 13622 'ease-in-out-circ': cubicBezier(0.785, 0.135, 0.15, 0.86), 13623 // user param easings... 13624 'spring': function spring(tension, friction, duration) { 13625 if (duration === 0) { 13626 // can't get a spring w/ duration 0 13627 return easings.linear; // duration 0 => jump to end so impl doesn't matter 13628 } 13629 13630 var spring = generateSpringRK4(tension, friction, duration); 13631 return function (start, end, percent) { 13632 return start + (end - start) * spring(percent); 13633 }; 13634 }, 13635 'cubic-bezier': cubicBezier 13636 }; 13637 13638 function getEasedValue(type, start, end, percent, easingFn) { 13639 if (percent === 1) { 13640 return end; 13641 } 13642 13643 if (start === end) { 13644 return end; 13645 } 13646 13647 var val = easingFn(start, end, percent); 13648 13649 if (type == null) { 13650 return val; 13651 } 13652 13653 if (type.roundValue || type.color) { 13654 val = Math.round(val); 13655 } 13656 13657 if (type.min !== undefined) { 13658 val = Math.max(val, type.min); 13659 } 13660 13661 if (type.max !== undefined) { 13662 val = Math.min(val, type.max); 13663 } 13664 13665 return val; 13666 } 13667 13668 function getValue(prop, spec) { 13669 if (prop.pfValue != null || prop.value != null) { 13670 if (prop.pfValue != null && (spec == null || spec.type.units !== '%')) { 13671 return prop.pfValue; 13672 } else { 13673 return prop.value; 13674 } 13675 } else { 13676 return prop; 13677 } 13678 } 13679 13680 function ease(startProp, endProp, percent, easingFn, propSpec) { 13681 var type = propSpec != null ? propSpec.type : null; 13682 13683 if (percent < 0) { 13684 percent = 0; 13685 } else if (percent > 1) { 13686 percent = 1; 13687 } 13688 13689 var start = getValue(startProp, propSpec); 13690 var end = getValue(endProp, propSpec); 13691 13692 if (number(start) && number(end)) { 13693 return getEasedValue(type, start, end, percent, easingFn); 13694 } else if (array(start) && array(end)) { 13695 var easedArr = []; 13696 13697 for (var i = 0; i < end.length; i++) { 13698 var si = start[i]; 13699 var ei = end[i]; 13700 13701 if (si != null && ei != null) { 13702 var val = getEasedValue(type, si, ei, percent, easingFn); 13703 easedArr.push(val); 13704 } else { 13705 easedArr.push(ei); 13706 } 13707 } 13708 13709 return easedArr; 13710 } 13711 13712 return undefined; 13713 } 13714 13715 function step(self, ani, now, isCore) { 13716 var isEles = !isCore; 13717 var _p = self._private; 13718 var ani_p = ani._private; 13719 var pEasing = ani_p.easing; 13720 var startTime = ani_p.startTime; 13721 var cy = isCore ? self : self.cy(); 13722 var style = cy.style(); 13723 13724 if (!ani_p.easingImpl) { 13725 if (pEasing == null) { 13726 // use default 13727 ani_p.easingImpl = easings['linear']; 13728 } else { 13729 // then define w/ name 13730 var easingVals; 13731 13732 if (string(pEasing)) { 13733 var easingProp = style.parse('transition-timing-function', pEasing); 13734 easingVals = easingProp.value; 13735 } else { 13736 // then assume preparsed array 13737 easingVals = pEasing; 13738 } 13739 13740 var name, args; 13741 13742 if (string(easingVals)) { 13743 name = easingVals; 13744 args = []; 13745 } else { 13746 name = easingVals[1]; 13747 args = easingVals.slice(2).map(function (n) { 13748 return +n; 13749 }); 13750 } 13751 13752 if (args.length > 0) { 13753 // create with args 13754 if (name === 'spring') { 13755 args.push(ani_p.duration); // need duration to generate spring 13756 } 13757 13758 ani_p.easingImpl = easings[name].apply(null, args); 13759 } else { 13760 // static impl by name 13761 ani_p.easingImpl = easings[name]; 13762 } 13763 } 13764 } 13765 13766 var easing = ani_p.easingImpl; 13767 var percent; 13768 13769 if (ani_p.duration === 0) { 13770 percent = 1; 13771 } else { 13772 percent = (now - startTime) / ani_p.duration; 13773 } 13774 13775 if (ani_p.applying) { 13776 percent = ani_p.progress; 13777 } 13778 13779 if (percent < 0) { 13780 percent = 0; 13781 } else if (percent > 1) { 13782 percent = 1; 13783 } 13784 13785 if (ani_p.delay == null) { 13786 // then update 13787 var startPos = ani_p.startPosition; 13788 var endPos = ani_p.position; 13789 13790 if (endPos && isEles && !self.locked()) { 13791 var newPos = {}; 13792 13793 if (valid(startPos.x, endPos.x)) { 13794 newPos.x = ease(startPos.x, endPos.x, percent, easing); 13795 } 13796 13797 if (valid(startPos.y, endPos.y)) { 13798 newPos.y = ease(startPos.y, endPos.y, percent, easing); 13799 } 13800 13801 self.position(newPos); 13802 } 13803 13804 var startPan = ani_p.startPan; 13805 var endPan = ani_p.pan; 13806 var pan = _p.pan; 13807 var animatingPan = endPan != null && isCore; 13808 13809 if (animatingPan) { 13810 if (valid(startPan.x, endPan.x)) { 13811 pan.x = ease(startPan.x, endPan.x, percent, easing); 13812 } 13813 13814 if (valid(startPan.y, endPan.y)) { 13815 pan.y = ease(startPan.y, endPan.y, percent, easing); 13816 } 13817 13818 self.emit('pan'); 13819 } 13820 13821 var startZoom = ani_p.startZoom; 13822 var endZoom = ani_p.zoom; 13823 var animatingZoom = endZoom != null && isCore; 13824 13825 if (animatingZoom) { 13826 if (valid(startZoom, endZoom)) { 13827 _p.zoom = bound(_p.minZoom, ease(startZoom, endZoom, percent, easing), _p.maxZoom); 13828 } 13829 13830 self.emit('zoom'); 13831 } 13832 13833 if (animatingPan || animatingZoom) { 13834 self.emit('viewport'); 13835 } 13836 13837 var props = ani_p.style; 13838 13839 if (props && props.length > 0 && isEles) { 13840 for (var i = 0; i < props.length; i++) { 13841 var prop = props[i]; 13842 var _name = prop.name; 13843 var end = prop; 13844 var start = ani_p.startStyle[_name]; 13845 var propSpec = style.properties[start.name]; 13846 var easedVal = ease(start, end, percent, easing, propSpec); 13847 style.overrideBypass(self, _name, easedVal); 13848 } // for props 13849 13850 13851 self.emit('style'); 13852 } // if 13853 13854 } 13855 13856 ani_p.progress = percent; 13857 return percent; 13858 } 13859 13860 function valid(start, end) { 13861 if (start == null || end == null) { 13862 return false; 13863 } 13864 13865 if (number(start) && number(end)) { 13866 return true; 13867 } else if (start && end) { 13868 return true; 13869 } 13870 13871 return false; 13872 } 13873 13874 function startAnimation(self, ani, now, isCore) { 13875 var ani_p = ani._private; 13876 ani_p.started = true; 13877 ani_p.startTime = now - ani_p.progress * ani_p.duration; 13878 } 13879 13880 function stepAll(now, cy) { 13881 var eles = cy._private.aniEles; 13882 var doneEles = []; 13883 13884 function stepOne(ele, isCore) { 13885 var _p = ele._private; 13886 var current = _p.animation.current; 13887 var queue = _p.animation.queue; 13888 var ranAnis = false; // cancel all animations on display:none ele 13889 13890 if (!isCore && ele.pstyle('display').value === 'none') { 13891 // put all current and queue animations in this tick's current list 13892 // and empty the lists for the element 13893 current = current.splice(0, current.length).concat(queue.splice(0, queue.length)); // stop all animations 13894 13895 for (var i = 0; i < current.length; i++) { 13896 current[i].stop(); 13897 } 13898 } // if nothing currently animating, get something from the queue 13899 13900 13901 if (current.length === 0) { 13902 var next = queue.shift(); 13903 13904 if (next) { 13905 current.push(next); 13906 } 13907 } 13908 13909 var callbacks = function callbacks(_callbacks) { 13910 for (var j = _callbacks.length - 1; j >= 0; j--) { 13911 var cb = _callbacks[j]; 13912 cb(); 13913 } 13914 13915 _callbacks.splice(0, _callbacks.length); 13916 }; // step and remove if done 13917 13918 13919 for (var _i = current.length - 1; _i >= 0; _i--) { 13920 var ani = current[_i]; 13921 var ani_p = ani._private; 13922 13923 if (ani_p.stopped) { 13924 current.splice(_i, 1); 13925 ani_p.hooked = false; 13926 ani_p.playing = false; 13927 ani_p.started = false; 13928 callbacks(ani_p.frames); 13929 continue; 13930 } 13931 13932 if (!ani_p.playing && !ani_p.applying) { 13933 continue; 13934 } // an apply() while playing shouldn't do anything 13935 13936 13937 if (ani_p.playing && ani_p.applying) { 13938 ani_p.applying = false; 13939 } 13940 13941 if (!ani_p.started) { 13942 startAnimation(ele, ani, now); 13943 } 13944 13945 step(ele, ani, now, isCore); 13946 13947 if (ani_p.applying) { 13948 ani_p.applying = false; 13949 } 13950 13951 callbacks(ani_p.frames); 13952 13953 if (ani_p.step != null) { 13954 ani_p.step(now); 13955 } 13956 13957 if (ani.completed()) { 13958 current.splice(_i, 1); 13959 ani_p.hooked = false; 13960 ani_p.playing = false; 13961 ani_p.started = false; 13962 callbacks(ani_p.completes); 13963 } 13964 13965 ranAnis = true; 13966 } 13967 13968 if (!isCore && current.length === 0 && queue.length === 0) { 13969 doneEles.push(ele); 13970 } 13971 13972 return ranAnis; 13973 } // stepElement 13974 // handle all eles 13975 13976 13977 var ranEleAni = false; 13978 13979 for (var e = 0; e < eles.length; e++) { 13980 var ele = eles[e]; 13981 var handledThisEle = stepOne(ele); 13982 ranEleAni = ranEleAni || handledThisEle; 13983 } // each element 13984 13985 13986 var ranCoreAni = stepOne(cy, true); // notify renderer 13987 13988 if (ranEleAni || ranCoreAni) { 13989 if (eles.length > 0) { 13990 cy.notify('draw', eles); 13991 } else { 13992 cy.notify('draw'); 13993 } 13994 } // remove elements from list of currently animating if its queues are empty 13995 13996 13997 eles.unmerge(doneEles); 13998 cy.emit('step'); 13999 } // stepAll 14000 14001 var corefn$1 = { 14002 // pull in animation functions 14003 animate: define$3.animate(), 14004 animation: define$3.animation(), 14005 animated: define$3.animated(), 14006 clearQueue: define$3.clearQueue(), 14007 delay: define$3.delay(), 14008 delayAnimation: define$3.delayAnimation(), 14009 stop: define$3.stop(), 14010 addToAnimationPool: function addToAnimationPool(eles) { 14011 var cy = this; 14012 14013 if (!cy.styleEnabled()) { 14014 return; 14015 } // save cycles when no style used 14016 14017 14018 cy._private.aniEles.merge(eles); 14019 }, 14020 stopAnimationLoop: function stopAnimationLoop() { 14021 this._private.animationsRunning = false; 14022 }, 14023 startAnimationLoop: function startAnimationLoop() { 14024 var cy = this; 14025 cy._private.animationsRunning = true; 14026 14027 if (!cy.styleEnabled()) { 14028 return; 14029 } // save cycles when no style used 14030 // NB the animation loop will exec in headless environments if style enabled 14031 // and explicit cy.destroy() is necessary to stop the loop 14032 14033 14034 function headlessStep() { 14035 if (!cy._private.animationsRunning) { 14036 return; 14037 } 14038 14039 requestAnimationFrame(function animationStep(now) { 14040 stepAll(now, cy); 14041 headlessStep(); 14042 }); 14043 } 14044 14045 var renderer = cy.renderer(); 14046 14047 if (renderer && renderer.beforeRender) { 14048 // let the renderer schedule animations 14049 renderer.beforeRender(function rendererAnimationStep(willDraw, now) { 14050 stepAll(now, cy); 14051 }, renderer.beforeRenderPriorities.animations); 14052 } else { 14053 // manage the animation loop ourselves 14054 headlessStep(); // first call 14055 } 14056 } 14057 }; 14058 14059 var emitterOptions$1 = { 14060 qualifierCompare: function qualifierCompare(selector1, selector2) { 14061 if (selector1 == null || selector2 == null) { 14062 return selector1 == null && selector2 == null; 14063 } else { 14064 return selector1.sameText(selector2); 14065 } 14066 }, 14067 eventMatches: function eventMatches(cy, listener, eventObj) { 14068 var selector = listener.qualifier; 14069 14070 if (selector != null) { 14071 return cy !== eventObj.target && element(eventObj.target) && selector.matches(eventObj.target); 14072 } 14073 14074 return true; 14075 }, 14076 addEventFields: function addEventFields(cy, evt) { 14077 evt.cy = cy; 14078 evt.target = cy; 14079 }, 14080 callbackContext: function callbackContext(cy, listener, eventObj) { 14081 return listener.qualifier != null ? eventObj.target : cy; 14082 } 14083 }; 14084 14085 var argSelector$1 = function argSelector(arg) { 14086 if (string(arg)) { 14087 return new Selector(arg); 14088 } else { 14089 return arg; 14090 } 14091 }; 14092 14093 var elesfn$v = { 14094 createEmitter: function createEmitter() { 14095 var _p = this._private; 14096 14097 if (!_p.emitter) { 14098 _p.emitter = new Emitter(emitterOptions$1, this); 14099 } 14100 14101 return this; 14102 }, 14103 emitter: function emitter() { 14104 return this._private.emitter; 14105 }, 14106 on: function on(events, selector, callback) { 14107 this.emitter().on(events, argSelector$1(selector), callback); 14108 return this; 14109 }, 14110 removeListener: function removeListener(events, selector, callback) { 14111 this.emitter().removeListener(events, argSelector$1(selector), callback); 14112 return this; 14113 }, 14114 removeAllListeners: function removeAllListeners() { 14115 this.emitter().removeAllListeners(); 14116 return this; 14117 }, 14118 one: function one(events, selector, callback) { 14119 this.emitter().one(events, argSelector$1(selector), callback); 14120 return this; 14121 }, 14122 once: function once(events, selector, callback) { 14123 this.emitter().one(events, argSelector$1(selector), callback); 14124 return this; 14125 }, 14126 emit: function emit(events, extraParams) { 14127 this.emitter().emit(events, extraParams); 14128 return this; 14129 }, 14130 emitAndNotify: function emitAndNotify(event, eles) { 14131 this.emit(event); 14132 this.notify(event, eles); 14133 return this; 14134 } 14135 }; 14136 define$3.eventAliasesOn(elesfn$v); 14137 14138 var corefn$2 = { 14139 png: function png(options) { 14140 var renderer = this._private.renderer; 14141 options = options || {}; 14142 return renderer.png(options); 14143 }, 14144 jpg: function jpg(options) { 14145 var renderer = this._private.renderer; 14146 options = options || {}; 14147 options.bg = options.bg || '#fff'; 14148 return renderer.jpg(options); 14149 } 14150 }; 14151 corefn$2.jpeg = corefn$2.jpg; 14152 14153 var corefn$3 = { 14154 layout: function layout(options) { 14155 var cy = this; 14156 14157 if (options == null) { 14158 error('Layout options must be specified to make a layout'); 14159 return; 14160 } 14161 14162 if (options.name == null) { 14163 error('A `name` must be specified to make a layout'); 14164 return; 14165 } 14166 14167 var name = options.name; 14168 var Layout = cy.extension('layout', name); 14169 14170 if (Layout == null) { 14171 error('No such layout `' + name + '` found. Did you forget to import it and `cytoscape.use()` it?'); 14172 return; 14173 } 14174 14175 var eles; 14176 14177 if (string(options.eles)) { 14178 eles = cy.$(options.eles); 14179 } else { 14180 eles = options.eles != null ? options.eles : cy.$(); 14181 } 14182 14183 var layout = new Layout(extend({}, options, { 14184 cy: cy, 14185 eles: eles 14186 })); 14187 return layout; 14188 } 14189 }; 14190 corefn$3.createLayout = corefn$3.makeLayout = corefn$3.layout; 14191 14192 var corefn$4 = { 14193 notify: function notify(eventName, eventEles) { 14194 var _p = this._private; 14195 14196 if (this.batching()) { 14197 _p.batchNotifications = _p.batchNotifications || {}; 14198 var eles = _p.batchNotifications[eventName] = _p.batchNotifications[eventName] || this.collection(); 14199 14200 if (eventEles != null) { 14201 eles.merge(eventEles); 14202 } 14203 14204 return; // notifications are disabled during batching 14205 } 14206 14207 if (!_p.notificationsEnabled) { 14208 return; 14209 } // exit on disabled 14210 14211 14212 var renderer = this.renderer(); // exit if destroy() called on core or renderer in between frames #1499 #1528 14213 14214 if (this.destroyed() || !renderer) { 14215 return; 14216 } 14217 14218 renderer.notify(eventName, eventEles); 14219 }, 14220 notifications: function notifications(bool) { 14221 var p = this._private; 14222 14223 if (bool === undefined) { 14224 return p.notificationsEnabled; 14225 } else { 14226 p.notificationsEnabled = bool ? true : false; 14227 } 14228 14229 return this; 14230 }, 14231 noNotifications: function noNotifications(callback) { 14232 this.notifications(false); 14233 callback(); 14234 this.notifications(true); 14235 }, 14236 batching: function batching() { 14237 return this._private.batchCount > 0; 14238 }, 14239 startBatch: function startBatch() { 14240 var _p = this._private; 14241 14242 if (_p.batchCount == null) { 14243 _p.batchCount = 0; 14244 } 14245 14246 if (_p.batchCount === 0) { 14247 _p.batchStyleEles = this.collection(); 14248 _p.batchNotifications = {}; 14249 } 14250 14251 _p.batchCount++; 14252 return this; 14253 }, 14254 endBatch: function endBatch() { 14255 var _p = this._private; 14256 14257 if (_p.batchCount === 0) { 14258 return this; 14259 } 14260 14261 _p.batchCount--; 14262 14263 if (_p.batchCount === 0) { 14264 // update style for dirty eles 14265 _p.batchStyleEles.updateStyle(); 14266 14267 var renderer = this.renderer(); // notify the renderer of queued eles and event types 14268 14269 Object.keys(_p.batchNotifications).forEach(function (eventName) { 14270 var eles = _p.batchNotifications[eventName]; 14271 14272 if (eles.empty()) { 14273 renderer.notify(eventName); 14274 } else { 14275 renderer.notify(eventName, eles); 14276 } 14277 }); 14278 } 14279 14280 return this; 14281 }, 14282 batch: function batch(callback) { 14283 this.startBatch(); 14284 callback(); 14285 this.endBatch(); 14286 return this; 14287 }, 14288 // for backwards compatibility 14289 batchData: function batchData(map) { 14290 var cy = this; 14291 return this.batch(function () { 14292 var ids = Object.keys(map); 14293 14294 for (var i = 0; i < ids.length; i++) { 14295 var id = ids[i]; 14296 var data = map[id]; 14297 var ele = cy.getElementById(id); 14298 ele.data(data); 14299 } 14300 }); 14301 } 14302 }; 14303 14304 var rendererDefaults = defaults({ 14305 hideEdgesOnViewport: false, 14306 textureOnViewport: false, 14307 motionBlur: false, 14308 motionBlurOpacity: 0.05, 14309 pixelRatio: undefined, 14310 desktopTapThreshold: 4, 14311 touchTapThreshold: 8, 14312 wheelSensitivity: 1, 14313 debug: false, 14314 showFps: false 14315 }); 14316 var corefn$5 = { 14317 renderTo: function renderTo(context, zoom, pan, pxRatio) { 14318 var r = this._private.renderer; 14319 r.renderTo(context, zoom, pan, pxRatio); 14320 return this; 14321 }, 14322 renderer: function renderer() { 14323 return this._private.renderer; 14324 }, 14325 forceRender: function forceRender() { 14326 this.notify('draw'); 14327 return this; 14328 }, 14329 resize: function resize() { 14330 this.invalidateSize(); 14331 this.emitAndNotify('resize'); 14332 return this; 14333 }, 14334 initRenderer: function initRenderer(options) { 14335 var cy = this; 14336 var RendererProto = cy.extension('renderer', options.name); 14337 14338 if (RendererProto == null) { 14339 error("Can not initialise: No such renderer `".concat(options.name, "` found. Did you forget to import it and `cytoscape.use()` it?")); 14340 return; 14341 } 14342 14343 if (options.wheelSensitivity !== undefined) { 14344 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."); 14345 } 14346 14347 var rOpts = rendererDefaults(options); 14348 rOpts.cy = cy; 14349 cy._private.renderer = new RendererProto(rOpts); 14350 this.notify('init'); 14351 }, 14352 destroyRenderer: function destroyRenderer() { 14353 var cy = this; 14354 cy.notify('destroy'); // destroy the renderer 14355 14356 var domEle = cy.container(); 14357 14358 if (domEle) { 14359 domEle._cyreg = null; 14360 14361 while (domEle.childNodes.length > 0) { 14362 domEle.removeChild(domEle.childNodes[0]); 14363 } 14364 } 14365 14366 cy._private.renderer = null; // to be extra safe, remove the ref 14367 14368 cy.mutableElements().forEach(function (ele) { 14369 var _p = ele._private; 14370 _p.rscratch = {}; 14371 _p.rstyle = {}; 14372 _p.animation.current = []; 14373 _p.animation.queue = []; 14374 }); 14375 }, 14376 onRender: function onRender(fn) { 14377 return this.on('render', fn); 14378 }, 14379 offRender: function offRender(fn) { 14380 return this.off('render', fn); 14381 } 14382 }; 14383 corefn$5.invalidateDimensions = corefn$5.resize; 14384 14385 var corefn$6 = { 14386 // get a collection 14387 // - empty collection on no args 14388 // - collection of elements in the graph on selector arg 14389 // - guarantee a returned collection when elements or collection specified 14390 collection: function collection(eles, opts) { 14391 if (string(eles)) { 14392 return this.$(eles); 14393 } else if (elementOrCollection(eles)) { 14394 return eles.collection(); 14395 } else if (array(eles)) { 14396 return new Collection(this, eles, opts); 14397 } 14398 14399 return new Collection(this); 14400 }, 14401 nodes: function nodes(selector) { 14402 var nodes = this.$(function (ele) { 14403 return ele.isNode(); 14404 }); 14405 14406 if (selector) { 14407 return nodes.filter(selector); 14408 } 14409 14410 return nodes; 14411 }, 14412 edges: function edges(selector) { 14413 var edges = this.$(function (ele) { 14414 return ele.isEdge(); 14415 }); 14416 14417 if (selector) { 14418 return edges.filter(selector); 14419 } 14420 14421 return edges; 14422 }, 14423 // search the graph like jQuery 14424 $: function $(selector) { 14425 var eles = this._private.elements; 14426 14427 if (selector) { 14428 return eles.filter(selector); 14429 } else { 14430 return eles.spawnSelf(); 14431 } 14432 }, 14433 mutableElements: function mutableElements() { 14434 return this._private.elements; 14435 } 14436 }; // aliases 14437 14438 corefn$6.elements = corefn$6.filter = corefn$6.$; 14439 14440 var styfn = {}; // keys for style blocks, e.g. ttfftt 14441 14442 var TRUE = 't'; 14443 var FALSE = 'f'; // (potentially expensive calculation) 14444 // apply the style to the element based on 14445 // - its bypass 14446 // - what selectors match it 14447 14448 styfn.apply = function (eles) { 14449 var self = this; 14450 var _p = self._private; 14451 var cy = _p.cy; 14452 var updatedEles = cy.collection(); 14453 14454 if (_p.newStyle) { 14455 // clear style caches 14456 _p.contextStyles = {}; 14457 _p.propDiffs = {}; 14458 self.cleanElements(eles, true); 14459 } 14460 14461 for (var ie = 0; ie < eles.length; ie++) { 14462 var ele = eles[ie]; 14463 var cxtMeta = self.getContextMeta(ele); 14464 14465 if (cxtMeta.empty) { 14466 continue; 14467 } 14468 14469 var cxtStyle = self.getContextStyle(cxtMeta); 14470 var app = self.applyContextStyle(cxtMeta, cxtStyle, ele); 14471 14472 if (!_p.newStyle) { 14473 self.updateTransitions(ele, app.diffProps); 14474 } 14475 14476 var hintsDiff = self.updateStyleHints(ele); 14477 14478 if (hintsDiff) { 14479 updatedEles.merge(ele); 14480 } 14481 } // for elements 14482 14483 14484 _p.newStyle = false; 14485 return updatedEles; 14486 }; 14487 14488 styfn.getPropertiesDiff = function (oldCxtKey, newCxtKey) { 14489 var self = this; 14490 var cache = self._private.propDiffs = self._private.propDiffs || {}; 14491 var dualCxtKey = oldCxtKey + '-' + newCxtKey; 14492 var cachedVal = cache[dualCxtKey]; 14493 14494 if (cachedVal) { 14495 return cachedVal; 14496 } 14497 14498 var diffProps = []; 14499 var addedProp = {}; 14500 14501 for (var i = 0; i < self.length; i++) { 14502 var cxt = self[i]; 14503 var oldHasCxt = oldCxtKey[i] === TRUE; 14504 var newHasCxt = newCxtKey[i] === TRUE; 14505 var cxtHasDiffed = oldHasCxt !== newHasCxt; 14506 var cxtHasMappedProps = cxt.mappedProperties.length > 0; 14507 14508 if (cxtHasDiffed || newHasCxt && cxtHasMappedProps) { 14509 var props = void 0; 14510 14511 if (cxtHasDiffed && cxtHasMappedProps) { 14512 props = cxt.properties; // suffices b/c mappedProperties is a subset of properties 14513 } else if (cxtHasDiffed) { 14514 props = cxt.properties; // need to check them all 14515 } else if (cxtHasMappedProps) { 14516 props = cxt.mappedProperties; // only need to check mapped 14517 } 14518 14519 for (var j = 0; j < props.length; j++) { 14520 var prop = props[j]; 14521 var name = prop.name; // if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter 14522 // (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result 14523 // is cached) 14524 14525 var laterCxtOverrides = false; 14526 14527 for (var k = i + 1; k < self.length; k++) { 14528 var laterCxt = self[k]; 14529 var hasLaterCxt = newCxtKey[k] === TRUE; 14530 14531 if (!hasLaterCxt) { 14532 continue; 14533 } // can't override unless the context is active 14534 14535 14536 laterCxtOverrides = laterCxt.properties[prop.name] != null; 14537 14538 if (laterCxtOverrides) { 14539 break; 14540 } // exit early as long as one later context overrides 14541 14542 } 14543 14544 if (!addedProp[name] && !laterCxtOverrides) { 14545 addedProp[name] = true; 14546 diffProps.push(name); 14547 } 14548 } // for props 14549 14550 } // if 14551 14552 } // for contexts 14553 14554 14555 cache[dualCxtKey] = diffProps; 14556 return diffProps; 14557 }; 14558 14559 styfn.getContextMeta = function (ele) { 14560 var self = this; 14561 var cxtKey = ''; 14562 var diffProps; 14563 var prevKey = ele._private.styleCxtKey || ''; 14564 14565 if (self._private.newStyle) { 14566 prevKey = ''; // since we need to apply all style if a fresh stylesheet 14567 } // get the cxt key 14568 14569 14570 for (var i = 0; i < self.length; i++) { 14571 var context = self[i]; 14572 var contextSelectorMatches = context.selector && context.selector.matches(ele); // NB: context.selector may be null for 'core' 14573 14574 if (contextSelectorMatches) { 14575 cxtKey += TRUE; 14576 } else { 14577 cxtKey += FALSE; 14578 } 14579 } // for context 14580 14581 14582 diffProps = self.getPropertiesDiff(prevKey, cxtKey); 14583 ele._private.styleCxtKey = cxtKey; 14584 return { 14585 key: cxtKey, 14586 diffPropNames: diffProps, 14587 empty: diffProps.length === 0 14588 }; 14589 }; // gets a computed ele style object based on matched contexts 14590 14591 14592 styfn.getContextStyle = function (cxtMeta) { 14593 var cxtKey = cxtMeta.key; 14594 var self = this; 14595 var cxtStyles = this._private.contextStyles = this._private.contextStyles || {}; // if already computed style, returned cached copy 14596 14597 if (cxtStyles[cxtKey]) { 14598 return cxtStyles[cxtKey]; 14599 } 14600 14601 var style = { 14602 _private: { 14603 key: cxtKey 14604 } 14605 }; 14606 14607 for (var i = 0; i < self.length; i++) { 14608 var cxt = self[i]; 14609 var hasCxt = cxtKey[i] === TRUE; 14610 14611 if (!hasCxt) { 14612 continue; 14613 } 14614 14615 for (var j = 0; j < cxt.properties.length; j++) { 14616 var prop = cxt.properties[j]; 14617 style[prop.name] = prop; 14618 } 14619 } 14620 14621 cxtStyles[cxtKey] = style; 14622 return style; 14623 }; 14624 14625 styfn.applyContextStyle = function (cxtMeta, cxtStyle, ele) { 14626 var self = this; 14627 var diffProps = cxtMeta.diffPropNames; 14628 var retDiffProps = {}; 14629 var types = self.types; 14630 14631 for (var i = 0; i < diffProps.length; i++) { 14632 var diffPropName = diffProps[i]; 14633 var cxtProp = cxtStyle[diffPropName]; 14634 var eleProp = ele.pstyle(diffPropName); 14635 14636 if (!cxtProp) { 14637 // no context prop means delete 14638 if (!eleProp) { 14639 continue; // no existing prop means nothing needs to be removed 14640 // nb affects initial application on mapped values like control-point-distances 14641 } else if (eleProp.bypass) { 14642 cxtProp = { 14643 name: diffPropName, 14644 deleteBypassed: true 14645 }; 14646 } else { 14647 cxtProp = { 14648 name: diffPropName, 14649 "delete": true 14650 }; 14651 } 14652 } // save cycles when the context prop doesn't need to be applied 14653 14654 14655 if (eleProp === cxtProp) { 14656 continue; 14657 } // save cycles when a mapped context prop doesn't need to be applied 14658 14659 14660 if (cxtProp.mapped === types.fn // context prop is function mapper 14661 && eleProp != null // some props can be null even by default (e.g. a prop that overrides another one) 14662 && eleProp.mapping != null // ele prop is a concrete value from from a mapper 14663 && eleProp.mapping.value === cxtProp.value // the current prop on the ele is a flat prop value for the function mapper 14664 ) { 14665 // NB don't write to cxtProp, as it's shared among eles (stored in stylesheet) 14666 var mapping = eleProp.mapping; // can write to mapping, as it's a per-ele copy 14667 14668 var fnValue = mapping.fnValue = cxtProp.value(ele); // temporarily cache the value in case of a miss 14669 14670 if (fnValue === mapping.prevFnValue) { 14671 continue; 14672 } 14673 } 14674 14675 var retDiffProp = retDiffProps[diffPropName] = { 14676 prev: eleProp 14677 }; 14678 self.applyParsedProperty(ele, cxtProp); 14679 retDiffProp.next = ele.pstyle(diffPropName); 14680 14681 if (retDiffProp.next && retDiffProp.next.bypass) { 14682 retDiffProp.next = retDiffProp.next.bypassed; 14683 } 14684 } 14685 14686 return { 14687 diffProps: retDiffProps 14688 }; 14689 }; 14690 14691 styfn.updateStyleHints = function (ele) { 14692 var _p = ele._private; 14693 var self = this; 14694 var propNames = self.propertyGroupNames; 14695 var propGrKeys = self.propertyGroupKeys; 14696 14697 var propHash = function propHash(ele, propNames, seedKey) { 14698 return self.getPropertiesHash(ele, propNames, seedKey); 14699 }; 14700 14701 var oldStyleKey = _p.styleKey; 14702 14703 if (ele.removed()) { 14704 return false; 14705 } 14706 14707 var isNode = _p.group === 'nodes'; // get the style key hashes per prop group 14708 // but lazily -- only use non-default prop values to reduce the number of hashes 14709 // 14710 14711 var overriddenStyles = ele._private.style; 14712 propNames = Object.keys(overriddenStyles); 14713 14714 for (var i = 0; i < propGrKeys.length; i++) { 14715 var grKey = propGrKeys[i]; 14716 _p.styleKeys[grKey] = 0; 14717 } 14718 14719 var updateGrKey = function updateGrKey(val, grKey) { 14720 return _p.styleKeys[grKey] = hashInt(val, _p.styleKeys[grKey]); 14721 }; 14722 14723 var updateGrKeyWStr = function updateGrKeyWStr(strVal, grKey) { 14724 for (var j = 0; j < strVal.length; j++) { 14725 updateGrKey(strVal.charCodeAt(j), grKey); 14726 } 14727 }; // - hashing works on 32 bit ints b/c we use bitwise ops 14728 // - small numbers get cut off (e.g. 0.123 is seen as 0 by the hashing function) 14729 // - raise up small numbers so more significant digits are seen by hashing 14730 // - make small numbers larger than a normal value to avoid collisions 14731 // - works in practice and it's relatively cheap 14732 14733 14734 var N = 2000000000; 14735 14736 var cleanNum = function cleanNum(val) { 14737 return -128 < val && val < 128 && Math.floor(val) !== val ? N - (val * 1024 | 0) : val; 14738 }; 14739 14740 for (var _i = 0; _i < propNames.length; _i++) { 14741 var name = propNames[_i]; 14742 var parsedProp = overriddenStyles[name]; 14743 14744 if (parsedProp == null) { 14745 continue; 14746 } 14747 14748 var propInfo = this.properties[name]; 14749 var type = propInfo.type; 14750 var _grKey = propInfo.groupKey; 14751 var normalizedNumberVal = void 0; 14752 14753 if (propInfo.hashOverride != null) { 14754 normalizedNumberVal = propInfo.hashOverride(ele, parsedProp); 14755 } else if (parsedProp.pfValue != null) { 14756 normalizedNumberVal = parsedProp.pfValue; 14757 } // might not be a number if it allows enums 14758 14759 14760 var numberVal = propInfo.enums == null ? parsedProp.value : null; 14761 var haveNormNum = normalizedNumberVal != null; 14762 var haveUnitedNum = numberVal != null; 14763 var haveNum = haveNormNum || haveUnitedNum; 14764 var units = parsedProp.units; // numbers are cheaper to hash than strings 14765 // 1 hash op vs n hash ops (for length n string) 14766 14767 if (type.number && haveNum) { 14768 var v = haveNormNum ? normalizedNumberVal : numberVal; 14769 14770 if (type.multiple) { 14771 for (var _i2 = 0; _i2 < v.length; _i2++) { 14772 updateGrKey(cleanNum(v[_i2]), _grKey); 14773 } 14774 } else { 14775 updateGrKey(cleanNum(v), _grKey); 14776 } 14777 14778 if (!haveNormNum && units != null) { 14779 updateGrKeyWStr(units, _grKey); 14780 } 14781 } else { 14782 updateGrKeyWStr(parsedProp.strValue, _grKey); 14783 } 14784 } // overall style key 14785 // 14786 14787 14788 var hash = 0; 14789 14790 for (var _i3 = 0; _i3 < propGrKeys.length; _i3++) { 14791 var _grKey2 = propGrKeys[_i3]; 14792 var grHash = _p.styleKeys[_grKey2]; 14793 hash = hashInt(grHash, hash); 14794 } 14795 14796 _p.styleKey = hash; // label dims 14797 // 14798 14799 var labelDimsKey = _p.labelDimsKey = _p.styleKeys.labelDimensions; 14800 _p.labelKey = propHash(ele, ['label'], labelDimsKey); 14801 _p.labelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.labelKey); 14802 14803 if (!isNode) { 14804 _p.sourceLabelKey = propHash(ele, ['source-label'], labelDimsKey); 14805 _p.sourceLabelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.sourceLabelKey); 14806 _p.targetLabelKey = propHash(ele, ['target-label'], labelDimsKey); 14807 _p.targetLabelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.targetLabelKey); 14808 } // node 14809 // 14810 14811 14812 if (isNode) { 14813 var _p$styleKeys = _p.styleKeys, 14814 nodeBody = _p$styleKeys.nodeBody, 14815 nodeBorder = _p$styleKeys.nodeBorder, 14816 backgroundImage = _p$styleKeys.backgroundImage, 14817 compound = _p$styleKeys.compound, 14818 pie = _p$styleKeys.pie; 14819 _p.nodeKey = hashIntsArray([nodeBorder, backgroundImage, compound, pie], nodeBody); 14820 _p.hasPie = pie != 0; 14821 } 14822 14823 return oldStyleKey !== _p.styleKey; 14824 }; 14825 14826 styfn.clearStyleHints = function (ele) { 14827 var _p = ele._private; 14828 _p.styleKeys = {}; 14829 _p.styleKey = null; 14830 _p.labelKey = null; 14831 _p.labelStyleKey = null; 14832 _p.sourceLabelKey = null; 14833 _p.sourceLabelStyleKey = null; 14834 _p.targetLabelKey = null; 14835 _p.targetLabelStyleKey = null; 14836 _p.nodeKey = null; 14837 _p.hasPie = null; 14838 }; // apply a property to the style (for internal use) 14839 // returns whether application was successful 14840 // 14841 // now, this function flattens the property, and here's how: 14842 // 14843 // for parsedProp:{ bypass: true, deleteBypass: true } 14844 // no property is generated, instead the bypass property in the 14845 // element's style is replaced by what's pointed to by the `bypassed` 14846 // field in the bypass property (i.e. restoring the property the 14847 // bypass was overriding) 14848 // 14849 // for parsedProp:{ mapped: truthy } 14850 // the generated flattenedProp:{ mapping: prop } 14851 // 14852 // for parsedProp:{ bypass: true } 14853 // the generated flattenedProp:{ bypassed: parsedProp } 14854 14855 14856 styfn.applyParsedProperty = function (ele, parsedProp) { 14857 var self = this; 14858 var prop = parsedProp; 14859 var style = ele._private.style; 14860 var flatProp; 14861 var types = self.types; 14862 var type = self.properties[prop.name].type; 14863 var propIsBypass = prop.bypass; 14864 var origProp = style[prop.name]; 14865 var origPropIsBypass = origProp && origProp.bypass; 14866 var _p = ele._private; 14867 var flatPropMapping = 'mapping'; 14868 14869 var getVal = function getVal(p) { 14870 if (p == null) { 14871 return null; 14872 } else if (p.pfValue != null) { 14873 return p.pfValue; 14874 } else { 14875 return p.value; 14876 } 14877 }; 14878 14879 var checkTriggers = function checkTriggers() { 14880 var fromVal = getVal(origProp); 14881 var toVal = getVal(prop); 14882 self.checkTriggers(ele, prop.name, fromVal, toVal); 14883 }; // edge sanity checks to prevent the client from making serious mistakes 14884 14885 14886 if (parsedProp.name === 'curve-style' && ele.isEdge() && ( // loops must be bundled beziers 14887 parsedProp.value !== 'bezier' && ele.isLoop() || // edges connected to compound nodes can not be haystacks 14888 parsedProp.value === 'haystack' && (ele.source().isParent() || ele.target().isParent()))) { 14889 prop = parsedProp = this.parse(parsedProp.name, 'bezier', propIsBypass); 14890 } 14891 14892 if (prop["delete"]) { 14893 // delete the property and use the default value on falsey value 14894 style[prop.name] = undefined; 14895 checkTriggers(); 14896 return true; 14897 } 14898 14899 if (prop.deleteBypassed) { 14900 // delete the property that the 14901 if (!origProp) { 14902 checkTriggers(); 14903 return true; // can't delete if no prop 14904 } else if (origProp.bypass) { 14905 // delete bypassed 14906 origProp.bypassed = undefined; 14907 checkTriggers(); 14908 return true; 14909 } else { 14910 return false; // we're unsuccessful deleting the bypassed 14911 } 14912 } // check if we need to delete the current bypass 14913 14914 14915 if (prop.deleteBypass) { 14916 // then this property is just here to indicate we need to delete 14917 if (!origProp) { 14918 checkTriggers(); 14919 return true; // property is already not defined 14920 } else if (origProp.bypass) { 14921 // then replace the bypass property with the original 14922 // because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary) 14923 style[prop.name] = origProp.bypassed; 14924 checkTriggers(); 14925 return true; 14926 } else { 14927 return false; // we're unsuccessful deleting the bypass 14928 } 14929 } 14930 14931 var printMappingErr = function printMappingErr() { 14932 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'); 14933 }; // put the property in the style objects 14934 14935 14936 switch (prop.mapped) { 14937 // flatten the property if mapped 14938 case types.mapData: 14939 { 14940 // flatten the field (e.g. data.foo.bar) 14941 var fields = prop.field.split('.'); 14942 var fieldVal = _p.data; 14943 14944 for (var i = 0; i < fields.length && fieldVal; i++) { 14945 var field = fields[i]; 14946 fieldVal = fieldVal[field]; 14947 } 14948 14949 if (fieldVal == null) { 14950 printMappingErr(); 14951 return false; 14952 } 14953 14954 var percent; 14955 14956 if (!number(fieldVal)) { 14957 // then don't apply and fall back on the existing style 14958 warn('Do not use continuous mappers without specifying numeric data (i.e. `' + prop.field + ': ' + fieldVal + '` for `' + ele.id() + '` is non-numeric)'); 14959 return false; 14960 } else { 14961 var fieldWidth = prop.fieldMax - prop.fieldMin; 14962 14963 if (fieldWidth === 0) { 14964 // safety check -- not strictly necessary as no props of zero range should be passed here 14965 percent = 0; 14966 } else { 14967 percent = (fieldVal - prop.fieldMin) / fieldWidth; 14968 } 14969 } // make sure to bound percent value 14970 14971 14972 if (percent < 0) { 14973 percent = 0; 14974 } else if (percent > 1) { 14975 percent = 1; 14976 } 14977 14978 if (type.color) { 14979 var r1 = prop.valueMin[0]; 14980 var r2 = prop.valueMax[0]; 14981 var g1 = prop.valueMin[1]; 14982 var g2 = prop.valueMax[1]; 14983 var b1 = prop.valueMin[2]; 14984 var b2 = prop.valueMax[2]; 14985 var a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3]; 14986 var a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3]; 14987 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)]; 14988 flatProp = { 14989 // colours are simple, so just create the flat property instead of expensive string parsing 14990 bypass: prop.bypass, 14991 // we're a bypass if the mapping property is a bypass 14992 name: prop.name, 14993 value: clr, 14994 strValue: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')' 14995 }; 14996 } else if (type.number) { 14997 var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent; 14998 flatProp = this.parse(prop.name, calcValue, prop.bypass, flatPropMapping); 14999 } else { 15000 return false; // can only map to colours and numbers 15001 } 15002 15003 if (!flatProp) { 15004 // if we can't flatten the property, then don't apply the property and fall back on the existing style 15005 printMappingErr(); 15006 return false; 15007 } 15008 15009 flatProp.mapping = prop; // keep a reference to the mapping 15010 15011 prop = flatProp; // the flattened (mapped) property is the one we want 15012 15013 break; 15014 } 15015 // direct mapping 15016 15017 case types.data: 15018 { 15019 // flatten the field (e.g. data.foo.bar) 15020 var _fields = prop.field.split('.'); 15021 15022 var _fieldVal = _p.data; 15023 15024 for (var _i4 = 0; _i4 < _fields.length && _fieldVal; _i4++) { 15025 var _field = _fields[_i4]; 15026 _fieldVal = _fieldVal[_field]; 15027 } 15028 15029 if (_fieldVal != null) { 15030 flatProp = this.parse(prop.name, _fieldVal, prop.bypass, flatPropMapping); 15031 } 15032 15033 if (!flatProp) { 15034 // if we can't flatten the property, then don't apply and fall back on the existing style 15035 printMappingErr(); 15036 return false; 15037 } 15038 15039 flatProp.mapping = prop; // keep a reference to the mapping 15040 15041 prop = flatProp; // the flattened (mapped) property is the one we want 15042 15043 break; 15044 } 15045 15046 case types.fn: 15047 { 15048 var fn = prop.value; 15049 var fnRetVal = prop.fnValue != null ? prop.fnValue : fn(ele); // check for cached value before calling function 15050 15051 prop.prevFnValue = fnRetVal; 15052 15053 if (fnRetVal == null) { 15054 warn('Custom function mappers may not return null (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is null)'); 15055 return false; 15056 } 15057 15058 flatProp = this.parse(prop.name, fnRetVal, prop.bypass, flatPropMapping); 15059 15060 if (!flatProp) { 15061 warn('Custom function mappers may not return invalid values for the property type (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is invalid)'); 15062 return false; 15063 } 15064 15065 flatProp.mapping = copy(prop); // keep a reference to the mapping 15066 15067 prop = flatProp; // the flattened (mapped) property is the one we want 15068 15069 break; 15070 } 15071 15072 case undefined: 15073 break; 15074 // just set the property 15075 15076 default: 15077 return false; 15078 // not a valid mapping 15079 } // if the property is a bypass property, then link the resultant property to the original one 15080 15081 15082 if (propIsBypass) { 15083 if (origPropIsBypass) { 15084 // then this bypass overrides the existing one 15085 prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass 15086 } else { 15087 // then link the orig prop to the new bypass 15088 prop.bypassed = origProp; 15089 } 15090 15091 style[prop.name] = prop; // and set 15092 } else { 15093 // prop is not bypass 15094 if (origPropIsBypass) { 15095 // then keep the orig prop (since it's a bypass) and link to the new prop 15096 origProp.bypassed = prop; 15097 } else { 15098 // then just replace the old prop with the new one 15099 style[prop.name] = prop; 15100 } 15101 } 15102 15103 checkTriggers(); 15104 return true; 15105 }; 15106 15107 styfn.cleanElements = function (eles, keepBypasses) { 15108 for (var i = 0; i < eles.length; i++) { 15109 var ele = eles[i]; 15110 this.clearStyleHints(ele); 15111 ele.dirtyCompoundBoundsCache(); 15112 ele.dirtyBoundingBoxCache(); 15113 15114 if (!keepBypasses) { 15115 ele._private.style = {}; 15116 } else { 15117 var style = ele._private.style; 15118 var propNames = Object.keys(style); 15119 15120 for (var j = 0; j < propNames.length; j++) { 15121 var propName = propNames[j]; 15122 var eleProp = style[propName]; 15123 15124 if (eleProp != null) { 15125 if (eleProp.bypass) { 15126 eleProp.bypassed = null; 15127 } else { 15128 style[propName] = null; 15129 } 15130 } 15131 } 15132 } 15133 } 15134 }; // updates the visual style for all elements (useful for manual style modification after init) 15135 15136 15137 styfn.update = function () { 15138 var cy = this._private.cy; 15139 var eles = cy.mutableElements(); 15140 eles.updateStyle(); 15141 }; // diffProps : { name => { prev, next } } 15142 15143 15144 styfn.updateTransitions = function (ele, diffProps) { 15145 var self = this; 15146 var _p = ele._private; 15147 var props = ele.pstyle('transition-property').value; 15148 var duration = ele.pstyle('transition-duration').pfValue; 15149 var delay = ele.pstyle('transition-delay').pfValue; 15150 15151 if (props.length > 0 && duration > 0) { 15152 var style = {}; // build up the style to animate towards 15153 15154 var anyPrev = false; 15155 15156 for (var i = 0; i < props.length; i++) { 15157 var prop = props[i]; 15158 var styProp = ele.pstyle(prop); 15159 var diffProp = diffProps[prop]; 15160 15161 if (!diffProp) { 15162 continue; 15163 } 15164 15165 var prevProp = diffProp.prev; 15166 var fromProp = prevProp; 15167 var toProp = diffProp.next != null ? diffProp.next : styProp; 15168 var diff = false; 15169 var initVal = void 0; 15170 var initDt = 0.000001; // delta time % value for initVal (allows animating out of init zero opacity) 15171 15172 if (!fromProp) { 15173 continue; 15174 } // consider px values 15175 15176 15177 if (number(fromProp.pfValue) && number(toProp.pfValue)) { 15178 diff = toProp.pfValue - fromProp.pfValue; // nonzero is truthy 15179 15180 initVal = fromProp.pfValue + initDt * diff; // consider numerical values 15181 } else if (number(fromProp.value) && number(toProp.value)) { 15182 diff = toProp.value - fromProp.value; // nonzero is truthy 15183 15184 initVal = fromProp.value + initDt * diff; // consider colour values 15185 } else if (array(fromProp.value) && array(toProp.value)) { 15186 diff = fromProp.value[0] !== toProp.value[0] || fromProp.value[1] !== toProp.value[1] || fromProp.value[2] !== toProp.value[2]; 15187 initVal = fromProp.strValue; 15188 } // the previous value is good for an animation only if it's different 15189 15190 15191 if (diff) { 15192 style[prop] = toProp.strValue; // to val 15193 15194 this.applyBypass(ele, prop, initVal); // from val 15195 15196 anyPrev = true; 15197 } 15198 } // end if props allow ani 15199 // can't transition if there's nothing previous to transition from 15200 15201 15202 if (!anyPrev) { 15203 return; 15204 } 15205 15206 _p.transitioning = true; 15207 new Promise$1(function (resolve) { 15208 if (delay > 0) { 15209 ele.delayAnimation(delay).play().promise().then(resolve); 15210 } else { 15211 resolve(); 15212 } 15213 }).then(function () { 15214 return ele.animation({ 15215 style: style, 15216 duration: duration, 15217 easing: ele.pstyle('transition-timing-function').value, 15218 queue: false 15219 }).play().promise(); 15220 }).then(function () { 15221 // if( !isBypass ){ 15222 self.removeBypasses(ele, props); 15223 ele.emitAndNotify('style'); // } 15224 15225 _p.transitioning = false; 15226 }); 15227 } else if (_p.transitioning) { 15228 this.removeBypasses(ele, props); 15229 ele.emitAndNotify('style'); 15230 _p.transitioning = false; 15231 } 15232 }; 15233 15234 styfn.checkTrigger = function (ele, name, fromValue, toValue, getTrigger, onTrigger) { 15235 var prop = this.properties[name]; 15236 var triggerCheck = getTrigger(prop); 15237 15238 if (triggerCheck != null && triggerCheck(fromValue, toValue)) { 15239 onTrigger(prop); 15240 } 15241 }; 15242 15243 styfn.checkZOrderTrigger = function (ele, name, fromValue, toValue) { 15244 var _this = this; 15245 15246 this.checkTrigger(ele, name, fromValue, toValue, function (prop) { 15247 return prop.triggersZOrder; 15248 }, function () { 15249 _this._private.cy.notify('zorder', ele); 15250 }); 15251 }; 15252 15253 styfn.checkBoundsTrigger = function (ele, name, fromValue, toValue) { 15254 this.checkTrigger(ele, name, fromValue, toValue, function (prop) { 15255 return prop.triggersBounds; 15256 }, function (prop) { 15257 ele.dirtyCompoundBoundsCache(); 15258 ele.dirtyBoundingBoxCache(); // if the prop change makes the bb of pll bezier edges invalid, 15259 // then dirty the pll edge bb cache as well 15260 15261 if ( // only for beziers -- so performance of other edges isn't affected 15262 (ele.pstyle('curve-style').value === 'bezier' // already a bezier 15263 // was just now changed to or from a bezier: 15264 || name === 'curve-style' && (fromValue === 'bezier' || toValue === 'bezier')) && prop.triggersBoundsOfParallelBeziers) { 15265 ele.parallelEdges().forEach(function (pllEdge) { 15266 if (pllEdge.isBundledBezier()) { 15267 pllEdge.dirtyBoundingBoxCache(); 15268 } 15269 }); 15270 } 15271 }); 15272 }; 15273 15274 styfn.checkTriggers = function (ele, name, fromValue, toValue) { 15275 ele.dirtyStyleCache(); 15276 this.checkZOrderTrigger(ele, name, fromValue, toValue); 15277 this.checkBoundsTrigger(ele, name, fromValue, toValue); 15278 }; 15279 15280 var styfn$1 = {}; // bypasses are applied to an existing style on an element, and just tacked on temporarily 15281 // returns true iff application was successful for at least 1 specified property 15282 15283 styfn$1.applyBypass = function (eles, name, value, updateTransitions) { 15284 var self = this; 15285 var props = []; 15286 var isBypass = true; // put all the properties (can specify one or many) in an array after parsing them 15287 15288 if (name === '*' || name === '**') { 15289 // apply to all property names 15290 if (value !== undefined) { 15291 for (var i = 0; i < self.properties.length; i++) { 15292 var prop = self.properties[i]; 15293 var _name = prop.name; 15294 var parsedProp = this.parse(_name, value, true); 15295 15296 if (parsedProp) { 15297 props.push(parsedProp); 15298 } 15299 } 15300 } 15301 } else if (string(name)) { 15302 // then parse the single property 15303 var _parsedProp = this.parse(name, value, true); 15304 15305 if (_parsedProp) { 15306 props.push(_parsedProp); 15307 } 15308 } else if (plainObject(name)) { 15309 // then parse each property 15310 var specifiedProps = name; 15311 updateTransitions = value; 15312 var names = Object.keys(specifiedProps); 15313 15314 for (var _i = 0; _i < names.length; _i++) { 15315 var _name2 = names[_i]; 15316 var _value = specifiedProps[_name2]; 15317 15318 if (_value === undefined) { 15319 // try camel case name too 15320 _value = specifiedProps[dash2camel(_name2)]; 15321 } 15322 15323 if (_value !== undefined) { 15324 var _parsedProp2 = this.parse(_name2, _value, true); 15325 15326 if (_parsedProp2) { 15327 props.push(_parsedProp2); 15328 } 15329 } 15330 } 15331 } else { 15332 // can't do anything without well defined properties 15333 return false; 15334 } // we've failed if there are no valid properties 15335 15336 15337 if (props.length === 0) { 15338 return false; 15339 } // now, apply the bypass properties on the elements 15340 15341 15342 var ret = false; // return true if at least one succesful bypass applied 15343 15344 for (var _i2 = 0; _i2 < eles.length; _i2++) { 15345 // for each ele 15346 var ele = eles[_i2]; 15347 var diffProps = {}; 15348 var diffProp = void 0; 15349 15350 for (var j = 0; j < props.length; j++) { 15351 // for each prop 15352 var _prop = props[j]; 15353 15354 if (updateTransitions) { 15355 var prevProp = ele.pstyle(_prop.name); 15356 diffProp = diffProps[_prop.name] = { 15357 prev: prevProp 15358 }; 15359 } 15360 15361 ret = this.applyParsedProperty(ele, _prop) || ret; 15362 15363 if (updateTransitions) { 15364 diffProp.next = ele.pstyle(_prop.name); 15365 } 15366 } // for props 15367 15368 15369 if (ret) { 15370 this.updateStyleHints(ele); 15371 } 15372 15373 if (updateTransitions) { 15374 this.updateTransitions(ele, diffProps, isBypass); 15375 } 15376 } // for eles 15377 15378 15379 return ret; 15380 }; // only useful in specific cases like animation 15381 15382 15383 styfn$1.overrideBypass = function (eles, name, value) { 15384 name = camel2dash(name); 15385 15386 for (var i = 0; i < eles.length; i++) { 15387 var ele = eles[i]; 15388 var prop = ele._private.style[name]; 15389 var type = this.properties[name].type; 15390 var isColor = type.color; 15391 var isMulti = type.mutiple; 15392 var oldValue = !prop ? null : prop.pfValue != null ? prop.pfValue : prop.value; 15393 15394 if (!prop || !prop.bypass) { 15395 // need a bypass if one doesn't exist 15396 this.applyBypass(ele, name, value); 15397 } else { 15398 prop.value = value; 15399 15400 if (prop.pfValue != null) { 15401 prop.pfValue = value; 15402 } 15403 15404 if (isColor) { 15405 prop.strValue = 'rgb(' + value.join(',') + ')'; 15406 } else if (isMulti) { 15407 prop.strValue = value.join(' '); 15408 } else { 15409 prop.strValue = '' + value; 15410 } 15411 15412 this.updateStyleHints(ele); 15413 } 15414 15415 this.checkTriggers(ele, name, oldValue, value); 15416 } 15417 }; 15418 15419 styfn$1.removeAllBypasses = function (eles, updateTransitions) { 15420 return this.removeBypasses(eles, this.propertyNames, updateTransitions); 15421 }; 15422 15423 styfn$1.removeBypasses = function (eles, props, updateTransitions) { 15424 var isBypass = true; 15425 15426 for (var j = 0; j < eles.length; j++) { 15427 var ele = eles[j]; 15428 var diffProps = {}; 15429 15430 for (var i = 0; i < props.length; i++) { 15431 var name = props[i]; 15432 var prop = this.properties[name]; 15433 var prevProp = ele.pstyle(prop.name); 15434 15435 if (!prevProp || !prevProp.bypass) { 15436 // if a bypass doesn't exist for the prop, nothing needs to be removed 15437 continue; 15438 } 15439 15440 var value = ''; // empty => remove bypass 15441 15442 var parsedProp = this.parse(name, value, true); 15443 var diffProp = diffProps[prop.name] = { 15444 prev: prevProp 15445 }; 15446 this.applyParsedProperty(ele, parsedProp); 15447 diffProp.next = ele.pstyle(prop.name); 15448 } // for props 15449 15450 15451 this.updateStyleHints(ele); 15452 15453 if (updateTransitions) { 15454 this.updateTransitions(ele, diffProps, isBypass); 15455 } 15456 } // for eles 15457 15458 }; 15459 15460 var styfn$2 = {}; // gets what an em size corresponds to in pixels relative to a dom element 15461 15462 styfn$2.getEmSizeInPixels = function () { 15463 var px = this.containerCss('font-size'); 15464 15465 if (px != null) { 15466 return parseFloat(px); 15467 } else { 15468 return 1; // for headless 15469 } 15470 }; // gets css property from the core container 15471 15472 15473 styfn$2.containerCss = function (propName) { 15474 var cy = this._private.cy; 15475 var domElement = cy.container(); 15476 15477 if (window$1 && domElement && window$1.getComputedStyle) { 15478 return window$1.getComputedStyle(domElement).getPropertyValue(propName); 15479 } 15480 }; 15481 15482 var styfn$3 = {}; // gets the rendered style for an element 15483 15484 styfn$3.getRenderedStyle = function (ele, prop) { 15485 if (prop) { 15486 return this.getStylePropertyValue(ele, prop, true); 15487 } else { 15488 return this.getRawStyle(ele, true); 15489 } 15490 }; // gets the raw style for an element 15491 15492 15493 styfn$3.getRawStyle = function (ele, isRenderedVal) { 15494 var self = this; 15495 ele = ele[0]; // insure it's an element 15496 15497 if (ele) { 15498 var rstyle = {}; 15499 15500 for (var i = 0; i < self.properties.length; i++) { 15501 var prop = self.properties[i]; 15502 var val = self.getStylePropertyValue(ele, prop.name, isRenderedVal); 15503 15504 if (val != null) { 15505 rstyle[prop.name] = val; 15506 rstyle[dash2camel(prop.name)] = val; 15507 } 15508 } 15509 15510 return rstyle; 15511 } 15512 }; 15513 15514 styfn$3.getIndexedStyle = function (ele, property, subproperty, index) { 15515 var pstyle = ele.pstyle(property)[subproperty][index]; 15516 return pstyle != null ? pstyle : ele.cy().style().getDefaultProperty(property)[subproperty][0]; 15517 }; 15518 15519 styfn$3.getStylePropertyValue = function (ele, propName, isRenderedVal) { 15520 var self = this; 15521 ele = ele[0]; // insure it's an element 15522 15523 if (ele) { 15524 var prop = self.properties[propName]; 15525 15526 if (prop.alias) { 15527 prop = prop.pointsTo; 15528 } 15529 15530 var type = prop.type; 15531 var styleProp = ele.pstyle(prop.name); 15532 15533 if (styleProp) { 15534 var value = styleProp.value, 15535 units = styleProp.units, 15536 strValue = styleProp.strValue; 15537 15538 if (isRenderedVal && type.number && value != null && number(value)) { 15539 var zoom = ele.cy().zoom(); 15540 15541 var getRenderedValue = function getRenderedValue(val) { 15542 return val * zoom; 15543 }; 15544 15545 var getValueStringWithUnits = function getValueStringWithUnits(val, units) { 15546 return getRenderedValue(val) + units; 15547 }; 15548 15549 var isArrayValue = array(value); 15550 var haveUnits = isArrayValue ? units.every(function (u) { 15551 return u != null; 15552 }) : units != null; 15553 15554 if (haveUnits) { 15555 if (isArrayValue) { 15556 return value.map(function (v, i) { 15557 return getValueStringWithUnits(v, units[i]); 15558 }).join(' '); 15559 } else { 15560 return getValueStringWithUnits(value, units); 15561 } 15562 } else { 15563 if (isArrayValue) { 15564 return value.map(function (v) { 15565 return string(v) ? v : '' + getRenderedValue(v); 15566 }).join(' '); 15567 } else { 15568 return '' + getRenderedValue(value); 15569 } 15570 } 15571 } else if (strValue != null) { 15572 return strValue; 15573 } 15574 } 15575 15576 return null; 15577 } 15578 }; 15579 15580 styfn$3.getAnimationStartStyle = function (ele, aniProps) { 15581 var rstyle = {}; 15582 15583 for (var i = 0; i < aniProps.length; i++) { 15584 var aniProp = aniProps[i]; 15585 var name = aniProp.name; 15586 var styleProp = ele.pstyle(name); 15587 15588 if (styleProp !== undefined) { 15589 // then make a prop of it 15590 if (plainObject(styleProp)) { 15591 styleProp = this.parse(name, styleProp.strValue); 15592 } else { 15593 styleProp = this.parse(name, styleProp); 15594 } 15595 } 15596 15597 if (styleProp) { 15598 rstyle[name] = styleProp; 15599 } 15600 } 15601 15602 return rstyle; 15603 }; 15604 15605 styfn$3.getPropsList = function (propsObj) { 15606 var self = this; 15607 var rstyle = []; 15608 var style = propsObj; 15609 var props = self.properties; 15610 15611 if (style) { 15612 var names = Object.keys(style); 15613 15614 for (var i = 0; i < names.length; i++) { 15615 var name = names[i]; 15616 var val = style[name]; 15617 var prop = props[name] || props[camel2dash(name)]; 15618 var styleProp = this.parse(prop.name, val); 15619 15620 if (styleProp) { 15621 rstyle.push(styleProp); 15622 } 15623 } 15624 } 15625 15626 return rstyle; 15627 }; 15628 15629 styfn$3.getNonDefaultPropertiesHash = function (ele, propNames, seed) { 15630 var hash = seed; 15631 var name, val, strVal, chVal; 15632 var i, j; 15633 15634 for (i = 0; i < propNames.length; i++) { 15635 name = propNames[i]; 15636 val = ele.pstyle(name, false); 15637 15638 if (val == null) { 15639 continue; 15640 } else if (val.pfValue != null) { 15641 hash = hashInt(chVal, hash); 15642 } else { 15643 strVal = val.strValue; 15644 15645 for (j = 0; j < strVal.length; j++) { 15646 chVal = strVal.charCodeAt(j); 15647 hash = hashInt(chVal, hash); 15648 } 15649 } 15650 } 15651 15652 return hash; 15653 }; 15654 15655 styfn$3.getPropertiesHash = styfn$3.getNonDefaultPropertiesHash; 15656 15657 var styfn$4 = {}; 15658 15659 styfn$4.appendFromJson = function (json) { 15660 var style = this; 15661 15662 for (var i = 0; i < json.length; i++) { 15663 var context = json[i]; 15664 var selector = context.selector; 15665 var props = context.style || context.css; 15666 var names = Object.keys(props); 15667 style.selector(selector); // apply selector 15668 15669 for (var j = 0; j < names.length; j++) { 15670 var name = names[j]; 15671 var value = props[name]; 15672 style.css(name, value); // apply property 15673 } 15674 } 15675 15676 return style; 15677 }; // accessible cy.style() function 15678 15679 15680 styfn$4.fromJson = function (json) { 15681 var style = this; 15682 style.resetToDefault(); 15683 style.appendFromJson(json); 15684 return style; 15685 }; // get json from cy.style() api 15686 15687 15688 styfn$4.json = function () { 15689 var json = []; 15690 15691 for (var i = this.defaultLength; i < this.length; i++) { 15692 var cxt = this[i]; 15693 var selector = cxt.selector; 15694 var props = cxt.properties; 15695 var css = {}; 15696 15697 for (var j = 0; j < props.length; j++) { 15698 var prop = props[j]; 15699 css[prop.name] = prop.strValue; 15700 } 15701 15702 json.push({ 15703 selector: !selector ? 'core' : selector.toString(), 15704 style: css 15705 }); 15706 } 15707 15708 return json; 15709 }; 15710 15711 var styfn$5 = {}; 15712 15713 styfn$5.appendFromString = function (string) { 15714 var self = this; 15715 var style = this; 15716 var remaining = '' + string; 15717 var selAndBlockStr; 15718 var blockRem; 15719 var propAndValStr; // remove comments from the style string 15720 15721 remaining = remaining.replace(/[/][*](\s|.)+?[*][/]/g, ''); 15722 15723 function removeSelAndBlockFromRemaining() { 15724 // remove the parsed selector and block from the remaining text to parse 15725 if (remaining.length > selAndBlockStr.length) { 15726 remaining = remaining.substr(selAndBlockStr.length); 15727 } else { 15728 remaining = ''; 15729 } 15730 } 15731 15732 function removePropAndValFromRem() { 15733 // remove the parsed property and value from the remaining block text to parse 15734 if (blockRem.length > propAndValStr.length) { 15735 blockRem = blockRem.substr(propAndValStr.length); 15736 } else { 15737 blockRem = ''; 15738 } 15739 } 15740 15741 for (;;) { 15742 var nothingLeftToParse = remaining.match(/^\s*$/); 15743 15744 if (nothingLeftToParse) { 15745 break; 15746 } 15747 15748 var selAndBlock = remaining.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/); 15749 15750 if (!selAndBlock) { 15751 warn('Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: ' + remaining); 15752 break; 15753 } 15754 15755 selAndBlockStr = selAndBlock[0]; // parse the selector 15756 15757 var selectorStr = selAndBlock[1]; 15758 15759 if (selectorStr !== 'core') { 15760 var selector = new Selector(selectorStr); 15761 15762 if (selector.invalid) { 15763 warn('Skipping parsing of block: Invalid selector found in string stylesheet: ' + selectorStr); // skip this selector and block 15764 15765 removeSelAndBlockFromRemaining(); 15766 continue; 15767 } 15768 } // parse the block of properties and values 15769 15770 15771 var blockStr = selAndBlock[2]; 15772 var invalidBlock = false; 15773 blockRem = blockStr; 15774 var props = []; 15775 15776 for (;;) { 15777 var _nothingLeftToParse = blockRem.match(/^\s*$/); 15778 15779 if (_nothingLeftToParse) { 15780 break; 15781 } 15782 15783 var propAndVal = blockRem.match(/^\s*(.+?)\s*:\s*(.+?)\s*;/); 15784 15785 if (!propAndVal) { 15786 warn('Skipping parsing of block: Invalid formatting of style property and value definitions found in:' + blockStr); 15787 invalidBlock = true; 15788 break; 15789 } 15790 15791 propAndValStr = propAndVal[0]; 15792 var propStr = propAndVal[1]; 15793 var valStr = propAndVal[2]; 15794 var prop = self.properties[propStr]; 15795 15796 if (!prop) { 15797 warn('Skipping property: Invalid property name in: ' + propAndValStr); // skip this property in the block 15798 15799 removePropAndValFromRem(); 15800 continue; 15801 } 15802 15803 var parsedProp = style.parse(propStr, valStr); 15804 15805 if (!parsedProp) { 15806 warn('Skipping property: Invalid property definition in: ' + propAndValStr); // skip this property in the block 15807 15808 removePropAndValFromRem(); 15809 continue; 15810 } 15811 15812 props.push({ 15813 name: propStr, 15814 val: valStr 15815 }); 15816 removePropAndValFromRem(); 15817 } 15818 15819 if (invalidBlock) { 15820 removeSelAndBlockFromRemaining(); 15821 break; 15822 } // put the parsed block in the style 15823 15824 15825 style.selector(selectorStr); 15826 15827 for (var i = 0; i < props.length; i++) { 15828 var _prop = props[i]; 15829 style.css(_prop.name, _prop.val); 15830 } 15831 15832 removeSelAndBlockFromRemaining(); 15833 } 15834 15835 return style; 15836 }; 15837 15838 styfn$5.fromString = function (string) { 15839 var style = this; 15840 style.resetToDefault(); 15841 style.appendFromString(string); 15842 return style; 15843 }; 15844 15845 var styfn$6 = {}; 15846 15847 (function () { 15848 var number = number$1; 15849 var rgba = rgbaNoBackRefs; 15850 var hsla = hslaNoBackRefs; 15851 var hex3$1 = hex3; 15852 var hex6$1 = hex6; 15853 15854 var data = function data(prefix) { 15855 return '^' + prefix + '\\s*\\(\\s*([\\w\\.]+)\\s*\\)$'; 15856 }; 15857 15858 var mapData = function mapData(prefix) { 15859 var mapArg = number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3$1 + '|' + hex6$1; 15860 return '^' + prefix + '\\s*\\(([\\w\\.]+)\\s*\\,\\s*(' + number + ')\\s*\\,\\s*(' + number + ')\\s*,\\s*(' + mapArg + ')\\s*\\,\\s*(' + mapArg + ')\\)$'; 15861 }; 15862 15863 var urlRegexes = ['^url\\s*\\(\\s*[\'"]?(.+?)[\'"]?\\s*\\)$', '^(none)$', '^(.+)$']; // each visual style property has a type and needs to be validated according to it 15864 15865 styfn$6.types = { 15866 time: { 15867 number: true, 15868 min: 0, 15869 units: 's|ms', 15870 implicitUnits: 'ms' 15871 }, 15872 percent: { 15873 number: true, 15874 min: 0, 15875 max: 100, 15876 units: '%', 15877 implicitUnits: '%' 15878 }, 15879 percentages: { 15880 number: true, 15881 min: 0, 15882 max: 100, 15883 units: '%', 15884 implicitUnits: '%', 15885 multiple: true 15886 }, 15887 zeroOneNumber: { 15888 number: true, 15889 min: 0, 15890 max: 1, 15891 unitless: true 15892 }, 15893 zeroOneNumbers: { 15894 number: true, 15895 min: 0, 15896 max: 1, 15897 unitless: true, 15898 multiple: true 15899 }, 15900 nOneOneNumber: { 15901 number: true, 15902 min: -1, 15903 max: 1, 15904 unitless: true 15905 }, 15906 nonNegativeInt: { 15907 number: true, 15908 min: 0, 15909 integer: true, 15910 unitless: true 15911 }, 15912 position: { 15913 enums: ['parent', 'origin'] 15914 }, 15915 nodeSize: { 15916 number: true, 15917 min: 0, 15918 enums: ['label'] 15919 }, 15920 number: { 15921 number: true, 15922 unitless: true 15923 }, 15924 numbers: { 15925 number: true, 15926 unitless: true, 15927 multiple: true 15928 }, 15929 positiveNumber: { 15930 number: true, 15931 unitless: true, 15932 min: 0, 15933 strictMin: true 15934 }, 15935 size: { 15936 number: true, 15937 min: 0 15938 }, 15939 bidirectionalSize: { 15940 number: true 15941 }, 15942 // allows negative 15943 bidirectionalSizes: { 15944 number: true, 15945 multiple: true 15946 }, 15947 // allows negative 15948 sizeMaybePercent: { 15949 number: true, 15950 min: 0, 15951 allowPercent: true 15952 }, 15953 axisDirection: { 15954 enums: ['horizontal', 'leftward', 'rightward', 'vertical', 'upward', 'downward', 'auto'] 15955 }, 15956 paddingRelativeTo: { 15957 enums: ['width', 'height', 'average', 'min', 'max'] 15958 }, 15959 bgWH: { 15960 number: true, 15961 min: 0, 15962 allowPercent: true, 15963 enums: ['auto'], 15964 multiple: true 15965 }, 15966 bgPos: { 15967 number: true, 15968 allowPercent: true, 15969 multiple: true 15970 }, 15971 bgRelativeTo: { 15972 enums: ['inner', 'include-padding'], 15973 multiple: true 15974 }, 15975 bgRepeat: { 15976 enums: ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'], 15977 multiple: true 15978 }, 15979 bgFit: { 15980 enums: ['none', 'contain', 'cover'], 15981 multiple: true 15982 }, 15983 bgCrossOrigin: { 15984 enums: ['anonymous', 'use-credentials'], 15985 multiple: true 15986 }, 15987 bgClip: { 15988 enums: ['none', 'node'], 15989 multiple: true 15990 }, 15991 color: { 15992 color: true 15993 }, 15994 colors: { 15995 color: true, 15996 multiple: true 15997 }, 15998 fill: { 15999 enums: ['solid', 'linear-gradient', 'radial-gradient'] 16000 }, 16001 bool: { 16002 enums: ['yes', 'no'] 16003 }, 16004 lineStyle: { 16005 enums: ['solid', 'dotted', 'dashed'] 16006 }, 16007 lineCap: { 16008 enums: ['butt', 'round', 'square'] 16009 }, 16010 borderStyle: { 16011 enums: ['solid', 'dotted', 'dashed', 'double'] 16012 }, 16013 curveStyle: { 16014 enums: ['bezier', 'unbundled-bezier', 'haystack', 'segments', 'straight', 'taxi'] 16015 }, 16016 fontFamily: { 16017 regex: '^([\\w- \\"]+(?:\\s*,\\s*[\\w- \\"]+)*)$' 16018 }, 16019 fontStyle: { 16020 enums: ['italic', 'normal', 'oblique'] 16021 }, 16022 fontWeight: { 16023 enums: ['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '800', '900', 100, 200, 300, 400, 500, 600, 700, 800, 900] 16024 }, 16025 textDecoration: { 16026 enums: ['none', 'underline', 'overline', 'line-through'] 16027 }, 16028 textTransform: { 16029 enums: ['none', 'uppercase', 'lowercase'] 16030 }, 16031 textWrap: { 16032 enums: ['none', 'wrap', 'ellipsis'] 16033 }, 16034 textOverflowWrap: { 16035 enums: ['whitespace', 'anywhere'] 16036 }, 16037 textBackgroundShape: { 16038 enums: ['rectangle', 'roundrectangle', 'round-rectangle'] 16039 }, 16040 nodeShape: { 16041 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'] 16042 }, 16043 compoundIncludeLabels: { 16044 enums: ['include', 'exclude'] 16045 }, 16046 arrowShape: { 16047 enums: ['tee', 'triangle', 'triangle-tee', 'triangle-cross', 'triangle-backcurve', 'vee', 'square', 'circle', 'diamond', 'chevron', 'none'] 16048 }, 16049 arrowFill: { 16050 enums: ['filled', 'hollow'] 16051 }, 16052 display: { 16053 enums: ['element', 'none'] 16054 }, 16055 visibility: { 16056 enums: ['hidden', 'visible'] 16057 }, 16058 zCompoundDepth: { 16059 enums: ['bottom', 'orphan', 'auto', 'top'] 16060 }, 16061 zIndexCompare: { 16062 enums: ['auto', 'manual'] 16063 }, 16064 valign: { 16065 enums: ['top', 'center', 'bottom'] 16066 }, 16067 halign: { 16068 enums: ['left', 'center', 'right'] 16069 }, 16070 justification: { 16071 enums: ['left', 'center', 'right', 'auto'] 16072 }, 16073 text: { 16074 string: true 16075 }, 16076 data: { 16077 mapping: true, 16078 regex: data('data') 16079 }, 16080 layoutData: { 16081 mapping: true, 16082 regex: data('layoutData') 16083 }, 16084 scratch: { 16085 mapping: true, 16086 regex: data('scratch') 16087 }, 16088 mapData: { 16089 mapping: true, 16090 regex: mapData('mapData') 16091 }, 16092 mapLayoutData: { 16093 mapping: true, 16094 regex: mapData('mapLayoutData') 16095 }, 16096 mapScratch: { 16097 mapping: true, 16098 regex: mapData('mapScratch') 16099 }, 16100 fn: { 16101 mapping: true, 16102 fn: true 16103 }, 16104 url: { 16105 regexes: urlRegexes, 16106 singleRegexMatchValue: true 16107 }, 16108 urls: { 16109 regexes: urlRegexes, 16110 singleRegexMatchValue: true, 16111 multiple: true 16112 }, 16113 propList: { 16114 propList: true 16115 }, 16116 angle: { 16117 number: true, 16118 units: 'deg|rad', 16119 implicitUnits: 'rad' 16120 }, 16121 textRotation: { 16122 number: true, 16123 units: 'deg|rad', 16124 implicitUnits: 'rad', 16125 enums: ['none', 'autorotate'] 16126 }, 16127 polygonPointList: { 16128 number: true, 16129 multiple: true, 16130 evenMultiple: true, 16131 min: -1, 16132 max: 1, 16133 unitless: true 16134 }, 16135 edgeDistances: { 16136 enums: ['intersection', 'node-position'] 16137 }, 16138 edgeEndpoint: { 16139 number: true, 16140 multiple: true, 16141 units: '%|px|em|deg|rad', 16142 implicitUnits: 'px', 16143 enums: ['inside-to-node', 'outside-to-node', 'outside-to-node-or-label', 'outside-to-line', 'outside-to-line-or-label'], 16144 singleEnum: true, 16145 validate: function validate(valArr, unitsArr) { 16146 switch (valArr.length) { 16147 case 2: 16148 // can be % or px only 16149 return unitsArr[0] !== 'deg' && unitsArr[0] !== 'rad' && unitsArr[1] !== 'deg' && unitsArr[1] !== 'rad'; 16150 16151 case 1: 16152 // can be enum, deg, or rad only 16153 return string(valArr[0]) || unitsArr[0] === 'deg' || unitsArr[0] === 'rad'; 16154 16155 default: 16156 return false; 16157 } 16158 } 16159 }, 16160 easing: { 16161 regexes: ['^(spring)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$', '^(cubic-bezier)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$'], 16162 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'] 16163 }, 16164 gradientDirection: { 16165 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'] 16166 }, 16167 boundsExpansion: { 16168 number: true, 16169 multiple: true, 16170 min: 0, 16171 validate: function validate(valArr) { 16172 var length = valArr.length; 16173 return length === 1 || length === 2 || length === 4; 16174 } 16175 } 16176 }; 16177 var diff = { 16178 zeroNonZero: function zeroNonZero(val1, val2) { 16179 if ((val1 == null || val2 == null) && val1 !== val2) { 16180 return true; // null cases could represent any value 16181 } 16182 16183 if (val1 == 0 && val2 != 0) { 16184 return true; 16185 } else if (val1 != 0 && val2 == 0) { 16186 return true; 16187 } else { 16188 return false; 16189 } 16190 }, 16191 any: function any(val1, val2) { 16192 return val1 != val2; 16193 } 16194 }; // define visual style properties 16195 // 16196 // - n.b. adding a new group of props may require updates to updateStyleHints() 16197 // - adding new props to an existing group gets handled automatically 16198 16199 var t = styfn$6.types; 16200 var mainLabel = [{ 16201 name: 'label', 16202 type: t.text, 16203 triggersBounds: diff.any 16204 }, { 16205 name: 'text-rotation', 16206 type: t.textRotation, 16207 triggersBounds: diff.any 16208 }, { 16209 name: 'text-margin-x', 16210 type: t.bidirectionalSize, 16211 triggersBounds: diff.any 16212 }, { 16213 name: 'text-margin-y', 16214 type: t.bidirectionalSize, 16215 triggersBounds: diff.any 16216 }]; 16217 var sourceLabel = [{ 16218 name: 'source-label', 16219 type: t.text, 16220 triggersBounds: diff.any 16221 }, { 16222 name: 'source-text-rotation', 16223 type: t.textRotation, 16224 triggersBounds: diff.any 16225 }, { 16226 name: 'source-text-margin-x', 16227 type: t.bidirectionalSize, 16228 triggersBounds: diff.any 16229 }, { 16230 name: 'source-text-margin-y', 16231 type: t.bidirectionalSize, 16232 triggersBounds: diff.any 16233 }, { 16234 name: 'source-text-offset', 16235 type: t.size, 16236 triggersBounds: diff.any 16237 }]; 16238 var targetLabel = [{ 16239 name: 'target-label', 16240 type: t.text, 16241 triggersBounds: diff.any 16242 }, { 16243 name: 'target-text-rotation', 16244 type: t.textRotation, 16245 triggersBounds: diff.any 16246 }, { 16247 name: 'target-text-margin-x', 16248 type: t.bidirectionalSize, 16249 triggersBounds: diff.any 16250 }, { 16251 name: 'target-text-margin-y', 16252 type: t.bidirectionalSize, 16253 triggersBounds: diff.any 16254 }, { 16255 name: 'target-text-offset', 16256 type: t.size, 16257 triggersBounds: diff.any 16258 }]; 16259 var labelDimensions = [{ 16260 name: 'font-family', 16261 type: t.fontFamily, 16262 triggersBounds: diff.any 16263 }, { 16264 name: 'font-style', 16265 type: t.fontStyle, 16266 triggersBounds: diff.any 16267 }, { 16268 name: 'font-weight', 16269 type: t.fontWeight, 16270 triggersBounds: diff.any 16271 }, { 16272 name: 'font-size', 16273 type: t.size, 16274 triggersBounds: diff.any 16275 }, { 16276 name: 'text-transform', 16277 type: t.textTransform, 16278 triggersBounds: diff.any 16279 }, { 16280 name: 'text-wrap', 16281 type: t.textWrap, 16282 triggersBounds: diff.any 16283 }, { 16284 name: 'text-overflow-wrap', 16285 type: t.textOverflowWrap, 16286 triggersBounds: diff.any 16287 }, { 16288 name: 'text-max-width', 16289 type: t.size, 16290 triggersBounds: diff.any 16291 }, { 16292 name: 'text-outline-width', 16293 type: t.size, 16294 triggersBounds: diff.any 16295 }, { 16296 name: 'line-height', 16297 type: t.positiveNumber, 16298 triggersBounds: diff.any 16299 }]; 16300 var commonLabel = [{ 16301 name: 'text-valign', 16302 type: t.valign, 16303 triggersBounds: diff.any 16304 }, { 16305 name: 'text-halign', 16306 type: t.halign, 16307 triggersBounds: diff.any 16308 }, { 16309 name: 'color', 16310 type: t.color 16311 }, { 16312 name: 'text-outline-color', 16313 type: t.color 16314 }, { 16315 name: 'text-outline-opacity', 16316 type: t.zeroOneNumber 16317 }, { 16318 name: 'text-background-color', 16319 type: t.color 16320 }, { 16321 name: 'text-background-opacity', 16322 type: t.zeroOneNumber 16323 }, { 16324 name: 'text-background-padding', 16325 type: t.size, 16326 triggersBounds: diff.any 16327 }, { 16328 name: 'text-border-opacity', 16329 type: t.zeroOneNumber 16330 }, { 16331 name: 'text-border-color', 16332 type: t.color 16333 }, { 16334 name: 'text-border-width', 16335 type: t.size, 16336 triggersBounds: diff.any 16337 }, { 16338 name: 'text-border-style', 16339 type: t.borderStyle, 16340 triggersBounds: diff.any 16341 }, { 16342 name: 'text-background-shape', 16343 type: t.textBackgroundShape, 16344 triggersBounds: diff.any 16345 }, { 16346 name: 'text-justification', 16347 type: t.justification 16348 }]; 16349 var behavior = [{ 16350 name: 'events', 16351 type: t.bool 16352 }, { 16353 name: 'text-events', 16354 type: t.bool 16355 }]; 16356 var visibility = [{ 16357 name: 'display', 16358 type: t.display, 16359 triggersZOrder: diff.any, 16360 triggersBounds: diff.any, 16361 triggersBoundsOfParallelBeziers: true 16362 }, { 16363 name: 'visibility', 16364 type: t.visibility, 16365 triggersZOrder: diff.any 16366 }, { 16367 name: 'opacity', 16368 type: t.zeroOneNumber, 16369 triggersZOrder: diff.zeroNonZero 16370 }, { 16371 name: 'text-opacity', 16372 type: t.zeroOneNumber 16373 }, { 16374 name: 'min-zoomed-font-size', 16375 type: t.size 16376 }, { 16377 name: 'z-compound-depth', 16378 type: t.zCompoundDepth, 16379 triggersZOrder: diff.any 16380 }, { 16381 name: 'z-index-compare', 16382 type: t.zIndexCompare, 16383 triggersZOrder: diff.any 16384 }, { 16385 name: 'z-index', 16386 type: t.nonNegativeInt, 16387 triggersZOrder: diff.any 16388 }]; 16389 var overlay = [{ 16390 name: 'overlay-padding', 16391 type: t.size, 16392 triggersBounds: diff.any 16393 }, { 16394 name: 'overlay-color', 16395 type: t.color 16396 }, { 16397 name: 'overlay-opacity', 16398 type: t.zeroOneNumber, 16399 triggersBounds: diff.zeroNonZero 16400 }]; 16401 var transition = [{ 16402 name: 'transition-property', 16403 type: t.propList 16404 }, { 16405 name: 'transition-duration', 16406 type: t.time 16407 }, { 16408 name: 'transition-delay', 16409 type: t.time 16410 }, { 16411 name: 'transition-timing-function', 16412 type: t.easing 16413 }]; 16414 16415 var nodeSizeHashOverride = function nodeSizeHashOverride(ele, parsedProp) { 16416 if (parsedProp.value === 'label') { 16417 return -ele.poolIndex(); // no hash key hits is using label size (hitrate for perf probably low anyway) 16418 } else { 16419 return parsedProp.pfValue; 16420 } 16421 }; 16422 16423 var nodeBody = [{ 16424 name: 'height', 16425 type: t.nodeSize, 16426 triggersBounds: diff.any, 16427 hashOverride: nodeSizeHashOverride 16428 }, { 16429 name: 'width', 16430 type: t.nodeSize, 16431 triggersBounds: diff.any, 16432 hashOverride: nodeSizeHashOverride 16433 }, { 16434 name: 'shape', 16435 type: t.nodeShape, 16436 triggersBounds: diff.any 16437 }, { 16438 name: 'shape-polygon-points', 16439 type: t.polygonPointList, 16440 triggersBounds: diff.any 16441 }, { 16442 name: 'background-color', 16443 type: t.color 16444 }, { 16445 name: 'background-fill', 16446 type: t.fill 16447 }, { 16448 name: 'background-opacity', 16449 type: t.zeroOneNumber 16450 }, { 16451 name: 'background-blacken', 16452 type: t.nOneOneNumber 16453 }, { 16454 name: 'background-gradient-stop-colors', 16455 type: t.colors 16456 }, { 16457 name: 'background-gradient-stop-positions', 16458 type: t.percentages 16459 }, { 16460 name: 'background-gradient-direction', 16461 type: t.gradientDirection 16462 }, { 16463 name: 'padding', 16464 type: t.sizeMaybePercent, 16465 triggersBounds: diff.any 16466 }, { 16467 name: 'padding-relative-to', 16468 type: t.paddingRelativeTo, 16469 triggersBounds: diff.any 16470 }, { 16471 name: 'bounds-expansion', 16472 type: t.boundsExpansion, 16473 triggersBounds: diff.any 16474 }]; 16475 var nodeBorder = [{ 16476 name: 'border-color', 16477 type: t.color 16478 }, { 16479 name: 'border-opacity', 16480 type: t.zeroOneNumber 16481 }, { 16482 name: 'border-width', 16483 type: t.size, 16484 triggersBounds: diff.any 16485 }, { 16486 name: 'border-style', 16487 type: t.borderStyle 16488 }]; 16489 var backgroundImage = [{ 16490 name: 'background-image', 16491 type: t.urls 16492 }, { 16493 name: 'background-image-crossorigin', 16494 type: t.bgCrossOrigin 16495 }, { 16496 name: 'background-image-opacity', 16497 type: t.zeroOneNumbers 16498 }, { 16499 name: 'background-position-x', 16500 type: t.bgPos 16501 }, { 16502 name: 'background-position-y', 16503 type: t.bgPos 16504 }, { 16505 name: 'background-width-relative-to', 16506 type: t.bgRelativeTo 16507 }, { 16508 name: 'background-height-relative-to', 16509 type: t.bgRelativeTo 16510 }, { 16511 name: 'background-repeat', 16512 type: t.bgRepeat 16513 }, { 16514 name: 'background-fit', 16515 type: t.bgFit 16516 }, { 16517 name: 'background-clip', 16518 type: t.bgClip 16519 }, { 16520 name: 'background-width', 16521 type: t.bgWH 16522 }, { 16523 name: 'background-height', 16524 type: t.bgWH 16525 }, { 16526 name: 'background-offset-x', 16527 type: t.bgPos 16528 }, { 16529 name: 'background-offset-y', 16530 type: t.bgPos 16531 }]; 16532 var compound = [{ 16533 name: 'position', 16534 type: t.position, 16535 triggersBounds: diff.any 16536 }, { 16537 name: 'compound-sizing-wrt-labels', 16538 type: t.compoundIncludeLabels, 16539 triggersBounds: diff.any 16540 }, { 16541 name: 'min-width', 16542 type: t.size, 16543 triggersBounds: diff.any 16544 }, { 16545 name: 'min-width-bias-left', 16546 type: t.sizeMaybePercent, 16547 triggersBounds: diff.any 16548 }, { 16549 name: 'min-width-bias-right', 16550 type: t.sizeMaybePercent, 16551 triggersBounds: diff.any 16552 }, { 16553 name: 'min-height', 16554 type: t.size, 16555 triggersBounds: diff.any 16556 }, { 16557 name: 'min-height-bias-top', 16558 type: t.sizeMaybePercent, 16559 triggersBounds: diff.any 16560 }, { 16561 name: 'min-height-bias-bottom', 16562 type: t.sizeMaybePercent, 16563 triggersBounds: diff.any 16564 }]; 16565 var edgeLine = [{ 16566 name: 'line-style', 16567 type: t.lineStyle 16568 }, { 16569 name: 'line-color', 16570 type: t.color 16571 }, { 16572 name: 'line-fill', 16573 type: t.fill 16574 }, { 16575 name: 'line-cap', 16576 type: t.lineCap 16577 }, { 16578 name: 'line-dash-pattern', 16579 type: t.numbers 16580 }, { 16581 name: 'line-dash-offset', 16582 type: t.number 16583 }, { 16584 name: 'line-gradient-stop-colors', 16585 type: t.colors 16586 }, { 16587 name: 'line-gradient-stop-positions', 16588 type: t.percentages 16589 }, { 16590 name: 'curve-style', 16591 type: t.curveStyle, 16592 triggersBounds: diff.any, 16593 triggersBoundsOfParallelBeziers: true 16594 }, { 16595 name: 'haystack-radius', 16596 type: t.zeroOneNumber, 16597 triggersBounds: diff.any 16598 }, { 16599 name: 'source-endpoint', 16600 type: t.edgeEndpoint, 16601 triggersBounds: diff.any 16602 }, { 16603 name: 'target-endpoint', 16604 type: t.edgeEndpoint, 16605 triggersBounds: diff.any 16606 }, { 16607 name: 'control-point-step-size', 16608 type: t.size, 16609 triggersBounds: diff.any 16610 }, { 16611 name: 'control-point-distances', 16612 type: t.bidirectionalSizes, 16613 triggersBounds: diff.any 16614 }, { 16615 name: 'control-point-weights', 16616 type: t.numbers, 16617 triggersBounds: diff.any 16618 }, { 16619 name: 'segment-distances', 16620 type: t.bidirectionalSizes, 16621 triggersBounds: diff.any 16622 }, { 16623 name: 'segment-weights', 16624 type: t.numbers, 16625 triggersBounds: diff.any 16626 }, { 16627 name: 'taxi-turn', 16628 type: t.sizeMaybePercent, 16629 triggersBounds: diff.any 16630 }, { 16631 name: 'taxi-turn-min-distance', 16632 type: t.size, 16633 triggersBounds: diff.any 16634 }, { 16635 name: 'taxi-direction', 16636 type: t.axisDirection, 16637 triggersBounds: diff.any 16638 }, { 16639 name: 'edge-distances', 16640 type: t.edgeDistances, 16641 triggersBounds: diff.any 16642 }, { 16643 name: 'arrow-scale', 16644 type: t.positiveNumber, 16645 triggersBounds: diff.any 16646 }, { 16647 name: 'loop-direction', 16648 type: t.angle, 16649 triggersBounds: diff.any 16650 }, { 16651 name: 'loop-sweep', 16652 type: t.angle, 16653 triggersBounds: diff.any 16654 }, { 16655 name: 'source-distance-from-node', 16656 type: t.size, 16657 triggersBounds: diff.any 16658 }, { 16659 name: 'target-distance-from-node', 16660 type: t.size, 16661 triggersBounds: diff.any 16662 }]; 16663 var ghost = [{ 16664 name: 'ghost', 16665 type: t.bool, 16666 triggersBounds: diff.any 16667 }, { 16668 name: 'ghost-offset-x', 16669 type: t.bidirectionalSize, 16670 triggersBounds: diff.any 16671 }, { 16672 name: 'ghost-offset-y', 16673 type: t.bidirectionalSize, 16674 triggersBounds: diff.any 16675 }, { 16676 name: 'ghost-opacity', 16677 type: t.zeroOneNumber 16678 }]; 16679 var core = [{ 16680 name: 'selection-box-color', 16681 type: t.color 16682 }, { 16683 name: 'selection-box-opacity', 16684 type: t.zeroOneNumber 16685 }, { 16686 name: 'selection-box-border-color', 16687 type: t.color 16688 }, { 16689 name: 'selection-box-border-width', 16690 type: t.size 16691 }, { 16692 name: 'active-bg-color', 16693 type: t.color 16694 }, { 16695 name: 'active-bg-opacity', 16696 type: t.zeroOneNumber 16697 }, { 16698 name: 'active-bg-size', 16699 type: t.size 16700 }, { 16701 name: 'outside-texture-bg-color', 16702 type: t.color 16703 }, { 16704 name: 'outside-texture-bg-opacity', 16705 type: t.zeroOneNumber 16706 }]; // pie backgrounds for nodes 16707 16708 var pie = []; 16709 styfn$6.pieBackgroundN = 16; // because the pie properties are numbered, give access to a constant N (for renderer use) 16710 16711 pie.push({ 16712 name: 'pie-size', 16713 type: t.sizeMaybePercent 16714 }); 16715 16716 for (var i = 1; i <= styfn$6.pieBackgroundN; i++) { 16717 pie.push({ 16718 name: 'pie-' + i + '-background-color', 16719 type: t.color 16720 }); 16721 pie.push({ 16722 name: 'pie-' + i + '-background-size', 16723 type: t.percent 16724 }); 16725 pie.push({ 16726 name: 'pie-' + i + '-background-opacity', 16727 type: t.zeroOneNumber 16728 }); 16729 } // edge arrows 16730 16731 16732 var edgeArrow = []; 16733 var arrowPrefixes = styfn$6.arrowPrefixes = ['source', 'mid-source', 'target', 'mid-target']; 16734 [{ 16735 name: 'arrow-shape', 16736 type: t.arrowShape, 16737 triggersBounds: diff.any 16738 }, { 16739 name: 'arrow-color', 16740 type: t.color 16741 }, { 16742 name: 'arrow-fill', 16743 type: t.arrowFill 16744 }].forEach(function (prop) { 16745 arrowPrefixes.forEach(function (prefix) { 16746 var name = prefix + '-' + prop.name; 16747 var type = prop.type, 16748 triggersBounds = prop.triggersBounds; 16749 edgeArrow.push({ 16750 name: name, 16751 type: type, 16752 triggersBounds: triggersBounds 16753 }); 16754 }); 16755 }, {}); 16756 var props = styfn$6.properties = [].concat(behavior, transition, visibility, overlay, ghost, commonLabel, labelDimensions, mainLabel, sourceLabel, targetLabel, nodeBody, nodeBorder, backgroundImage, pie, compound, edgeLine, edgeArrow, core); 16757 var propGroups = styfn$6.propertyGroups = { 16758 // common to all eles 16759 behavior: behavior, 16760 transition: transition, 16761 visibility: visibility, 16762 overlay: overlay, 16763 ghost: ghost, 16764 // labels 16765 commonLabel: commonLabel, 16766 labelDimensions: labelDimensions, 16767 mainLabel: mainLabel, 16768 sourceLabel: sourceLabel, 16769 targetLabel: targetLabel, 16770 // node props 16771 nodeBody: nodeBody, 16772 nodeBorder: nodeBorder, 16773 backgroundImage: backgroundImage, 16774 pie: pie, 16775 compound: compound, 16776 // edge props 16777 edgeLine: edgeLine, 16778 edgeArrow: edgeArrow, 16779 core: core 16780 }; 16781 var propGroupNames = styfn$6.propertyGroupNames = {}; 16782 var propGroupKeys = styfn$6.propertyGroupKeys = Object.keys(propGroups); 16783 propGroupKeys.forEach(function (key) { 16784 propGroupNames[key] = propGroups[key].map(function (prop) { 16785 return prop.name; 16786 }); 16787 propGroups[key].forEach(function (prop) { 16788 return prop.groupKey = key; 16789 }); 16790 }); // define aliases 16791 16792 var aliases = styfn$6.aliases = [{ 16793 name: 'content', 16794 pointsTo: 'label' 16795 }, { 16796 name: 'control-point-distance', 16797 pointsTo: 'control-point-distances' 16798 }, { 16799 name: 'control-point-weight', 16800 pointsTo: 'control-point-weights' 16801 }, { 16802 name: 'edge-text-rotation', 16803 pointsTo: 'text-rotation' 16804 }, { 16805 name: 'padding-left', 16806 pointsTo: 'padding' 16807 }, { 16808 name: 'padding-right', 16809 pointsTo: 'padding' 16810 }, { 16811 name: 'padding-top', 16812 pointsTo: 'padding' 16813 }, { 16814 name: 'padding-bottom', 16815 pointsTo: 'padding' 16816 }]; // list of property names 16817 16818 styfn$6.propertyNames = props.map(function (p) { 16819 return p.name; 16820 }); // allow access of properties by name ( e.g. style.properties.height ) 16821 16822 for (var _i = 0; _i < props.length; _i++) { 16823 var prop = props[_i]; 16824 props[prop.name] = prop; // allow lookup by name 16825 } // map aliases 16826 16827 16828 for (var _i2 = 0; _i2 < aliases.length; _i2++) { 16829 var alias = aliases[_i2]; 16830 var pointsToProp = props[alias.pointsTo]; 16831 var aliasProp = { 16832 name: alias.name, 16833 alias: true, 16834 pointsTo: pointsToProp 16835 }; // add alias prop for parsing 16836 16837 props.push(aliasProp); 16838 props[alias.name] = aliasProp; // allow lookup by name 16839 } 16840 })(); 16841 16842 styfn$6.getDefaultProperty = function (name) { 16843 return this.getDefaultProperties()[name]; 16844 }; 16845 16846 styfn$6.getDefaultProperties = function () { 16847 var _p = this._private; 16848 16849 if (_p.defaultProperties != null) { 16850 return _p.defaultProperties; 16851 } 16852 16853 var rawProps = extend({ 16854 // core props 16855 'selection-box-color': '#ddd', 16856 'selection-box-opacity': 0.65, 16857 'selection-box-border-color': '#aaa', 16858 'selection-box-border-width': 1, 16859 'active-bg-color': 'black', 16860 'active-bg-opacity': 0.15, 16861 'active-bg-size': 30, 16862 'outside-texture-bg-color': '#000', 16863 'outside-texture-bg-opacity': 0.125, 16864 // common node/edge props 16865 'events': 'yes', 16866 'text-events': 'no', 16867 'text-valign': 'top', 16868 'text-halign': 'center', 16869 'text-justification': 'auto', 16870 'line-height': 1, 16871 'color': '#000', 16872 'text-outline-color': '#000', 16873 'text-outline-width': 0, 16874 'text-outline-opacity': 1, 16875 'text-opacity': 1, 16876 'text-decoration': 'none', 16877 'text-transform': 'none', 16878 'text-wrap': 'none', 16879 'text-overflow-wrap': 'whitespace', 16880 'text-max-width': 9999, 16881 'text-background-color': '#000', 16882 'text-background-opacity': 0, 16883 'text-background-shape': 'rectangle', 16884 'text-background-padding': 0, 16885 'text-border-opacity': 0, 16886 'text-border-width': 0, 16887 'text-border-style': 'solid', 16888 'text-border-color': '#000', 16889 'font-family': 'Helvetica Neue, Helvetica, sans-serif', 16890 'font-style': 'normal', 16891 'font-weight': 'normal', 16892 'font-size': 16, 16893 'min-zoomed-font-size': 0, 16894 'text-rotation': 'none', 16895 'source-text-rotation': 'none', 16896 'target-text-rotation': 'none', 16897 'visibility': 'visible', 16898 'display': 'element', 16899 'opacity': 1, 16900 'z-compound-depth': 'auto', 16901 'z-index-compare': 'auto', 16902 'z-index': 0, 16903 'label': '', 16904 'text-margin-x': 0, 16905 'text-margin-y': 0, 16906 'source-label': '', 16907 'source-text-offset': 0, 16908 'source-text-margin-x': 0, 16909 'source-text-margin-y': 0, 16910 'target-label': '', 16911 'target-text-offset': 0, 16912 'target-text-margin-x': 0, 16913 'target-text-margin-y': 0, 16914 'overlay-opacity': 0, 16915 'overlay-color': '#000', 16916 'overlay-padding': 10, 16917 'transition-property': 'none', 16918 'transition-duration': 0, 16919 'transition-delay': 0, 16920 'transition-timing-function': 'linear', 16921 // node props 16922 'background-blacken': 0, 16923 'background-color': '#999', 16924 'background-fill': 'solid', 16925 'background-opacity': 1, 16926 'background-image': 'none', 16927 'background-image-crossorigin': 'anonymous', 16928 'background-image-opacity': 1, 16929 'background-position-x': '50%', 16930 'background-position-y': '50%', 16931 'background-offset-x': 0, 16932 'background-offset-y': 0, 16933 'background-width-relative-to': 'include-padding', 16934 'background-height-relative-to': 'include-padding', 16935 'background-repeat': 'no-repeat', 16936 'background-fit': 'none', 16937 'background-clip': 'node', 16938 'background-width': 'auto', 16939 'background-height': 'auto', 16940 'border-color': '#000', 16941 'border-opacity': 1, 16942 'border-width': 0, 16943 'border-style': 'solid', 16944 'height': 30, 16945 'width': 30, 16946 'shape': 'ellipse', 16947 'shape-polygon-points': '-1, -1, 1, -1, 1, 1, -1, 1', 16948 'bounds-expansion': 0, 16949 // node gradient 16950 'background-gradient-direction': 'to-bottom', 16951 'background-gradient-stop-colors': '#999', 16952 'background-gradient-stop-positions': '0%', 16953 // ghost props 16954 'ghost': 'no', 16955 'ghost-offset-y': 0, 16956 'ghost-offset-x': 0, 16957 'ghost-opacity': 0, 16958 // compound props 16959 'padding': 0, 16960 'padding-relative-to': 'width', 16961 'position': 'origin', 16962 'compound-sizing-wrt-labels': 'include', 16963 'min-width': 0, 16964 'min-width-bias-left': 0, 16965 'min-width-bias-right': 0, 16966 'min-height': 0, 16967 'min-height-bias-top': 0, 16968 'min-height-bias-bottom': 0 16969 }, { 16970 // node pie bg 16971 'pie-size': '100%' 16972 }, [{ 16973 name: 'pie-{{i}}-background-color', 16974 value: 'black' 16975 }, { 16976 name: 'pie-{{i}}-background-size', 16977 value: '0%' 16978 }, { 16979 name: 'pie-{{i}}-background-opacity', 16980 value: 1 16981 }].reduce(function (css, prop) { 16982 for (var i = 1; i <= styfn$6.pieBackgroundN; i++) { 16983 var name = prop.name.replace('{{i}}', i); 16984 var val = prop.value; 16985 css[name] = val; 16986 } 16987 16988 return css; 16989 }, {}), { 16990 // edge props 16991 'line-style': 'solid', 16992 'line-color': '#999', 16993 'line-fill': 'solid', 16994 'line-cap': 'butt', 16995 'line-gradient-stop-colors': '#999', 16996 'line-gradient-stop-positions': '0%', 16997 'control-point-step-size': 40, 16998 'control-point-weights': 0.5, 16999 'segment-weights': 0.5, 17000 'segment-distances': 20, 17001 'taxi-turn': '50%', 17002 'taxi-turn-min-distance': 10, 17003 'taxi-direction': 'auto', 17004 'edge-distances': 'intersection', 17005 'curve-style': 'haystack', 17006 'haystack-radius': 0, 17007 'arrow-scale': 1, 17008 'loop-direction': '-45deg', 17009 'loop-sweep': '-90deg', 17010 'source-distance-from-node': 0, 17011 'target-distance-from-node': 0, 17012 'source-endpoint': 'outside-to-node', 17013 'target-endpoint': 'outside-to-node', 17014 'line-dash-pattern': [6, 3], 17015 'line-dash-offset': 0 17016 }, [{ 17017 name: 'arrow-shape', 17018 value: 'none' 17019 }, { 17020 name: 'arrow-color', 17021 value: '#999' 17022 }, { 17023 name: 'arrow-fill', 17024 value: 'filled' 17025 }].reduce(function (css, prop) { 17026 styfn$6.arrowPrefixes.forEach(function (prefix) { 17027 var name = prefix + '-' + prop.name; 17028 var val = prop.value; 17029 css[name] = val; 17030 }); 17031 return css; 17032 }, {})); 17033 var parsedProps = {}; 17034 17035 for (var i = 0; i < this.properties.length; i++) { 17036 var prop = this.properties[i]; 17037 17038 if (prop.pointsTo) { 17039 continue; 17040 } 17041 17042 var name = prop.name; 17043 var val = rawProps[name]; 17044 var parsedProp = this.parse(name, val); 17045 parsedProps[name] = parsedProp; 17046 } 17047 17048 _p.defaultProperties = parsedProps; 17049 return _p.defaultProperties; 17050 }; 17051 17052 styfn$6.addDefaultStylesheet = function () { 17053 this.selector(':parent').css({ 17054 'shape': 'rectangle', 17055 'padding': 10, 17056 'background-color': '#eee', 17057 'border-color': '#ccc', 17058 'border-width': 1 17059 }).selector('edge').css({ 17060 'width': 3 17061 }).selector(':loop').css({ 17062 'curve-style': 'bezier' 17063 }).selector('edge:compound').css({ 17064 'curve-style': 'bezier', 17065 'source-endpoint': 'outside-to-line', 17066 'target-endpoint': 'outside-to-line' 17067 }).selector(':selected').css({ 17068 'background-color': '#0169D9', 17069 'line-color': '#0169D9', 17070 'source-arrow-color': '#0169D9', 17071 'target-arrow-color': '#0169D9', 17072 'mid-source-arrow-color': '#0169D9', 17073 'mid-target-arrow-color': '#0169D9' 17074 }).selector(':parent:selected').css({ 17075 'background-color': '#CCE1F9', 17076 'border-color': '#aec8e5' 17077 }).selector(':active').css({ 17078 'overlay-color': 'black', 17079 'overlay-padding': 10, 17080 'overlay-opacity': 0.25 17081 }); 17082 this.defaultLength = this.length; 17083 }; 17084 17085 var styfn$7 = {}; // a caching layer for property parsing 17086 17087 styfn$7.parse = function (name, value, propIsBypass, propIsFlat) { 17088 var self = this; // function values can't be cached in all cases, and there isn't much benefit of caching them anyway 17089 17090 if (fn(value)) { 17091 return self.parseImplWarn(name, value, propIsBypass, propIsFlat); 17092 } 17093 17094 var flatKey = propIsFlat === 'mapping' || propIsFlat === true || propIsFlat === false || propIsFlat == null ? 'dontcare' : propIsFlat; 17095 var bypassKey = propIsBypass ? 't' : 'f'; 17096 var valueKey = '' + value; 17097 var argHash = hashStrings(name, valueKey, bypassKey, flatKey); 17098 var propCache = self.propCache = self.propCache || []; 17099 var ret; 17100 17101 if (!(ret = propCache[argHash])) { 17102 ret = propCache[argHash] = self.parseImplWarn(name, value, propIsBypass, propIsFlat); 17103 } // - bypasses can't be shared b/c the value can be changed by animations or otherwise overridden 17104 // - mappings can't be shared b/c mappings are per-element 17105 17106 17107 if (propIsBypass || propIsFlat === 'mapping') { 17108 // need a copy since props are mutated later in their lifecycles 17109 ret = copy(ret); 17110 17111 if (ret) { 17112 ret.value = copy(ret.value); // because it could be an array, e.g. colour 17113 } 17114 } 17115 17116 return ret; 17117 }; 17118 17119 styfn$7.parseImplWarn = function (name, value, propIsBypass, propIsFlat) { 17120 var prop = this.parseImpl(name, value, propIsBypass, propIsFlat); 17121 17122 if (!prop && value != null) { 17123 warn("The style property `".concat(name, ": ").concat(value, "` is invalid")); 17124 } 17125 17126 return prop; 17127 }; // parse a property; return null on invalid; return parsed property otherwise 17128 // fields : 17129 // - name : the name of the property 17130 // - value : the parsed, native-typed value of the property 17131 // - strValue : a string value that represents the property value in valid css 17132 // - bypass : true iff the property is a bypass property 17133 17134 17135 styfn$7.parseImpl = function (name, value, propIsBypass, propIsFlat) { 17136 var self = this; 17137 name = camel2dash(name); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName') 17138 17139 var property = self.properties[name]; 17140 var passedValue = value; 17141 var types = self.types; 17142 17143 if (!property) { 17144 return null; 17145 } // return null on property of unknown name 17146 17147 17148 if (value === undefined) { 17149 return null; 17150 } // can't assign undefined 17151 // the property may be an alias 17152 17153 17154 if (property.alias) { 17155 property = property.pointsTo; 17156 name = property.name; 17157 } 17158 17159 var valueIsString = string(value); 17160 17161 if (valueIsString) { 17162 // trim the value to make parsing easier 17163 value = value.trim(); 17164 } 17165 17166 var type = property.type; 17167 17168 if (!type) { 17169 return null; 17170 } // no type, no luck 17171 // check if bypass is null or empty string (i.e. indication to delete bypass property) 17172 17173 17174 if (propIsBypass && (value === '' || value === null)) { 17175 return { 17176 name: name, 17177 value: value, 17178 bypass: true, 17179 deleteBypass: true 17180 }; 17181 } // check if value is a function used as a mapper 17182 17183 17184 if (fn(value)) { 17185 return { 17186 name: name, 17187 value: value, 17188 strValue: 'fn', 17189 mapped: types.fn, 17190 bypass: propIsBypass 17191 }; 17192 } // check if value is mapped 17193 17194 17195 var data, mapData; 17196 17197 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))) { 17198 if (propIsBypass) { 17199 return false; 17200 } // mappers not allowed in bypass 17201 17202 17203 var mapped = types.data; 17204 return { 17205 name: name, 17206 value: data, 17207 strValue: '' + value, 17208 mapped: mapped, 17209 field: data[1], 17210 bypass: propIsBypass 17211 }; 17212 } else if (value.length >= 10 && value[0] === 'm' && (mapData = new RegExp(types.mapData.regex).exec(value))) { 17213 if (propIsBypass) { 17214 return false; 17215 } // mappers not allowed in bypass 17216 17217 17218 if (type.multiple) { 17219 return false; 17220 } // impossible to map to num 17221 17222 17223 var _mapped = types.mapData; // we can map only if the type is a colour or a number 17224 17225 if (!(type.color || type.number)) { 17226 return false; 17227 } 17228 17229 var valueMin = this.parse(name, mapData[4]); // parse to validate 17230 17231 if (!valueMin || valueMin.mapped) { 17232 return false; 17233 } // can't be invalid or mapped 17234 17235 17236 var valueMax = this.parse(name, mapData[5]); // parse to validate 17237 17238 if (!valueMax || valueMax.mapped) { 17239 return false; 17240 } // can't be invalid or mapped 17241 // check if valueMin and valueMax are the same 17242 17243 17244 if (valueMin.pfValue === valueMax.pfValue || valueMin.strValue === valueMax.strValue) { 17245 warn('`' + name + ': ' + value + '` is not a valid mapper because the output range is zero; converting to `' + name + ': ' + valueMin.strValue + '`'); 17246 return this.parse(name, valueMin.strValue); // can't make much of a mapper without a range 17247 } else if (type.color) { 17248 var c1 = valueMin.value; 17249 var c2 = valueMax.value; 17250 var same = c1[0] === c2[0] // red 17251 && c1[1] === c2[1] // green 17252 && c1[2] === c2[2] // blue 17253 && ( // optional alpha 17254 c1[3] === c2[3] // same alpha outright 17255 || (c1[3] == null || c1[3] === 1) && ( // full opacity for colour 1? 17256 c2[3] == null || c2[3] === 1) // full opacity for colour 2? 17257 ); 17258 17259 if (same) { 17260 return false; 17261 } // can't make a mapper without a range 17262 17263 } 17264 17265 return { 17266 name: name, 17267 value: mapData, 17268 strValue: '' + value, 17269 mapped: _mapped, 17270 field: mapData[1], 17271 fieldMin: parseFloat(mapData[2]), 17272 // min & max are numeric 17273 fieldMax: parseFloat(mapData[3]), 17274 valueMin: valueMin.value, 17275 valueMax: valueMax.value, 17276 bypass: propIsBypass 17277 }; 17278 } 17279 17280 if (type.multiple && propIsFlat !== 'multiple') { 17281 var vals; 17282 17283 if (valueIsString) { 17284 vals = value.split(/\s+/); 17285 } else if (array(value)) { 17286 vals = value; 17287 } else { 17288 vals = [value]; 17289 } 17290 17291 if (type.evenMultiple && vals.length % 2 !== 0) { 17292 return null; 17293 } 17294 17295 var valArr = []; 17296 var unitsArr = []; 17297 var pfValArr = []; 17298 var strVal = ''; 17299 var hasEnum = false; 17300 17301 for (var i = 0; i < vals.length; i++) { 17302 var p = self.parse(name, vals[i], propIsBypass, 'multiple'); 17303 hasEnum = hasEnum || string(p.value); 17304 valArr.push(p.value); 17305 pfValArr.push(p.pfValue != null ? p.pfValue : p.value); 17306 unitsArr.push(p.units); 17307 strVal += (i > 0 ? ' ' : '') + p.strValue; 17308 } 17309 17310 if (type.validate && !type.validate(valArr, unitsArr)) { 17311 return null; 17312 } 17313 17314 if (type.singleEnum && hasEnum) { 17315 if (valArr.length === 1 && string(valArr[0])) { 17316 return { 17317 name: name, 17318 value: valArr[0], 17319 strValue: valArr[0], 17320 bypass: propIsBypass 17321 }; 17322 } else { 17323 return null; 17324 } 17325 } 17326 17327 return { 17328 name: name, 17329 value: valArr, 17330 pfValue: pfValArr, 17331 strValue: strVal, 17332 bypass: propIsBypass, 17333 units: unitsArr 17334 }; 17335 } // several types also allow enums 17336 17337 17338 var checkEnums = function checkEnums() { 17339 for (var _i = 0; _i < type.enums.length; _i++) { 17340 var en = type.enums[_i]; 17341 17342 if (en === value) { 17343 return { 17344 name: name, 17345 value: value, 17346 strValue: '' + value, 17347 bypass: propIsBypass 17348 }; 17349 } 17350 } 17351 17352 return null; 17353 }; // check the type and return the appropriate object 17354 17355 17356 if (type.number) { 17357 var units; 17358 var implicitUnits = 'px'; // not set => px 17359 17360 if (type.units) { 17361 // use specified units if set 17362 units = type.units; 17363 } 17364 17365 if (type.implicitUnits) { 17366 implicitUnits = type.implicitUnits; 17367 } 17368 17369 if (!type.unitless) { 17370 if (valueIsString) { 17371 var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : ''); 17372 17373 if (units) { 17374 unitsRegex = units; 17375 } // only allow explicit units if so set 17376 17377 17378 var match = value.match('^(' + number$1 + ')(' + unitsRegex + ')?' + '$'); 17379 17380 if (match) { 17381 value = match[1]; 17382 units = match[2] || implicitUnits; 17383 } 17384 } else if (!units || type.implicitUnits) { 17385 units = implicitUnits; // implicitly px if unspecified 17386 } 17387 } 17388 17389 value = parseFloat(value); // if not a number and enums not allowed, then the value is invalid 17390 17391 if (isNaN(value) && type.enums === undefined) { 17392 return null; 17393 } // check if this number type also accepts special keywords in place of numbers 17394 // (i.e. `left`, `auto`, etc) 17395 17396 17397 if (isNaN(value) && type.enums !== undefined) { 17398 value = passedValue; 17399 return checkEnums(); 17400 } // check if value must be an integer 17401 17402 17403 if (type.integer && !integer(value)) { 17404 return null; 17405 } // check value is within range 17406 17407 17408 if (type.min !== undefined && (value < type.min || type.strictMin && value === type.min) || type.max !== undefined && (value > type.max || type.strictMax && value === type.max)) { 17409 return null; 17410 } 17411 17412 var ret = { 17413 name: name, 17414 value: value, 17415 strValue: '' + value + (units ? units : ''), 17416 units: units, 17417 bypass: propIsBypass 17418 }; // normalise value in pixels 17419 17420 if (type.unitless || units !== 'px' && units !== 'em') { 17421 ret.pfValue = value; 17422 } else { 17423 ret.pfValue = units === 'px' || !units ? value : this.getEmSizeInPixels() * value; 17424 } // normalise value in ms 17425 17426 17427 if (units === 'ms' || units === 's') { 17428 ret.pfValue = units === 'ms' ? value : 1000 * value; 17429 } // normalise value in rad 17430 17431 17432 if (units === 'deg' || units === 'rad') { 17433 ret.pfValue = units === 'rad' ? value : deg2rad(value); 17434 } // normalize value in % 17435 17436 17437 if (units === '%') { 17438 ret.pfValue = value / 100; 17439 } 17440 17441 return ret; 17442 } else if (type.propList) { 17443 var props = []; 17444 var propsStr = '' + value; 17445 17446 if (propsStr === 'none') ; else { 17447 // go over each prop 17448 var propsSplit = propsStr.split(/\s*,\s*|\s+/); 17449 17450 for (var _i2 = 0; _i2 < propsSplit.length; _i2++) { 17451 var propName = propsSplit[_i2].trim(); 17452 17453 if (self.properties[propName]) { 17454 props.push(propName); 17455 } else { 17456 warn('`' + propName + '` is not a valid property name'); 17457 } 17458 } 17459 17460 if (props.length === 0) { 17461 return null; 17462 } 17463 } 17464 17465 return { 17466 name: name, 17467 value: props, 17468 strValue: props.length === 0 ? 'none' : props.join(' '), 17469 bypass: propIsBypass 17470 }; 17471 } else if (type.color) { 17472 var tuple = color2tuple(value); 17473 17474 if (!tuple) { 17475 return null; 17476 } 17477 17478 return { 17479 name: name, 17480 value: tuple, 17481 pfValue: tuple, 17482 strValue: 'rgb(' + tuple[0] + ',' + tuple[1] + ',' + tuple[2] + ')', 17483 // n.b. no spaces b/c of multiple support 17484 bypass: propIsBypass 17485 }; 17486 } else if (type.regex || type.regexes) { 17487 // first check enums 17488 if (type.enums) { 17489 var enumProp = checkEnums(); 17490 17491 if (enumProp) { 17492 return enumProp; 17493 } 17494 } 17495 17496 var regexes = type.regexes ? type.regexes : [type.regex]; 17497 17498 for (var _i3 = 0; _i3 < regexes.length; _i3++) { 17499 var regex = new RegExp(regexes[_i3]); // make a regex from the type string 17500 17501 var m = regex.exec(value); 17502 17503 if (m) { 17504 // regex matches 17505 return { 17506 name: name, 17507 value: type.singleRegexMatchValue ? m[1] : m, 17508 strValue: '' + value, 17509 bypass: propIsBypass 17510 }; 17511 } 17512 } 17513 17514 return null; // didn't match any 17515 } else if (type.string) { 17516 // just return 17517 return { 17518 name: name, 17519 value: '' + value, 17520 strValue: '' + value, 17521 bypass: propIsBypass 17522 }; 17523 } else if (type.enums) { 17524 // check enums last because it's a combo type in others 17525 return checkEnums(); 17526 } else { 17527 return null; // not a type we can handle 17528 } 17529 }; 17530 17531 var Style = function Style(cy) { 17532 if (!(this instanceof Style)) { 17533 return new Style(cy); 17534 } 17535 17536 if (!core(cy)) { 17537 error('A style must have a core reference'); 17538 return; 17539 } 17540 17541 this._private = { 17542 cy: cy, 17543 coreStyle: {} 17544 }; 17545 this.length = 0; 17546 this.resetToDefault(); 17547 }; 17548 17549 var styfn$8 = Style.prototype; 17550 17551 styfn$8.instanceString = function () { 17552 return 'style'; 17553 }; // remove all contexts 17554 17555 17556 styfn$8.clear = function () { 17557 for (var i = 0; i < this.length; i++) { 17558 this[i] = undefined; 17559 } 17560 17561 this.length = 0; 17562 var _p = this._private; 17563 _p.newStyle = true; 17564 return this; // chaining 17565 }; 17566 17567 styfn$8.resetToDefault = function () { 17568 this.clear(); 17569 this.addDefaultStylesheet(); 17570 return this; 17571 }; // builds a style object for the 'core' selector 17572 17573 17574 styfn$8.core = function (propName) { 17575 return this._private.coreStyle[propName] || this.getDefaultProperty(propName); 17576 }; // create a new context from the specified selector string and switch to that context 17577 17578 17579 styfn$8.selector = function (selectorStr) { 17580 // 'core' is a special case and does not need a selector 17581 var selector = selectorStr === 'core' ? null : new Selector(selectorStr); 17582 var i = this.length++; // new context means new index 17583 17584 this[i] = { 17585 selector: selector, 17586 properties: [], 17587 mappedProperties: [], 17588 index: i 17589 }; 17590 return this; // chaining 17591 }; // add one or many css rules to the current context 17592 17593 17594 styfn$8.css = function () { 17595 var self = this; 17596 var args = arguments; 17597 17598 if (args.length === 1) { 17599 var map = args[0]; 17600 17601 for (var i = 0; i < self.properties.length; i++) { 17602 var prop = self.properties[i]; 17603 var mapVal = map[prop.name]; 17604 17605 if (mapVal === undefined) { 17606 mapVal = map[dash2camel(prop.name)]; 17607 } 17608 17609 if (mapVal !== undefined) { 17610 this.cssRule(prop.name, mapVal); 17611 } 17612 } 17613 } else if (args.length === 2) { 17614 this.cssRule(args[0], args[1]); 17615 } // do nothing if args are invalid 17616 17617 17618 return this; // chaining 17619 }; 17620 17621 styfn$8.style = styfn$8.css; // add a single css rule to the current context 17622 17623 styfn$8.cssRule = function (name, value) { 17624 // name-value pair 17625 var property = this.parse(name, value); // add property to current context if valid 17626 17627 if (property) { 17628 var i = this.length - 1; 17629 this[i].properties.push(property); 17630 this[i].properties[property.name] = property; // allow access by name as well 17631 17632 if (property.name.match(/pie-(\d+)-background-size/) && property.value) { 17633 this._private.hasPie = true; 17634 } 17635 17636 if (property.mapped) { 17637 this[i].mappedProperties.push(property); 17638 } // add to core style if necessary 17639 17640 17641 var currentSelectorIsCore = !this[i].selector; 17642 17643 if (currentSelectorIsCore) { 17644 this._private.coreStyle[property.name] = property; 17645 } 17646 } 17647 17648 return this; // chaining 17649 }; 17650 17651 styfn$8.append = function (style) { 17652 if (stylesheet(style)) { 17653 style.appendToStyle(this); 17654 } else if (array(style)) { 17655 this.appendFromJson(style); 17656 } else if (string(style)) { 17657 this.appendFromString(style); 17658 } // you probably wouldn't want to append a Style, since you'd duplicate the default parts 17659 17660 17661 return this; 17662 }; // static function 17663 17664 17665 Style.fromJson = function (cy, json) { 17666 var style = new Style(cy); 17667 style.fromJson(json); 17668 return style; 17669 }; 17670 17671 Style.fromString = function (cy, string) { 17672 return new Style(cy).fromString(string); 17673 }; 17674 17675 [styfn, styfn$1, styfn$2, styfn$3, styfn$4, styfn$5, styfn$6, styfn$7].forEach(function (props) { 17676 extend(styfn$8, props); 17677 }); 17678 Style.types = styfn$8.types; 17679 Style.properties = styfn$8.properties; 17680 Style.propertyGroups = styfn$8.propertyGroups; 17681 Style.propertyGroupNames = styfn$8.propertyGroupNames; 17682 Style.propertyGroupKeys = styfn$8.propertyGroupKeys; 17683 17684 var corefn$7 = { 17685 style: function style(newStyle) { 17686 if (newStyle) { 17687 var s = this.setStyle(newStyle); 17688 s.update(); 17689 } 17690 17691 return this._private.style; 17692 }, 17693 setStyle: function setStyle(style) { 17694 var _p = this._private; 17695 17696 if (stylesheet(style)) { 17697 _p.style = style.generateStyle(this); 17698 } else if (array(style)) { 17699 _p.style = Style.fromJson(this, style); 17700 } else if (string(style)) { 17701 _p.style = Style.fromString(this, style); 17702 } else { 17703 _p.style = Style(this); 17704 } 17705 17706 return _p.style; 17707 } 17708 }; 17709 17710 var defaultSelectionType = 'single'; 17711 var corefn$8 = { 17712 autolock: function autolock(bool) { 17713 if (bool !== undefined) { 17714 this._private.autolock = bool ? true : false; 17715 } else { 17716 return this._private.autolock; 17717 } 17718 17719 return this; // chaining 17720 }, 17721 autoungrabify: function autoungrabify(bool) { 17722 if (bool !== undefined) { 17723 this._private.autoungrabify = bool ? true : false; 17724 } else { 17725 return this._private.autoungrabify; 17726 } 17727 17728 return this; // chaining 17729 }, 17730 autounselectify: function autounselectify(bool) { 17731 if (bool !== undefined) { 17732 this._private.autounselectify = bool ? true : false; 17733 } else { 17734 return this._private.autounselectify; 17735 } 17736 17737 return this; // chaining 17738 }, 17739 selectionType: function selectionType(selType) { 17740 var _p = this._private; 17741 17742 if (_p.selectionType == null) { 17743 _p.selectionType = defaultSelectionType; 17744 } 17745 17746 if (selType !== undefined) { 17747 if (selType === 'additive' || selType === 'single') { 17748 _p.selectionType = selType; 17749 } 17750 } else { 17751 return _p.selectionType; 17752 } 17753 17754 return this; 17755 }, 17756 panningEnabled: function panningEnabled(bool) { 17757 if (bool !== undefined) { 17758 this._private.panningEnabled = bool ? true : false; 17759 } else { 17760 return this._private.panningEnabled; 17761 } 17762 17763 return this; // chaining 17764 }, 17765 userPanningEnabled: function userPanningEnabled(bool) { 17766 if (bool !== undefined) { 17767 this._private.userPanningEnabled = bool ? true : false; 17768 } else { 17769 return this._private.userPanningEnabled; 17770 } 17771 17772 return this; // chaining 17773 }, 17774 zoomingEnabled: function zoomingEnabled(bool) { 17775 if (bool !== undefined) { 17776 this._private.zoomingEnabled = bool ? true : false; 17777 } else { 17778 return this._private.zoomingEnabled; 17779 } 17780 17781 return this; // chaining 17782 }, 17783 userZoomingEnabled: function userZoomingEnabled(bool) { 17784 if (bool !== undefined) { 17785 this._private.userZoomingEnabled = bool ? true : false; 17786 } else { 17787 return this._private.userZoomingEnabled; 17788 } 17789 17790 return this; // chaining 17791 }, 17792 boxSelectionEnabled: function boxSelectionEnabled(bool) { 17793 if (bool !== undefined) { 17794 this._private.boxSelectionEnabled = bool ? true : false; 17795 } else { 17796 return this._private.boxSelectionEnabled; 17797 } 17798 17799 return this; // chaining 17800 }, 17801 pan: function pan() { 17802 var args = arguments; 17803 var pan = this._private.pan; 17804 var dim, val, dims, x, y; 17805 17806 switch (args.length) { 17807 case 0: 17808 // .pan() 17809 return pan; 17810 17811 case 1: 17812 if (string(args[0])) { 17813 // .pan('x') 17814 dim = args[0]; 17815 return pan[dim]; 17816 } else if (plainObject(args[0])) { 17817 // .pan({ x: 0, y: 100 }) 17818 if (!this._private.panningEnabled) { 17819 return this; 17820 } 17821 17822 dims = args[0]; 17823 x = dims.x; 17824 y = dims.y; 17825 17826 if (number(x)) { 17827 pan.x = x; 17828 } 17829 17830 if (number(y)) { 17831 pan.y = y; 17832 } 17833 17834 this.emit('pan viewport'); 17835 } 17836 17837 break; 17838 17839 case 2: 17840 // .pan('x', 100) 17841 if (!this._private.panningEnabled) { 17842 return this; 17843 } 17844 17845 dim = args[0]; 17846 val = args[1]; 17847 17848 if ((dim === 'x' || dim === 'y') && number(val)) { 17849 pan[dim] = val; 17850 } 17851 17852 this.emit('pan viewport'); 17853 break; 17854 // invalid 17855 } 17856 17857 this.notify('viewport'); 17858 return this; // chaining 17859 }, 17860 panBy: function panBy(arg0, arg1) { 17861 var args = arguments; 17862 var pan = this._private.pan; 17863 var dim, val, dims, x, y; 17864 17865 if (!this._private.panningEnabled) { 17866 return this; 17867 } 17868 17869 switch (args.length) { 17870 case 1: 17871 if (plainObject(arg0)) { 17872 // .panBy({ x: 0, y: 100 }) 17873 dims = args[0]; 17874 x = dims.x; 17875 y = dims.y; 17876 17877 if (number(x)) { 17878 pan.x += x; 17879 } 17880 17881 if (number(y)) { 17882 pan.y += y; 17883 } 17884 17885 this.emit('pan viewport'); 17886 } 17887 17888 break; 17889 17890 case 2: 17891 // .panBy('x', 100) 17892 dim = arg0; 17893 val = arg1; 17894 17895 if ((dim === 'x' || dim === 'y') && number(val)) { 17896 pan[dim] += val; 17897 } 17898 17899 this.emit('pan viewport'); 17900 break; 17901 // invalid 17902 } 17903 17904 this.notify('viewport'); 17905 return this; // chaining 17906 }, 17907 fit: function fit(elements, padding) { 17908 var viewportState = this.getFitViewport(elements, padding); 17909 17910 if (viewportState) { 17911 var _p = this._private; 17912 _p.zoom = viewportState.zoom; 17913 _p.pan = viewportState.pan; 17914 this.emit('pan zoom viewport'); 17915 this.notify('viewport'); 17916 } 17917 17918 return this; // chaining 17919 }, 17920 getFitViewport: function getFitViewport(elements, padding) { 17921 if (number(elements) && padding === undefined) { 17922 // elements is optional 17923 padding = elements; 17924 elements = undefined; 17925 } 17926 17927 if (!this._private.panningEnabled || !this._private.zoomingEnabled) { 17928 return; 17929 } 17930 17931 var bb; 17932 17933 if (string(elements)) { 17934 var sel = elements; 17935 elements = this.$(sel); 17936 } else if (boundingBox(elements)) { 17937 // assume bb 17938 var bbe = elements; 17939 bb = { 17940 x1: bbe.x1, 17941 y1: bbe.y1, 17942 x2: bbe.x2, 17943 y2: bbe.y2 17944 }; 17945 bb.w = bb.x2 - bb.x1; 17946 bb.h = bb.y2 - bb.y1; 17947 } else if (!elementOrCollection(elements)) { 17948 elements = this.mutableElements(); 17949 } 17950 17951 if (elementOrCollection(elements) && elements.empty()) { 17952 return; 17953 } // can't fit to nothing 17954 17955 17956 bb = bb || elements.boundingBox(); 17957 var w = this.width(); 17958 var h = this.height(); 17959 var zoom; 17960 padding = number(padding) ? padding : 0; 17961 17962 if (!isNaN(w) && !isNaN(h) && w > 0 && h > 0 && !isNaN(bb.w) && !isNaN(bb.h) && bb.w > 0 && bb.h > 0) { 17963 zoom = Math.min((w - 2 * padding) / bb.w, (h - 2 * padding) / bb.h); // crop zoom 17964 17965 zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; 17966 zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; 17967 var pan = { 17968 // now pan to middle 17969 x: (w - zoom * (bb.x1 + bb.x2)) / 2, 17970 y: (h - zoom * (bb.y1 + bb.y2)) / 2 17971 }; 17972 return { 17973 zoom: zoom, 17974 pan: pan 17975 }; 17976 } 17977 17978 return; 17979 }, 17980 zoomRange: function zoomRange(min, max) { 17981 var _p = this._private; 17982 17983 if (max == null) { 17984 var opts = min; 17985 min = opts.min; 17986 max = opts.max; 17987 } 17988 17989 if (number(min) && number(max) && min <= max) { 17990 _p.minZoom = min; 17991 _p.maxZoom = max; 17992 } else if (number(min) && max === undefined && min <= _p.maxZoom) { 17993 _p.minZoom = min; 17994 } else if (number(max) && min === undefined && max >= _p.minZoom) { 17995 _p.maxZoom = max; 17996 } 17997 17998 return this; 17999 }, 18000 minZoom: function minZoom(zoom) { 18001 if (zoom === undefined) { 18002 return this._private.minZoom; 18003 } else { 18004 return this.zoomRange({ 18005 min: zoom 18006 }); 18007 } 18008 }, 18009 maxZoom: function maxZoom(zoom) { 18010 if (zoom === undefined) { 18011 return this._private.maxZoom; 18012 } else { 18013 return this.zoomRange({ 18014 max: zoom 18015 }); 18016 } 18017 }, 18018 getZoomedViewport: function getZoomedViewport(params) { 18019 var _p = this._private; 18020 var currentPan = _p.pan; 18021 var currentZoom = _p.zoom; 18022 var pos; // in rendered px 18023 18024 var zoom; 18025 var bail = false; 18026 18027 if (!_p.zoomingEnabled) { 18028 // zooming disabled 18029 bail = true; 18030 } 18031 18032 if (number(params)) { 18033 // then set the zoom 18034 zoom = params; 18035 } else if (plainObject(params)) { 18036 // then zoom about a point 18037 zoom = params.level; 18038 18039 if (params.position != null) { 18040 pos = modelToRenderedPosition(params.position, currentZoom, currentPan); 18041 } else if (params.renderedPosition != null) { 18042 pos = params.renderedPosition; 18043 } 18044 18045 if (pos != null && !_p.panningEnabled) { 18046 // panning disabled 18047 bail = true; 18048 } 18049 } // crop zoom 18050 18051 18052 zoom = zoom > _p.maxZoom ? _p.maxZoom : zoom; 18053 zoom = zoom < _p.minZoom ? _p.minZoom : zoom; // can't zoom with invalid params 18054 18055 if (bail || !number(zoom) || zoom === currentZoom || pos != null && (!number(pos.x) || !number(pos.y))) { 18056 return null; 18057 } 18058 18059 if (pos != null) { 18060 // set zoom about position 18061 var pan1 = currentPan; 18062 var zoom1 = currentZoom; 18063 var zoom2 = zoom; 18064 var pan2 = { 18065 x: -zoom2 / zoom1 * (pos.x - pan1.x) + pos.x, 18066 y: -zoom2 / zoom1 * (pos.y - pan1.y) + pos.y 18067 }; 18068 return { 18069 zoomed: true, 18070 panned: true, 18071 zoom: zoom2, 18072 pan: pan2 18073 }; 18074 } else { 18075 // just set the zoom 18076 return { 18077 zoomed: true, 18078 panned: false, 18079 zoom: zoom, 18080 pan: currentPan 18081 }; 18082 } 18083 }, 18084 zoom: function zoom(params) { 18085 if (params === undefined) { 18086 // get 18087 return this._private.zoom; 18088 } else { 18089 // set 18090 var vp = this.getZoomedViewport(params); 18091 var _p = this._private; 18092 18093 if (vp == null || !vp.zoomed) { 18094 return this; 18095 } 18096 18097 _p.zoom = vp.zoom; 18098 18099 if (vp.panned) { 18100 _p.pan.x = vp.pan.x; 18101 _p.pan.y = vp.pan.y; 18102 } 18103 18104 this.emit('zoom' + (vp.panned ? ' pan' : '') + ' viewport'); 18105 this.notify('viewport'); 18106 return this; // chaining 18107 } 18108 }, 18109 viewport: function viewport(opts) { 18110 var _p = this._private; 18111 var zoomDefd = true; 18112 var panDefd = true; 18113 var events = []; // to trigger 18114 18115 var zoomFailed = false; 18116 var panFailed = false; 18117 18118 if (!opts) { 18119 return this; 18120 } 18121 18122 if (!number(opts.zoom)) { 18123 zoomDefd = false; 18124 } 18125 18126 if (!plainObject(opts.pan)) { 18127 panDefd = false; 18128 } 18129 18130 if (!zoomDefd && !panDefd) { 18131 return this; 18132 } 18133 18134 if (zoomDefd) { 18135 var z = opts.zoom; 18136 18137 if (z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled) { 18138 zoomFailed = true; 18139 } else { 18140 _p.zoom = z; 18141 events.push('zoom'); 18142 } 18143 } 18144 18145 if (panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled) { 18146 var p = opts.pan; 18147 18148 if (number(p.x)) { 18149 _p.pan.x = p.x; 18150 panFailed = false; 18151 } 18152 18153 if (number(p.y)) { 18154 _p.pan.y = p.y; 18155 panFailed = false; 18156 } 18157 18158 if (!panFailed) { 18159 events.push('pan'); 18160 } 18161 } 18162 18163 if (events.length > 0) { 18164 events.push('viewport'); 18165 this.emit(events.join(' ')); 18166 this.notify('viewport'); 18167 } 18168 18169 return this; // chaining 18170 }, 18171 center: function center(elements) { 18172 var pan = this.getCenterPan(elements); 18173 18174 if (pan) { 18175 this._private.pan = pan; 18176 this.emit('pan viewport'); 18177 this.notify('viewport'); 18178 } 18179 18180 return this; // chaining 18181 }, 18182 getCenterPan: function getCenterPan(elements, zoom) { 18183 if (!this._private.panningEnabled) { 18184 return; 18185 } 18186 18187 if (string(elements)) { 18188 var selector = elements; 18189 elements = this.mutableElements().filter(selector); 18190 } else if (!elementOrCollection(elements)) { 18191 elements = this.mutableElements(); 18192 } 18193 18194 if (elements.length === 0) { 18195 return; 18196 } // can't centre pan to nothing 18197 18198 18199 var bb = elements.boundingBox(); 18200 var w = this.width(); 18201 var h = this.height(); 18202 zoom = zoom === undefined ? this._private.zoom : zoom; 18203 var pan = { 18204 // middle 18205 x: (w - zoom * (bb.x1 + bb.x2)) / 2, 18206 y: (h - zoom * (bb.y1 + bb.y2)) / 2 18207 }; 18208 return pan; 18209 }, 18210 reset: function reset() { 18211 if (!this._private.panningEnabled || !this._private.zoomingEnabled) { 18212 return this; 18213 } 18214 18215 this.viewport({ 18216 pan: { 18217 x: 0, 18218 y: 0 18219 }, 18220 zoom: 1 18221 }); 18222 return this; // chaining 18223 }, 18224 invalidateSize: function invalidateSize() { 18225 this._private.sizeCache = null; 18226 }, 18227 size: function size() { 18228 var _p = this._private; 18229 var container = _p.container; 18230 return _p.sizeCache = _p.sizeCache || (container ? function () { 18231 var style = window$1.getComputedStyle(container); 18232 18233 var val = function val(name) { 18234 return parseFloat(style.getPropertyValue(name)); 18235 }; 18236 18237 return { 18238 width: container.clientWidth - val('padding-left') - val('padding-right'), 18239 height: container.clientHeight - val('padding-top') - val('padding-bottom') 18240 }; 18241 }() : { 18242 // fallback if no container (not 0 b/c can be used for dividing etc) 18243 width: 1, 18244 height: 1 18245 }); 18246 }, 18247 width: function width() { 18248 return this.size().width; 18249 }, 18250 height: function height() { 18251 return this.size().height; 18252 }, 18253 extent: function extent() { 18254 var pan = this._private.pan; 18255 var zoom = this._private.zoom; 18256 var rb = this.renderedExtent(); 18257 var b = { 18258 x1: (rb.x1 - pan.x) / zoom, 18259 x2: (rb.x2 - pan.x) / zoom, 18260 y1: (rb.y1 - pan.y) / zoom, 18261 y2: (rb.y2 - pan.y) / zoom 18262 }; 18263 b.w = b.x2 - b.x1; 18264 b.h = b.y2 - b.y1; 18265 return b; 18266 }, 18267 renderedExtent: function renderedExtent() { 18268 var width = this.width(); 18269 var height = this.height(); 18270 return { 18271 x1: 0, 18272 y1: 0, 18273 x2: width, 18274 y2: height, 18275 w: width, 18276 h: height 18277 }; 18278 } 18279 }; // aliases 18280 18281 corefn$8.centre = corefn$8.center; // backwards compatibility 18282 18283 corefn$8.autolockNodes = corefn$8.autolock; 18284 corefn$8.autoungrabifyNodes = corefn$8.autoungrabify; 18285 18286 var fn$6 = { 18287 data: define$3.data({ 18288 field: 'data', 18289 bindingEvent: 'data', 18290 allowBinding: true, 18291 allowSetting: true, 18292 settingEvent: 'data', 18293 settingTriggersEvent: true, 18294 triggerFnName: 'trigger', 18295 allowGetting: true 18296 }), 18297 removeData: define$3.removeData({ 18298 field: 'data', 18299 event: 'data', 18300 triggerFnName: 'trigger', 18301 triggerEvent: true 18302 }), 18303 scratch: define$3.data({ 18304 field: 'scratch', 18305 bindingEvent: 'scratch', 18306 allowBinding: true, 18307 allowSetting: true, 18308 settingEvent: 'scratch', 18309 settingTriggersEvent: true, 18310 triggerFnName: 'trigger', 18311 allowGetting: true 18312 }), 18313 removeScratch: define$3.removeData({ 18314 field: 'scratch', 18315 event: 'scratch', 18316 triggerFnName: 'trigger', 18317 triggerEvent: true 18318 }) 18319 }; // aliases 18320 18321 fn$6.attr = fn$6.data; 18322 fn$6.removeAttr = fn$6.removeData; 18323 18324 var Core = function Core(opts) { 18325 var cy = this; 18326 opts = extend({}, opts); 18327 var container = opts.container; // allow for passing a wrapped jquery object 18328 // e.g. cytoscape({ container: $('#cy') }) 18329 18330 if (container && !htmlElement(container) && htmlElement(container[0])) { 18331 container = container[0]; 18332 } 18333 18334 var reg = container ? container._cyreg : null; // e.g. already registered some info (e.g. readies) via jquery 18335 18336 reg = reg || {}; 18337 18338 if (reg && reg.cy) { 18339 reg.cy.destroy(); 18340 reg = {}; // old instance => replace reg completely 18341 } 18342 18343 var readies = reg.readies = reg.readies || []; 18344 18345 if (container) { 18346 container._cyreg = reg; 18347 } // make sure container assoc'd reg points to this cy 18348 18349 18350 reg.cy = cy; 18351 var head = window$1 !== undefined && container !== undefined && !opts.headless; 18352 var options = opts; 18353 options.layout = extend({ 18354 name: head ? 'grid' : 'null' 18355 }, options.layout); 18356 options.renderer = extend({ 18357 name: head ? 'canvas' : 'null' 18358 }, options.renderer); 18359 18360 var defVal = function defVal(def, val, altVal) { 18361 if (val !== undefined) { 18362 return val; 18363 } else if (altVal !== undefined) { 18364 return altVal; 18365 } else { 18366 return def; 18367 } 18368 }; 18369 18370 var _p = this._private = { 18371 container: container, 18372 // html dom ele container 18373 ready: false, 18374 // whether ready has been triggered 18375 options: options, 18376 // cached options 18377 elements: new Collection(this), 18378 // elements in the graph 18379 listeners: [], 18380 // list of listeners 18381 aniEles: new Collection(this), 18382 // elements being animated 18383 data: {}, 18384 // data for the core 18385 scratch: {}, 18386 // scratch object for core 18387 layout: null, 18388 renderer: null, 18389 destroyed: false, 18390 // whether destroy was called 18391 notificationsEnabled: true, 18392 // whether notifications are sent to the renderer 18393 minZoom: 1e-50, 18394 maxZoom: 1e50, 18395 zoomingEnabled: defVal(true, options.zoomingEnabled), 18396 userZoomingEnabled: defVal(true, options.userZoomingEnabled), 18397 panningEnabled: defVal(true, options.panningEnabled), 18398 userPanningEnabled: defVal(true, options.userPanningEnabled), 18399 boxSelectionEnabled: defVal(true, options.boxSelectionEnabled), 18400 autolock: defVal(false, options.autolock, options.autolockNodes), 18401 autoungrabify: defVal(false, options.autoungrabify, options.autoungrabifyNodes), 18402 autounselectify: defVal(false, options.autounselectify), 18403 styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled, 18404 zoom: number(options.zoom) ? options.zoom : 1, 18405 pan: { 18406 x: plainObject(options.pan) && number(options.pan.x) ? options.pan.x : 0, 18407 y: plainObject(options.pan) && number(options.pan.y) ? options.pan.y : 0 18408 }, 18409 animation: { 18410 // object for currently-running animations 18411 current: [], 18412 queue: [] 18413 }, 18414 hasCompoundNodes: false 18415 }; 18416 18417 this.createEmitter(); // set selection type 18418 18419 this.selectionType(options.selectionType); // init zoom bounds 18420 18421 this.zoomRange({ 18422 min: options.minZoom, 18423 max: options.maxZoom 18424 }); 18425 18426 var loadExtData = function loadExtData(extData, next) { 18427 var anyIsPromise = extData.some(promise); 18428 18429 if (anyIsPromise) { 18430 return Promise$1.all(extData).then(next); // load all data asynchronously, then exec rest of init 18431 } else { 18432 next(extData); // exec synchronously for convenience 18433 } 18434 }; // start with the default stylesheet so we have something before loading an external stylesheet 18435 18436 18437 if (_p.styleEnabled) { 18438 cy.setStyle([]); 18439 } // create the renderer 18440 18441 18442 var rendererOptions = extend({}, options, options.renderer); // allow rendering hints in top level options 18443 18444 cy.initRenderer(rendererOptions); 18445 18446 var setElesAndLayout = function setElesAndLayout(elements, onload, ondone) { 18447 cy.notifications(false); // remove old elements 18448 18449 var oldEles = cy.mutableElements(); 18450 18451 if (oldEles.length > 0) { 18452 oldEles.remove(); 18453 } 18454 18455 if (elements != null) { 18456 if (plainObject(elements) || array(elements)) { 18457 cy.add(elements); 18458 } 18459 } 18460 18461 cy.one('layoutready', function (e) { 18462 cy.notifications(true); 18463 cy.emit(e); // we missed this event by turning notifications off, so pass it on 18464 18465 cy.one('load', onload); 18466 cy.emitAndNotify('load'); 18467 }).one('layoutstop', function () { 18468 cy.one('done', ondone); 18469 cy.emit('done'); 18470 }); 18471 var layoutOpts = extend({}, cy._private.options.layout); 18472 layoutOpts.eles = cy.elements(); 18473 cy.layout(layoutOpts).run(); 18474 }; 18475 18476 loadExtData([options.style, options.elements], function (thens) { 18477 var initStyle = thens[0]; 18478 var initEles = thens[1]; // init style 18479 18480 if (_p.styleEnabled) { 18481 cy.style().append(initStyle); 18482 } // initial load 18483 18484 18485 setElesAndLayout(initEles, function () { 18486 // onready 18487 cy.startAnimationLoop(); 18488 _p.ready = true; // if a ready callback is specified as an option, the bind it 18489 18490 if (fn(options.ready)) { 18491 cy.on('ready', options.ready); 18492 } // bind all the ready handlers registered before creating this instance 18493 18494 18495 for (var i = 0; i < readies.length; i++) { 18496 var fn$1 = readies[i]; 18497 cy.on('ready', fn$1); 18498 } 18499 18500 if (reg) { 18501 reg.readies = []; 18502 } // 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 18503 18504 18505 cy.emit('ready'); 18506 }, options.done); 18507 }); 18508 }; 18509 18510 var corefn$9 = Core.prototype; // short alias 18511 18512 extend(corefn$9, { 18513 instanceString: function instanceString() { 18514 return 'core'; 18515 }, 18516 isReady: function isReady() { 18517 return this._private.ready; 18518 }, 18519 destroyed: function destroyed() { 18520 return this._private.destroyed; 18521 }, 18522 ready: function ready(fn) { 18523 if (this.isReady()) { 18524 this.emitter().emit('ready', [], fn); // just calls fn as though triggered via ready event 18525 } else { 18526 this.on('ready', fn); 18527 } 18528 18529 return this; 18530 }, 18531 destroy: function destroy() { 18532 var cy = this; 18533 if (cy.destroyed()) return; 18534 cy.stopAnimationLoop(); 18535 cy.destroyRenderer(); 18536 this.emit('destroy'); 18537 cy._private.destroyed = true; 18538 return cy; 18539 }, 18540 hasElementWithId: function hasElementWithId(id) { 18541 return this._private.elements.hasElementWithId(id); 18542 }, 18543 getElementById: function getElementById(id) { 18544 return this._private.elements.getElementById(id); 18545 }, 18546 hasCompoundNodes: function hasCompoundNodes() { 18547 return this._private.hasCompoundNodes; 18548 }, 18549 headless: function headless() { 18550 return this._private.renderer.isHeadless(); 18551 }, 18552 styleEnabled: function styleEnabled() { 18553 return this._private.styleEnabled; 18554 }, 18555 addToPool: function addToPool(eles) { 18556 this._private.elements.merge(eles); 18557 18558 return this; // chaining 18559 }, 18560 removeFromPool: function removeFromPool(eles) { 18561 this._private.elements.unmerge(eles); 18562 18563 return this; 18564 }, 18565 container: function container() { 18566 return this._private.container || null; 18567 }, 18568 mount: function mount(container) { 18569 if (container == null) { 18570 return; 18571 } 18572 18573 var cy = this; 18574 var _p = cy._private; 18575 var options = _p.options; 18576 18577 if (!htmlElement(container) && htmlElement(container[0])) { 18578 container = container[0]; 18579 } 18580 18581 cy.stopAnimationLoop(); 18582 cy.destroyRenderer(); 18583 _p.container = container; 18584 _p.styleEnabled = true; 18585 cy.invalidateSize(); 18586 cy.initRenderer(extend({}, options, options.renderer, { 18587 // allow custom renderer name to be re-used, otherwise use canvas 18588 name: options.renderer.name === 'null' ? 'canvas' : options.renderer.name 18589 })); 18590 cy.startAnimationLoop(); 18591 cy.style(options.style); 18592 cy.emit('mount'); 18593 return cy; 18594 }, 18595 unmount: function unmount() { 18596 var cy = this; 18597 cy.stopAnimationLoop(); 18598 cy.destroyRenderer(); 18599 cy.initRenderer({ 18600 name: 'null' 18601 }); 18602 cy.emit('unmount'); 18603 return cy; 18604 }, 18605 options: function options() { 18606 return copy(this._private.options); 18607 }, 18608 json: function json(obj) { 18609 var cy = this; 18610 var _p = cy._private; 18611 var eles = cy.mutableElements(); 18612 18613 var getFreshRef = function getFreshRef(ele) { 18614 return cy.getElementById(ele.id()); 18615 }; 18616 18617 if (plainObject(obj)) { 18618 // set 18619 cy.startBatch(); 18620 18621 if (obj.elements) { 18622 var idInJson = {}; 18623 18624 var updateEles = function updateEles(jsons, gr) { 18625 var toAdd = []; 18626 var toMod = []; 18627 18628 for (var i = 0; i < jsons.length; i++) { 18629 var json = jsons[i]; 18630 var id = '' + json.data.id; // id must be string 18631 18632 var ele = cy.getElementById(id); 18633 idInJson[id] = true; 18634 18635 if (ele.length !== 0) { 18636 // existing element should be updated 18637 toMod.push({ 18638 ele: ele, 18639 json: json 18640 }); 18641 } else { 18642 // otherwise should be added 18643 if (gr) { 18644 json.group = gr; 18645 toAdd.push(json); 18646 } else { 18647 toAdd.push(json); 18648 } 18649 } 18650 } 18651 18652 cy.add(toAdd); 18653 18654 for (var _i = 0; _i < toMod.length; _i++) { 18655 var _toMod$_i = toMod[_i], 18656 _ele = _toMod$_i.ele, 18657 _json = _toMod$_i.json; 18658 18659 _ele.json(_json); 18660 } 18661 }; 18662 18663 if (array(obj.elements)) { 18664 // elements: [] 18665 updateEles(obj.elements); 18666 } else { 18667 // elements: { nodes: [], edges: [] } 18668 var grs = ['nodes', 'edges']; 18669 18670 for (var i = 0; i < grs.length; i++) { 18671 var gr = grs[i]; 18672 var elements = obj.elements[gr]; 18673 18674 if (array(elements)) { 18675 updateEles(elements, gr); 18676 } 18677 } 18678 } 18679 18680 var parentsToRemove = cy.collection(); 18681 eles.filter(function (ele) { 18682 return !idInJson[ele.id()]; 18683 }).forEach(function (ele) { 18684 if (ele.isParent()) { 18685 parentsToRemove.merge(ele); 18686 } else { 18687 ele.remove(); 18688 } 18689 }); // so that children are not removed w/parent 18690 18691 parentsToRemove.forEach(function (ele) { 18692 return ele.children().move({ 18693 parent: null 18694 }); 18695 }); // intermediate parents may be moved by prior line, so make sure we remove by fresh refs 18696 18697 parentsToRemove.forEach(function (ele) { 18698 return getFreshRef(ele).remove(); 18699 }); 18700 } 18701 18702 if (obj.style) { 18703 cy.style(obj.style); 18704 } 18705 18706 if (obj.zoom != null && obj.zoom !== _p.zoom) { 18707 cy.zoom(obj.zoom); 18708 } 18709 18710 if (obj.pan) { 18711 if (obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y) { 18712 cy.pan(obj.pan); 18713 } 18714 } 18715 18716 if (obj.data) { 18717 cy.data(obj.data); 18718 } 18719 18720 var fields = ['minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled', 'panningEnabled', 'userPanningEnabled', 'boxSelectionEnabled', 'autolock', 'autoungrabify', 'autounselectify']; 18721 18722 for (var _i2 = 0; _i2 < fields.length; _i2++) { 18723 var f = fields[_i2]; 18724 18725 if (obj[f] != null) { 18726 cy[f](obj[f]); 18727 } 18728 } 18729 18730 cy.endBatch(); 18731 return this; // chaining 18732 } else { 18733 // get 18734 var flat = !!obj; 18735 var json = {}; 18736 18737 if (flat) { 18738 json.elements = this.elements().map(function (ele) { 18739 return ele.json(); 18740 }); 18741 } else { 18742 json.elements = {}; 18743 eles.forEach(function (ele) { 18744 var group = ele.group(); 18745 18746 if (!json.elements[group]) { 18747 json.elements[group] = []; 18748 } 18749 18750 json.elements[group].push(ele.json()); 18751 }); 18752 } 18753 18754 if (this._private.styleEnabled) { 18755 json.style = cy.style().json(); 18756 } 18757 18758 json.data = copy(cy.data()); 18759 var options = _p.options; 18760 json.zoomingEnabled = _p.zoomingEnabled; 18761 json.userZoomingEnabled = _p.userZoomingEnabled; 18762 json.zoom = _p.zoom; 18763 json.minZoom = _p.minZoom; 18764 json.maxZoom = _p.maxZoom; 18765 json.panningEnabled = _p.panningEnabled; 18766 json.userPanningEnabled = _p.userPanningEnabled; 18767 json.pan = copy(_p.pan); 18768 json.boxSelectionEnabled = _p.boxSelectionEnabled; 18769 json.renderer = copy(options.renderer); 18770 json.hideEdgesOnViewport = options.hideEdgesOnViewport; 18771 json.textureOnViewport = options.textureOnViewport; 18772 json.wheelSensitivity = options.wheelSensitivity; 18773 json.motionBlur = options.motionBlur; 18774 return json; 18775 } 18776 } 18777 }); 18778 corefn$9.$id = corefn$9.getElementById; 18779 [corefn, corefn$1, elesfn$v, corefn$2, corefn$3, corefn$4, corefn$5, corefn$6, corefn$7, corefn$8, fn$6].forEach(function (props) { 18780 extend(corefn$9, props); 18781 }); 18782 18783 /* eslint-disable no-unused-vars */ 18784 18785 var defaults$9 = { 18786 fit: true, 18787 // whether to fit the viewport to the graph 18788 directed: false, 18789 // whether the tree is directed downwards (or edges can point in any direction if false) 18790 padding: 30, 18791 // padding on fit 18792 circle: false, 18793 // put depths in concentric circles if true, put depths top down if false 18794 grid: false, 18795 // whether to create an even grid into which the DAG is placed (circle:false only) 18796 spacingFactor: 1.75, 18797 // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap) 18798 boundingBox: undefined, 18799 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 18800 avoidOverlap: true, 18801 // prevents node overlap, may overflow boundingBox if not enough space 18802 nodeDimensionsIncludeLabels: false, 18803 // Excludes the label when calculating node bounding boxes for the layout algorithm 18804 roots: undefined, 18805 // the roots of the trees 18806 maximal: false, 18807 // whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only) 18808 animate: false, 18809 // whether to transition the node positions 18810 animationDuration: 500, 18811 // duration of animation in ms if enabled 18812 animationEasing: undefined, 18813 // easing of animation if enabled, 18814 animateFilter: function animateFilter(node, i) { 18815 return true; 18816 }, 18817 // 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 18818 ready: undefined, 18819 // callback on layoutready 18820 stop: undefined, 18821 // callback on layoutstop 18822 transform: function transform(node, position) { 18823 return position; 18824 } // transform a given node position. Useful for changing flow direction in discrete layouts 18825 18826 }; 18827 /* eslint-enable */ 18828 18829 var getInfo = function getInfo(ele) { 18830 return ele.scratch('breadthfirst'); 18831 }; 18832 18833 var setInfo = function setInfo(ele, obj) { 18834 return ele.scratch('breadthfirst', obj); 18835 }; 18836 18837 function BreadthFirstLayout(options) { 18838 this.options = extend({}, defaults$9, options); 18839 } 18840 18841 BreadthFirstLayout.prototype.run = function () { 18842 var params = this.options; 18843 var options = params; 18844 var cy = params.cy; 18845 var eles = options.eles; 18846 var nodes = eles.nodes().filter(function (n) { 18847 return !n.isParent(); 18848 }); 18849 var graph = eles; 18850 var directed = options.directed; 18851 var maximal = options.maximal || options.maximalAdjustments > 0; // maximalAdjustments for compat. w/ old code 18852 18853 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 18854 x1: 0, 18855 y1: 0, 18856 w: cy.width(), 18857 h: cy.height() 18858 }); 18859 var roots; 18860 18861 if (elementOrCollection(options.roots)) { 18862 roots = options.roots; 18863 } else if (array(options.roots)) { 18864 var rootsArray = []; 18865 18866 for (var i = 0; i < options.roots.length; i++) { 18867 var id = options.roots[i]; 18868 var ele = cy.getElementById(id); 18869 rootsArray.push(ele); 18870 } 18871 18872 roots = cy.collection(rootsArray); 18873 } else if (string(options.roots)) { 18874 roots = cy.$(options.roots); 18875 } else { 18876 if (directed) { 18877 roots = nodes.roots(); 18878 } else { 18879 var components = eles.components(); 18880 roots = cy.collection(); 18881 18882 var _loop = function _loop(_i) { 18883 var comp = components[_i]; 18884 var maxDegree = comp.maxDegree(false); 18885 var compRoots = comp.filter(function (ele) { 18886 return ele.degree(false) === maxDegree; 18887 }); 18888 roots = roots.add(compRoots); 18889 }; 18890 18891 for (var _i = 0; _i < components.length; _i++) { 18892 _loop(_i); 18893 } 18894 } 18895 } 18896 18897 var depths = []; 18898 var foundByBfs = {}; 18899 18900 var addToDepth = function addToDepth(ele, d) { 18901 if (depths[d] == null) { 18902 depths[d] = []; 18903 } 18904 18905 var i = depths[d].length; 18906 depths[d].push(ele); 18907 setInfo(ele, { 18908 index: i, 18909 depth: d 18910 }); 18911 }; 18912 18913 var changeDepth = function changeDepth(ele, newDepth) { 18914 var _getInfo = getInfo(ele), 18915 depth = _getInfo.depth, 18916 index = _getInfo.index; 18917 18918 depths[depth][index] = null; 18919 addToDepth(ele, newDepth); 18920 }; // find the depths of the nodes 18921 18922 18923 graph.bfs({ 18924 roots: roots, 18925 directed: options.directed, 18926 visit: function visit(node, edge, pNode, i, depth) { 18927 var ele = node[0]; 18928 var id = ele.id(); 18929 addToDepth(ele, depth); 18930 foundByBfs[id] = true; 18931 } 18932 }); // check for nodes not found by bfs 18933 18934 var orphanNodes = []; 18935 18936 for (var _i2 = 0; _i2 < nodes.length; _i2++) { 18937 var _ele = nodes[_i2]; 18938 18939 if (foundByBfs[_ele.id()]) { 18940 continue; 18941 } else { 18942 orphanNodes.push(_ele); 18943 } 18944 } // assign the nodes a depth and index 18945 18946 18947 var assignDepthsAt = function assignDepthsAt(i) { 18948 var eles = depths[i]; 18949 18950 for (var j = 0; j < eles.length; j++) { 18951 var _ele2 = eles[j]; 18952 18953 if (_ele2 == null) { 18954 eles.splice(j, 1); 18955 j--; 18956 continue; 18957 } 18958 18959 setInfo(_ele2, { 18960 depth: i, 18961 index: j 18962 }); 18963 } 18964 }; 18965 18966 var assignDepths = function assignDepths() { 18967 for (var _i3 = 0; _i3 < depths.length; _i3++) { 18968 assignDepthsAt(_i3); 18969 } 18970 }; 18971 18972 var adjustMaximally = function adjustMaximally(ele, shifted) { 18973 var eInfo = getInfo(ele); 18974 var incomers = ele.incomers().filter(function (el) { 18975 return el.isNode() && eles.has(el); 18976 }); 18977 var maxDepth = -1; 18978 var id = ele.id(); 18979 18980 for (var k = 0; k < incomers.length; k++) { 18981 var incmr = incomers[k]; 18982 var iInfo = getInfo(incmr); 18983 maxDepth = Math.max(maxDepth, iInfo.depth); 18984 } 18985 18986 if (eInfo.depth <= maxDepth) { 18987 if (shifted[id]) { 18988 return null; 18989 } 18990 18991 changeDepth(ele, maxDepth + 1); 18992 shifted[id] = true; 18993 return true; 18994 } 18995 18996 return false; 18997 }; // for the directed case, try to make the edges all go down (i.e. depth i => depth i + 1) 18998 18999 19000 if (directed && maximal) { 19001 var Q = []; 19002 var shifted = {}; 19003 19004 var enqueue = function enqueue(n) { 19005 return Q.push(n); 19006 }; 19007 19008 var dequeue = function dequeue() { 19009 return Q.shift(); 19010 }; 19011 19012 nodes.forEach(function (n) { 19013 return Q.push(n); 19014 }); 19015 19016 while (Q.length > 0) { 19017 var _ele3 = dequeue(); 19018 19019 var didShift = adjustMaximally(_ele3, shifted); 19020 19021 if (didShift) { 19022 _ele3.outgoers().filter(function (el) { 19023 return el.isNode() && eles.has(el); 19024 }).forEach(enqueue); 19025 } else if (didShift === null) { 19026 warn('Detected double maximal shift for node `' + _ele3.id() + '`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.'); 19027 break; // exit on failure 19028 } 19029 } 19030 } 19031 19032 assignDepths(); // clear holes 19033 // find min distance we need to leave between nodes 19034 19035 var minDistance = 0; 19036 19037 if (options.avoidOverlap) { 19038 for (var _i4 = 0; _i4 < nodes.length; _i4++) { 19039 var n = nodes[_i4]; 19040 var nbb = n.layoutDimensions(options); 19041 var w = nbb.w; 19042 var h = nbb.h; 19043 minDistance = Math.max(minDistance, w, h); 19044 } 19045 } // get the weighted percent for an element based on its connectivity to other levels 19046 19047 19048 var cachedWeightedPercent = {}; 19049 19050 var getWeightedPercent = function getWeightedPercent(ele) { 19051 if (cachedWeightedPercent[ele.id()]) { 19052 return cachedWeightedPercent[ele.id()]; 19053 } 19054 19055 var eleDepth = getInfo(ele).depth; 19056 var neighbors = ele.neighborhood(); 19057 var percent = 0; 19058 var samples = 0; 19059 19060 for (var _i5 = 0; _i5 < neighbors.length; _i5++) { 19061 var neighbor = neighbors[_i5]; 19062 19063 if (neighbor.isEdge() || neighbor.isParent() || !nodes.has(neighbor)) { 19064 continue; 19065 } 19066 19067 var bf = getInfo(neighbor); 19068 var index = bf.index; 19069 var depth = bf.depth; // unassigned neighbours shouldn't affect the ordering 19070 19071 if (index == null || depth == null) { 19072 continue; 19073 } 19074 19075 var nDepth = depths[depth].length; 19076 19077 if (depth < eleDepth) { 19078 // only get influenced by elements above 19079 percent += index / nDepth; 19080 samples++; 19081 } 19082 } 19083 19084 samples = Math.max(1, samples); 19085 percent = percent / samples; 19086 19087 if (samples === 0) { 19088 // put lone nodes at the start 19089 percent = 0; 19090 } 19091 19092 cachedWeightedPercent[ele.id()] = percent; 19093 return percent; 19094 }; // rearrange the indices in each depth level based on connectivity 19095 19096 19097 var sortFn = function sortFn(a, b) { 19098 var apct = getWeightedPercent(a); 19099 var bpct = getWeightedPercent(b); 19100 var diff = apct - bpct; 19101 19102 if (diff === 0) { 19103 return ascending(a.id(), b.id()); // make sure sort doesn't have don't-care comparisons 19104 } else { 19105 return diff; 19106 } 19107 }; // sort each level to make connected nodes closer 19108 19109 19110 for (var _i6 = 0; _i6 < depths.length; _i6++) { 19111 depths[_i6].sort(sortFn); 19112 19113 assignDepthsAt(_i6); 19114 } // assign orphan nodes to a new top-level depth 19115 19116 19117 var orphanDepth = []; 19118 19119 for (var _i7 = 0; _i7 < orphanNodes.length; _i7++) { 19120 orphanDepth.push(orphanNodes[_i7]); 19121 } 19122 19123 depths.unshift(orphanDepth); 19124 assignDepths(); 19125 var biggestDepthSize = 0; 19126 19127 for (var _i8 = 0; _i8 < depths.length; _i8++) { 19128 biggestDepthSize = Math.max(depths[_i8].length, biggestDepthSize); 19129 } 19130 19131 var center = { 19132 x: bb.x1 + bb.w / 2, 19133 y: bb.x1 + bb.h / 2 19134 }; 19135 var maxDepthSize = depths.reduce(function (max, eles) { 19136 return Math.max(max, eles.length); 19137 }, 0); 19138 19139 var getPosition = function getPosition(ele) { 19140 var _getInfo2 = getInfo(ele), 19141 depth = _getInfo2.depth, 19142 index = _getInfo2.index; 19143 19144 var depthSize = depths[depth].length; 19145 var distanceX = Math.max(bb.w / ((options.grid ? maxDepthSize : depthSize) + 1), minDistance); 19146 var distanceY = Math.max(bb.h / (depths.length + 1), minDistance); 19147 var radiusStepSize = Math.min(bb.w / 2 / depths.length, bb.h / 2 / depths.length); 19148 radiusStepSize = Math.max(radiusStepSize, minDistance); 19149 19150 if (!options.circle) { 19151 var epos = { 19152 x: center.x + (index + 1 - (depthSize + 1) / 2) * distanceX, 19153 y: (depth + 1) * distanceY 19154 }; 19155 return epos; 19156 } else { 19157 var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize / 2 : 0); 19158 var theta = 2 * Math.PI / depths[depth].length * index; 19159 19160 if (depth === 0 && depths[0].length === 1) { 19161 radius = 1; 19162 } 19163 19164 return { 19165 x: center.x + radius * Math.cos(theta), 19166 y: center.y + radius * Math.sin(theta) 19167 }; 19168 } 19169 }; 19170 19171 nodes.layoutPositions(this, options, getPosition); 19172 return this; // chaining 19173 }; 19174 19175 var defaults$a = { 19176 fit: true, 19177 // whether to fit the viewport to the graph 19178 padding: 30, 19179 // the padding on fit 19180 boundingBox: undefined, 19181 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19182 avoidOverlap: true, 19183 // prevents node overlap, may overflow boundingBox and radius if not enough space 19184 nodeDimensionsIncludeLabels: false, 19185 // Excludes the label when calculating node bounding boxes for the layout algorithm 19186 spacingFactor: undefined, 19187 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 19188 radius: undefined, 19189 // the radius of the circle 19190 startAngle: 3 / 2 * Math.PI, 19191 // where nodes start in radians 19192 sweep: undefined, 19193 // how many radians should be between the first and last node (defaults to full circle) 19194 clockwise: true, 19195 // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 19196 sort: undefined, 19197 // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 19198 animate: false, 19199 // whether to transition the node positions 19200 animationDuration: 500, 19201 // duration of animation in ms if enabled 19202 animationEasing: undefined, 19203 // easing of animation if enabled 19204 animateFilter: function animateFilter(node, i) { 19205 return true; 19206 }, 19207 // 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 19208 ready: undefined, 19209 // callback on layoutready 19210 stop: undefined, 19211 // callback on layoutstop 19212 transform: function transform(node, position) { 19213 return position; 19214 } // transform a given node position. Useful for changing flow direction in discrete layouts 19215 19216 }; 19217 19218 function CircleLayout(options) { 19219 this.options = extend({}, defaults$a, options); 19220 } 19221 19222 CircleLayout.prototype.run = function () { 19223 var params = this.options; 19224 var options = params; 19225 var cy = params.cy; 19226 var eles = options.eles; 19227 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 19228 var nodes = eles.nodes().not(':parent'); 19229 19230 if (options.sort) { 19231 nodes = nodes.sort(options.sort); 19232 } 19233 19234 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 19235 x1: 0, 19236 y1: 0, 19237 w: cy.width(), 19238 h: cy.height() 19239 }); 19240 var center = { 19241 x: bb.x1 + bb.w / 2, 19242 y: bb.y1 + bb.h / 2 19243 }; 19244 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / nodes.length : options.sweep; 19245 var dTheta = sweep / Math.max(1, nodes.length - 1); 19246 var r; 19247 var minDistance = 0; 19248 19249 for (var i = 0; i < nodes.length; i++) { 19250 var n = nodes[i]; 19251 var nbb = n.layoutDimensions(options); 19252 var w = nbb.w; 19253 var h = nbb.h; 19254 minDistance = Math.max(minDistance, w, h); 19255 } 19256 19257 if (number(options.radius)) { 19258 r = options.radius; 19259 } else if (nodes.length <= 1) { 19260 r = 0; 19261 } else { 19262 r = Math.min(bb.h, bb.w) / 2 - minDistance; 19263 } // calculate the radius 19264 19265 19266 if (nodes.length > 1 && options.avoidOverlap) { 19267 // but only if more than one node (can't overlap) 19268 minDistance *= 1.75; // just to have some nice spacing 19269 19270 var dcos = Math.cos(dTheta) - Math.cos(0); 19271 var dsin = Math.sin(dTheta) - Math.sin(0); 19272 var rMin = Math.sqrt(minDistance * minDistance / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping 19273 19274 r = Math.max(rMin, r); 19275 } 19276 19277 var getPos = function getPos(ele, i) { 19278 var theta = options.startAngle + i * dTheta * (clockwise ? 1 : -1); 19279 var rx = r * Math.cos(theta); 19280 var ry = r * Math.sin(theta); 19281 var pos = { 19282 x: center.x + rx, 19283 y: center.y + ry 19284 }; 19285 return pos; 19286 }; 19287 19288 nodes.layoutPositions(this, options, getPos); 19289 return this; // chaining 19290 }; 19291 19292 var defaults$b = { 19293 fit: true, 19294 // whether to fit the viewport to the graph 19295 padding: 30, 19296 // the padding on fit 19297 startAngle: 3 / 2 * Math.PI, 19298 // where nodes start in radians 19299 sweep: undefined, 19300 // how many radians should be between the first and last node (defaults to full circle) 19301 clockwise: true, 19302 // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 19303 equidistant: false, 19304 // whether levels have an equal radial distance betwen them, may cause bounding box overflow 19305 minNodeSpacing: 10, 19306 // min spacing between outside of nodes (used for radius adjustment) 19307 boundingBox: undefined, 19308 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19309 avoidOverlap: true, 19310 // prevents node overlap, may overflow boundingBox if not enough space 19311 nodeDimensionsIncludeLabels: false, 19312 // Excludes the label when calculating node bounding boxes for the layout algorithm 19313 height: undefined, 19314 // height of layout area (overrides container height) 19315 width: undefined, 19316 // width of layout area (overrides container width) 19317 spacingFactor: undefined, 19318 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 19319 concentric: function concentric(node) { 19320 // returns numeric value for each node, placing higher nodes in levels towards the centre 19321 return node.degree(); 19322 }, 19323 levelWidth: function levelWidth(nodes) { 19324 // the letiation of concentric values in each level 19325 return nodes.maxDegree() / 4; 19326 }, 19327 animate: false, 19328 // whether to transition the node positions 19329 animationDuration: 500, 19330 // duration of animation in ms if enabled 19331 animationEasing: undefined, 19332 // easing of animation if enabled 19333 animateFilter: function animateFilter(node, i) { 19334 return true; 19335 }, 19336 // 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 19337 ready: undefined, 19338 // callback on layoutready 19339 stop: undefined, 19340 // callback on layoutstop 19341 transform: function transform(node, position) { 19342 return position; 19343 } // transform a given node position. Useful for changing flow direction in discrete layouts 19344 19345 }; 19346 19347 function ConcentricLayout(options) { 19348 this.options = extend({}, defaults$b, options); 19349 } 19350 19351 ConcentricLayout.prototype.run = function () { 19352 var params = this.options; 19353 var options = params; 19354 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 19355 var cy = params.cy; 19356 var eles = options.eles; 19357 var nodes = eles.nodes().not(':parent'); 19358 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 19359 x1: 0, 19360 y1: 0, 19361 w: cy.width(), 19362 h: cy.height() 19363 }); 19364 var center = { 19365 x: bb.x1 + bb.w / 2, 19366 y: bb.y1 + bb.h / 2 19367 }; 19368 var nodeValues = []; // { node, value } 19369 19370 var maxNodeSize = 0; 19371 19372 for (var i = 0; i < nodes.length; i++) { 19373 var node = nodes[i]; 19374 var value = void 0; // calculate the node value 19375 19376 value = options.concentric(node); 19377 nodeValues.push({ 19378 value: value, 19379 node: node 19380 }); // for style mapping 19381 19382 node._private.scratch.concentric = value; 19383 } // in case we used the `concentric` in style 19384 19385 19386 nodes.updateStyle(); // calculate max size now based on potentially updated mappers 19387 19388 for (var _i = 0; _i < nodes.length; _i++) { 19389 var _node = nodes[_i]; 19390 19391 var nbb = _node.layoutDimensions(options); 19392 19393 maxNodeSize = Math.max(maxNodeSize, nbb.w, nbb.h); 19394 } // sort node values in descreasing order 19395 19396 19397 nodeValues.sort(function (a, b) { 19398 return b.value - a.value; 19399 }); 19400 var levelWidth = options.levelWidth(nodes); // put the values into levels 19401 19402 var levels = [[]]; 19403 var currentLevel = levels[0]; 19404 19405 for (var _i2 = 0; _i2 < nodeValues.length; _i2++) { 19406 var val = nodeValues[_i2]; 19407 19408 if (currentLevel.length > 0) { 19409 var diff = Math.abs(currentLevel[0].value - val.value); 19410 19411 if (diff >= levelWidth) { 19412 currentLevel = []; 19413 levels.push(currentLevel); 19414 } 19415 } 19416 19417 currentLevel.push(val); 19418 } // create positions from levels 19419 19420 19421 var minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes 19422 19423 if (!options.avoidOverlap) { 19424 // then strictly constrain to bb 19425 var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; 19426 var maxR = Math.min(bb.w, bb.h) / 2 - minDist; 19427 var rStep = maxR / (levels.length + firstLvlHasMulti ? 1 : 0); 19428 minDist = Math.min(minDist, rStep); 19429 } // find the metrics for each level 19430 19431 19432 var r = 0; 19433 19434 for (var _i3 = 0; _i3 < levels.length; _i3++) { 19435 var level = levels[_i3]; 19436 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / level.length : options.sweep; 19437 var dTheta = level.dTheta = sweep / Math.max(1, level.length - 1); // calculate the radius 19438 19439 if (level.length > 1 && options.avoidOverlap) { 19440 // but only if more than one node (can't overlap) 19441 var dcos = Math.cos(dTheta) - Math.cos(0); 19442 var dsin = Math.sin(dTheta) - Math.sin(0); 19443 var rMin = Math.sqrt(minDist * minDist / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping 19444 19445 r = Math.max(rMin, r); 19446 } 19447 19448 level.r = r; 19449 r += minDist; 19450 } 19451 19452 if (options.equidistant) { 19453 var rDeltaMax = 0; 19454 var _r = 0; 19455 19456 for (var _i4 = 0; _i4 < levels.length; _i4++) { 19457 var _level = levels[_i4]; 19458 var rDelta = _level.r - _r; 19459 rDeltaMax = Math.max(rDeltaMax, rDelta); 19460 } 19461 19462 _r = 0; 19463 19464 for (var _i5 = 0; _i5 < levels.length; _i5++) { 19465 var _level2 = levels[_i5]; 19466 19467 if (_i5 === 0) { 19468 _r = _level2.r; 19469 } 19470 19471 _level2.r = _r; 19472 _r += rDeltaMax; 19473 } 19474 } // calculate the node positions 19475 19476 19477 var pos = {}; // id => position 19478 19479 for (var _i6 = 0; _i6 < levels.length; _i6++) { 19480 var _level3 = levels[_i6]; 19481 var _dTheta = _level3.dTheta; 19482 var _r2 = _level3.r; 19483 19484 for (var j = 0; j < _level3.length; j++) { 19485 var _val = _level3[j]; 19486 var theta = options.startAngle + (clockwise ? 1 : -1) * _dTheta * j; 19487 var p = { 19488 x: center.x + _r2 * Math.cos(theta), 19489 y: center.y + _r2 * Math.sin(theta) 19490 }; 19491 pos[_val.node.id()] = p; 19492 } 19493 } // position the nodes 19494 19495 19496 nodes.layoutPositions(this, options, function (ele) { 19497 var id = ele.id(); 19498 return pos[id]; 19499 }); 19500 return this; // chaining 19501 }; 19502 19503 /* 19504 The CoSE layout was written by Gerardo Huck. 19505 https://www.linkedin.com/in/gerardohuck/ 19506 19507 Based on the following article: 19508 http://dl.acm.org/citation.cfm?id=1498047 19509 19510 Modifications tracked on Github. 19511 */ 19512 var DEBUG; 19513 /** 19514 * @brief : default layout options 19515 */ 19516 19517 var defaults$c = { 19518 // Called on `layoutready` 19519 ready: function ready() {}, 19520 // Called on `layoutstop` 19521 stop: function stop() {}, 19522 // Whether to animate while running the layout 19523 // true : Animate continuously as the layout is running 19524 // false : Just show the end result 19525 // 'end' : Animate with the end result, from the initial positions to the end positions 19526 animate: true, 19527 // Easing of the animation for animate:'end' 19528 animationEasing: undefined, 19529 // The duration of the animation for animate:'end' 19530 animationDuration: undefined, 19531 // A function that determines whether the node should be animated 19532 // All nodes animated by default on animate enabled 19533 // Non-animated nodes are positioned immediately when the layout starts 19534 animateFilter: function animateFilter(node, i) { 19535 return true; 19536 }, 19537 // The layout animates only after this many milliseconds for animate:true 19538 // (prevents flashing on fast runs) 19539 animationThreshold: 250, 19540 // Number of iterations between consecutive screen positions update 19541 refresh: 20, 19542 // Whether to fit the network view after when done 19543 fit: true, 19544 // Padding on fit 19545 padding: 30, 19546 // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19547 boundingBox: undefined, 19548 // Excludes the label when calculating node bounding boxes for the layout algorithm 19549 nodeDimensionsIncludeLabels: false, 19550 // Randomize the initial positions of the nodes (true) or use existing positions (false) 19551 randomize: false, 19552 // Extra spacing between components in non-compound graphs 19553 componentSpacing: 40, 19554 // Node repulsion (non overlapping) multiplier 19555 nodeRepulsion: function nodeRepulsion(node) { 19556 return 2048; 19557 }, 19558 // Node repulsion (overlapping) multiplier 19559 nodeOverlap: 4, 19560 // Ideal edge (non nested) length 19561 idealEdgeLength: function idealEdgeLength(edge) { 19562 return 32; 19563 }, 19564 // Divisor to compute edge forces 19565 edgeElasticity: function edgeElasticity(edge) { 19566 return 32; 19567 }, 19568 // Nesting factor (multiplier) to compute ideal edge length for nested edges 19569 nestingFactor: 1.2, 19570 // Gravity force (constant) 19571 gravity: 1, 19572 // Maximum number of iterations to perform 19573 numIter: 1000, 19574 // Initial temperature (maximum node displacement) 19575 initialTemp: 1000, 19576 // Cooling factor (how the temperature is reduced between consecutive iterations 19577 coolingFactor: 0.99, 19578 // Lower temperature threshold (below this point the layout will end) 19579 minTemp: 1.0 19580 }; 19581 /** 19582 * @brief : constructor 19583 * @arg options : object containing layout options 19584 */ 19585 19586 function CoseLayout(options) { 19587 this.options = extend({}, defaults$c, options); 19588 this.options.layout = this; 19589 } 19590 /** 19591 * @brief : runs the layout 19592 */ 19593 19594 19595 CoseLayout.prototype.run = function () { 19596 var options = this.options; 19597 var cy = options.cy; 19598 var layout = this; 19599 layout.stopped = false; 19600 19601 if (options.animate === true || options.animate === false) { 19602 layout.emit({ 19603 type: 'layoutstart', 19604 layout: layout 19605 }); 19606 } // Set DEBUG - Global variable 19607 19608 19609 if (true === options.debug) { 19610 DEBUG = true; 19611 } else { 19612 DEBUG = false; 19613 } // Initialize layout info 19614 19615 19616 var layoutInfo = createLayoutInfo(cy, layout, options); // Show LayoutInfo contents if debugging 19617 19618 if (DEBUG) { 19619 printLayoutInfo(layoutInfo); 19620 } // If required, randomize node positions 19621 19622 19623 if (options.randomize) { 19624 randomizePositions(layoutInfo); 19625 } 19626 19627 var startTime = performanceNow(); 19628 19629 var refresh = function refresh() { 19630 refreshPositions(layoutInfo, cy, options); // Fit the graph if necessary 19631 19632 if (true === options.fit) { 19633 cy.fit(options.padding); 19634 } 19635 }; 19636 19637 var mainLoop = function mainLoop(i) { 19638 if (layout.stopped || i >= options.numIter) { 19639 // logDebug("Layout manually stopped. Stopping computation in step " + i); 19640 return false; 19641 } // Do one step in the phisical simulation 19642 19643 19644 step$1(layoutInfo, options); // Update temperature 19645 19646 layoutInfo.temperature = layoutInfo.temperature * options.coolingFactor; // logDebug("New temperature: " + layoutInfo.temperature); 19647 19648 if (layoutInfo.temperature < options.minTemp) { 19649 // logDebug("Temperature drop below minimum threshold. Stopping computation in step " + i); 19650 return false; 19651 } 19652 19653 return true; 19654 }; 19655 19656 var done = function done() { 19657 if (options.animate === true || options.animate === false) { 19658 refresh(); // Layout has finished 19659 19660 layout.one('layoutstop', options.stop); 19661 layout.emit({ 19662 type: 'layoutstop', 19663 layout: layout 19664 }); 19665 } else { 19666 var nodes = options.eles.nodes(); 19667 var getScaledPos = getScaleInBoundsFn(layoutInfo, options, nodes); 19668 nodes.layoutPositions(layout, options, getScaledPos); 19669 } 19670 }; 19671 19672 var i = 0; 19673 var loopRet = true; 19674 19675 if (options.animate === true) { 19676 var frame = function frame() { 19677 var f = 0; 19678 19679 while (loopRet && f < options.refresh) { 19680 loopRet = mainLoop(i); 19681 i++; 19682 f++; 19683 } 19684 19685 if (!loopRet) { 19686 // it's done 19687 separateComponents(layoutInfo, options); 19688 done(); 19689 } else { 19690 var now = performanceNow(); 19691 19692 if (now - startTime >= options.animationThreshold) { 19693 refresh(); 19694 } 19695 19696 requestAnimationFrame(frame); 19697 } 19698 }; 19699 19700 frame(); 19701 } else { 19702 while (loopRet) { 19703 loopRet = mainLoop(i); 19704 i++; 19705 } 19706 19707 separateComponents(layoutInfo, options); 19708 done(); 19709 } 19710 19711 return this; // chaining 19712 }; 19713 /** 19714 * @brief : called on continuous layouts to stop them before they finish 19715 */ 19716 19717 19718 CoseLayout.prototype.stop = function () { 19719 this.stopped = true; 19720 19721 if (this.thread) { 19722 this.thread.stop(); 19723 } 19724 19725 this.emit('layoutstop'); 19726 return this; // chaining 19727 }; 19728 19729 CoseLayout.prototype.destroy = function () { 19730 if (this.thread) { 19731 this.thread.stop(); 19732 } 19733 19734 return this; // chaining 19735 }; 19736 /** 19737 * @brief : Creates an object which is contains all the data 19738 * used in the layout process 19739 * @arg cy : cytoscape.js object 19740 * @return : layoutInfo object initialized 19741 */ 19742 19743 19744 var createLayoutInfo = function createLayoutInfo(cy, layout, options) { 19745 // Shortcut 19746 var edges = options.eles.edges(); 19747 var nodes = options.eles.nodes(); 19748 var layoutInfo = { 19749 isCompound: cy.hasCompoundNodes(), 19750 layoutNodes: [], 19751 idToIndex: {}, 19752 nodeSize: nodes.size(), 19753 graphSet: [], 19754 indexToGraph: [], 19755 layoutEdges: [], 19756 edgeSize: edges.size(), 19757 temperature: options.initialTemp, 19758 clientWidth: cy.width(), 19759 clientHeight: cy.width(), 19760 boundingBox: makeBoundingBox(options.boundingBox ? options.boundingBox : { 19761 x1: 0, 19762 y1: 0, 19763 w: cy.width(), 19764 h: cy.height() 19765 }) 19766 }; 19767 var components = options.eles.components(); 19768 var id2cmptId = {}; 19769 19770 for (var i = 0; i < components.length; i++) { 19771 var component = components[i]; 19772 19773 for (var j = 0; j < component.length; j++) { 19774 var node = component[j]; 19775 id2cmptId[node.id()] = i; 19776 } 19777 } // Iterate over all nodes, creating layout nodes 19778 19779 19780 for (var i = 0; i < layoutInfo.nodeSize; i++) { 19781 var n = nodes[i]; 19782 var nbb = n.layoutDimensions(options); 19783 var tempNode = {}; 19784 tempNode.isLocked = n.locked(); 19785 tempNode.id = n.data('id'); 19786 tempNode.parentId = n.data('parent'); 19787 tempNode.cmptId = id2cmptId[n.id()]; 19788 tempNode.children = []; 19789 tempNode.positionX = n.position('x'); 19790 tempNode.positionY = n.position('y'); 19791 tempNode.offsetX = 0; 19792 tempNode.offsetY = 0; 19793 tempNode.height = nbb.w; 19794 tempNode.width = nbb.h; 19795 tempNode.maxX = tempNode.positionX + tempNode.width / 2; 19796 tempNode.minX = tempNode.positionX - tempNode.width / 2; 19797 tempNode.maxY = tempNode.positionY + tempNode.height / 2; 19798 tempNode.minY = tempNode.positionY - tempNode.height / 2; 19799 tempNode.padLeft = parseFloat(n.style('padding')); 19800 tempNode.padRight = parseFloat(n.style('padding')); 19801 tempNode.padTop = parseFloat(n.style('padding')); 19802 tempNode.padBottom = parseFloat(n.style('padding')); // forces 19803 19804 tempNode.nodeRepulsion = fn(options.nodeRepulsion) ? options.nodeRepulsion(n) : options.nodeRepulsion; // Add new node 19805 19806 layoutInfo.layoutNodes.push(tempNode); // Add entry to id-index map 19807 19808 layoutInfo.idToIndex[tempNode.id] = i; 19809 } // Inline implementation of a queue, used for traversing the graph in BFS order 19810 19811 19812 var queue = []; 19813 var start = 0; // Points to the start the queue 19814 19815 var end = -1; // Points to the end of the queue 19816 19817 var tempGraph = []; // Second pass to add child information and 19818 // initialize queue for hierarchical traversal 19819 19820 for (var i = 0; i < layoutInfo.nodeSize; i++) { 19821 var n = layoutInfo.layoutNodes[i]; 19822 var p_id = n.parentId; // Check if node n has a parent node 19823 19824 if (null != p_id) { 19825 // Add node Id to parent's list of children 19826 layoutInfo.layoutNodes[layoutInfo.idToIndex[p_id]].children.push(n.id); 19827 } else { 19828 // If a node doesn't have a parent, then it's in the root graph 19829 queue[++end] = n.id; 19830 tempGraph.push(n.id); 19831 } 19832 } // Add root graph to graphSet 19833 19834 19835 layoutInfo.graphSet.push(tempGraph); // Traverse the graph, level by level, 19836 19837 while (start <= end) { 19838 // Get the node to visit and remove it from queue 19839 var node_id = queue[start++]; 19840 var node_ix = layoutInfo.idToIndex[node_id]; 19841 var node = layoutInfo.layoutNodes[node_ix]; 19842 var children = node.children; 19843 19844 if (children.length > 0) { 19845 // Add children nodes as a new graph to graph set 19846 layoutInfo.graphSet.push(children); // Add children to que queue to be visited 19847 19848 for (var i = 0; i < children.length; i++) { 19849 queue[++end] = children[i]; 19850 } 19851 } 19852 } // Create indexToGraph map 19853 19854 19855 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 19856 var graph = layoutInfo.graphSet[i]; 19857 19858 for (var j = 0; j < graph.length; j++) { 19859 var index = layoutInfo.idToIndex[graph[j]]; 19860 layoutInfo.indexToGraph[index] = i; 19861 } 19862 } // Iterate over all edges, creating Layout Edges 19863 19864 19865 for (var i = 0; i < layoutInfo.edgeSize; i++) { 19866 var e = edges[i]; 19867 var tempEdge = {}; 19868 tempEdge.id = e.data('id'); 19869 tempEdge.sourceId = e.data('source'); 19870 tempEdge.targetId = e.data('target'); // Compute ideal length 19871 19872 var idealLength = fn(options.idealEdgeLength) ? options.idealEdgeLength(e) : options.idealEdgeLength; 19873 var elasticity = fn(options.edgeElasticity) ? options.edgeElasticity(e) : options.edgeElasticity; // Check if it's an inter graph edge 19874 19875 var sourceIx = layoutInfo.idToIndex[tempEdge.sourceId]; 19876 var targetIx = layoutInfo.idToIndex[tempEdge.targetId]; 19877 var sourceGraph = layoutInfo.indexToGraph[sourceIx]; 19878 var targetGraph = layoutInfo.indexToGraph[targetIx]; 19879 19880 if (sourceGraph != targetGraph) { 19881 // Find lowest common graph ancestor 19882 var lca = findLCA(tempEdge.sourceId, tempEdge.targetId, layoutInfo); // Compute sum of node depths, relative to lca graph 19883 19884 var lcaGraph = layoutInfo.graphSet[lca]; 19885 var depth = 0; // Source depth 19886 19887 var tempNode = layoutInfo.layoutNodes[sourceIx]; 19888 19889 while (-1 === lcaGraph.indexOf(tempNode.id)) { 19890 tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]]; 19891 depth++; 19892 } // Target depth 19893 19894 19895 tempNode = layoutInfo.layoutNodes[targetIx]; 19896 19897 while (-1 === lcaGraph.indexOf(tempNode.id)) { 19898 tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]]; 19899 depth++; 19900 } // logDebug('LCA of nodes ' + tempEdge.sourceId + ' and ' + tempEdge.targetId + 19901 // ". Index: " + lca + " Contents: " + lcaGraph.toString() + 19902 // ". Depth: " + depth); 19903 // Update idealLength 19904 19905 19906 idealLength *= depth * options.nestingFactor; 19907 } 19908 19909 tempEdge.idealLength = idealLength; 19910 tempEdge.elasticity = elasticity; 19911 layoutInfo.layoutEdges.push(tempEdge); 19912 } // Finally, return layoutInfo object 19913 19914 19915 return layoutInfo; 19916 }; 19917 /** 19918 * @brief : This function finds the index of the lowest common 19919 * graph ancestor between 2 nodes in the subtree 19920 * (from the graph hierarchy induced tree) whose 19921 * root is graphIx 19922 * 19923 * @arg node1: node1's ID 19924 * @arg node2: node2's ID 19925 * @arg layoutInfo: layoutInfo object 19926 * 19927 */ 19928 19929 19930 var findLCA = function findLCA(node1, node2, layoutInfo) { 19931 // Find their common ancester, starting from the root graph 19932 var res = findLCA_aux(node1, node2, 0, layoutInfo); 19933 19934 if (2 > res.count) { 19935 // If aux function couldn't find the common ancester, 19936 // then it is the root graph 19937 return 0; 19938 } else { 19939 return res.graph; 19940 } 19941 }; 19942 /** 19943 * @brief : Auxiliary function used for LCA computation 19944 * 19945 * @arg node1 : node1's ID 19946 * @arg node2 : node2's ID 19947 * @arg graphIx : subgraph index 19948 * @arg layoutInfo : layoutInfo object 19949 * 19950 * @return : object of the form {count: X, graph: Y}, where: 19951 * X is the number of ancesters (max: 2) found in 19952 * graphIx (and it's subgraphs), 19953 * Y is the graph index of the lowest graph containing 19954 * all X nodes 19955 */ 19956 19957 19958 var findLCA_aux = function findLCA_aux(node1, node2, graphIx, layoutInfo) { 19959 var graph = layoutInfo.graphSet[graphIx]; // If both nodes belongs to graphIx 19960 19961 if (-1 < graph.indexOf(node1) && -1 < graph.indexOf(node2)) { 19962 return { 19963 count: 2, 19964 graph: graphIx 19965 }; 19966 } // Make recursive calls for all subgraphs 19967 19968 19969 var c = 0; 19970 19971 for (var i = 0; i < graph.length; i++) { 19972 var nodeId = graph[i]; 19973 var nodeIx = layoutInfo.idToIndex[nodeId]; 19974 var children = layoutInfo.layoutNodes[nodeIx].children; // If the node has no child, skip it 19975 19976 if (0 === children.length) { 19977 continue; 19978 } 19979 19980 var childGraphIx = layoutInfo.indexToGraph[layoutInfo.idToIndex[children[0]]]; 19981 var result = findLCA_aux(node1, node2, childGraphIx, layoutInfo); 19982 19983 if (0 === result.count) { 19984 // Neither node1 nor node2 are present in this subgraph 19985 continue; 19986 } else if (1 === result.count) { 19987 // One of (node1, node2) is present in this subgraph 19988 c++; 19989 19990 if (2 === c) { 19991 // We've already found both nodes, no need to keep searching 19992 break; 19993 } 19994 } else { 19995 // Both nodes are present in this subgraph 19996 return result; 19997 } 19998 } 19999 20000 return { 20001 count: c, 20002 graph: graphIx 20003 }; 20004 }; 20005 /** 20006 * @brief: printsLayoutInfo into js console 20007 * Only used for debbuging 20008 */ 20009 20010 20011 if (false) { 20012 var printLayoutInfo; 20013 } 20014 /** 20015 * @brief : Randomizes the position of all nodes 20016 */ 20017 20018 20019 var randomizePositions = function randomizePositions(layoutInfo, cy) { 20020 var width = layoutInfo.clientWidth; 20021 var height = layoutInfo.clientHeight; 20022 20023 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20024 var n = layoutInfo.layoutNodes[i]; // No need to randomize compound nodes or locked nodes 20025 20026 if (0 === n.children.length && !n.isLocked) { 20027 n.positionX = Math.random() * width; 20028 n.positionY = Math.random() * height; 20029 } 20030 } 20031 }; 20032 20033 var getScaleInBoundsFn = function getScaleInBoundsFn(layoutInfo, options, nodes) { 20034 var bb = layoutInfo.boundingBox; 20035 var coseBB = { 20036 x1: Infinity, 20037 x2: -Infinity, 20038 y1: Infinity, 20039 y2: -Infinity 20040 }; 20041 20042 if (options.boundingBox) { 20043 nodes.forEach(function (node) { 20044 var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[node.data('id')]]; 20045 coseBB.x1 = Math.min(coseBB.x1, lnode.positionX); 20046 coseBB.x2 = Math.max(coseBB.x2, lnode.positionX); 20047 coseBB.y1 = Math.min(coseBB.y1, lnode.positionY); 20048 coseBB.y2 = Math.max(coseBB.y2, lnode.positionY); 20049 }); 20050 coseBB.w = coseBB.x2 - coseBB.x1; 20051 coseBB.h = coseBB.y2 - coseBB.y1; 20052 } 20053 20054 return function (ele, i) { 20055 var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[ele.data('id')]]; 20056 20057 if (options.boundingBox) { 20058 // then add extra bounding box constraint 20059 var pctX = (lnode.positionX - coseBB.x1) / coseBB.w; 20060 var pctY = (lnode.positionY - coseBB.y1) / coseBB.h; 20061 return { 20062 x: bb.x1 + pctX * bb.w, 20063 y: bb.y1 + pctY * bb.h 20064 }; 20065 } else { 20066 return { 20067 x: lnode.positionX, 20068 y: lnode.positionY 20069 }; 20070 } 20071 }; 20072 }; 20073 /** 20074 * @brief : Updates the positions of nodes in the network 20075 * @arg layoutInfo : LayoutInfo object 20076 * @arg cy : Cytoscape object 20077 * @arg options : Layout options 20078 */ 20079 20080 20081 var refreshPositions = function refreshPositions(layoutInfo, cy, options) { 20082 // var s = 'Refreshing positions'; 20083 // logDebug(s); 20084 var layout = options.layout; 20085 var nodes = options.eles.nodes(); 20086 var getScaledPos = getScaleInBoundsFn(layoutInfo, options, nodes); 20087 nodes.positions(getScaledPos); // Trigger layoutReady only on first call 20088 20089 if (true !== layoutInfo.ready) { 20090 // s = 'Triggering layoutready'; 20091 // logDebug(s); 20092 layoutInfo.ready = true; 20093 layout.one('layoutready', options.ready); 20094 layout.emit({ 20095 type: 'layoutready', 20096 layout: this 20097 }); 20098 } 20099 }; 20100 /** 20101 * @brief : Logs a debug message in JS console, if DEBUG is ON 20102 */ 20103 // var logDebug = function(text) { 20104 // if (DEBUG) { 20105 // console.debug(text); 20106 // } 20107 // }; 20108 20109 /** 20110 * @brief : Performs one iteration of the physical simulation 20111 * @arg layoutInfo : LayoutInfo object already initialized 20112 * @arg cy : Cytoscape object 20113 * @arg options : Layout options 20114 */ 20115 20116 20117 var step$1 = function step(layoutInfo, options, _step) { 20118 // var s = "\n\n###############################"; 20119 // s += "\nSTEP: " + step; 20120 // s += "\n###############################\n"; 20121 // logDebug(s); 20122 // Calculate node repulsions 20123 calculateNodeForces(layoutInfo, options); // Calculate edge forces 20124 20125 calculateEdgeForces(layoutInfo); // Calculate gravity forces 20126 20127 calculateGravityForces(layoutInfo, options); // Propagate forces from parent to child 20128 20129 propagateForces(layoutInfo); // Update positions based on calculated forces 20130 20131 updatePositions(layoutInfo); 20132 }; 20133 /** 20134 * @brief : Computes the node repulsion forces 20135 */ 20136 20137 20138 var calculateNodeForces = function calculateNodeForces(layoutInfo, options) { 20139 // Go through each of the graphs in graphSet 20140 // Nodes only repel each other if they belong to the same graph 20141 // var s = 'calculateNodeForces'; 20142 // logDebug(s); 20143 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 20144 var graph = layoutInfo.graphSet[i]; 20145 var numNodes = graph.length; // s = "Set: " + graph.toString(); 20146 // logDebug(s); 20147 // Now get all the pairs of nodes 20148 // Only get each pair once, (A, B) = (B, A) 20149 20150 for (var j = 0; j < numNodes; j++) { 20151 var node1 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]]; 20152 20153 for (var k = j + 1; k < numNodes; k++) { 20154 var node2 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[k]]]; 20155 nodeRepulsion(node1, node2, layoutInfo, options); 20156 } 20157 } 20158 } 20159 }; 20160 20161 var randomDistance = function randomDistance(max) { 20162 return -max + 2 * max * Math.random(); 20163 }; 20164 /** 20165 * @brief : Compute the node repulsion forces between a pair of nodes 20166 */ 20167 20168 20169 var nodeRepulsion = function nodeRepulsion(node1, node2, layoutInfo, options) { 20170 // var s = "Node repulsion. Node1: " + node1.id + " Node2: " + node2.id; 20171 var cmptId1 = node1.cmptId; 20172 var cmptId2 = node2.cmptId; 20173 20174 if (cmptId1 !== cmptId2 && !layoutInfo.isCompound) { 20175 return; 20176 } // Get direction of line connecting both node centers 20177 20178 20179 var directionX = node2.positionX - node1.positionX; 20180 var directionY = node2.positionY - node1.positionY; 20181 var maxRandDist = 1; // s += "\ndirectionX: " + directionX + ", directionY: " + directionY; 20182 // If both centers are the same, apply a random force 20183 20184 if (0 === directionX && 0 === directionY) { 20185 directionX = randomDistance(maxRandDist); 20186 directionY = randomDistance(maxRandDist); 20187 } 20188 20189 var overlap = nodesOverlap(node1, node2, directionX, directionY); 20190 20191 if (overlap > 0) { 20192 // s += "\nNodes DO overlap."; 20193 // s += "\nOverlap: " + overlap; 20194 // If nodes overlap, repulsion force is proportional 20195 // to the overlap 20196 var force = options.nodeOverlap * overlap; // Compute the module and components of the force vector 20197 20198 var distance = Math.sqrt(directionX * directionX + directionY * directionY); // s += "\nDistance: " + distance; 20199 20200 var forceX = force * directionX / distance; 20201 var forceY = force * directionY / distance; 20202 } else { 20203 // s += "\nNodes do NOT overlap."; 20204 // If there's no overlap, force is inversely proportional 20205 // to squared distance 20206 // Get clipping points for both nodes 20207 var point1 = findClippingPoint(node1, directionX, directionY); 20208 var point2 = findClippingPoint(node2, -1 * directionX, -1 * directionY); // Use clipping points to compute distance 20209 20210 var distanceX = point2.x - point1.x; 20211 var distanceY = point2.y - point1.y; 20212 var distanceSqr = distanceX * distanceX + distanceY * distanceY; 20213 var distance = Math.sqrt(distanceSqr); // s += "\nDistance: " + distance; 20214 // Compute the module and components of the force vector 20215 20216 var force = (node1.nodeRepulsion + node2.nodeRepulsion) / distanceSqr; 20217 var forceX = force * distanceX / distance; 20218 var forceY = force * distanceY / distance; 20219 } // Apply force 20220 20221 20222 if (!node1.isLocked) { 20223 node1.offsetX -= forceX; 20224 node1.offsetY -= forceY; 20225 } 20226 20227 if (!node2.isLocked) { 20228 node2.offsetX += forceX; 20229 node2.offsetY += forceY; 20230 } // s += "\nForceX: " + forceX + " ForceY: " + forceY; 20231 // logDebug(s); 20232 20233 20234 return; 20235 }; 20236 /** 20237 * @brief : Determines whether two nodes overlap or not 20238 * @return : Amount of overlapping (0 => no overlap) 20239 */ 20240 20241 20242 var nodesOverlap = function nodesOverlap(node1, node2, dX, dY) { 20243 if (dX > 0) { 20244 var overlapX = node1.maxX - node2.minX; 20245 } else { 20246 var overlapX = node2.maxX - node1.minX; 20247 } 20248 20249 if (dY > 0) { 20250 var overlapY = node1.maxY - node2.minY; 20251 } else { 20252 var overlapY = node2.maxY - node1.minY; 20253 } 20254 20255 if (overlapX >= 0 && overlapY >= 0) { 20256 return Math.sqrt(overlapX * overlapX + overlapY * overlapY); 20257 } else { 20258 return 0; 20259 } 20260 }; 20261 /** 20262 * @brief : Finds the point in which an edge (direction dX, dY) intersects 20263 * the rectangular bounding box of it's source/target node 20264 */ 20265 20266 20267 var findClippingPoint = function findClippingPoint(node, dX, dY) { 20268 // Shorcuts 20269 var X = node.positionX; 20270 var Y = node.positionY; 20271 var H = node.height || 1; 20272 var W = node.width || 1; 20273 var dirSlope = dY / dX; 20274 var nodeSlope = H / W; // var s = 'Computing clipping point of node ' + node.id + 20275 // " . Height: " + H + ", Width: " + W + 20276 // "\nDirection " + dX + ", " + dY; 20277 // 20278 // Compute intersection 20279 20280 var res = {}; // Case: Vertical direction (up) 20281 20282 if (0 === dX && 0 < dY) { 20283 res.x = X; // s += "\nUp direction"; 20284 20285 res.y = Y + H / 2; 20286 return res; 20287 } // Case: Vertical direction (down) 20288 20289 20290 if (0 === dX && 0 > dY) { 20291 res.x = X; 20292 res.y = Y + H / 2; // s += "\nDown direction"; 20293 20294 return res; 20295 } // Case: Intersects the right border 20296 20297 20298 if (0 < dX && -1 * nodeSlope <= dirSlope && dirSlope <= nodeSlope) { 20299 res.x = X + W / 2; 20300 res.y = Y + W * dY / 2 / dX; // s += "\nRightborder"; 20301 20302 return res; 20303 } // Case: Intersects the left border 20304 20305 20306 if (0 > dX && -1 * nodeSlope <= dirSlope && dirSlope <= nodeSlope) { 20307 res.x = X - W / 2; 20308 res.y = Y - W * dY / 2 / dX; // s += "\nLeftborder"; 20309 20310 return res; 20311 } // Case: Intersects the top border 20312 20313 20314 if (0 < dY && (dirSlope <= -1 * nodeSlope || dirSlope >= nodeSlope)) { 20315 res.x = X + H * dX / 2 / dY; 20316 res.y = Y + H / 2; // s += "\nTop border"; 20317 20318 return res; 20319 } // Case: Intersects the bottom border 20320 20321 20322 if (0 > dY && (dirSlope <= -1 * nodeSlope || dirSlope >= nodeSlope)) { 20323 res.x = X - H * dX / 2 / dY; 20324 res.y = Y - H / 2; // s += "\nBottom border"; 20325 20326 return res; 20327 } // s += "\nClipping point found at " + res.x + ", " + res.y; 20328 // logDebug(s); 20329 20330 20331 return res; 20332 }; 20333 /** 20334 * @brief : Calculates all edge forces 20335 */ 20336 20337 20338 var calculateEdgeForces = function calculateEdgeForces(layoutInfo, options) { 20339 // Iterate over all edges 20340 for (var i = 0; i < layoutInfo.edgeSize; i++) { 20341 // Get edge, source & target nodes 20342 var edge = layoutInfo.layoutEdges[i]; 20343 var sourceIx = layoutInfo.idToIndex[edge.sourceId]; 20344 var source = layoutInfo.layoutNodes[sourceIx]; 20345 var targetIx = layoutInfo.idToIndex[edge.targetId]; 20346 var target = layoutInfo.layoutNodes[targetIx]; // Get direction of line connecting both node centers 20347 20348 var directionX = target.positionX - source.positionX; 20349 var directionY = target.positionY - source.positionY; // If both centers are the same, do nothing. 20350 // A random force has already been applied as node repulsion 20351 20352 if (0 === directionX && 0 === directionY) { 20353 continue; 20354 } // Get clipping points for both nodes 20355 20356 20357 var point1 = findClippingPoint(source, directionX, directionY); 20358 var point2 = findClippingPoint(target, -1 * directionX, -1 * directionY); 20359 var lx = point2.x - point1.x; 20360 var ly = point2.y - point1.y; 20361 var l = Math.sqrt(lx * lx + ly * ly); 20362 var force = Math.pow(edge.idealLength - l, 2) / edge.elasticity; 20363 20364 if (0 !== l) { 20365 var forceX = force * lx / l; 20366 var forceY = force * ly / l; 20367 } else { 20368 var forceX = 0; 20369 var forceY = 0; 20370 } // Add this force to target and source nodes 20371 20372 20373 if (!source.isLocked) { 20374 source.offsetX += forceX; 20375 source.offsetY += forceY; 20376 } 20377 20378 if (!target.isLocked) { 20379 target.offsetX -= forceX; 20380 target.offsetY -= forceY; 20381 } // var s = 'Edge force between nodes ' + source.id + ' and ' + target.id; 20382 // s += "\nDistance: " + l + " Force: (" + forceX + ", " + forceY + ")"; 20383 // logDebug(s); 20384 20385 } 20386 }; 20387 /** 20388 * @brief : Computes gravity forces for all nodes 20389 */ 20390 20391 20392 var calculateGravityForces = function calculateGravityForces(layoutInfo, options) { 20393 var distThreshold = 1; // var s = 'calculateGravityForces'; 20394 // logDebug(s); 20395 20396 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 20397 var graph = layoutInfo.graphSet[i]; 20398 var numNodes = graph.length; // s = "Set: " + graph.toString(); 20399 // logDebug(s); 20400 // Compute graph center 20401 20402 if (0 === i) { 20403 var centerX = layoutInfo.clientHeight / 2; 20404 var centerY = layoutInfo.clientWidth / 2; 20405 } else { 20406 // Get Parent node for this graph, and use its position as center 20407 var temp = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[0]]]; 20408 var parent = layoutInfo.layoutNodes[layoutInfo.idToIndex[temp.parentId]]; 20409 var centerX = parent.positionX; 20410 var centerY = parent.positionY; 20411 } // s = "Center found at: " + centerX + ", " + centerY; 20412 // logDebug(s); 20413 // Apply force to all nodes in graph 20414 20415 20416 for (var j = 0; j < numNodes; j++) { 20417 var node = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]]; // s = "Node: " + node.id; 20418 20419 if (node.isLocked) { 20420 continue; 20421 } 20422 20423 var dx = centerX - node.positionX; 20424 var dy = centerY - node.positionY; 20425 var d = Math.sqrt(dx * dx + dy * dy); 20426 20427 if (d > distThreshold) { 20428 var fx = options.gravity * dx / d; 20429 var fy = options.gravity * dy / d; 20430 node.offsetX += fx; 20431 node.offsetY += fy; // s += ": Applied force: " + fx + ", " + fy; 20432 } // s += ": skypped since it's too close to center"; 20433 // logDebug(s); 20434 20435 } 20436 } 20437 }; 20438 /** 20439 * @brief : This function propagates the existing offsets from 20440 * parent nodes to its descendents. 20441 * @arg layoutInfo : layoutInfo Object 20442 * @arg cy : cytoscape Object 20443 * @arg options : Layout options 20444 */ 20445 20446 20447 var propagateForces = function propagateForces(layoutInfo, options) { 20448 // Inline implementation of a queue, used for traversing the graph in BFS order 20449 var queue = []; 20450 var start = 0; // Points to the start the queue 20451 20452 var end = -1; // Points to the end of the queue 20453 // logDebug('propagateForces'); 20454 // Start by visiting the nodes in the root graph 20455 20456 queue.push.apply(queue, layoutInfo.graphSet[0]); 20457 end += layoutInfo.graphSet[0].length; // Traverse the graph, level by level, 20458 20459 while (start <= end) { 20460 // Get the node to visit and remove it from queue 20461 var nodeId = queue[start++]; 20462 var nodeIndex = layoutInfo.idToIndex[nodeId]; 20463 var node = layoutInfo.layoutNodes[nodeIndex]; 20464 var children = node.children; // We only need to process the node if it's compound 20465 20466 if (0 < children.length && !node.isLocked) { 20467 var offX = node.offsetX; 20468 var offY = node.offsetY; // var s = "Propagating offset from parent node : " + node.id + 20469 // ". OffsetX: " + offX + ". OffsetY: " + offY; 20470 // s += "\n Children: " + children.toString(); 20471 // logDebug(s); 20472 20473 for (var i = 0; i < children.length; i++) { 20474 var childNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[children[i]]]; // Propagate offset 20475 20476 childNode.offsetX += offX; 20477 childNode.offsetY += offY; // Add children to queue to be visited 20478 20479 queue[++end] = children[i]; 20480 } // Reset parent offsets 20481 20482 20483 node.offsetX = 0; 20484 node.offsetY = 0; 20485 } 20486 } 20487 }; 20488 /** 20489 * @brief : Updates the layout model positions, based on 20490 * the accumulated forces 20491 */ 20492 20493 20494 var updatePositions = function updatePositions(layoutInfo, options) { 20495 // var s = 'Updating positions'; 20496 // logDebug(s); 20497 // Reset boundaries for compound nodes 20498 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20499 var n = layoutInfo.layoutNodes[i]; 20500 20501 if (0 < n.children.length) { 20502 // logDebug("Resetting boundaries of compound node: " + n.id); 20503 n.maxX = undefined; 20504 n.minX = undefined; 20505 n.maxY = undefined; 20506 n.minY = undefined; 20507 } 20508 } 20509 20510 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20511 var n = layoutInfo.layoutNodes[i]; 20512 20513 if (0 < n.children.length || n.isLocked) { 20514 // No need to set compound or locked node position 20515 // logDebug("Skipping position update of node: " + n.id); 20516 continue; 20517 } // s = "Node: " + n.id + " Previous position: (" + 20518 // n.positionX + ", " + n.positionY + ")."; 20519 // Limit displacement in order to improve stability 20520 20521 20522 var tempForce = limitForce(n.offsetX, n.offsetY, layoutInfo.temperature); 20523 n.positionX += tempForce.x; 20524 n.positionY += tempForce.y; 20525 n.offsetX = 0; 20526 n.offsetY = 0; 20527 n.minX = n.positionX - n.width; 20528 n.maxX = n.positionX + n.width; 20529 n.minY = n.positionY - n.height; 20530 n.maxY = n.positionY + n.height; // s += " New Position: (" + n.positionX + ", " + n.positionY + ")."; 20531 // logDebug(s); 20532 // Update ancestry boudaries 20533 20534 updateAncestryBoundaries(n, layoutInfo); 20535 } // Update size, position of compund nodes 20536 20537 20538 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20539 var n = layoutInfo.layoutNodes[i]; 20540 20541 if (0 < n.children.length && !n.isLocked) { 20542 n.positionX = (n.maxX + n.minX) / 2; 20543 n.positionY = (n.maxY + n.minY) / 2; 20544 n.width = n.maxX - n.minX; 20545 n.height = n.maxY - n.minY; // s = "Updating position, size of compound node " + n.id; 20546 // s += "\nPositionX: " + n.positionX + ", PositionY: " + n.positionY; 20547 // s += "\nWidth: " + n.width + ", Height: " + n.height; 20548 // logDebug(s); 20549 } 20550 } 20551 }; 20552 /** 20553 * @brief : Limits a force (forceX, forceY) to be not 20554 * greater (in modulo) than max. 20555 8 Preserves force direction. 20556 */ 20557 20558 20559 var limitForce = function limitForce(forceX, forceY, max) { 20560 // var s = "Limiting force: (" + forceX + ", " + forceY + "). Max: " + max; 20561 var force = Math.sqrt(forceX * forceX + forceY * forceY); 20562 20563 if (force > max) { 20564 var res = { 20565 x: max * forceX / force, 20566 y: max * forceY / force 20567 }; 20568 } else { 20569 var res = { 20570 x: forceX, 20571 y: forceY 20572 }; 20573 } // s += ".\nResult: (" + res.x + ", " + res.y + ")"; 20574 // logDebug(s); 20575 20576 20577 return res; 20578 }; 20579 /** 20580 * @brief : Function used for keeping track of compound node 20581 * sizes, since they should bound all their subnodes. 20582 */ 20583 20584 20585 var updateAncestryBoundaries = function updateAncestryBoundaries(node, layoutInfo) { 20586 // var s = "Propagating new position/size of node " + node.id; 20587 var parentId = node.parentId; 20588 20589 if (null == parentId) { 20590 // If there's no parent, we are done 20591 // s += ". No parent node."; 20592 // logDebug(s); 20593 return; 20594 } // Get Parent Node 20595 20596 20597 var p = layoutInfo.layoutNodes[layoutInfo.idToIndex[parentId]]; 20598 var flag = false; // MaxX 20599 20600 if (null == p.maxX || node.maxX + p.padRight > p.maxX) { 20601 p.maxX = node.maxX + p.padRight; 20602 flag = true; // s += "\nNew maxX for parent node " + p.id + ": " + p.maxX; 20603 } // MinX 20604 20605 20606 if (null == p.minX || node.minX - p.padLeft < p.minX) { 20607 p.minX = node.minX - p.padLeft; 20608 flag = true; // s += "\nNew minX for parent node " + p.id + ": " + p.minX; 20609 } // MaxY 20610 20611 20612 if (null == p.maxY || node.maxY + p.padBottom > p.maxY) { 20613 p.maxY = node.maxY + p.padBottom; 20614 flag = true; // s += "\nNew maxY for parent node " + p.id + ": " + p.maxY; 20615 } // MinY 20616 20617 20618 if (null == p.minY || node.minY - p.padTop < p.minY) { 20619 p.minY = node.minY - p.padTop; 20620 flag = true; // s += "\nNew minY for parent node " + p.id + ": " + p.minY; 20621 } // If updated boundaries, propagate changes upward 20622 20623 20624 if (flag) { 20625 // logDebug(s); 20626 return updateAncestryBoundaries(p, layoutInfo); 20627 } // s += ". No changes in boundaries/position of parent node " + p.id; 20628 // logDebug(s); 20629 20630 20631 return; 20632 }; 20633 20634 var separateComponents = function separateComponents(layoutInfo, options) { 20635 var nodes = layoutInfo.layoutNodes; 20636 var components = []; 20637 20638 for (var i = 0; i < nodes.length; i++) { 20639 var node = nodes[i]; 20640 var cid = node.cmptId; 20641 var component = components[cid] = components[cid] || []; 20642 component.push(node); 20643 } 20644 20645 var totalA = 0; 20646 20647 for (var i = 0; i < components.length; i++) { 20648 var c = components[i]; 20649 20650 if (!c) { 20651 continue; 20652 } 20653 20654 c.x1 = Infinity; 20655 c.x2 = -Infinity; 20656 c.y1 = Infinity; 20657 c.y2 = -Infinity; 20658 20659 for (var j = 0; j < c.length; j++) { 20660 var n = c[j]; 20661 c.x1 = Math.min(c.x1, n.positionX - n.width / 2); 20662 c.x2 = Math.max(c.x2, n.positionX + n.width / 2); 20663 c.y1 = Math.min(c.y1, n.positionY - n.height / 2); 20664 c.y2 = Math.max(c.y2, n.positionY + n.height / 2); 20665 } 20666 20667 c.w = c.x2 - c.x1; 20668 c.h = c.y2 - c.y1; 20669 totalA += c.w * c.h; 20670 } 20671 20672 components.sort(function (c1, c2) { 20673 return c2.w * c2.h - c1.w * c1.h; 20674 }); 20675 var x = 0; 20676 var y = 0; 20677 var usedW = 0; 20678 var rowH = 0; 20679 var maxRowW = Math.sqrt(totalA) * layoutInfo.clientWidth / layoutInfo.clientHeight; 20680 20681 for (var i = 0; i < components.length; i++) { 20682 var c = components[i]; 20683 20684 if (!c) { 20685 continue; 20686 } 20687 20688 for (var j = 0; j < c.length; j++) { 20689 var n = c[j]; 20690 20691 if (!n.isLocked) { 20692 n.positionX += x - c.x1; 20693 n.positionY += y - c.y1; 20694 } 20695 } 20696 20697 x += c.w + options.componentSpacing; 20698 usedW += c.w + options.componentSpacing; 20699 rowH = Math.max(rowH, c.h); 20700 20701 if (usedW > maxRowW) { 20702 y += rowH + options.componentSpacing; 20703 x = 0; 20704 usedW = 0; 20705 rowH = 0; 20706 } 20707 } 20708 }; 20709 20710 var defaults$d = { 20711 fit: true, 20712 // whether to fit the viewport to the graph 20713 padding: 30, 20714 // padding used on fit 20715 boundingBox: undefined, 20716 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 20717 avoidOverlap: true, 20718 // prevents node overlap, may overflow boundingBox if not enough space 20719 avoidOverlapPadding: 10, 20720 // extra spacing around nodes when avoidOverlap: true 20721 nodeDimensionsIncludeLabels: false, 20722 // Excludes the label when calculating node bounding boxes for the layout algorithm 20723 spacingFactor: undefined, 20724 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 20725 condense: false, 20726 // uses all available space on false, uses minimal space on true 20727 rows: undefined, 20728 // force num of rows in the grid 20729 cols: undefined, 20730 // force num of columns in the grid 20731 position: function position(node) {}, 20732 // returns { row, col } for element 20733 sort: undefined, 20734 // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 20735 animate: false, 20736 // whether to transition the node positions 20737 animationDuration: 500, 20738 // duration of animation in ms if enabled 20739 animationEasing: undefined, 20740 // easing of animation if enabled 20741 animateFilter: function animateFilter(node, i) { 20742 return true; 20743 }, 20744 // 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 20745 ready: undefined, 20746 // callback on layoutready 20747 stop: undefined, 20748 // callback on layoutstop 20749 transform: function transform(node, position) { 20750 return position; 20751 } // transform a given node position. Useful for changing flow direction in discrete layouts 20752 20753 }; 20754 20755 function GridLayout(options) { 20756 this.options = extend({}, defaults$d, options); 20757 } 20758 20759 GridLayout.prototype.run = function () { 20760 var params = this.options; 20761 var options = params; 20762 var cy = params.cy; 20763 var eles = options.eles; 20764 var nodes = eles.nodes().not(':parent'); 20765 20766 if (options.sort) { 20767 nodes = nodes.sort(options.sort); 20768 } 20769 20770 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 20771 x1: 0, 20772 y1: 0, 20773 w: cy.width(), 20774 h: cy.height() 20775 }); 20776 20777 if (bb.h === 0 || bb.w === 0) { 20778 nodes.layoutPositions(this, options, function (ele) { 20779 return { 20780 x: bb.x1, 20781 y: bb.y1 20782 }; 20783 }); 20784 } else { 20785 // width/height * splits^2 = cells where splits is number of times to split width 20786 var cells = nodes.size(); 20787 var splits = Math.sqrt(cells * bb.h / bb.w); 20788 var rows = Math.round(splits); 20789 var cols = Math.round(bb.w / bb.h * splits); 20790 20791 var small = function small(val) { 20792 if (val == null) { 20793 return Math.min(rows, cols); 20794 } else { 20795 var min = Math.min(rows, cols); 20796 20797 if (min == rows) { 20798 rows = val; 20799 } else { 20800 cols = val; 20801 } 20802 } 20803 }; 20804 20805 var large = function large(val) { 20806 if (val == null) { 20807 return Math.max(rows, cols); 20808 } else { 20809 var max = Math.max(rows, cols); 20810 20811 if (max == rows) { 20812 rows = val; 20813 } else { 20814 cols = val; 20815 } 20816 } 20817 }; 20818 20819 var oRows = options.rows; 20820 var oCols = options.cols != null ? options.cols : options.columns; // if rows or columns were set in options, use those values 20821 20822 if (oRows != null && oCols != null) { 20823 rows = oRows; 20824 cols = oCols; 20825 } else if (oRows != null && oCols == null) { 20826 rows = oRows; 20827 cols = Math.ceil(cells / rows); 20828 } else if (oRows == null && oCols != null) { 20829 cols = oCols; 20830 rows = Math.ceil(cells / cols); 20831 } // otherwise use the automatic values and adjust accordingly 20832 // if rounding was up, see if we can reduce rows or columns 20833 else if (cols * rows > cells) { 20834 var sm = small(); 20835 var lg = large(); // reducing the small side takes away the most cells, so try it first 20836 20837 if ((sm - 1) * lg >= cells) { 20838 small(sm - 1); 20839 } else if ((lg - 1) * sm >= cells) { 20840 large(lg - 1); 20841 } 20842 } else { 20843 // if rounding was too low, add rows or columns 20844 while (cols * rows < cells) { 20845 var _sm = small(); 20846 20847 var _lg = large(); // try to add to larger side first (adds less in multiplication) 20848 20849 20850 if ((_lg + 1) * _sm >= cells) { 20851 large(_lg + 1); 20852 } else { 20853 small(_sm + 1); 20854 } 20855 } 20856 } 20857 20858 var cellWidth = bb.w / cols; 20859 var cellHeight = bb.h / rows; 20860 20861 if (options.condense) { 20862 cellWidth = 0; 20863 cellHeight = 0; 20864 } 20865 20866 if (options.avoidOverlap) { 20867 for (var i = 0; i < nodes.length; i++) { 20868 var node = nodes[i]; 20869 var pos = node._private.position; 20870 20871 if (pos.x == null || pos.y == null) { 20872 // for bb 20873 pos.x = 0; 20874 pos.y = 0; 20875 } 20876 20877 var nbb = node.layoutDimensions(options); 20878 var p = options.avoidOverlapPadding; 20879 var w = nbb.w + p; 20880 var h = nbb.h + p; 20881 cellWidth = Math.max(cellWidth, w); 20882 cellHeight = Math.max(cellHeight, h); 20883 } 20884 } 20885 20886 var cellUsed = {}; // e.g. 'c-0-2' => true 20887 20888 var used = function used(row, col) { 20889 return cellUsed['c-' + row + '-' + col] ? true : false; 20890 }; 20891 20892 var use = function use(row, col) { 20893 cellUsed['c-' + row + '-' + col] = true; 20894 }; // to keep track of current cell position 20895 20896 20897 var row = 0; 20898 var col = 0; 20899 20900 var moveToNextCell = function moveToNextCell() { 20901 col++; 20902 20903 if (col >= cols) { 20904 col = 0; 20905 row++; 20906 } 20907 }; // get a cache of all the manual positions 20908 20909 20910 var id2manPos = {}; 20911 20912 for (var _i = 0; _i < nodes.length; _i++) { 20913 var _node = nodes[_i]; 20914 var rcPos = options.position(_node); 20915 20916 if (rcPos && (rcPos.row !== undefined || rcPos.col !== undefined)) { 20917 // must have at least row or col def'd 20918 var _pos = { 20919 row: rcPos.row, 20920 col: rcPos.col 20921 }; 20922 20923 if (_pos.col === undefined) { 20924 // find unused col 20925 _pos.col = 0; 20926 20927 while (used(_pos.row, _pos.col)) { 20928 _pos.col++; 20929 } 20930 } else if (_pos.row === undefined) { 20931 // find unused row 20932 _pos.row = 0; 20933 20934 while (used(_pos.row, _pos.col)) { 20935 _pos.row++; 20936 } 20937 } 20938 20939 id2manPos[_node.id()] = _pos; 20940 use(_pos.row, _pos.col); 20941 } 20942 } 20943 20944 var getPos = function getPos(element, i) { 20945 var x, y; 20946 20947 if (element.locked() || element.isParent()) { 20948 return false; 20949 } // see if we have a manual position set 20950 20951 20952 var rcPos = id2manPos[element.id()]; 20953 20954 if (rcPos) { 20955 x = rcPos.col * cellWidth + cellWidth / 2 + bb.x1; 20956 y = rcPos.row * cellHeight + cellHeight / 2 + bb.y1; 20957 } else { 20958 // otherwise set automatically 20959 while (used(row, col)) { 20960 moveToNextCell(); 20961 } 20962 20963 x = col * cellWidth + cellWidth / 2 + bb.x1; 20964 y = row * cellHeight + cellHeight / 2 + bb.y1; 20965 use(row, col); 20966 moveToNextCell(); 20967 } 20968 20969 return { 20970 x: x, 20971 y: y 20972 }; 20973 }; 20974 20975 nodes.layoutPositions(this, options, getPos); 20976 } 20977 20978 return this; // chaining 20979 }; 20980 20981 var defaults$e = { 20982 ready: function ready() {}, 20983 // on layoutready 20984 stop: function stop() {} // on layoutstop 20985 20986 }; // constructor 20987 // options : object containing layout options 20988 20989 function NullLayout(options) { 20990 this.options = extend({}, defaults$e, options); 20991 } // runs the layout 20992 20993 20994 NullLayout.prototype.run = function () { 20995 var options = this.options; 20996 var eles = options.eles; // elements to consider in the layout 20997 20998 var layout = this; // cy is automatically populated for us in the constructor 20999 // (disable eslint for next line as this serves as example layout code to external developers) 21000 // eslint-disable-next-line no-unused-vars 21001 21002 var cy = options.cy; 21003 layout.emit('layoutstart'); // puts all nodes at (0, 0) 21004 // n.b. most layouts would use layoutPositions(), instead of positions() and manual events 21005 21006 eles.nodes().positions(function () { 21007 return { 21008 x: 0, 21009 y: 0 21010 }; 21011 }); // trigger layoutready when each node has had its position set at least once 21012 21013 layout.one('layoutready', options.ready); 21014 layout.emit('layoutready'); // trigger layoutstop when the layout stops (e.g. finishes) 21015 21016 layout.one('layoutstop', options.stop); 21017 layout.emit('layoutstop'); 21018 return this; // chaining 21019 }; // called on continuous layouts to stop them before they finish 21020 21021 21022 NullLayout.prototype.stop = function () { 21023 return this; // chaining 21024 }; 21025 21026 var defaults$f = { 21027 positions: undefined, 21028 // map of (node id) => (position obj); or function(node){ return somPos; } 21029 zoom: undefined, 21030 // the zoom level to set (prob want fit = false if set) 21031 pan: undefined, 21032 // the pan level to set (prob want fit = false if set) 21033 fit: true, 21034 // whether to fit to viewport 21035 padding: 30, 21036 // padding on fit 21037 animate: false, 21038 // whether to transition the node positions 21039 animationDuration: 500, 21040 // duration of animation in ms if enabled 21041 animationEasing: undefined, 21042 // easing of animation if enabled 21043 animateFilter: function animateFilter(node, i) { 21044 return true; 21045 }, 21046 // 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 21047 ready: undefined, 21048 // callback on layoutready 21049 stop: undefined, 21050 // callback on layoutstop 21051 transform: function transform(node, position) { 21052 return position; 21053 } // transform a given node position. Useful for changing flow direction in discrete layouts 21054 21055 }; 21056 21057 function PresetLayout(options) { 21058 this.options = extend({}, defaults$f, options); 21059 } 21060 21061 PresetLayout.prototype.run = function () { 21062 var options = this.options; 21063 var eles = options.eles; 21064 var nodes = eles.nodes(); 21065 var posIsFn = fn(options.positions); 21066 21067 function getPosition(node) { 21068 if (options.positions == null) { 21069 return copyPosition(node.position()); 21070 } 21071 21072 if (posIsFn) { 21073 return options.positions(node); 21074 } 21075 21076 var pos = options.positions[node._private.data.id]; 21077 21078 if (pos == null) { 21079 return null; 21080 } 21081 21082 return pos; 21083 } 21084 21085 nodes.layoutPositions(this, options, function (node, i) { 21086 var position = getPosition(node); 21087 21088 if (node.locked() || position == null) { 21089 return false; 21090 } 21091 21092 return position; 21093 }); 21094 return this; // chaining 21095 }; 21096 21097 var defaults$g = { 21098 fit: true, 21099 // whether to fit to viewport 21100 padding: 30, 21101 // fit padding 21102 boundingBox: undefined, 21103 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 21104 animate: false, 21105 // whether to transition the node positions 21106 animationDuration: 500, 21107 // duration of animation in ms if enabled 21108 animationEasing: undefined, 21109 // easing of animation if enabled 21110 animateFilter: function animateFilter(node, i) { 21111 return true; 21112 }, 21113 // 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 21114 ready: undefined, 21115 // callback on layoutready 21116 stop: undefined, 21117 // callback on layoutstop 21118 transform: function transform(node, position) { 21119 return position; 21120 } // transform a given node position. Useful for changing flow direction in discrete layouts 21121 21122 }; 21123 21124 function RandomLayout(options) { 21125 this.options = extend({}, defaults$g, options); 21126 } 21127 21128 RandomLayout.prototype.run = function () { 21129 var options = this.options; 21130 var cy = options.cy; 21131 var eles = options.eles; 21132 var nodes = eles.nodes().not(':parent'); 21133 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 21134 x1: 0, 21135 y1: 0, 21136 w: cy.width(), 21137 h: cy.height() 21138 }); 21139 21140 var getPos = function getPos(node, i) { 21141 return { 21142 x: bb.x1 + Math.round(Math.random() * bb.w), 21143 y: bb.y1 + Math.round(Math.random() * bb.h) 21144 }; 21145 }; 21146 21147 nodes.layoutPositions(this, options, getPos); 21148 return this; // chaining 21149 }; 21150 21151 var layout = [{ 21152 name: 'breadthfirst', 21153 impl: BreadthFirstLayout 21154 }, { 21155 name: 'circle', 21156 impl: CircleLayout 21157 }, { 21158 name: 'concentric', 21159 impl: ConcentricLayout 21160 }, { 21161 name: 'cose', 21162 impl: CoseLayout 21163 }, { 21164 name: 'grid', 21165 impl: GridLayout 21166 }, { 21167 name: 'null', 21168 impl: NullLayout 21169 }, { 21170 name: 'preset', 21171 impl: PresetLayout 21172 }, { 21173 name: 'random', 21174 impl: RandomLayout 21175 }]; 21176 21177 function NullRenderer(options) { 21178 this.options = options; 21179 this.notifications = 0; // for testing 21180 } 21181 21182 var noop$1 = function noop() {}; 21183 21184 var throwImgErr = function throwImgErr() { 21185 throw new Error('A headless instance can not render images'); 21186 }; 21187 21188 NullRenderer.prototype = { 21189 recalculateRenderedStyle: noop$1, 21190 notify: function notify() { 21191 this.notifications++; 21192 }, 21193 init: noop$1, 21194 isHeadless: function isHeadless() { 21195 return true; 21196 }, 21197 png: throwImgErr, 21198 jpg: throwImgErr 21199 }; 21200 21201 var BRp = {}; 21202 BRp.arrowShapeWidth = 0.3; 21203 21204 BRp.registerArrowShapes = function () { 21205 var arrowShapes = this.arrowShapes = {}; 21206 var renderer = this; // Contract for arrow shapes: 21207 // 0, 0 is arrow tip 21208 // (0, 1) is direction towards node 21209 // (1, 0) is right 21210 // 21211 // functional api: 21212 // collide: check x, y in shape 21213 // roughCollide: called before collide, no false negatives 21214 // draw: draw 21215 // spacing: dist(arrowTip, nodeBoundary) 21216 // gap: dist(edgeTip, nodeBoundary), edgeTip may != arrowTip 21217 21218 var bbCollide = function bbCollide(x, y, size, angle, translation, edgeWidth, padding) { 21219 var x1 = translation.x - size / 2 - padding; 21220 var x2 = translation.x + size / 2 + padding; 21221 var y1 = translation.y - size / 2 - padding; 21222 var y2 = translation.y + size / 2 + padding; 21223 var inside = x1 <= x && x <= x2 && y1 <= y && y <= y2; 21224 return inside; 21225 }; 21226 21227 var transform = function transform(x, y, size, angle, translation) { 21228 var xRotated = x * Math.cos(angle) - y * Math.sin(angle); 21229 var yRotated = x * Math.sin(angle) + y * Math.cos(angle); 21230 var xScaled = xRotated * size; 21231 var yScaled = yRotated * size; 21232 var xTranslated = xScaled + translation.x; 21233 var yTranslated = yScaled + translation.y; 21234 return { 21235 x: xTranslated, 21236 y: yTranslated 21237 }; 21238 }; 21239 21240 var transformPoints = function transformPoints(pts, size, angle, translation) { 21241 var retPts = []; 21242 21243 for (var i = 0; i < pts.length; i += 2) { 21244 var x = pts[i]; 21245 var y = pts[i + 1]; 21246 retPts.push(transform(x, y, size, angle, translation)); 21247 } 21248 21249 return retPts; 21250 }; 21251 21252 var pointsToArr = function pointsToArr(pts) { 21253 var ret = []; 21254 21255 for (var i = 0; i < pts.length; i++) { 21256 var p = pts[i]; 21257 ret.push(p.x, p.y); 21258 } 21259 21260 return ret; 21261 }; 21262 21263 var standardGap = function standardGap(edge) { 21264 return edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').pfValue * 2; 21265 }; 21266 21267 var defineArrowShape = function defineArrowShape(name, defn) { 21268 if (string(defn)) { 21269 defn = arrowShapes[defn]; 21270 } 21271 21272 arrowShapes[name] = extend({ 21273 name: name, 21274 points: [-0.15, -0.3, 0.15, -0.3, 0.15, 0.3, -0.15, 0.3], 21275 collide: function collide(x, y, size, angle, translation, padding) { 21276 var points = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 21277 var inside = pointInsidePolygonPoints(x, y, points); 21278 return inside; 21279 }, 21280 roughCollide: bbCollide, 21281 draw: function draw(context, size, angle, translation) { 21282 var points = transformPoints(this.points, size, angle, translation); 21283 renderer.arrowShapeImpl('polygon')(context, points); 21284 }, 21285 spacing: function spacing(edge) { 21286 return 0; 21287 }, 21288 gap: standardGap 21289 }, defn); 21290 }; 21291 21292 defineArrowShape('none', { 21293 collide: falsify, 21294 roughCollide: falsify, 21295 draw: noop, 21296 spacing: zeroify, 21297 gap: zeroify 21298 }); 21299 defineArrowShape('triangle', { 21300 points: [-0.15, -0.3, 0, 0, 0.15, -0.3] 21301 }); 21302 defineArrowShape('arrow', 'triangle'); 21303 defineArrowShape('triangle-backcurve', { 21304 points: arrowShapes['triangle'].points, 21305 controlPoint: [0, -0.15], 21306 roughCollide: bbCollide, 21307 draw: function draw(context, size, angle, translation, edgeWidth) { 21308 var ptsTrans = transformPoints(this.points, size, angle, translation); 21309 var ctrlPt = this.controlPoint; 21310 var ctrlPtTrans = transform(ctrlPt[0], ctrlPt[1], size, angle, translation); 21311 renderer.arrowShapeImpl(this.name)(context, ptsTrans, ctrlPtTrans); 21312 }, 21313 gap: function gap(edge) { 21314 return standardGap(edge) * 0.8; 21315 } 21316 }); 21317 defineArrowShape('triangle-tee', { 21318 points: [0, 0, 0.15, -0.3, -0.15, -0.3, 0, 0], 21319 pointsTee: [-0.15, -0.4, -0.15, -0.5, 0.15, -0.5, 0.15, -0.4], 21320 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 21321 var triPts = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 21322 var teePts = pointsToArr(transformPoints(this.pointsTee, size + 2 * padding, angle, translation)); 21323 var inside = pointInsidePolygonPoints(x, y, triPts) || pointInsidePolygonPoints(x, y, teePts); 21324 return inside; 21325 }, 21326 draw: function draw(context, size, angle, translation, edgeWidth) { 21327 var triPts = transformPoints(this.points, size, angle, translation); 21328 var teePts = transformPoints(this.pointsTee, size, angle, translation); 21329 renderer.arrowShapeImpl(this.name)(context, triPts, teePts); 21330 } 21331 }); 21332 defineArrowShape('triangle-cross', { 21333 points: [0, 0, 0.15, -0.3, -0.15, -0.3, 0, 0], 21334 baseCrossLinePts: [-0.15, -0.4, // first half of the rectangle 21335 -0.15, -0.4, 0.15, -0.4, // second half of the rectangle 21336 0.15, -0.4], 21337 crossLinePts: function crossLinePts(size, edgeWidth) { 21338 // shift points so that the distance between the cross points matches edge width 21339 var p = this.baseCrossLinePts.slice(); 21340 var shiftFactor = edgeWidth / size; 21341 var y0 = 3; 21342 var y1 = 5; 21343 p[y0] = p[y0] - shiftFactor; 21344 p[y1] = p[y1] - shiftFactor; 21345 return p; 21346 }, 21347 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 21348 var triPts = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 21349 var teePts = pointsToArr(transformPoints(this.crossLinePts(size, edgeWidth), size + 2 * padding, angle, translation)); 21350 var inside = pointInsidePolygonPoints(x, y, triPts) || pointInsidePolygonPoints(x, y, teePts); 21351 return inside; 21352 }, 21353 draw: function draw(context, size, angle, translation, edgeWidth) { 21354 var triPts = transformPoints(this.points, size, angle, translation); 21355 var crossLinePts = transformPoints(this.crossLinePts(size, edgeWidth), size, angle, translation); 21356 renderer.arrowShapeImpl(this.name)(context, triPts, crossLinePts); 21357 } 21358 }); 21359 defineArrowShape('vee', { 21360 points: [-0.15, -0.3, 0, 0, 0.15, -0.3, 0, -0.15], 21361 gap: function gap(edge) { 21362 return standardGap(edge) * 0.525; 21363 } 21364 }); 21365 defineArrowShape('circle', { 21366 radius: 0.15, 21367 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 21368 var t = translation; 21369 var inside = Math.pow(t.x - x, 2) + Math.pow(t.y - y, 2) <= Math.pow((size + 2 * padding) * this.radius, 2); 21370 return inside; 21371 }, 21372 draw: function draw(context, size, angle, translation, edgeWidth) { 21373 renderer.arrowShapeImpl(this.name)(context, translation.x, translation.y, this.radius * size); 21374 }, 21375 spacing: function spacing(edge) { 21376 return renderer.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.radius; 21377 } 21378 }); 21379 defineArrowShape('tee', { 21380 points: [-0.15, 0, -0.15, -0.1, 0.15, -0.1, 0.15, 0], 21381 spacing: function spacing(edge) { 21382 return 1; 21383 }, 21384 gap: function gap(edge) { 21385 return 1; 21386 } 21387 }); 21388 defineArrowShape('square', { 21389 points: [-0.15, 0.00, 0.15, 0.00, 0.15, -0.3, -0.15, -0.3] 21390 }); 21391 defineArrowShape('diamond', { 21392 points: [-0.15, -0.15, 0, -0.3, 0.15, -0.15, 0, 0], 21393 gap: function gap(edge) { 21394 return edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').value; 21395 } 21396 }); 21397 defineArrowShape('chevron', { 21398 points: [0, 0, -0.15, -0.15, -0.1, -0.2, 0, -0.1, 0.1, -0.2, 0.15, -0.15], 21399 gap: function gap(edge) { 21400 return 0.95 * edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').value; 21401 } 21402 }); 21403 }; 21404 21405 var BRp$1 = {}; // Project mouse 21406 21407 BRp$1.projectIntoViewport = function (clientX, clientY) { 21408 var cy = this.cy; 21409 var offsets = this.findContainerClientCoords(); 21410 var offsetLeft = offsets[0]; 21411 var offsetTop = offsets[1]; 21412 var scale = offsets[4]; 21413 var pan = cy.pan(); 21414 var zoom = cy.zoom(); 21415 var x = ((clientX - offsetLeft) / scale - pan.x) / zoom; 21416 var y = ((clientY - offsetTop) / scale - pan.y) / zoom; 21417 return [x, y]; 21418 }; 21419 21420 BRp$1.findContainerClientCoords = function () { 21421 if (this.containerBB) { 21422 return this.containerBB; 21423 } 21424 21425 var container = this.container; 21426 var rect = container.getBoundingClientRect(); 21427 var style = window$1.getComputedStyle(container); 21428 21429 var styleValue = function styleValue(name) { 21430 return parseFloat(style.getPropertyValue(name)); 21431 }; 21432 21433 var padding = { 21434 left: styleValue('padding-left'), 21435 right: styleValue('padding-right'), 21436 top: styleValue('padding-top'), 21437 bottom: styleValue('padding-bottom') 21438 }; 21439 var border = { 21440 left: styleValue('border-left-width'), 21441 right: styleValue('border-right-width'), 21442 top: styleValue('border-top-width'), 21443 bottom: styleValue('border-bottom-width') 21444 }; 21445 var clientWidth = container.clientWidth; 21446 var clientHeight = container.clientHeight; 21447 var paddingHor = padding.left + padding.right; 21448 var paddingVer = padding.top + padding.bottom; 21449 var borderHor = border.left + border.right; 21450 var scale = rect.width / (clientWidth + borderHor); 21451 var unscaledW = clientWidth - paddingHor; 21452 var unscaledH = clientHeight - paddingVer; 21453 var left = rect.left + padding.left + border.left; 21454 var top = rect.top + padding.top + border.top; 21455 return this.containerBB = [left, top, unscaledW, unscaledH, scale]; 21456 }; 21457 21458 BRp$1.invalidateContainerClientCoordsCache = function () { 21459 this.containerBB = null; 21460 }; 21461 21462 BRp$1.findNearestElement = function (x, y, interactiveElementsOnly, isTouch) { 21463 return this.findNearestElements(x, y, interactiveElementsOnly, isTouch)[0]; 21464 }; 21465 21466 BRp$1.findNearestElements = function (x, y, interactiveElementsOnly, isTouch) { 21467 var self = this; 21468 var r = this; 21469 var eles = r.getCachedZSortedEles(); 21470 var near = []; // 1 node max, 1 edge max 21471 21472 var zoom = r.cy.zoom(); 21473 var hasCompounds = r.cy.hasCompoundNodes(); 21474 var edgeThreshold = (isTouch ? 24 : 8) / zoom; 21475 var nodeThreshold = (isTouch ? 8 : 2) / zoom; 21476 var labelThreshold = (isTouch ? 8 : 2) / zoom; 21477 var minSqDist = Infinity; 21478 var nearEdge; 21479 var nearNode; 21480 21481 if (interactiveElementsOnly) { 21482 eles = eles.interactive; 21483 } 21484 21485 function addEle(ele, sqDist) { 21486 if (ele.isNode()) { 21487 if (nearNode) { 21488 return; // can't replace node 21489 } else { 21490 nearNode = ele; 21491 near.push(ele); 21492 } 21493 } 21494 21495 if (ele.isEdge() && (sqDist == null || sqDist < minSqDist)) { 21496 if (nearEdge) { 21497 // then replace existing edge 21498 // can replace only if same z-index 21499 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) { 21500 for (var i = 0; i < near.length; i++) { 21501 if (near[i].isEdge()) { 21502 near[i] = ele; 21503 nearEdge = ele; 21504 minSqDist = sqDist != null ? sqDist : minSqDist; 21505 break; 21506 } 21507 } 21508 } 21509 } else { 21510 near.push(ele); 21511 nearEdge = ele; 21512 minSqDist = sqDist != null ? sqDist : minSqDist; 21513 } 21514 } 21515 } 21516 21517 function checkNode(node) { 21518 var width = node.outerWidth() + 2 * nodeThreshold; 21519 var height = node.outerHeight() + 2 * nodeThreshold; 21520 var hw = width / 2; 21521 var hh = height / 2; 21522 var pos = node.position(); 21523 21524 if (pos.x - hw <= x && x <= pos.x + hw // bb check x 21525 && pos.y - hh <= y && y <= pos.y + hh // bb check y 21526 ) { 21527 var shape = r.nodeShapes[self.getNodeShape(node)]; 21528 21529 if (shape.checkPoint(x, y, 0, width, height, pos.x, pos.y)) { 21530 addEle(node, 0); 21531 return true; 21532 } 21533 } 21534 } 21535 21536 function checkEdge(edge) { 21537 var _p = edge._private; 21538 var rs = _p.rscratch; 21539 var styleWidth = edge.pstyle('width').pfValue; 21540 var scale = edge.pstyle('arrow-scale').value; 21541 var width = styleWidth / 2 + edgeThreshold; // more like a distance radius from centre 21542 21543 var widthSq = width * width; 21544 var width2 = width * 2; 21545 var src = _p.source; 21546 var tgt = _p.target; 21547 var sqDist; 21548 21549 if (rs.edgeType === 'segments' || rs.edgeType === 'straight' || rs.edgeType === 'haystack') { 21550 var pts = rs.allpts; 21551 21552 for (var i = 0; i + 3 < pts.length; i += 2) { 21553 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]))) { 21554 addEle(edge, sqDist); 21555 return true; 21556 } 21557 } 21558 } else if (rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound') { 21559 var pts = rs.allpts; 21560 21561 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 21562 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]))) { 21563 addEle(edge, sqDist); 21564 return true; 21565 } 21566 } 21567 } // if we're close to the edge but didn't hit it, maybe we hit its arrows 21568 21569 21570 var src = src || _p.source; 21571 var tgt = tgt || _p.target; 21572 var arSize = self.getArrowWidth(styleWidth, scale); 21573 var arrows = [{ 21574 name: 'source', 21575 x: rs.arrowStartX, 21576 y: rs.arrowStartY, 21577 angle: rs.srcArrowAngle 21578 }, { 21579 name: 'target', 21580 x: rs.arrowEndX, 21581 y: rs.arrowEndY, 21582 angle: rs.tgtArrowAngle 21583 }, { 21584 name: 'mid-source', 21585 x: rs.midX, 21586 y: rs.midY, 21587 angle: rs.midsrcArrowAngle 21588 }, { 21589 name: 'mid-target', 21590 x: rs.midX, 21591 y: rs.midY, 21592 angle: rs.midtgtArrowAngle 21593 }]; 21594 21595 for (var i = 0; i < arrows.length; i++) { 21596 var ar = arrows[i]; 21597 var shape = r.arrowShapes[edge.pstyle(ar.name + '-arrow-shape').value]; 21598 var edgeWidth = edge.pstyle('width').pfValue; 21599 21600 if (shape.roughCollide(x, y, arSize, ar.angle, { 21601 x: ar.x, 21602 y: ar.y 21603 }, edgeWidth, edgeThreshold) && shape.collide(x, y, arSize, ar.angle, { 21604 x: ar.x, 21605 y: ar.y 21606 }, edgeWidth, edgeThreshold)) { 21607 addEle(edge); 21608 return true; 21609 } 21610 } // for compound graphs, hitting edge may actually want a connected node instead (b/c edge may have greater z-index precedence) 21611 21612 21613 if (hasCompounds && near.length > 0) { 21614 checkNode(src); 21615 checkNode(tgt); 21616 } 21617 } 21618 21619 function preprop(obj, name, pre) { 21620 return getPrefixedProperty(obj, name, pre); 21621 } 21622 21623 function checkLabel(ele, prefix) { 21624 var _p = ele._private; 21625 var th = labelThreshold; 21626 var prefixDash; 21627 21628 if (prefix) { 21629 prefixDash = prefix + '-'; 21630 } else { 21631 prefixDash = ''; 21632 } 21633 21634 ele.boundingBox(); 21635 var bb = _p.labelBounds[prefix || 'main']; 21636 var text = ele.pstyle(prefixDash + 'label').value; 21637 var eventsEnabled = ele.pstyle('text-events').strValue === 'yes'; 21638 21639 if (!eventsEnabled || !text) { 21640 return; 21641 } 21642 21643 var rstyle = _p.rstyle; 21644 var lx = preprop(rstyle, 'labelX', prefix); 21645 var ly = preprop(rstyle, 'labelY', prefix); 21646 var theta = preprop(_p.rscratch, 'labelAngle', prefix); 21647 var lx1 = bb.x1 - th; 21648 var lx2 = bb.x2 + th; 21649 var ly1 = bb.y1 - th; 21650 var ly2 = bb.y2 + th; 21651 21652 if (theta) { 21653 var cos = Math.cos(theta); 21654 var sin = Math.sin(theta); 21655 21656 var rotate = function rotate(x, y) { 21657 x = x - lx; 21658 y = y - ly; 21659 return { 21660 x: x * cos - y * sin + lx, 21661 y: x * sin + y * cos + ly 21662 }; 21663 }; 21664 21665 var px1y1 = rotate(lx1, ly1); 21666 var px1y2 = rotate(lx1, ly2); 21667 var px2y1 = rotate(lx2, ly1); 21668 var px2y2 = rotate(lx2, ly2); 21669 var points = [px1y1.x, px1y1.y, px2y1.x, px2y1.y, px2y2.x, px2y2.y, px1y2.x, px1y2.y]; 21670 21671 if (pointInsidePolygonPoints(x, y, points)) { 21672 addEle(ele); 21673 return true; 21674 } 21675 } else { 21676 // do a cheaper bb check 21677 if (inBoundingBox(bb, x, y)) { 21678 addEle(ele); 21679 return true; 21680 } 21681 } 21682 } 21683 21684 for (var i = eles.length - 1; i >= 0; i--) { 21685 // reverse order for precedence 21686 var ele = eles[i]; 21687 21688 if (ele.isNode()) { 21689 checkNode(ele) || checkLabel(ele); 21690 } else { 21691 // then edge 21692 checkEdge(ele) || checkLabel(ele) || checkLabel(ele, 'source') || checkLabel(ele, 'target'); 21693 } 21694 } 21695 21696 return near; 21697 }; // 'Give me everything from this box' 21698 21699 21700 BRp$1.getAllInBox = function (x1, y1, x2, y2) { 21701 var eles = this.getCachedZSortedEles().interactive; 21702 var box = []; 21703 var x1c = Math.min(x1, x2); 21704 var x2c = Math.max(x1, x2); 21705 var y1c = Math.min(y1, y2); 21706 var y2c = Math.max(y1, y2); 21707 x1 = x1c; 21708 x2 = x2c; 21709 y1 = y1c; 21710 y2 = y2c; 21711 var boxBb = makeBoundingBox({ 21712 x1: x1, 21713 y1: y1, 21714 x2: x2, 21715 y2: y2 21716 }); 21717 21718 for (var e = 0; e < eles.length; e++) { 21719 var ele = eles[e]; 21720 21721 if (ele.isNode()) { 21722 var node = ele; 21723 var nodeBb = node.boundingBox({ 21724 includeNodes: true, 21725 includeEdges: false, 21726 includeLabels: false 21727 }); 21728 21729 if (boundingBoxesIntersect(boxBb, nodeBb) && !boundingBoxInBoundingBox(nodeBb, boxBb)) { 21730 box.push(node); 21731 } 21732 } else { 21733 var edge = ele; 21734 var _p = edge._private; 21735 var rs = _p.rscratch; 21736 21737 if (rs.startX != null && rs.startY != null && !inBoundingBox(boxBb, rs.startX, rs.startY)) { 21738 continue; 21739 } 21740 21741 if (rs.endX != null && rs.endY != null && !inBoundingBox(boxBb, rs.endX, rs.endY)) { 21742 continue; 21743 } 21744 21745 if (rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' || rs.edgeType === 'segments' || rs.edgeType === 'haystack') { 21746 var pts = _p.rstyle.bezierPts || _p.rstyle.linePts || _p.rstyle.haystackPts; 21747 var allInside = true; 21748 21749 for (var i = 0; i < pts.length; i++) { 21750 if (!pointInBoundingBox(boxBb, pts[i])) { 21751 allInside = false; 21752 break; 21753 } 21754 } 21755 21756 if (allInside) { 21757 box.push(edge); 21758 } 21759 } else if (rs.edgeType === 'haystack' || rs.edgeType === 'straight') { 21760 box.push(edge); 21761 } 21762 } 21763 } 21764 21765 return box; 21766 }; 21767 21768 var BRp$2 = {}; 21769 21770 BRp$2.calculateArrowAngles = function (edge) { 21771 var rs = edge._private.rscratch; 21772 var isHaystack = rs.edgeType === 'haystack'; 21773 var isBezier = rs.edgeType === 'bezier'; 21774 var isMultibezier = rs.edgeType === 'multibezier'; 21775 var isSegments = rs.edgeType === 'segments'; 21776 var isCompound = rs.edgeType === 'compound'; 21777 var isSelf = rs.edgeType === 'self'; // Displacement gives direction for arrowhead orientation 21778 21779 var dispX, dispY; 21780 var startX, startY, endX, endY, midX, midY; 21781 21782 if (isHaystack) { 21783 startX = rs.haystackPts[0]; 21784 startY = rs.haystackPts[1]; 21785 endX = rs.haystackPts[2]; 21786 endY = rs.haystackPts[3]; 21787 } else { 21788 startX = rs.arrowStartX; 21789 startY = rs.arrowStartY; 21790 endX = rs.arrowEndX; 21791 endY = rs.arrowEndY; 21792 } 21793 21794 midX = rs.midX; 21795 midY = rs.midY; // source 21796 // 21797 21798 if (isSegments) { 21799 dispX = startX - rs.segpts[0]; 21800 dispY = startY - rs.segpts[1]; 21801 } else if (isMultibezier || isCompound || isSelf || isBezier) { 21802 var pts = rs.allpts; 21803 var bX = qbezierAt(pts[0], pts[2], pts[4], 0.1); 21804 var bY = qbezierAt(pts[1], pts[3], pts[5], 0.1); 21805 dispX = startX - bX; 21806 dispY = startY - bY; 21807 } else { 21808 dispX = startX - midX; 21809 dispY = startY - midY; 21810 } 21811 21812 rs.srcArrowAngle = getAngleFromDisp(dispX, dispY); // mid target 21813 // 21814 21815 var midX = rs.midX; 21816 var midY = rs.midY; 21817 21818 if (isHaystack) { 21819 midX = (startX + endX) / 2; 21820 midY = (startY + endY) / 2; 21821 } 21822 21823 dispX = endX - startX; 21824 dispY = endY - startY; 21825 21826 if (isSegments) { 21827 var pts = rs.allpts; 21828 21829 if (pts.length / 2 % 2 === 0) { 21830 var i2 = pts.length / 2; 21831 var i1 = i2 - 2; 21832 dispX = pts[i2] - pts[i1]; 21833 dispY = pts[i2 + 1] - pts[i1 + 1]; 21834 } else { 21835 var i2 = pts.length / 2 - 1; 21836 var i1 = i2 - 2; 21837 var i3 = i2 + 2; 21838 dispX = pts[i2] - pts[i1]; 21839 dispY = pts[i2 + 1] - pts[i1 + 1]; 21840 } 21841 } else if (isMultibezier || isCompound || isSelf) { 21842 var pts = rs.allpts; 21843 var cpts = rs.ctrlpts; 21844 var bp0x, bp0y; 21845 var bp1x, bp1y; 21846 21847 if (cpts.length / 2 % 2 === 0) { 21848 var p0 = pts.length / 2 - 1; // startpt 21849 21850 var ic = p0 + 2; 21851 var p1 = ic + 2; 21852 bp0x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.0); 21853 bp0y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.0); 21854 bp1x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.0001); 21855 bp1y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.0001); 21856 } else { 21857 var ic = pts.length / 2 - 1; // ctrpt 21858 21859 var p0 = ic - 2; // startpt 21860 21861 var p1 = ic + 2; // endpt 21862 21863 bp0x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.4999); 21864 bp0y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.4999); 21865 bp1x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.5); 21866 bp1y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.5); 21867 } 21868 21869 dispX = bp1x - bp0x; 21870 dispY = bp1y - bp0y; 21871 } 21872 21873 rs.midtgtArrowAngle = getAngleFromDisp(dispX, dispY); 21874 rs.midDispX = dispX; 21875 rs.midDispY = dispY; // mid source 21876 // 21877 21878 dispX *= -1; 21879 dispY *= -1; 21880 21881 if (isSegments) { 21882 var pts = rs.allpts; 21883 21884 if (pts.length / 2 % 2 === 0) ; else { 21885 var i2 = pts.length / 2 - 1; 21886 var i3 = i2 + 2; 21887 dispX = -(pts[i3] - pts[i2]); 21888 dispY = -(pts[i3 + 1] - pts[i2 + 1]); 21889 } 21890 } 21891 21892 rs.midsrcArrowAngle = getAngleFromDisp(dispX, dispY); // target 21893 // 21894 21895 if (isSegments) { 21896 dispX = endX - rs.segpts[rs.segpts.length - 2]; 21897 dispY = endY - rs.segpts[rs.segpts.length - 1]; 21898 } else if (isMultibezier || isCompound || isSelf || isBezier) { 21899 var pts = rs.allpts; 21900 var l = pts.length; 21901 var bX = qbezierAt(pts[l - 6], pts[l - 4], pts[l - 2], 0.9); 21902 var bY = qbezierAt(pts[l - 5], pts[l - 3], pts[l - 1], 0.9); 21903 dispX = endX - bX; 21904 dispY = endY - bY; 21905 } else { 21906 dispX = endX - midX; 21907 dispY = endY - midY; 21908 } 21909 21910 rs.tgtArrowAngle = getAngleFromDisp(dispX, dispY); 21911 }; 21912 21913 BRp$2.getArrowWidth = BRp$2.getArrowHeight = function (edgeWidth, scale) { 21914 var cache = this.arrowWidthCache = this.arrowWidthCache || {}; 21915 var cachedVal = cache[edgeWidth + ', ' + scale]; 21916 21917 if (cachedVal) { 21918 return cachedVal; 21919 } 21920 21921 cachedVal = Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29) * scale; 21922 cache[edgeWidth + ', ' + scale] = cachedVal; 21923 return cachedVal; 21924 }; 21925 21926 var BRp$3 = {}; 21927 21928 BRp$3.findHaystackPoints = function (edges) { 21929 for (var i = 0; i < edges.length; i++) { 21930 var edge = edges[i]; 21931 var _p = edge._private; 21932 var rs = _p.rscratch; 21933 21934 if (!rs.haystack) { 21935 var angle = Math.random() * 2 * Math.PI; 21936 rs.source = { 21937 x: Math.cos(angle), 21938 y: Math.sin(angle) 21939 }; 21940 angle = Math.random() * 2 * Math.PI; 21941 rs.target = { 21942 x: Math.cos(angle), 21943 y: Math.sin(angle) 21944 }; 21945 } 21946 21947 var src = _p.source; 21948 var tgt = _p.target; 21949 var srcPos = src.position(); 21950 var tgtPos = tgt.position(); 21951 var srcW = src.width(); 21952 var tgtW = tgt.width(); 21953 var srcH = src.height(); 21954 var tgtH = tgt.height(); 21955 var radius = edge.pstyle('haystack-radius').value; 21956 var halfRadius = radius / 2; // b/c have to half width/height 21957 21958 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]; 21959 rs.midX = (rs.allpts[0] + rs.allpts[2]) / 2; 21960 rs.midY = (rs.allpts[1] + rs.allpts[3]) / 2; // always override as haystack in case set to different type previously 21961 21962 rs.edgeType = 'haystack'; 21963 rs.haystack = true; 21964 this.storeEdgeProjections(edge); 21965 this.calculateArrowAngles(edge); 21966 this.recalculateEdgeLabelProjections(edge); 21967 this.calculateLabelAngles(edge); 21968 } 21969 }; 21970 21971 BRp$3.findSegmentsPoints = function (edge, pairInfo) { 21972 // Segments (multiple straight lines) 21973 var rs = edge._private.rscratch; 21974 var posPts = pairInfo.posPts, 21975 intersectionPts = pairInfo.intersectionPts, 21976 vectorNormInverse = pairInfo.vectorNormInverse; 21977 var edgeDistances = edge.pstyle('edge-distances').value; 21978 var segmentWs = edge.pstyle('segment-weights'); 21979 var segmentDs = edge.pstyle('segment-distances'); 21980 var segmentsN = Math.min(segmentWs.pfValue.length, segmentDs.pfValue.length); 21981 rs.edgeType = 'segments'; 21982 rs.segpts = []; 21983 21984 for (var s = 0; s < segmentsN; s++) { 21985 var w = segmentWs.pfValue[s]; 21986 var d = segmentDs.pfValue[s]; 21987 var w1 = 1 - w; 21988 var w2 = w; 21989 var midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts; 21990 var adjustedMidpt = { 21991 x: midptPts.x1 * w1 + midptPts.x2 * w2, 21992 y: midptPts.y1 * w1 + midptPts.y2 * w2 21993 }; 21994 rs.segpts.push(adjustedMidpt.x + vectorNormInverse.x * d, adjustedMidpt.y + vectorNormInverse.y * d); 21995 } 21996 }; 21997 21998 BRp$3.findLoopPoints = function (edge, pairInfo, i, edgeIsUnbundled) { 21999 // Self-edge 22000 var rs = edge._private.rscratch; 22001 var dirCounts = pairInfo.dirCounts, 22002 srcPos = pairInfo.srcPos; 22003 var ctrlptDists = edge.pstyle('control-point-distances'); 22004 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22005 var loopDir = edge.pstyle('loop-direction').pfValue; 22006 var loopSwp = edge.pstyle('loop-sweep').pfValue; 22007 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22008 rs.edgeType = 'self'; 22009 var j = i; 22010 var loopDist = stepSize; 22011 22012 if (edgeIsUnbundled) { 22013 j = 0; 22014 loopDist = ctrlptDist; 22015 } 22016 22017 var loopAngle = loopDir - Math.PI / 2; 22018 var outAngle = loopAngle - loopSwp / 2; 22019 var inAngle = loopAngle + loopSwp / 2; // increase by step size for overlapping loops, keyed on direction and sweep values 22020 22021 var dc = String(loopDir + '_' + loopSwp); 22022 j = dirCounts[dc] === undefined ? dirCounts[dc] = 0 : ++dirCounts[dc]; 22023 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)]; 22024 }; 22025 22026 BRp$3.findCompoundLoopPoints = function (edge, pairInfo, i, edgeIsUnbundled) { 22027 // Compound edge 22028 var rs = edge._private.rscratch; 22029 rs.edgeType = 'compound'; 22030 var srcPos = pairInfo.srcPos, 22031 tgtPos = pairInfo.tgtPos, 22032 srcW = pairInfo.srcW, 22033 srcH = pairInfo.srcH, 22034 tgtW = pairInfo.tgtW, 22035 tgtH = pairInfo.tgtH; 22036 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22037 var ctrlptDists = edge.pstyle('control-point-distances'); 22038 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22039 var j = i; 22040 var loopDist = stepSize; 22041 22042 if (edgeIsUnbundled) { 22043 j = 0; 22044 loopDist = ctrlptDist; 22045 } 22046 22047 var loopW = 50; 22048 var loopaPos = { 22049 x: srcPos.x - srcW / 2, 22050 y: srcPos.y - srcH / 2 22051 }; 22052 var loopbPos = { 22053 x: tgtPos.x - tgtW / 2, 22054 y: tgtPos.y - tgtH / 2 22055 }; 22056 var loopPos = { 22057 x: Math.min(loopaPos.x, loopbPos.x), 22058 y: Math.min(loopaPos.y, loopbPos.y) 22059 }; // avoids cases with impossible beziers 22060 22061 var minCompoundStretch = 0.5; 22062 var compoundStretchA = Math.max(minCompoundStretch, Math.log(srcW * 0.01)); 22063 var compoundStretchB = Math.max(minCompoundStretch, Math.log(tgtW * 0.01)); 22064 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]; 22065 }; 22066 22067 BRp$3.findStraightEdgePoints = function (edge) { 22068 // Straight edge within bundle 22069 edge._private.rscratch.edgeType = 'straight'; 22070 }; 22071 22072 BRp$3.findBezierPoints = function (edge, pairInfo, i, edgeIsUnbundled, edgeIsSwapped) { 22073 var rs = edge._private.rscratch; 22074 var vectorNormInverse = pairInfo.vectorNormInverse, 22075 posPts = pairInfo.posPts, 22076 intersectionPts = pairInfo.intersectionPts; 22077 var edgeDistances = edge.pstyle('edge-distances').value; 22078 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22079 var ctrlptDists = edge.pstyle('control-point-distances'); 22080 var ctrlptWs = edge.pstyle('control-point-weights'); 22081 var bezierN = ctrlptDists && ctrlptWs ? Math.min(ctrlptDists.value.length, ctrlptWs.value.length) : 1; 22082 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22083 var ctrlptWeight = ctrlptWs.value[0]; // (Multi)bezier 22084 22085 var multi = edgeIsUnbundled; 22086 rs.edgeType = multi ? 'multibezier' : 'bezier'; 22087 rs.ctrlpts = []; 22088 22089 for (var b = 0; b < bezierN; b++) { 22090 var normctrlptDist = (0.5 - pairInfo.eles.length / 2 + i) * stepSize * (edgeIsSwapped ? -1 : 1); 22091 var manctrlptDist = void 0; 22092 var sign = signum(normctrlptDist); 22093 22094 if (multi) { 22095 ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[b] : stepSize; // fall back on step size 22096 22097 ctrlptWeight = ctrlptWs.value[b]; 22098 } 22099 22100 if (edgeIsUnbundled) { 22101 // multi or single unbundled 22102 manctrlptDist = ctrlptDist; 22103 } else { 22104 manctrlptDist = ctrlptDist !== undefined ? sign * ctrlptDist : undefined; 22105 } 22106 22107 var distanceFromMidpoint = manctrlptDist !== undefined ? manctrlptDist : normctrlptDist; 22108 var w1 = 1 - ctrlptWeight; 22109 var w2 = ctrlptWeight; 22110 var midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts; 22111 var adjustedMidpt = { 22112 x: midptPts.x1 * w1 + midptPts.x2 * w2, 22113 y: midptPts.y1 * w1 + midptPts.y2 * w2 22114 }; 22115 rs.ctrlpts.push(adjustedMidpt.x + vectorNormInverse.x * distanceFromMidpoint, adjustedMidpt.y + vectorNormInverse.y * distanceFromMidpoint); 22116 } 22117 }; 22118 22119 BRp$3.findTaxiPoints = function (edge, pairInfo) { 22120 // Taxicab geometry with two turns maximum 22121 var rs = edge._private.rscratch; 22122 rs.edgeType = 'segments'; 22123 var VERTICAL = 'vertical'; 22124 var HORIZONTAL = 'horizontal'; 22125 var LEFTWARD = 'leftward'; 22126 var RIGHTWARD = 'rightward'; 22127 var DOWNWARD = 'downward'; 22128 var UPWARD = 'upward'; 22129 var AUTO = 'auto'; 22130 var posPts = pairInfo.posPts, 22131 srcW = pairInfo.srcW, 22132 srcH = pairInfo.srcH, 22133 tgtW = pairInfo.tgtW, 22134 tgtH = pairInfo.tgtH; 22135 var edgeDistances = edge.pstyle('edge-distances').value; 22136 var dIncludesNodeBody = edgeDistances !== 'node-position'; 22137 var taxiDir = edge.pstyle('taxi-direction').value; 22138 var rawTaxiDir = taxiDir; // unprocessed value 22139 22140 var taxiTurn = edge.pstyle('taxi-turn'); 22141 var taxiTurnPfVal = taxiTurn.pfValue; 22142 var minD = edge.pstyle('taxi-turn-min-distance').pfValue; 22143 var turnIsPercent = taxiTurn.units === '%'; 22144 var dw = dIncludesNodeBody ? (srcW + tgtW) / 2 : 0; 22145 var dh = dIncludesNodeBody ? (srcH + tgtH) / 2 : 0; 22146 var pdx = posPts.x2 - posPts.x1; 22147 var pdy = posPts.y2 - posPts.y1; // take away the effective w/h from the magnitude of the delta value 22148 22149 var subDWH = function subDWH(dxy, dwh) { 22150 if (dxy > 0) { 22151 return Math.max(dxy - dwh, 0); 22152 } else { 22153 return Math.min(dxy + dwh, 0); 22154 } 22155 }; 22156 22157 var dx = subDWH(pdx, dw); 22158 var dy = subDWH(pdy, dh); 22159 var isExplicitDir = false; 22160 22161 if (taxiDir === AUTO) { 22162 taxiDir = Math.abs(dx) > Math.abs(dy) ? HORIZONTAL : VERTICAL; 22163 } else if (taxiDir === UPWARD || taxiDir === DOWNWARD) { 22164 taxiDir = VERTICAL; 22165 isExplicitDir = true; 22166 } else if (taxiDir === LEFTWARD || taxiDir === RIGHTWARD) { 22167 taxiDir = HORIZONTAL; 22168 isExplicitDir = true; 22169 } 22170 22171 var isVert = taxiDir === VERTICAL; 22172 var l = isVert ? dy : dx; 22173 var pl = isVert ? pdy : pdx; 22174 var sgnL = signum(pl); 22175 var forcedDir = false; 22176 22177 if (!(isExplicitDir && turnIsPercent) // forcing in this case would cause weird growing in the opposite direction 22178 && (rawTaxiDir === DOWNWARD && pl < 0 || rawTaxiDir === UPWARD && pl > 0 || rawTaxiDir === LEFTWARD && pl > 0 || rawTaxiDir === RIGHTWARD && pl < 0)) { 22179 sgnL *= -1; 22180 l = sgnL * Math.abs(l); 22181 forcedDir = true; 22182 } 22183 22184 var d = turnIsPercent ? taxiTurnPfVal * l : taxiTurnPfVal * sgnL; 22185 22186 var getIsTooClose = function getIsTooClose(d) { 22187 return Math.abs(d) < minD || Math.abs(d) >= Math.abs(l); 22188 }; 22189 22190 var isTooCloseSrc = getIsTooClose(d); 22191 var isTooCloseTgt = getIsTooClose(l - d); 22192 var isTooClose = isTooCloseSrc || isTooCloseTgt; 22193 22194 if (isTooClose && !forcedDir) { 22195 // non-ideal routing 22196 if (isVert) { 22197 // vertical fallbacks 22198 var lShapeInsideSrc = Math.abs(pl) <= srcH / 2; 22199 var lShapeInsideTgt = Math.abs(pdx) <= tgtW / 2; 22200 22201 if (lShapeInsideSrc) { 22202 // horizontal Z-shape (direction not respected) 22203 var x = (posPts.x1 + posPts.x2) / 2; 22204 var y1 = posPts.y1, 22205 y2 = posPts.y2; 22206 rs.segpts = [x, y1, x, y2]; 22207 } else if (lShapeInsideTgt) { 22208 // vertical Z-shape (distance not respected) 22209 var y = (posPts.y1 + posPts.y2) / 2; 22210 var x1 = posPts.x1, 22211 x2 = posPts.x2; 22212 rs.segpts = [x1, y, x2, y]; 22213 } else { 22214 // L-shape fallback (turn distance not respected, but works well with tree siblings) 22215 rs.segpts = [posPts.x1, posPts.y2]; 22216 } 22217 } else { 22218 // horizontal fallbacks 22219 var _lShapeInsideSrc = Math.abs(pl) <= srcW / 2; 22220 22221 var _lShapeInsideTgt = Math.abs(pdy) <= tgtH / 2; 22222 22223 if (_lShapeInsideSrc) { 22224 // vertical Z-shape (direction not respected) 22225 var _y = (posPts.y1 + posPts.y2) / 2; 22226 22227 var _x = posPts.x1, 22228 _x2 = posPts.x2; 22229 rs.segpts = [_x, _y, _x2, _y]; 22230 } else if (_lShapeInsideTgt) { 22231 // horizontal Z-shape (turn distance not respected) 22232 var _x3 = (posPts.x1 + posPts.x2) / 2; 22233 22234 var _y2 = posPts.y1, 22235 _y3 = posPts.y2; 22236 rs.segpts = [_x3, _y2, _x3, _y3]; 22237 } else { 22238 // L-shape (turn distance not respected, but works well for tree siblings) 22239 rs.segpts = [posPts.x2, posPts.y1]; 22240 } 22241 } 22242 } else { 22243 // ideal routing 22244 if (isVert) { 22245 var _y4 = posPts.y1 + d + (dIncludesNodeBody ? srcH / 2 * sgnL : 0); 22246 22247 var _x4 = posPts.x1, 22248 _x5 = posPts.x2; 22249 rs.segpts = [_x4, _y4, _x5, _y4]; 22250 } else { 22251 // horizontal 22252 var _x6 = posPts.x1 + d + (dIncludesNodeBody ? srcW / 2 * sgnL : 0); 22253 22254 var _y5 = posPts.y1, 22255 _y6 = posPts.y2; 22256 rs.segpts = [_x6, _y5, _x6, _y6]; 22257 } 22258 } 22259 }; 22260 22261 BRp$3.tryToCorrectInvalidPoints = function (edge, pairInfo) { 22262 var rs = edge._private.rscratch; // can only correct beziers for now... 22263 22264 if (rs.edgeType === 'bezier') { 22265 var srcPos = pairInfo.srcPos, 22266 tgtPos = pairInfo.tgtPos, 22267 srcW = pairInfo.srcW, 22268 srcH = pairInfo.srcH, 22269 tgtW = pairInfo.tgtW, 22270 tgtH = pairInfo.tgtH, 22271 srcShape = pairInfo.srcShape, 22272 tgtShape = pairInfo.tgtShape; 22273 var badStart = !number(rs.startX) || !number(rs.startY); 22274 var badAStart = !number(rs.arrowStartX) || !number(rs.arrowStartY); 22275 var badEnd = !number(rs.endX) || !number(rs.endY); 22276 var badAEnd = !number(rs.arrowEndX) || !number(rs.arrowEndY); 22277 var minCpADistFactor = 3; 22278 var arrowW = this.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.arrowShapeWidth; 22279 var minCpADist = minCpADistFactor * arrowW; 22280 var startACpDist = dist({ 22281 x: rs.ctrlpts[0], 22282 y: rs.ctrlpts[1] 22283 }, { 22284 x: rs.startX, 22285 y: rs.startY 22286 }); 22287 var closeStartACp = startACpDist < minCpADist; 22288 var endACpDist = dist({ 22289 x: rs.ctrlpts[0], 22290 y: rs.ctrlpts[1] 22291 }, { 22292 x: rs.endX, 22293 y: rs.endY 22294 }); 22295 var closeEndACp = endACpDist < minCpADist; 22296 var overlapping = false; 22297 22298 if (badStart || badAStart || closeStartACp) { 22299 overlapping = true; // project control point along line from src centre to outside the src shape 22300 // (otherwise intersection will yield nothing) 22301 22302 var cpD = { 22303 // delta 22304 x: rs.ctrlpts[0] - srcPos.x, 22305 y: rs.ctrlpts[1] - srcPos.y 22306 }; 22307 var cpL = Math.sqrt(cpD.x * cpD.x + cpD.y * cpD.y); // length of line 22308 22309 var cpM = { 22310 // normalised delta 22311 x: cpD.x / cpL, 22312 y: cpD.y / cpL 22313 }; 22314 var radius = Math.max(srcW, srcH); 22315 var cpProj = { 22316 // *2 radius guarantees outside shape 22317 x: rs.ctrlpts[0] + cpM.x * 2 * radius, 22318 y: rs.ctrlpts[1] + cpM.y * 2 * radius 22319 }; 22320 var srcCtrlPtIntn = srcShape.intersectLine(srcPos.x, srcPos.y, srcW, srcH, cpProj.x, cpProj.y, 0); 22321 22322 if (closeStartACp) { 22323 rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - startACpDist); 22324 rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - startACpDist); 22325 } else { 22326 rs.ctrlpts[0] = srcCtrlPtIntn[0] + cpM.x * minCpADist; 22327 rs.ctrlpts[1] = srcCtrlPtIntn[1] + cpM.y * minCpADist; 22328 } 22329 } 22330 22331 if (badEnd || badAEnd || closeEndACp) { 22332 overlapping = true; // project control point along line from tgt centre to outside the tgt shape 22333 // (otherwise intersection will yield nothing) 22334 22335 var _cpD = { 22336 // delta 22337 x: rs.ctrlpts[0] - tgtPos.x, 22338 y: rs.ctrlpts[1] - tgtPos.y 22339 }; 22340 22341 var _cpL = Math.sqrt(_cpD.x * _cpD.x + _cpD.y * _cpD.y); // length of line 22342 22343 22344 var _cpM = { 22345 // normalised delta 22346 x: _cpD.x / _cpL, 22347 y: _cpD.y / _cpL 22348 }; 22349 22350 var _radius = Math.max(srcW, srcH); 22351 22352 var _cpProj = { 22353 // *2 radius guarantees outside shape 22354 x: rs.ctrlpts[0] + _cpM.x * 2 * _radius, 22355 y: rs.ctrlpts[1] + _cpM.y * 2 * _radius 22356 }; 22357 var tgtCtrlPtIntn = tgtShape.intersectLine(tgtPos.x, tgtPos.y, tgtW, tgtH, _cpProj.x, _cpProj.y, 0); 22358 22359 if (closeEndACp) { 22360 rs.ctrlpts[0] = rs.ctrlpts[0] + _cpM.x * (minCpADist - endACpDist); 22361 rs.ctrlpts[1] = rs.ctrlpts[1] + _cpM.y * (minCpADist - endACpDist); 22362 } else { 22363 rs.ctrlpts[0] = tgtCtrlPtIntn[0] + _cpM.x * minCpADist; 22364 rs.ctrlpts[1] = tgtCtrlPtIntn[1] + _cpM.y * minCpADist; 22365 } 22366 } 22367 22368 if (overlapping) { 22369 // recalc endpts 22370 this.findEndpoints(edge); 22371 } 22372 } 22373 }; 22374 22375 BRp$3.storeAllpts = function (edge) { 22376 var rs = edge._private.rscratch; 22377 22378 if (rs.edgeType === 'multibezier' || rs.edgeType === 'bezier' || rs.edgeType === 'self' || rs.edgeType === 'compound') { 22379 rs.allpts = []; 22380 rs.allpts.push(rs.startX, rs.startY); 22381 22382 for (var b = 0; b + 1 < rs.ctrlpts.length; b += 2) { 22383 // ctrl pt itself 22384 rs.allpts.push(rs.ctrlpts[b], rs.ctrlpts[b + 1]); // the midpt between ctrlpts as intermediate destination pts 22385 22386 if (b + 3 < rs.ctrlpts.length) { 22387 rs.allpts.push((rs.ctrlpts[b] + rs.ctrlpts[b + 2]) / 2, (rs.ctrlpts[b + 1] + rs.ctrlpts[b + 3]) / 2); 22388 } 22389 } 22390 22391 rs.allpts.push(rs.endX, rs.endY); 22392 var m, mt; 22393 22394 if (rs.ctrlpts.length / 2 % 2 === 0) { 22395 m = rs.allpts.length / 2 - 1; 22396 rs.midX = rs.allpts[m]; 22397 rs.midY = rs.allpts[m + 1]; 22398 } else { 22399 m = rs.allpts.length / 2 - 3; 22400 mt = 0.5; 22401 rs.midX = qbezierAt(rs.allpts[m], rs.allpts[m + 2], rs.allpts[m + 4], mt); 22402 rs.midY = qbezierAt(rs.allpts[m + 1], rs.allpts[m + 3], rs.allpts[m + 5], mt); 22403 } 22404 } else if (rs.edgeType === 'straight') { 22405 // need to calc these after endpts 22406 rs.allpts = [rs.startX, rs.startY, rs.endX, rs.endY]; // default midpt for labels etc 22407 22408 rs.midX = (rs.startX + rs.endX + rs.arrowStartX + rs.arrowEndX) / 4; 22409 rs.midY = (rs.startY + rs.endY + rs.arrowStartY + rs.arrowEndY) / 4; 22410 } else if (rs.edgeType === 'segments') { 22411 rs.allpts = []; 22412 rs.allpts.push(rs.startX, rs.startY); 22413 rs.allpts.push.apply(rs.allpts, rs.segpts); 22414 rs.allpts.push(rs.endX, rs.endY); 22415 22416 if (rs.segpts.length % 4 === 0) { 22417 var i2 = rs.segpts.length / 2; 22418 var i1 = i2 - 2; 22419 rs.midX = (rs.segpts[i1] + rs.segpts[i2]) / 2; 22420 rs.midY = (rs.segpts[i1 + 1] + rs.segpts[i2 + 1]) / 2; 22421 } else { 22422 var _i = rs.segpts.length / 2 - 1; 22423 22424 rs.midX = rs.segpts[_i]; 22425 rs.midY = rs.segpts[_i + 1]; 22426 } 22427 } 22428 }; 22429 22430 BRp$3.checkForInvalidEdgeWarning = function (edge) { 22431 var rs = edge[0]._private.rscratch; 22432 22433 if (rs.nodesOverlap || number(rs.startX) && number(rs.startY) && number(rs.endX) && number(rs.endY)) { 22434 rs.loggedErr = false; 22435 } else { 22436 if (!rs.loggedErr) { 22437 rs.loggedErr = true; 22438 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.'); 22439 } 22440 } 22441 }; 22442 22443 BRp$3.findEdgeControlPoints = function (edges) { 22444 var _this = this; 22445 22446 if (!edges || edges.length === 0) { 22447 return; 22448 } 22449 22450 var r = this; 22451 var cy = r.cy; 22452 var hasCompounds = cy.hasCompoundNodes(); 22453 var hashTable = { 22454 map: new Map$1(), 22455 get: function get(pairId) { 22456 var map2 = this.map.get(pairId[0]); 22457 22458 if (map2 != null) { 22459 return map2.get(pairId[1]); 22460 } else { 22461 return null; 22462 } 22463 }, 22464 set: function set(pairId, val) { 22465 var map2 = this.map.get(pairId[0]); 22466 22467 if (map2 == null) { 22468 map2 = new Map$1(); 22469 this.map.set(pairId[0], map2); 22470 } 22471 22472 map2.set(pairId[1], val); 22473 } 22474 }; 22475 var pairIds = []; 22476 var haystackEdges = []; // create a table of edge (src, tgt) => list of edges between them 22477 22478 for (var i = 0; i < edges.length; i++) { 22479 var edge = edges[i]; 22480 var _p = edge._private; 22481 var curveStyle = edge.pstyle('curve-style').value; // ignore edges who are not to be displayed 22482 // they shouldn't take up space 22483 22484 if (edge.removed() || !edge.takesUpSpace()) { 22485 continue; 22486 } 22487 22488 if (curveStyle === 'haystack') { 22489 haystackEdges.push(edge); 22490 continue; 22491 } 22492 22493 var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments' || curveStyle === 'straight' || curveStyle === 'taxi'; 22494 var edgeIsBezier = curveStyle === 'unbundled-bezier' || curveStyle === 'bezier'; 22495 var src = _p.source; 22496 var tgt = _p.target; 22497 var srcIndex = src.poolIndex(); 22498 var tgtIndex = tgt.poolIndex(); 22499 var pairId = [srcIndex, tgtIndex].sort(); 22500 var tableEntry = hashTable.get(pairId); 22501 22502 if (tableEntry == null) { 22503 tableEntry = { 22504 eles: [] 22505 }; 22506 hashTable.set(pairId, tableEntry); 22507 pairIds.push(pairId); 22508 } 22509 22510 tableEntry.eles.push(edge); 22511 22512 if (edgeIsUnbundled) { 22513 tableEntry.hasUnbundled = true; 22514 } 22515 22516 if (edgeIsBezier) { 22517 tableEntry.hasBezier = true; 22518 } 22519 } // for each pair (src, tgt), create the ctrl pts 22520 // Nested for loop is OK; total number of iterations for both loops = edgeCount 22521 22522 22523 var _loop = function _loop(p) { 22524 var pairId = pairIds[p]; 22525 var pairInfo = hashTable.get(pairId); 22526 var swappedpairInfo = void 0; 22527 22528 if (!pairInfo.hasUnbundled) { 22529 var pllEdges = pairInfo.eles[0].parallelEdges().filter(function (e) { 22530 return e.isBundledBezier(); 22531 }); 22532 clearArray(pairInfo.eles); 22533 pllEdges.forEach(function (edge) { 22534 return pairInfo.eles.push(edge); 22535 }); // for each pair id, the edges should be sorted by index 22536 22537 pairInfo.eles.sort(function (edge1, edge2) { 22538 return edge1.poolIndex() - edge2.poolIndex(); 22539 }); 22540 } 22541 22542 var firstEdge = pairInfo.eles[0]; 22543 var src = firstEdge.source(); 22544 var tgt = firstEdge.target(); // make sure src/tgt distinction is consistent w.r.t. pairId 22545 22546 if (src.poolIndex() > tgt.poolIndex()) { 22547 var temp = src; 22548 src = tgt; 22549 tgt = temp; 22550 } 22551 22552 var srcPos = pairInfo.srcPos = src.position(); 22553 var tgtPos = pairInfo.tgtPos = tgt.position(); 22554 var srcW = pairInfo.srcW = src.outerWidth(); 22555 var srcH = pairInfo.srcH = src.outerHeight(); 22556 var tgtW = pairInfo.tgtW = tgt.outerWidth(); 22557 var tgtH = pairInfo.tgtH = tgt.outerHeight(); 22558 22559 var srcShape = pairInfo.srcShape = r.nodeShapes[_this.getNodeShape(src)]; 22560 22561 var tgtShape = pairInfo.tgtShape = r.nodeShapes[_this.getNodeShape(tgt)]; 22562 22563 pairInfo.dirCounts = { 22564 'north': 0, 22565 'west': 0, 22566 'south': 0, 22567 'east': 0, 22568 'northwest': 0, 22569 'southwest': 0, 22570 'northeast': 0, 22571 'southeast': 0 22572 }; 22573 22574 for (var _i2 = 0; _i2 < pairInfo.eles.length; _i2++) { 22575 var _edge = pairInfo.eles[_i2]; 22576 var rs = _edge[0]._private.rscratch; 22577 22578 var _curveStyle = _edge.pstyle('curve-style').value; 22579 22580 var _edgeIsUnbundled = _curveStyle === 'unbundled-bezier' || _curveStyle === 'segments' || _curveStyle === 'taxi'; // whether the normalised pair order is the reverse of the edge's src-tgt order 22581 22582 22583 var edgeIsSwapped = !src.same(_edge.source()); 22584 22585 if (!pairInfo.calculatedIntersection && src !== tgt && (pairInfo.hasBezier || pairInfo.hasUnbundled)) { 22586 pairInfo.calculatedIntersection = true; // pt outside src shape to calc distance/displacement from src to tgt 22587 22588 var srcOutside = srcShape.intersectLine(srcPos.x, srcPos.y, srcW, srcH, tgtPos.x, tgtPos.y, 0); 22589 var srcIntn = pairInfo.srcIntn = srcOutside; // pt outside tgt shape to calc distance/displacement from src to tgt 22590 22591 var tgtOutside = tgtShape.intersectLine(tgtPos.x, tgtPos.y, tgtW, tgtH, srcPos.x, srcPos.y, 0); 22592 var tgtIntn = pairInfo.tgtIntn = tgtOutside; 22593 var intersectionPts = pairInfo.intersectionPts = { 22594 x1: srcOutside[0], 22595 x2: tgtOutside[0], 22596 y1: srcOutside[1], 22597 y2: tgtOutside[1] 22598 }; 22599 var posPts = pairInfo.posPts = { 22600 x1: srcPos.x, 22601 x2: tgtPos.x, 22602 y1: srcPos.y, 22603 y2: tgtPos.y 22604 }; 22605 var dy = tgtOutside[1] - srcOutside[1]; 22606 var dx = tgtOutside[0] - srcOutside[0]; 22607 var l = Math.sqrt(dx * dx + dy * dy); 22608 var vector = pairInfo.vector = { 22609 x: dx, 22610 y: dy 22611 }; 22612 var vectorNorm = pairInfo.vectorNorm = { 22613 x: vector.x / l, 22614 y: vector.y / l 22615 }; 22616 var vectorNormInverse = { 22617 x: -vectorNorm.y, 22618 y: vectorNorm.x 22619 }; // if node shapes overlap, then no ctrl pts to draw 22620 22621 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); 22622 pairInfo.vectorNormInverse = vectorNormInverse; 22623 swappedpairInfo = { 22624 nodesOverlap: pairInfo.nodesOverlap, 22625 dirCounts: pairInfo.dirCounts, 22626 calculatedIntersection: true, 22627 hasBezier: pairInfo.hasBezier, 22628 hasUnbundled: pairInfo.hasUnbundled, 22629 eles: pairInfo.eles, 22630 srcPos: tgtPos, 22631 tgtPos: srcPos, 22632 srcW: tgtW, 22633 srcH: tgtH, 22634 tgtW: srcW, 22635 tgtH: srcH, 22636 srcIntn: tgtIntn, 22637 tgtIntn: srcIntn, 22638 srcShape: tgtShape, 22639 tgtShape: srcShape, 22640 posPts: { 22641 x1: posPts.x2, 22642 y1: posPts.y2, 22643 x2: posPts.x1, 22644 y2: posPts.y1 22645 }, 22646 intersectionPts: { 22647 x1: intersectionPts.x2, 22648 y1: intersectionPts.y2, 22649 x2: intersectionPts.x1, 22650 y2: intersectionPts.y1 22651 }, 22652 vector: { 22653 x: -vector.x, 22654 y: -vector.y 22655 }, 22656 vectorNorm: { 22657 x: -vectorNorm.x, 22658 y: -vectorNorm.y 22659 }, 22660 vectorNormInverse: { 22661 x: -vectorNormInverse.x, 22662 y: -vectorNormInverse.y 22663 } 22664 }; 22665 } 22666 22667 var passedPairInfo = edgeIsSwapped ? swappedpairInfo : pairInfo; 22668 rs.nodesOverlap = passedPairInfo.nodesOverlap; 22669 rs.srcIntn = passedPairInfo.srcIntn; 22670 rs.tgtIntn = passedPairInfo.tgtIntn; 22671 22672 if (hasCompounds && (src.isParent() || src.isChild() || tgt.isParent() || tgt.isChild()) && (src.parents().anySame(tgt) || tgt.parents().anySame(src) || src.same(tgt) && src.isParent())) { 22673 _this.findCompoundLoopPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled); 22674 } else if (src === tgt) { 22675 _this.findLoopPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled); 22676 } else if (_curveStyle === 'segments') { 22677 _this.findSegmentsPoints(_edge, passedPairInfo); 22678 } else if (_curveStyle === 'taxi') { 22679 _this.findTaxiPoints(_edge, passedPairInfo); 22680 } else if (_curveStyle === 'straight' || !_edgeIsUnbundled && pairInfo.eles.length % 2 === 1 && _i2 === Math.floor(pairInfo.eles.length / 2)) { 22681 _this.findStraightEdgePoints(_edge); 22682 } else { 22683 _this.findBezierPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled, edgeIsSwapped); 22684 } 22685 22686 _this.findEndpoints(_edge); 22687 22688 _this.tryToCorrectInvalidPoints(_edge, passedPairInfo); 22689 22690 _this.checkForInvalidEdgeWarning(_edge); 22691 22692 _this.storeAllpts(_edge); 22693 22694 _this.storeEdgeProjections(_edge); 22695 22696 _this.calculateArrowAngles(_edge); 22697 22698 _this.recalculateEdgeLabelProjections(_edge); 22699 22700 _this.calculateLabelAngles(_edge); 22701 } // for pair edges 22702 22703 }; 22704 22705 for (var p = 0; p < pairIds.length; p++) { 22706 _loop(p); 22707 } // for pair ids 22708 // haystacks avoid the expense of pairInfo stuff (intersections etc.) 22709 22710 22711 this.findHaystackPoints(haystackEdges); 22712 }; 22713 22714 function getPts(pts) { 22715 var retPts = []; 22716 22717 if (pts == null) { 22718 return; 22719 } 22720 22721 for (var i = 0; i < pts.length; i += 2) { 22722 var x = pts[i]; 22723 var y = pts[i + 1]; 22724 retPts.push({ 22725 x: x, 22726 y: y 22727 }); 22728 } 22729 22730 return retPts; 22731 } 22732 22733 BRp$3.getSegmentPoints = function (edge) { 22734 var rs = edge[0]._private.rscratch; 22735 var type = rs.edgeType; 22736 22737 if (type === 'segments') { 22738 this.recalculateRenderedStyle(edge); 22739 return getPts(rs.segpts); 22740 } 22741 }; 22742 22743 BRp$3.getControlPoints = function (edge) { 22744 var rs = edge[0]._private.rscratch; 22745 var type = rs.edgeType; 22746 22747 if (type === 'bezier' || type === 'multibezier' || type === 'self' || type === 'compound') { 22748 this.recalculateRenderedStyle(edge); 22749 return getPts(rs.ctrlpts); 22750 } 22751 }; 22752 22753 BRp$3.getEdgeMidpoint = function (edge) { 22754 var rs = edge[0]._private.rscratch; 22755 this.recalculateRenderedStyle(edge); 22756 return { 22757 x: rs.midX, 22758 y: rs.midY 22759 }; 22760 }; 22761 22762 var BRp$4 = {}; 22763 22764 BRp$4.manualEndptToPx = function (node, prop) { 22765 var r = this; 22766 var npos = node.position(); 22767 var w = node.outerWidth(); 22768 var h = node.outerHeight(); 22769 22770 if (prop.value.length === 2) { 22771 var p = [prop.pfValue[0], prop.pfValue[1]]; 22772 22773 if (prop.units[0] === '%') { 22774 p[0] = p[0] * w; 22775 } 22776 22777 if (prop.units[1] === '%') { 22778 p[1] = p[1] * h; 22779 } 22780 22781 p[0] += npos.x; 22782 p[1] += npos.y; 22783 return p; 22784 } else { 22785 var angle = prop.pfValue[0]; 22786 angle = -Math.PI / 2 + angle; // start at 12 o'clock 22787 22788 var l = 2 * Math.max(w, h); 22789 var _p = [npos.x + Math.cos(angle) * l, npos.y + Math.sin(angle) * l]; 22790 return r.nodeShapes[this.getNodeShape(node)].intersectLine(npos.x, npos.y, w, h, _p[0], _p[1], 0); 22791 } 22792 }; 22793 22794 BRp$4.findEndpoints = function (edge) { 22795 var r = this; 22796 var intersect; 22797 var source = edge.source()[0]; 22798 var target = edge.target()[0]; 22799 var srcPos = source.position(); 22800 var tgtPos = target.position(); 22801 var tgtArShape = edge.pstyle('target-arrow-shape').value; 22802 var srcArShape = edge.pstyle('source-arrow-shape').value; 22803 var tgtDist = edge.pstyle('target-distance-from-node').pfValue; 22804 var srcDist = edge.pstyle('source-distance-from-node').pfValue; 22805 var curveStyle = edge.pstyle('curve-style').value; 22806 var rs = edge._private.rscratch; 22807 var et = rs.edgeType; 22808 var taxi = curveStyle === 'taxi'; 22809 var self = et === 'self' || et === 'compound'; 22810 var bezier = et === 'bezier' || et === 'multibezier' || self; 22811 var multi = et !== 'bezier'; 22812 var lines = et === 'straight' || et === 'segments'; 22813 var segments = et === 'segments'; 22814 var hasEndpts = bezier || multi || lines; 22815 var overrideEndpts = self || taxi; 22816 var srcManEndpt = edge.pstyle('source-endpoint'); 22817 var srcManEndptVal = overrideEndpts ? 'outside-to-node' : srcManEndpt.value; 22818 var tgtManEndpt = edge.pstyle('target-endpoint'); 22819 var tgtManEndptVal = overrideEndpts ? 'outside-to-node' : tgtManEndpt.value; 22820 rs.srcManEndpt = srcManEndpt; 22821 rs.tgtManEndpt = tgtManEndpt; 22822 var p1; // last known point of edge on target side 22823 22824 var p2; // last known point of edge on source side 22825 22826 var p1_i; // point to intersect with target shape 22827 22828 var p2_i; // point to intersect with source shape 22829 22830 if (bezier) { 22831 var cpStart = [rs.ctrlpts[0], rs.ctrlpts[1]]; 22832 var cpEnd = multi ? [rs.ctrlpts[rs.ctrlpts.length - 2], rs.ctrlpts[rs.ctrlpts.length - 1]] : cpStart; 22833 p1 = cpEnd; 22834 p2 = cpStart; 22835 } else if (lines) { 22836 var srcArrowFromPt = !segments ? [tgtPos.x, tgtPos.y] : rs.segpts.slice(0, 2); 22837 var tgtArrowFromPt = !segments ? [srcPos.x, srcPos.y] : rs.segpts.slice(rs.segpts.length - 2); 22838 p1 = tgtArrowFromPt; 22839 p2 = srcArrowFromPt; 22840 } 22841 22842 if (tgtManEndptVal === 'inside-to-node') { 22843 intersect = [tgtPos.x, tgtPos.y]; 22844 } else if (tgtManEndpt.units) { 22845 intersect = this.manualEndptToPx(target, tgtManEndpt); 22846 } else if (tgtManEndptVal === 'outside-to-line') { 22847 intersect = rs.tgtIntn; // use cached value from ctrlpt calc 22848 } else { 22849 if (tgtManEndptVal === 'outside-to-node' || tgtManEndptVal === 'outside-to-node-or-label') { 22850 p1_i = p1; 22851 } else if (tgtManEndptVal === 'outside-to-line' || tgtManEndptVal === 'outside-to-line-or-label') { 22852 p1_i = [srcPos.x, srcPos.y]; 22853 } 22854 22855 intersect = r.nodeShapes[this.getNodeShape(target)].intersectLine(tgtPos.x, tgtPos.y, target.outerWidth(), target.outerHeight(), p1_i[0], p1_i[1], 0); 22856 22857 if (tgtManEndptVal === 'outside-to-node-or-label' || tgtManEndptVal === 'outside-to-line-or-label') { 22858 var trs = target._private.rscratch; 22859 var lw = trs.labelWidth; 22860 var lh = trs.labelHeight; 22861 var lx = trs.labelX; 22862 var ly = trs.labelY; 22863 var lw2 = lw / 2; 22864 var lh2 = lh / 2; 22865 var va = target.pstyle('text-valign').value; 22866 22867 if (va === 'top') { 22868 ly -= lh2; 22869 } else if (va === 'bottom') { 22870 ly += lh2; 22871 } 22872 22873 var ha = target.pstyle('text-halign').value; 22874 22875 if (ha === 'left') { 22876 lx -= lw2; 22877 } else if (ha === 'right') { 22878 lx += lw2; 22879 } 22880 22881 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); 22882 22883 if (labelIntersect.length > 0) { 22884 var refPt = srcPos; 22885 var intSqdist = sqdist(refPt, array2point(intersect)); 22886 var labIntSqdist = sqdist(refPt, array2point(labelIntersect)); 22887 var minSqDist = intSqdist; 22888 22889 if (labIntSqdist < intSqdist) { 22890 intersect = labelIntersect; 22891 minSqDist = labIntSqdist; 22892 } 22893 22894 if (labelIntersect.length > 2) { 22895 var labInt2SqDist = sqdist(refPt, { 22896 x: labelIntersect[2], 22897 y: labelIntersect[3] 22898 }); 22899 22900 if (labInt2SqDist < minSqDist) { 22901 intersect = [labelIntersect[2], labelIntersect[3]]; 22902 } 22903 } 22904 } 22905 } 22906 } 22907 22908 var arrowEnd = shortenIntersection(intersect, p1, r.arrowShapes[tgtArShape].spacing(edge) + tgtDist); 22909 var edgeEnd = shortenIntersection(intersect, p1, r.arrowShapes[tgtArShape].gap(edge) + tgtDist); 22910 rs.endX = edgeEnd[0]; 22911 rs.endY = edgeEnd[1]; 22912 rs.arrowEndX = arrowEnd[0]; 22913 rs.arrowEndY = arrowEnd[1]; 22914 22915 if (srcManEndptVal === 'inside-to-node') { 22916 intersect = [srcPos.x, srcPos.y]; 22917 } else if (srcManEndpt.units) { 22918 intersect = this.manualEndptToPx(source, srcManEndpt); 22919 } else if (srcManEndptVal === 'outside-to-line') { 22920 intersect = rs.srcIntn; // use cached value from ctrlpt calc 22921 } else { 22922 if (srcManEndptVal === 'outside-to-node' || srcManEndptVal === 'outside-to-node-or-label') { 22923 p2_i = p2; 22924 } else if (srcManEndptVal === 'outside-to-line' || srcManEndptVal === 'outside-to-line-or-label') { 22925 p2_i = [tgtPos.x, tgtPos.y]; 22926 } 22927 22928 intersect = r.nodeShapes[this.getNodeShape(source)].intersectLine(srcPos.x, srcPos.y, source.outerWidth(), source.outerHeight(), p2_i[0], p2_i[1], 0); 22929 22930 if (srcManEndptVal === 'outside-to-node-or-label' || srcManEndptVal === 'outside-to-line-or-label') { 22931 var srs = source._private.rscratch; 22932 var _lw = srs.labelWidth; 22933 var _lh = srs.labelHeight; 22934 var _lx = srs.labelX; 22935 var _ly = srs.labelY; 22936 22937 var _lw2 = _lw / 2; 22938 22939 var _lh2 = _lh / 2; 22940 22941 var _va = source.pstyle('text-valign').value; 22942 22943 if (_va === 'top') { 22944 _ly -= _lh2; 22945 } else if (_va === 'bottom') { 22946 _ly += _lh2; 22947 } 22948 22949 var _ha = source.pstyle('text-halign').value; 22950 22951 if (_ha === 'left') { 22952 _lx -= _lw2; 22953 } else if (_ha === 'right') { 22954 _lx += _lw2; 22955 } 22956 22957 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); 22958 22959 if (_labelIntersect.length > 0) { 22960 var _refPt = tgtPos; 22961 22962 var _intSqdist = sqdist(_refPt, array2point(intersect)); 22963 22964 var _labIntSqdist = sqdist(_refPt, array2point(_labelIntersect)); 22965 22966 var _minSqDist = _intSqdist; 22967 22968 if (_labIntSqdist < _intSqdist) { 22969 intersect = [_labelIntersect[0], _labelIntersect[1]]; 22970 _minSqDist = _labIntSqdist; 22971 } 22972 22973 if (_labelIntersect.length > 2) { 22974 var _labInt2SqDist = sqdist(_refPt, { 22975 x: _labelIntersect[2], 22976 y: _labelIntersect[3] 22977 }); 22978 22979 if (_labInt2SqDist < _minSqDist) { 22980 intersect = [_labelIntersect[2], _labelIntersect[3]]; 22981 } 22982 } 22983 } 22984 } 22985 } 22986 22987 var arrowStart = shortenIntersection(intersect, p2, r.arrowShapes[srcArShape].spacing(edge) + srcDist); 22988 var edgeStart = shortenIntersection(intersect, p2, r.arrowShapes[srcArShape].gap(edge) + srcDist); 22989 rs.startX = edgeStart[0]; 22990 rs.startY = edgeStart[1]; 22991 rs.arrowStartX = arrowStart[0]; 22992 rs.arrowStartY = arrowStart[1]; 22993 22994 if (hasEndpts) { 22995 if (!number(rs.startX) || !number(rs.startY) || !number(rs.endX) || !number(rs.endY)) { 22996 rs.badLine = true; 22997 } else { 22998 rs.badLine = false; 22999 } 23000 } 23001 }; 23002 23003 BRp$4.getSourceEndpoint = function (edge) { 23004 var rs = edge[0]._private.rscratch; 23005 this.recalculateRenderedStyle(edge); 23006 23007 switch (rs.edgeType) { 23008 case 'haystack': 23009 return { 23010 x: rs.haystackPts[0], 23011 y: rs.haystackPts[1] 23012 }; 23013 23014 default: 23015 return { 23016 x: rs.arrowStartX, 23017 y: rs.arrowStartY 23018 }; 23019 } 23020 }; 23021 23022 BRp$4.getTargetEndpoint = function (edge) { 23023 var rs = edge[0]._private.rscratch; 23024 this.recalculateRenderedStyle(edge); 23025 23026 switch (rs.edgeType) { 23027 case 'haystack': 23028 return { 23029 x: rs.haystackPts[2], 23030 y: rs.haystackPts[3] 23031 }; 23032 23033 default: 23034 return { 23035 x: rs.arrowEndX, 23036 y: rs.arrowEndY 23037 }; 23038 } 23039 }; 23040 23041 var BRp$5 = {}; 23042 23043 function pushBezierPts(r, edge, pts) { 23044 var qbezierAt$1 = function qbezierAt$1(p1, p2, p3, t) { 23045 return qbezierAt(p1, p2, p3, t); 23046 }; 23047 23048 var _p = edge._private; 23049 var bpts = _p.rstyle.bezierPts; 23050 23051 for (var i = 0; i < r.bezierProjPcts.length; i++) { 23052 var p = r.bezierProjPcts[i]; 23053 bpts.push({ 23054 x: qbezierAt$1(pts[0], pts[2], pts[4], p), 23055 y: qbezierAt$1(pts[1], pts[3], pts[5], p) 23056 }); 23057 } 23058 } 23059 23060 BRp$5.storeEdgeProjections = function (edge) { 23061 var _p = edge._private; 23062 var rs = _p.rscratch; 23063 var et = rs.edgeType; // clear the cached points state 23064 23065 _p.rstyle.bezierPts = null; 23066 _p.rstyle.linePts = null; 23067 _p.rstyle.haystackPts = null; 23068 23069 if (et === 'multibezier' || et === 'bezier' || et === 'self' || et === 'compound') { 23070 _p.rstyle.bezierPts = []; 23071 23072 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 23073 pushBezierPts(this, edge, rs.allpts.slice(i, i + 6)); 23074 } 23075 } else if (et === 'segments') { 23076 var lpts = _p.rstyle.linePts = []; 23077 23078 for (var i = 0; i + 1 < rs.allpts.length; i += 2) { 23079 lpts.push({ 23080 x: rs.allpts[i], 23081 y: rs.allpts[i + 1] 23082 }); 23083 } 23084 } else if (et === 'haystack') { 23085 var hpts = rs.haystackPts; 23086 _p.rstyle.haystackPts = [{ 23087 x: hpts[0], 23088 y: hpts[1] 23089 }, { 23090 x: hpts[2], 23091 y: hpts[3] 23092 }]; 23093 } 23094 23095 _p.rstyle.arrowWidth = this.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.arrowShapeWidth; 23096 }; 23097 23098 BRp$5.recalculateEdgeProjections = function (edges) { 23099 this.findEdgeControlPoints(edges); 23100 }; 23101 23102 var BRp$6 = {}; 23103 23104 BRp$6.recalculateNodeLabelProjection = function (node) { 23105 var content = node.pstyle('label').strValue; 23106 23107 if (emptyString(content)) { 23108 return; 23109 } 23110 23111 var textX, textY; 23112 var _p = node._private; 23113 var nodeWidth = node.width(); 23114 var nodeHeight = node.height(); 23115 var padding = node.padding(); 23116 var nodePos = node.position(); 23117 var textHalign = node.pstyle('text-halign').strValue; 23118 var textValign = node.pstyle('text-valign').strValue; 23119 var rs = _p.rscratch; 23120 var rstyle = _p.rstyle; 23121 23122 switch (textHalign) { 23123 case 'left': 23124 textX = nodePos.x - nodeWidth / 2 - padding; 23125 break; 23126 23127 case 'right': 23128 textX = nodePos.x + nodeWidth / 2 + padding; 23129 break; 23130 23131 default: 23132 // e.g. center 23133 textX = nodePos.x; 23134 } 23135 23136 switch (textValign) { 23137 case 'top': 23138 textY = nodePos.y - nodeHeight / 2 - padding; 23139 break; 23140 23141 case 'bottom': 23142 textY = nodePos.y + nodeHeight / 2 + padding; 23143 break; 23144 23145 default: 23146 // e.g. middle 23147 textY = nodePos.y; 23148 } 23149 23150 rs.labelX = textX; 23151 rs.labelY = textY; 23152 rstyle.labelX = textX; 23153 rstyle.labelY = textY; 23154 this.applyLabelDimensions(node); 23155 }; 23156 23157 var lineAngleFromDelta = function lineAngleFromDelta(dx, dy) { 23158 var angle = Math.atan(dy / dx); 23159 23160 if (dx === 0 && angle < 0) { 23161 angle = angle * -1; 23162 } 23163 23164 return angle; 23165 }; 23166 23167 var lineAngle = function lineAngle(p0, p1) { 23168 var dx = p1.x - p0.x; 23169 var dy = p1.y - p0.y; 23170 return lineAngleFromDelta(dx, dy); 23171 }; 23172 23173 var bezierAngle = function bezierAngle(p0, p1, p2, t) { 23174 var t0 = bound(0, t - 0.001, 1); 23175 var t1 = bound(0, t + 0.001, 1); 23176 var lp0 = qbezierPtAt(p0, p1, p2, t0); 23177 var lp1 = qbezierPtAt(p0, p1, p2, t1); 23178 return lineAngle(lp0, lp1); 23179 }; 23180 23181 BRp$6.recalculateEdgeLabelProjections = function (edge) { 23182 var p; 23183 var _p = edge._private; 23184 var rs = _p.rscratch; 23185 var r = this; 23186 var content = { 23187 mid: edge.pstyle('label').strValue, 23188 source: edge.pstyle('source-label').strValue, 23189 target: edge.pstyle('target-label').strValue 23190 }; 23191 23192 if (content.mid || content.source || content.target) ; else { 23193 return; // no labels => no calcs 23194 } // add center point to style so bounding box calculations can use it 23195 // 23196 23197 23198 p = { 23199 x: rs.midX, 23200 y: rs.midY 23201 }; 23202 23203 var setRs = function setRs(propName, prefix, value) { 23204 setPrefixedProperty(_p.rscratch, propName, prefix, value); 23205 setPrefixedProperty(_p.rstyle, propName, prefix, value); 23206 }; 23207 23208 setRs('labelX', null, p.x); 23209 setRs('labelY', null, p.y); 23210 var midAngle = lineAngleFromDelta(rs.midDispX, rs.midDispY); 23211 setRs('labelAutoAngle', null, midAngle); 23212 23213 var createControlPointInfo = function createControlPointInfo() { 23214 if (createControlPointInfo.cache) { 23215 return createControlPointInfo.cache; 23216 } // use cache so only 1x per edge 23217 23218 23219 var ctrlpts = []; // store each ctrlpt info init 23220 23221 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 23222 var p0 = { 23223 x: rs.allpts[i], 23224 y: rs.allpts[i + 1] 23225 }; 23226 var p1 = { 23227 x: rs.allpts[i + 2], 23228 y: rs.allpts[i + 3] 23229 }; // ctrlpt 23230 23231 var p2 = { 23232 x: rs.allpts[i + 4], 23233 y: rs.allpts[i + 5] 23234 }; 23235 ctrlpts.push({ 23236 p0: p0, 23237 p1: p1, 23238 p2: p2, 23239 startDist: 0, 23240 length: 0, 23241 segments: [] 23242 }); 23243 } 23244 23245 var bpts = _p.rstyle.bezierPts; 23246 var nProjs = r.bezierProjPcts.length; 23247 23248 function addSegment(cp, p0, p1, t0, t1) { 23249 var length = dist(p0, p1); 23250 var prevSegment = cp.segments[cp.segments.length - 1]; 23251 var segment = { 23252 p0: p0, 23253 p1: p1, 23254 t0: t0, 23255 t1: t1, 23256 startDist: prevSegment ? prevSegment.startDist + prevSegment.length : 0, 23257 length: length 23258 }; 23259 cp.segments.push(segment); 23260 cp.length += length; 23261 } // update each ctrlpt with segment info 23262 23263 23264 for (var _i = 0; _i < ctrlpts.length; _i++) { 23265 var cp = ctrlpts[_i]; 23266 var prevCp = ctrlpts[_i - 1]; 23267 23268 if (prevCp) { 23269 cp.startDist = prevCp.startDist + prevCp.length; 23270 } 23271 23272 addSegment(cp, cp.p0, bpts[_i * nProjs], 0, r.bezierProjPcts[0]); // first 23273 23274 for (var j = 0; j < nProjs - 1; j++) { 23275 addSegment(cp, bpts[_i * nProjs + j], bpts[_i * nProjs + j + 1], r.bezierProjPcts[j], r.bezierProjPcts[j + 1]); 23276 } 23277 23278 addSegment(cp, bpts[_i * nProjs + nProjs - 1], cp.p2, r.bezierProjPcts[nProjs - 1], 1); // last 23279 } 23280 23281 return createControlPointInfo.cache = ctrlpts; 23282 }; 23283 23284 var calculateEndProjection = function calculateEndProjection(prefix) { 23285 var angle; 23286 var isSrc = prefix === 'source'; 23287 23288 if (!content[prefix]) { 23289 return; 23290 } 23291 23292 var offset = edge.pstyle(prefix + '-text-offset').pfValue; 23293 23294 switch (rs.edgeType) { 23295 case 'self': 23296 case 'compound': 23297 case 'bezier': 23298 case 'multibezier': 23299 { 23300 var cps = createControlPointInfo(); 23301 var selected; 23302 var startDist = 0; 23303 var totalDist = 0; // find the segment we're on 23304 23305 for (var i = 0; i < cps.length; i++) { 23306 var _cp = cps[isSrc ? i : cps.length - 1 - i]; 23307 23308 for (var j = 0; j < _cp.segments.length; j++) { 23309 var _seg = _cp.segments[isSrc ? j : _cp.segments.length - 1 - j]; 23310 var lastSeg = i === cps.length - 1 && j === _cp.segments.length - 1; 23311 startDist = totalDist; 23312 totalDist += _seg.length; 23313 23314 if (totalDist >= offset || lastSeg) { 23315 selected = { 23316 cp: _cp, 23317 segment: _seg 23318 }; 23319 break; 23320 } 23321 } 23322 23323 if (selected) { 23324 break; 23325 } 23326 } 23327 23328 var cp = selected.cp; 23329 var seg = selected.segment; 23330 var tSegment = (offset - startDist) / seg.length; 23331 var segDt = seg.t1 - seg.t0; 23332 var t = isSrc ? seg.t0 + segDt * tSegment : seg.t1 - segDt * tSegment; 23333 t = bound(0, t, 1); 23334 p = qbezierPtAt(cp.p0, cp.p1, cp.p2, t); 23335 angle = bezierAngle(cp.p0, cp.p1, cp.p2, t); 23336 break; 23337 } 23338 23339 case 'straight': 23340 case 'segments': 23341 case 'haystack': 23342 { 23343 var d = 0, 23344 di, 23345 d0; 23346 var p0, p1; 23347 var l = rs.allpts.length; 23348 23349 for (var _i2 = 0; _i2 + 3 < l; _i2 += 2) { 23350 if (isSrc) { 23351 p0 = { 23352 x: rs.allpts[_i2], 23353 y: rs.allpts[_i2 + 1] 23354 }; 23355 p1 = { 23356 x: rs.allpts[_i2 + 2], 23357 y: rs.allpts[_i2 + 3] 23358 }; 23359 } else { 23360 p0 = { 23361 x: rs.allpts[l - 2 - _i2], 23362 y: rs.allpts[l - 1 - _i2] 23363 }; 23364 p1 = { 23365 x: rs.allpts[l - 4 - _i2], 23366 y: rs.allpts[l - 3 - _i2] 23367 }; 23368 } 23369 23370 di = dist(p0, p1); 23371 d0 = d; 23372 d += di; 23373 23374 if (d >= offset) { 23375 break; 23376 } 23377 } 23378 23379 var pD = offset - d0; 23380 23381 var _t = pD / di; 23382 23383 _t = bound(0, _t, 1); 23384 p = lineAt(p0, p1, _t); 23385 angle = lineAngle(p0, p1); 23386 break; 23387 } 23388 } 23389 23390 setRs('labelX', prefix, p.x); 23391 setRs('labelY', prefix, p.y); 23392 setRs('labelAutoAngle', prefix, angle); 23393 }; 23394 23395 calculateEndProjection('source'); 23396 calculateEndProjection('target'); 23397 this.applyLabelDimensions(edge); 23398 }; 23399 23400 BRp$6.applyLabelDimensions = function (ele) { 23401 this.applyPrefixedLabelDimensions(ele); 23402 23403 if (ele.isEdge()) { 23404 this.applyPrefixedLabelDimensions(ele, 'source'); 23405 this.applyPrefixedLabelDimensions(ele, 'target'); 23406 } 23407 }; 23408 23409 BRp$6.applyPrefixedLabelDimensions = function (ele, prefix) { 23410 var _p = ele._private; 23411 var text = this.getLabelText(ele, prefix); 23412 var labelDims = this.calculateLabelDimensions(ele, text); 23413 var lineHeight = ele.pstyle('line-height').pfValue; 23414 var textWrap = ele.pstyle('text-wrap').strValue; 23415 var lines = getPrefixedProperty(_p.rscratch, 'labelWrapCachedLines', prefix) || []; 23416 var numLines = textWrap !== 'wrap' ? 1 : Math.max(lines.length, 1); 23417 var normPerLineHeight = labelDims.height / numLines; 23418 var labelLineHeight = normPerLineHeight * lineHeight; 23419 var width = labelDims.width; 23420 var height = labelDims.height + (numLines - 1) * (lineHeight - 1) * normPerLineHeight; 23421 setPrefixedProperty(_p.rstyle, 'labelWidth', prefix, width); 23422 setPrefixedProperty(_p.rscratch, 'labelWidth', prefix, width); 23423 setPrefixedProperty(_p.rstyle, 'labelHeight', prefix, height); 23424 setPrefixedProperty(_p.rscratch, 'labelHeight', prefix, height); 23425 setPrefixedProperty(_p.rscratch, 'labelLineHeight', prefix, labelLineHeight); 23426 }; 23427 23428 BRp$6.getLabelText = function (ele, prefix) { 23429 var _p = ele._private; 23430 var pfd = prefix ? prefix + '-' : ''; 23431 var text = ele.pstyle(pfd + 'label').strValue; 23432 var textTransform = ele.pstyle('text-transform').value; 23433 23434 var rscratch = function rscratch(propName, value) { 23435 if (value) { 23436 setPrefixedProperty(_p.rscratch, propName, prefix, value); 23437 return value; 23438 } else { 23439 return getPrefixedProperty(_p.rscratch, propName, prefix); 23440 } 23441 }; // for empty text, skip all processing 23442 23443 23444 if (!text) { 23445 return ''; 23446 } 23447 23448 if (textTransform == 'none') ; else if (textTransform == 'uppercase') { 23449 text = text.toUpperCase(); 23450 } else if (textTransform == 'lowercase') { 23451 text = text.toLowerCase(); 23452 } 23453 23454 var wrapStyle = ele.pstyle('text-wrap').value; 23455 23456 if (wrapStyle === 'wrap') { 23457 var labelKey = rscratch('labelKey'); // save recalc if the label is the same as before 23458 23459 if (labelKey != null && rscratch('labelWrapKey') === labelKey) { 23460 return rscratch('labelWrapCachedText'); 23461 } 23462 23463 var zwsp = "\u200B"; 23464 var lines = text.split('\n'); 23465 var maxW = ele.pstyle('text-max-width').pfValue; 23466 var overflow = ele.pstyle('text-overflow-wrap').value; 23467 var overflowAny = overflow === 'anywhere'; 23468 var wrappedLines = []; 23469 var wordsRegex = /[\s\u200b]+/; 23470 var wordSeparator = overflowAny ? '' : ' '; 23471 23472 for (var l = 0; l < lines.length; l++) { 23473 var line = lines[l]; 23474 var lineDims = this.calculateLabelDimensions(ele, line); 23475 var lineW = lineDims.width; 23476 23477 if (overflowAny) { 23478 var processedLine = line.split('').join(zwsp); 23479 line = processedLine; 23480 } 23481 23482 if (lineW > maxW) { 23483 // line is too long 23484 var words = line.split(wordsRegex); 23485 var subline = ''; 23486 23487 for (var w = 0; w < words.length; w++) { 23488 var word = words[w]; 23489 var testLine = subline.length === 0 ? word : subline + wordSeparator + word; 23490 var testDims = this.calculateLabelDimensions(ele, testLine); 23491 var testW = testDims.width; 23492 23493 if (testW <= maxW) { 23494 // word fits on current line 23495 subline += word + wordSeparator; 23496 } else { 23497 // word starts new line 23498 if (subline) { 23499 wrappedLines.push(subline); 23500 } 23501 23502 subline = word + wordSeparator; 23503 } 23504 } // if there's remaining text, put it in a wrapped line 23505 23506 23507 if (!subline.match(/^[\s\u200b]+$/)) { 23508 wrappedLines.push(subline); 23509 } 23510 } else { 23511 // line is already short enough 23512 wrappedLines.push(line); 23513 } 23514 } // for 23515 23516 23517 rscratch('labelWrapCachedLines', wrappedLines); 23518 text = rscratch('labelWrapCachedText', wrappedLines.join('\n')); 23519 rscratch('labelWrapKey', labelKey); 23520 } else if (wrapStyle === 'ellipsis') { 23521 var _maxW = ele.pstyle('text-max-width').pfValue; 23522 var ellipsized = ''; 23523 var ellipsis = "\u2026"; 23524 var incLastCh = false; 23525 23526 for (var i = 0; i < text.length; i++) { 23527 var widthWithNextCh = this.calculateLabelDimensions(ele, ellipsized + text[i] + ellipsis).width; 23528 23529 if (widthWithNextCh > _maxW) { 23530 break; 23531 } 23532 23533 ellipsized += text[i]; 23534 23535 if (i === text.length - 1) { 23536 incLastCh = true; 23537 } 23538 } 23539 23540 if (!incLastCh) { 23541 ellipsized += ellipsis; 23542 } 23543 23544 return ellipsized; 23545 } // if ellipsize 23546 23547 23548 return text; 23549 }; 23550 23551 BRp$6.getLabelJustification = function (ele) { 23552 var justification = ele.pstyle('text-justification').strValue; 23553 var textHalign = ele.pstyle('text-halign').strValue; 23554 23555 if (justification === 'auto') { 23556 if (ele.isNode()) { 23557 switch (textHalign) { 23558 case 'left': 23559 return 'right'; 23560 23561 case 'right': 23562 return 'left'; 23563 23564 default: 23565 return 'center'; 23566 } 23567 } else { 23568 return 'center'; 23569 } 23570 } else { 23571 return justification; 23572 } 23573 }; 23574 23575 BRp$6.calculateLabelDimensions = function (ele, text) { 23576 var r = this; 23577 var cacheKey = hashString(text, ele._private.labelDimsKey); 23578 var cache = r.labelDimCache || (r.labelDimCache = []); 23579 var existingVal = cache[cacheKey]; 23580 23581 if (existingVal != null) { 23582 return existingVal; 23583 } 23584 23585 var sizeMult = 1; // increase the scale to increase accuracy w.r.t. zoomed text 23586 23587 var fStyle = ele.pstyle('font-style').strValue; 23588 var size = sizeMult * ele.pstyle('font-size').pfValue + 'px'; 23589 var family = ele.pstyle('font-family').strValue; 23590 var weight = ele.pstyle('font-weight').strValue; 23591 var div = this.labelCalcDiv; 23592 23593 if (!div) { 23594 div = this.labelCalcDiv = document.createElement('div'); // eslint-disable-line no-undef 23595 23596 document.body.appendChild(div); // eslint-disable-line no-undef 23597 } 23598 23599 var ds = div.style; // from ele style 23600 23601 ds.fontFamily = family; 23602 ds.fontStyle = fStyle; 23603 ds.fontSize = size; 23604 ds.fontWeight = weight; // forced style 23605 23606 ds.position = 'absolute'; 23607 ds.left = '-9999px'; 23608 ds.top = '-9999px'; 23609 ds.zIndex = '-1'; 23610 ds.visibility = 'hidden'; 23611 ds.pointerEvents = 'none'; 23612 ds.padding = '0'; 23613 ds.lineHeight = '1'; // - newlines must be taken into account for text-wrap:wrap 23614 // - since spaces are not collapsed, each space must be taken into account 23615 23616 ds.whiteSpace = 'pre'; // put label content in div 23617 23618 div.textContent = text; 23619 return cache[cacheKey] = { 23620 width: Math.ceil(div.clientWidth / sizeMult), 23621 height: Math.ceil(div.clientHeight / sizeMult) 23622 }; 23623 }; 23624 23625 BRp$6.calculateLabelAngle = function (ele, prefix) { 23626 var _p = ele._private; 23627 var rs = _p.rscratch; 23628 var isEdge = ele.isEdge(); 23629 var prefixDash = prefix ? prefix + '-' : ''; 23630 var rot = ele.pstyle(prefixDash + 'text-rotation'); 23631 var rotStr = rot.strValue; 23632 23633 if (rotStr === 'none') { 23634 return 0; 23635 } else if (isEdge && rotStr === 'autorotate') { 23636 return rs.labelAutoAngle; 23637 } else if (rotStr === 'autorotate') { 23638 return 0; 23639 } else { 23640 return rot.pfValue; 23641 } 23642 }; 23643 23644 BRp$6.calculateLabelAngles = function (ele) { 23645 var r = this; 23646 var isEdge = ele.isEdge(); 23647 var _p = ele._private; 23648 var rs = _p.rscratch; 23649 rs.labelAngle = r.calculateLabelAngle(ele); 23650 23651 if (isEdge) { 23652 rs.sourceLabelAngle = r.calculateLabelAngle(ele, 'source'); 23653 rs.targetLabelAngle = r.calculateLabelAngle(ele, 'target'); 23654 } 23655 }; 23656 23657 var BRp$7 = {}; 23658 var TOO_SMALL_CUT_RECT = 28; 23659 var warnedCutRect = false; 23660 23661 BRp$7.getNodeShape = function (node) { 23662 var r = this; 23663 var shape = node.pstyle('shape').value; 23664 23665 if (shape === 'cutrectangle' && (node.width() < TOO_SMALL_CUT_RECT || node.height() < TOO_SMALL_CUT_RECT)) { 23666 if (!warnedCutRect) { 23667 warn('The `cutrectangle` node shape can not be used at small sizes so `rectangle` is used instead'); 23668 warnedCutRect = true; 23669 } 23670 23671 return 'rectangle'; 23672 } 23673 23674 if (node.isParent()) { 23675 if (shape === 'rectangle' || shape === 'roundrectangle' || shape === 'cutrectangle' || shape === 'barrel') { 23676 return shape; 23677 } else { 23678 return 'rectangle'; 23679 } 23680 } 23681 23682 if (shape === 'polygon') { 23683 var points = node.pstyle('shape-polygon-points').value; 23684 return r.nodeShapes.makePolygon(points).name; 23685 } 23686 23687 return shape; 23688 }; 23689 23690 var BRp$8 = {}; 23691 23692 BRp$8.registerCalculationListeners = function () { 23693 var cy = this.cy; 23694 var elesToUpdate = cy.collection(); 23695 var r = this; 23696 23697 var enqueue = function enqueue(eles) { 23698 var dirtyStyleCaches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 23699 elesToUpdate.merge(eles); 23700 23701 if (dirtyStyleCaches) { 23702 for (var i = 0; i < eles.length; i++) { 23703 var ele = eles[i]; 23704 var _p = ele._private; 23705 var rstyle = _p.rstyle; 23706 rstyle.clean = false; 23707 rstyle.cleanConnected = false; 23708 } 23709 } 23710 }; 23711 23712 r.binder(cy).on('bounds.* dirty.*', function onDirtyBounds(e) { 23713 var ele = e.target; 23714 enqueue(ele); 23715 }).on('style.* background.*', function onDirtyStyle(e) { 23716 var ele = e.target; 23717 enqueue(ele, false); 23718 }); 23719 23720 var updateEleCalcs = function updateEleCalcs(willDraw) { 23721 if (willDraw) { 23722 var fns = r.onUpdateEleCalcsFns; 23723 23724 for (var i = 0; i < elesToUpdate.length; i++) { 23725 var ele = elesToUpdate[i]; 23726 var rstyle = ele._private.rstyle; 23727 23728 if (ele.isNode() && !rstyle.cleanConnected) { 23729 enqueue(ele.connectedEdges()); 23730 rstyle.cleanConnected = true; 23731 } 23732 } 23733 23734 if (fns) { 23735 for (var i = 0; i < fns.length; i++) { 23736 var fn = fns[i]; 23737 fn(willDraw, elesToUpdate); 23738 } 23739 } 23740 23741 r.recalculateRenderedStyle(elesToUpdate); 23742 elesToUpdate = cy.collection(); 23743 } 23744 }; 23745 23746 r.flushRenderedStyleQueue = function () { 23747 updateEleCalcs(true); 23748 }; 23749 23750 r.beforeRender(updateEleCalcs, r.beforeRenderPriorities.eleCalcs); 23751 }; 23752 23753 BRp$8.onUpdateEleCalcs = function (fn) { 23754 var fns = this.onUpdateEleCalcsFns = this.onUpdateEleCalcsFns || []; 23755 fns.push(fn); 23756 }; 23757 23758 BRp$8.recalculateRenderedStyle = function (eles, useCache) { 23759 var isCleanConnected = function isCleanConnected(ele) { 23760 return ele._private.rstyle.cleanConnected; 23761 }; 23762 23763 var edges = []; 23764 var nodes = []; // the renderer can't be used for calcs when destroyed, e.g. ele.boundingBox() 23765 23766 if (this.destroyed) { 23767 return; 23768 } // use cache by default for perf 23769 23770 23771 if (useCache === undefined) { 23772 useCache = true; 23773 } 23774 23775 for (var i = 0; i < eles.length; i++) { 23776 var ele = eles[i]; 23777 var _p = ele._private; 23778 var rstyle = _p.rstyle; // an edge may be implicitly dirty b/c of one of its connected nodes 23779 // (and a request for recalc may come in between frames) 23780 23781 if (ele.isEdge() && (!isCleanConnected(ele.source()) || !isCleanConnected(ele.target()))) { 23782 rstyle.clean = false; 23783 } // only update if dirty and in graph 23784 23785 23786 if (useCache && rstyle.clean || ele.removed()) { 23787 continue; 23788 } // only update if not display: none 23789 23790 23791 if (ele.pstyle('display').value === 'none') { 23792 continue; 23793 } 23794 23795 if (_p.group === 'nodes') { 23796 nodes.push(ele); 23797 } else { 23798 // edges 23799 edges.push(ele); 23800 } 23801 23802 rstyle.clean = true; 23803 } // update node data from projections 23804 23805 23806 for (var i = 0; i < nodes.length; i++) { 23807 var ele = nodes[i]; 23808 var _p = ele._private; 23809 var rstyle = _p.rstyle; 23810 var pos = ele.position(); 23811 this.recalculateNodeLabelProjection(ele); 23812 rstyle.nodeX = pos.x; 23813 rstyle.nodeY = pos.y; 23814 rstyle.nodeW = ele.pstyle('width').pfValue; 23815 rstyle.nodeH = ele.pstyle('height').pfValue; 23816 } 23817 23818 this.recalculateEdgeProjections(edges); // update edge data from projections 23819 23820 for (var i = 0; i < edges.length; i++) { 23821 var ele = edges[i]; 23822 var _p = ele._private; 23823 var rstyle = _p.rstyle; 23824 var rs = _p.rscratch; // update rstyle positions 23825 23826 rstyle.srcX = rs.arrowStartX; 23827 rstyle.srcY = rs.arrowStartY; 23828 rstyle.tgtX = rs.arrowEndX; 23829 rstyle.tgtY = rs.arrowEndY; 23830 rstyle.midX = rs.midX; 23831 rstyle.midY = rs.midY; 23832 rstyle.labelAngle = rs.labelAngle; 23833 rstyle.sourceLabelAngle = rs.sourceLabelAngle; 23834 rstyle.targetLabelAngle = rs.targetLabelAngle; 23835 } 23836 }; 23837 23838 var BRp$9 = {}; 23839 23840 BRp$9.updateCachedGrabbedEles = function () { 23841 var eles = this.cachedZSortedEles; 23842 23843 if (!eles) { 23844 // just let this be recalculated on the next z sort tick 23845 return; 23846 } 23847 23848 eles.drag = []; 23849 eles.nondrag = []; 23850 var grabTargets = []; 23851 23852 for (var i = 0; i < eles.length; i++) { 23853 var ele = eles[i]; 23854 var rs = ele._private.rscratch; 23855 23856 if (ele.grabbed() && !ele.isParent()) { 23857 grabTargets.push(ele); 23858 } else if (rs.inDragLayer) { 23859 eles.drag.push(ele); 23860 } else { 23861 eles.nondrag.push(ele); 23862 } 23863 } // put the grab target nodes last so it's on top of its neighbourhood 23864 23865 23866 for (var i = 0; i < grabTargets.length; i++) { 23867 var ele = grabTargets[i]; 23868 eles.drag.push(ele); 23869 } 23870 }; 23871 23872 BRp$9.invalidateCachedZSortedEles = function () { 23873 this.cachedZSortedEles = null; 23874 }; 23875 23876 BRp$9.getCachedZSortedEles = function (forceRecalc) { 23877 if (forceRecalc || !this.cachedZSortedEles) { 23878 var eles = this.cy.mutableElements().toArray(); 23879 eles.sort(zIndexSort); 23880 eles.interactive = eles.filter(function (ele) { 23881 return ele.interactive(); 23882 }); 23883 this.cachedZSortedEles = eles; 23884 this.updateCachedGrabbedEles(); 23885 } else { 23886 eles = this.cachedZSortedEles; 23887 } 23888 23889 return eles; 23890 }; 23891 23892 var BRp$a = {}; 23893 [BRp$1, BRp$2, BRp$3, BRp$4, BRp$5, BRp$6, BRp$7, BRp$8, BRp$9].forEach(function (props) { 23894 extend(BRp$a, props); 23895 }); 23896 23897 var BRp$b = {}; 23898 23899 BRp$b.getCachedImage = function (url, crossOrigin, onLoad) { 23900 var r = this; 23901 var imageCache = r.imageCache = r.imageCache || {}; 23902 var cache = imageCache[url]; 23903 23904 if (cache) { 23905 if (!cache.image.complete) { 23906 cache.image.addEventListener('load', onLoad); 23907 } 23908 23909 return cache.image; 23910 } else { 23911 cache = imageCache[url] = imageCache[url] || {}; 23912 var image = cache.image = new Image(); // eslint-disable-line no-undef 23913 23914 image.addEventListener('load', onLoad); 23915 image.addEventListener('error', function () { 23916 image.error = true; 23917 }); // #1582 safari doesn't load data uris with crossOrigin properly 23918 // https://bugs.webkit.org/show_bug.cgi?id=123978 23919 23920 var dataUriPrefix = 'data:'; 23921 var isDataUri = url.substring(0, dataUriPrefix.length).toLowerCase() === dataUriPrefix; 23922 23923 if (!isDataUri) { 23924 image.crossOrigin = crossOrigin; // prevent tainted canvas 23925 } 23926 23927 image.src = url; 23928 return image; 23929 } 23930 }; 23931 23932 var BRp$c = {}; 23933 /* global document, window, ResizeObserver, MutationObserver */ 23934 23935 BRp$c.registerBinding = function (target, event, handler, useCapture) { 23936 // eslint-disable-line no-unused-vars 23937 var args = Array.prototype.slice.apply(arguments, [1]); // copy 23938 23939 var b = this.binder(target); 23940 return b.on.apply(b, args); 23941 }; 23942 23943 BRp$c.binder = function (tgt) { 23944 var r = this; 23945 var tgtIsDom = tgt === window || tgt === document || tgt === document.body || domElement(tgt); 23946 23947 if (r.supportsPassiveEvents == null) { 23948 // from https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection 23949 var supportsPassive = false; 23950 23951 try { 23952 var opts = Object.defineProperty({}, 'passive', { 23953 get: function get() { 23954 supportsPassive = true; 23955 return true; 23956 } 23957 }); 23958 window.addEventListener('test', null, opts); 23959 } catch (err) {// not supported 23960 } 23961 23962 r.supportsPassiveEvents = supportsPassive; 23963 } 23964 23965 var on = function on(event, handler, useCapture) { 23966 var args = Array.prototype.slice.call(arguments); 23967 23968 if (tgtIsDom && r.supportsPassiveEvents) { 23969 // replace useCapture w/ opts obj 23970 args[2] = { 23971 capture: useCapture != null ? useCapture : false, 23972 passive: false, 23973 once: false 23974 }; 23975 } 23976 23977 r.bindings.push({ 23978 target: tgt, 23979 args: args 23980 }); 23981 (tgt.addEventListener || tgt.on).apply(tgt, args); 23982 return this; 23983 }; 23984 23985 return { 23986 on: on, 23987 addEventListener: on, 23988 addListener: on, 23989 bind: on 23990 }; 23991 }; 23992 23993 BRp$c.nodeIsDraggable = function (node) { 23994 return node && node.isNode() && !node.locked() && node.grabbable(); 23995 }; 23996 23997 BRp$c.nodeIsGrabbable = function (node) { 23998 return this.nodeIsDraggable(node) && node.interactive(); 23999 }; 24000 24001 BRp$c.load = function () { 24002 var r = this; 24003 24004 var isSelected = function isSelected(ele) { 24005 return ele.selected(); 24006 }; 24007 24008 var triggerEvents = function triggerEvents(target, names, e, position) { 24009 if (target == null) { 24010 target = r.cy; 24011 } 24012 24013 for (var i = 0; i < names.length; i++) { 24014 var name = names[i]; 24015 target.emit({ 24016 originalEvent: e, 24017 type: name, 24018 position: position 24019 }); 24020 } 24021 }; 24022 24023 var isMultSelKeyDown = function isMultSelKeyDown(e) { 24024 return e.shiftKey || e.metaKey || e.ctrlKey; // maybe e.altKey 24025 }; 24026 24027 var allowPanningPassthrough = function allowPanningPassthrough(down, downs) { 24028 var allowPassthrough = true; 24029 24030 if (r.cy.hasCompoundNodes() && down && down.pannable()) { 24031 // a grabbable compound node below the ele => no passthrough panning 24032 for (var i = 0; downs && i < downs.length; i++) { 24033 var down = downs[i]; 24034 24035 if (down.isNode() && down.isParent()) { 24036 allowPassthrough = false; 24037 break; 24038 } 24039 } 24040 } else { 24041 allowPassthrough = true; 24042 } 24043 24044 return allowPassthrough; 24045 }; 24046 24047 var setGrabbed = function setGrabbed(ele) { 24048 ele[0]._private.grabbed = true; 24049 }; 24050 24051 var setFreed = function setFreed(ele) { 24052 ele[0]._private.grabbed = false; 24053 }; 24054 24055 var setInDragLayer = function setInDragLayer(ele) { 24056 ele[0]._private.rscratch.inDragLayer = true; 24057 }; 24058 24059 var setOutDragLayer = function setOutDragLayer(ele) { 24060 ele[0]._private.rscratch.inDragLayer = false; 24061 }; 24062 24063 var setGrabTarget = function setGrabTarget(ele) { 24064 ele[0]._private.rscratch.isGrabTarget = true; 24065 }; 24066 24067 var removeGrabTarget = function removeGrabTarget(ele) { 24068 ele[0]._private.rscratch.isGrabTarget = false; 24069 }; 24070 24071 var addToDragList = function addToDragList(ele, opts) { 24072 var list = opts.addToList; 24073 var listHasEle = list.has(ele); 24074 24075 if (!listHasEle) { 24076 list.merge(ele); 24077 setGrabbed(ele); 24078 } 24079 }; // helper function to determine which child nodes and inner edges 24080 // of a compound node to be dragged as well as the grabbed and selected nodes 24081 24082 24083 var addDescendantsToDrag = function addDescendantsToDrag(node, opts) { 24084 if (!node.cy().hasCompoundNodes()) { 24085 return; 24086 } 24087 24088 if (opts.inDragLayer == null && opts.addToList == null) { 24089 return; 24090 } // nothing to do 24091 24092 24093 var innerNodes = node.descendants(); 24094 24095 if (opts.inDragLayer) { 24096 innerNodes.forEach(setInDragLayer); 24097 innerNodes.connectedEdges().forEach(setInDragLayer); 24098 } 24099 24100 if (opts.addToList) { 24101 opts.addToList.unmerge(innerNodes); 24102 } 24103 }; // adds the given nodes and its neighbourhood to the drag layer 24104 24105 24106 var addNodesToDrag = function addNodesToDrag(nodes, opts) { 24107 opts = opts || {}; 24108 var hasCompoundNodes = nodes.cy().hasCompoundNodes(); 24109 24110 if (opts.inDragLayer) { 24111 nodes.forEach(setInDragLayer); 24112 nodes.neighborhood().stdFilter(function (ele) { 24113 return !hasCompoundNodes || ele.isEdge(); 24114 }).forEach(setInDragLayer); 24115 } 24116 24117 if (opts.addToList) { 24118 nodes.forEach(function (ele) { 24119 addToDragList(ele, opts); 24120 }); 24121 } 24122 24123 addDescendantsToDrag(nodes, opts); // always add to drag 24124 // also add nodes and edges related to the topmost ancestor 24125 24126 updateAncestorsInDragLayer(nodes, { 24127 inDragLayer: opts.inDragLayer 24128 }); 24129 r.updateCachedGrabbedEles(); 24130 }; 24131 24132 var addNodeToDrag = addNodesToDrag; 24133 24134 var freeDraggedElements = function freeDraggedElements(grabbedEles) { 24135 if (!grabbedEles) { 24136 return; 24137 } // just go over all elements rather than doing a bunch of (possibly expensive) traversals 24138 24139 24140 r.getCachedZSortedEles().forEach(function (ele) { 24141 setFreed(ele); 24142 setOutDragLayer(ele); 24143 removeGrabTarget(ele); 24144 }); 24145 r.updateCachedGrabbedEles(); 24146 }; // helper function to determine which ancestor nodes and edges should go 24147 // to the drag layer (or should be removed from drag layer). 24148 24149 24150 var updateAncestorsInDragLayer = function updateAncestorsInDragLayer(node, opts) { 24151 if (opts.inDragLayer == null && opts.addToList == null) { 24152 return; 24153 } // nothing to do 24154 24155 24156 if (!node.cy().hasCompoundNodes()) { 24157 return; 24158 } // find top-level parent 24159 24160 24161 var parent = node.ancestors().orphans(); // no parent node: no nodes to add to the drag layer 24162 24163 if (parent.same(node)) { 24164 return; 24165 } 24166 24167 var nodes = parent.descendants().spawnSelf().merge(parent).unmerge(node).unmerge(node.descendants()); 24168 var edges = nodes.connectedEdges(); 24169 24170 if (opts.inDragLayer) { 24171 edges.forEach(setInDragLayer); 24172 nodes.forEach(setInDragLayer); 24173 } 24174 24175 if (opts.addToList) { 24176 nodes.forEach(function (ele) { 24177 addToDragList(ele, opts); 24178 }); 24179 } 24180 }; 24181 24182 var blurActiveDomElement = function blurActiveDomElement() { 24183 if (document.activeElement != null && document.activeElement.blur != null) { 24184 document.activeElement.blur(); 24185 } 24186 }; 24187 24188 var haveMutationsApi = typeof MutationObserver !== 'undefined'; 24189 var haveResizeObserverApi = typeof ResizeObserver !== 'undefined'; // watch for when the cy container is removed from the dom 24190 24191 if (haveMutationsApi) { 24192 r.removeObserver = new MutationObserver(function (mutns) { 24193 // eslint-disable-line no-undef 24194 for (var i = 0; i < mutns.length; i++) { 24195 var mutn = mutns[i]; 24196 var rNodes = mutn.removedNodes; 24197 24198 if (rNodes) { 24199 for (var j = 0; j < rNodes.length; j++) { 24200 var rNode = rNodes[j]; 24201 24202 if (rNode === r.container) { 24203 r.destroy(); 24204 break; 24205 } 24206 } 24207 } 24208 } 24209 }); 24210 24211 if (r.container.parentNode) { 24212 r.removeObserver.observe(r.container.parentNode, { 24213 childList: true 24214 }); 24215 } 24216 } else { 24217 r.registerBinding(r.container, 'DOMNodeRemoved', function (e) { 24218 // eslint-disable-line no-unused-vars 24219 r.destroy(); 24220 }); 24221 } 24222 24223 var onResize = util(function () { 24224 r.cy.resize(); 24225 }, 100); 24226 24227 if (haveMutationsApi) { 24228 r.styleObserver = new MutationObserver(onResize); // eslint-disable-line no-undef 24229 24230 r.styleObserver.observe(r.container, { 24231 attributes: true 24232 }); 24233 } // auto resize 24234 24235 24236 r.registerBinding(window, 'resize', onResize); // eslint-disable-line no-undef 24237 24238 if (haveResizeObserverApi) { 24239 r.resizeObserver = new ResizeObserver(onResize); // eslint-disable-line no-undef 24240 24241 r.resizeObserver.observe(r.container); 24242 } 24243 24244 var forEachUp = function forEachUp(domEle, fn) { 24245 while (domEle != null) { 24246 fn(domEle); 24247 domEle = domEle.parentNode; 24248 } 24249 }; 24250 24251 var invalidateCoords = function invalidateCoords() { 24252 r.invalidateContainerClientCoordsCache(); 24253 }; 24254 24255 forEachUp(r.container, function (domEle) { 24256 r.registerBinding(domEle, 'transitionend', invalidateCoords); 24257 r.registerBinding(domEle, 'animationend', invalidateCoords); 24258 r.registerBinding(domEle, 'scroll', invalidateCoords); 24259 }); // stop right click menu from appearing on cy 24260 24261 r.registerBinding(r.container, 'contextmenu', function (e) { 24262 e.preventDefault(); 24263 }); 24264 24265 var inBoxSelection = function inBoxSelection() { 24266 return r.selection[4] !== 0; 24267 }; 24268 24269 var eventInContainer = function eventInContainer(e) { 24270 // save cycles if mouse events aren't to be captured 24271 var containerPageCoords = r.findContainerClientCoords(); 24272 var x = containerPageCoords[0]; 24273 var y = containerPageCoords[1]; 24274 var width = containerPageCoords[2]; 24275 var height = containerPageCoords[3]; 24276 var positions = e.touches ? e.touches : [e]; 24277 var atLeastOnePosInside = false; 24278 24279 for (var i = 0; i < positions.length; i++) { 24280 var p = positions[i]; 24281 24282 if (x <= p.clientX && p.clientX <= x + width && y <= p.clientY && p.clientY <= y + height) { 24283 atLeastOnePosInside = true; 24284 break; 24285 } 24286 } 24287 24288 if (!atLeastOnePosInside) { 24289 return false; 24290 } 24291 24292 var container = r.container; 24293 var target = e.target; 24294 var tParent = target.parentNode; 24295 var containerIsTarget = false; 24296 24297 while (tParent) { 24298 if (tParent === container) { 24299 containerIsTarget = true; 24300 break; 24301 } 24302 24303 tParent = tParent.parentNode; 24304 } 24305 24306 if (!containerIsTarget) { 24307 return false; 24308 } // if target is outisde cy container, then this event is not for us 24309 24310 24311 return true; 24312 }; // Primary key 24313 24314 24315 r.registerBinding(r.container, 'mousedown', function mousedownHandler(e) { 24316 if (!eventInContainer(e)) { 24317 return; 24318 } 24319 24320 e.preventDefault(); 24321 blurActiveDomElement(); 24322 r.hoverData.capture = true; 24323 r.hoverData.which = e.which; 24324 var cy = r.cy; 24325 var gpos = [e.clientX, e.clientY]; 24326 var pos = r.projectIntoViewport(gpos[0], gpos[1]); 24327 var select = r.selection; 24328 var nears = r.findNearestElements(pos[0], pos[1], true, false); 24329 var near = nears[0]; 24330 var draggedElements = r.dragData.possibleDragElements; 24331 r.hoverData.mdownPos = pos; 24332 r.hoverData.mdownGPos = gpos; 24333 24334 var checkForTaphold = function checkForTaphold() { 24335 r.hoverData.tapholdCancelled = false; 24336 clearTimeout(r.hoverData.tapholdTimeout); 24337 r.hoverData.tapholdTimeout = setTimeout(function () { 24338 if (r.hoverData.tapholdCancelled) { 24339 return; 24340 } else { 24341 var ele = r.hoverData.down; 24342 24343 if (ele) { 24344 ele.emit({ 24345 originalEvent: e, 24346 type: 'taphold', 24347 position: { 24348 x: pos[0], 24349 y: pos[1] 24350 } 24351 }); 24352 } else { 24353 cy.emit({ 24354 originalEvent: e, 24355 type: 'taphold', 24356 position: { 24357 x: pos[0], 24358 y: pos[1] 24359 } 24360 }); 24361 } 24362 } 24363 }, r.tapholdDuration); 24364 }; // Right click button 24365 24366 24367 if (e.which == 3) { 24368 r.hoverData.cxtStarted = true; 24369 var cxtEvt = { 24370 originalEvent: e, 24371 type: 'cxttapstart', 24372 position: { 24373 x: pos[0], 24374 y: pos[1] 24375 } 24376 }; 24377 24378 if (near) { 24379 near.activate(); 24380 near.emit(cxtEvt); 24381 r.hoverData.down = near; 24382 } else { 24383 cy.emit(cxtEvt); 24384 } 24385 24386 r.hoverData.downTime = new Date().getTime(); 24387 r.hoverData.cxtDragged = false; // Primary button 24388 } else if (e.which == 1) { 24389 if (near) { 24390 near.activate(); 24391 } // Element dragging 24392 24393 24394 { 24395 // If something is under the cursor and it is draggable, prepare to grab it 24396 if (near != null) { 24397 if (r.nodeIsGrabbable(near)) { 24398 var makeEvent = function makeEvent(type) { 24399 return { 24400 originalEvent: e, 24401 type: type, 24402 position: { 24403 x: pos[0], 24404 y: pos[1] 24405 } 24406 }; 24407 }; 24408 24409 var triggerGrab = function triggerGrab(ele) { 24410 ele.emit(makeEvent('grab')); 24411 }; 24412 24413 setGrabTarget(near); 24414 24415 if (!near.selected()) { 24416 draggedElements = r.dragData.possibleDragElements = cy.collection(); 24417 addNodeToDrag(near, { 24418 addToList: draggedElements 24419 }); 24420 near.emit(makeEvent('grabon')).emit(makeEvent('grab')); 24421 } else { 24422 draggedElements = r.dragData.possibleDragElements = cy.collection(); 24423 var selectedNodes = cy.$(function (ele) { 24424 return ele.isNode() && ele.selected() && r.nodeIsGrabbable(ele); 24425 }); 24426 addNodesToDrag(selectedNodes, { 24427 addToList: draggedElements 24428 }); 24429 near.emit(makeEvent('grabon')); 24430 selectedNodes.forEach(triggerGrab); 24431 } 24432 24433 r.redrawHint('eles', true); 24434 r.redrawHint('drag', true); 24435 } 24436 } 24437 24438 r.hoverData.down = near; 24439 r.hoverData.downs = nears; 24440 r.hoverData.downTime = new Date().getTime(); 24441 } 24442 triggerEvents(near, ['mousedown', 'tapstart', 'vmousedown'], e, { 24443 x: pos[0], 24444 y: pos[1] 24445 }); 24446 24447 if (near == null) { 24448 select[4] = 1; 24449 r.data.bgActivePosistion = { 24450 x: pos[0], 24451 y: pos[1] 24452 }; 24453 r.redrawHint('select', true); 24454 r.redraw(); 24455 } else if (near.pannable()) { 24456 select[4] = 1; // for future pan 24457 } 24458 24459 checkForTaphold(); 24460 } // Initialize selection box coordinates 24461 24462 24463 select[0] = select[2] = pos[0]; 24464 select[1] = select[3] = pos[1]; 24465 }, false); 24466 r.registerBinding(window, 'mousemove', function mousemoveHandler(e) { 24467 // eslint-disable-line no-undef 24468 var capture = r.hoverData.capture; 24469 24470 if (!capture && !eventInContainer(e)) { 24471 return; 24472 } 24473 24474 var preventDefault = false; 24475 var cy = r.cy; 24476 var zoom = cy.zoom(); 24477 var gpos = [e.clientX, e.clientY]; 24478 var pos = r.projectIntoViewport(gpos[0], gpos[1]); 24479 var mdownPos = r.hoverData.mdownPos; 24480 var mdownGPos = r.hoverData.mdownGPos; 24481 var select = r.selection; 24482 var near = null; 24483 24484 if (!r.hoverData.draggingEles && !r.hoverData.dragging && !r.hoverData.selecting) { 24485 near = r.findNearestElement(pos[0], pos[1], true, false); 24486 } 24487 24488 var last = r.hoverData.last; 24489 var down = r.hoverData.down; 24490 var disp = [pos[0] - select[2], pos[1] - select[3]]; 24491 var draggedElements = r.dragData.possibleDragElements; 24492 var isOverThresholdDrag; 24493 24494 if (mdownGPos) { 24495 var dx = gpos[0] - mdownGPos[0]; 24496 var dx2 = dx * dx; 24497 var dy = gpos[1] - mdownGPos[1]; 24498 var dy2 = dy * dy; 24499 var dist2 = dx2 + dy2; 24500 r.hoverData.isOverThresholdDrag = isOverThresholdDrag = dist2 >= r.desktopTapThreshold2; 24501 } 24502 24503 var multSelKeyDown = isMultSelKeyDown(e); 24504 24505 if (isOverThresholdDrag) { 24506 r.hoverData.tapholdCancelled = true; 24507 } 24508 24509 var updateDragDelta = function updateDragDelta() { 24510 var dragDelta = r.hoverData.dragDelta = r.hoverData.dragDelta || []; 24511 24512 if (dragDelta.length === 0) { 24513 dragDelta.push(disp[0]); 24514 dragDelta.push(disp[1]); 24515 } else { 24516 dragDelta[0] += disp[0]; 24517 dragDelta[1] += disp[1]; 24518 } 24519 }; 24520 24521 preventDefault = true; 24522 triggerEvents(near, ['mousemove', 'vmousemove', 'tapdrag'], e, { 24523 x: pos[0], 24524 y: pos[1] 24525 }); 24526 24527 var goIntoBoxMode = function goIntoBoxMode() { 24528 r.data.bgActivePosistion = undefined; 24529 24530 if (!r.hoverData.selecting) { 24531 cy.emit({ 24532 originalEvent: e, 24533 type: 'boxstart', 24534 position: { 24535 x: pos[0], 24536 y: pos[1] 24537 } 24538 }); 24539 } 24540 24541 select[4] = 1; 24542 r.hoverData.selecting = true; 24543 r.redrawHint('select', true); 24544 r.redraw(); 24545 }; // trigger context drag if rmouse down 24546 24547 24548 if (r.hoverData.which === 3) { 24549 // but only if over threshold 24550 if (isOverThresholdDrag) { 24551 var cxtEvt = { 24552 originalEvent: e, 24553 type: 'cxtdrag', 24554 position: { 24555 x: pos[0], 24556 y: pos[1] 24557 } 24558 }; 24559 24560 if (down) { 24561 down.emit(cxtEvt); 24562 } else { 24563 cy.emit(cxtEvt); 24564 } 24565 24566 r.hoverData.cxtDragged = true; 24567 24568 if (!r.hoverData.cxtOver || near !== r.hoverData.cxtOver) { 24569 if (r.hoverData.cxtOver) { 24570 r.hoverData.cxtOver.emit({ 24571 originalEvent: e, 24572 type: 'cxtdragout', 24573 position: { 24574 x: pos[0], 24575 y: pos[1] 24576 } 24577 }); 24578 } 24579 24580 r.hoverData.cxtOver = near; 24581 24582 if (near) { 24583 near.emit({ 24584 originalEvent: e, 24585 type: 'cxtdragover', 24586 position: { 24587 x: pos[0], 24588 y: pos[1] 24589 } 24590 }); 24591 } 24592 } 24593 } // Check if we are drag panning the entire graph 24594 24595 } else if (r.hoverData.dragging) { 24596 preventDefault = true; 24597 24598 if (cy.panningEnabled() && cy.userPanningEnabled()) { 24599 var deltaP; 24600 24601 if (r.hoverData.justStartedPan) { 24602 var mdPos = r.hoverData.mdownPos; 24603 deltaP = { 24604 x: (pos[0] - mdPos[0]) * zoom, 24605 y: (pos[1] - mdPos[1]) * zoom 24606 }; 24607 r.hoverData.justStartedPan = false; 24608 } else { 24609 deltaP = { 24610 x: disp[0] * zoom, 24611 y: disp[1] * zoom 24612 }; 24613 } 24614 24615 cy.panBy(deltaP); 24616 r.hoverData.dragged = true; 24617 } // Needs reproject due to pan changing viewport 24618 24619 24620 pos = r.projectIntoViewport(e.clientX, e.clientY); // Checks primary button down & out of time & mouse not moved much 24621 } else if (select[4] == 1 && (down == null || down.pannable())) { 24622 if (isOverThresholdDrag) { 24623 if (!r.hoverData.dragging && cy.boxSelectionEnabled() && (multSelKeyDown || !cy.panningEnabled() || !cy.userPanningEnabled())) { 24624 goIntoBoxMode(); 24625 } else if (!r.hoverData.selecting && cy.panningEnabled() && cy.userPanningEnabled()) { 24626 var allowPassthrough = allowPanningPassthrough(down, r.hoverData.downs); 24627 24628 if (allowPassthrough) { 24629 r.hoverData.dragging = true; 24630 r.hoverData.justStartedPan = true; 24631 select[4] = 0; 24632 r.data.bgActivePosistion = array2point(mdownPos); 24633 r.redrawHint('select', true); 24634 r.redraw(); 24635 } 24636 } 24637 24638 if (down && down.pannable() && down.active()) { 24639 down.unactivate(); 24640 } 24641 } 24642 } else { 24643 if (down && down.pannable() && down.active()) { 24644 down.unactivate(); 24645 } 24646 24647 if ((!down || !down.grabbed()) && near != last) { 24648 if (last) { 24649 triggerEvents(last, ['mouseout', 'tapdragout'], e, { 24650 x: pos[0], 24651 y: pos[1] 24652 }); 24653 } 24654 24655 if (near) { 24656 triggerEvents(near, ['mouseover', 'tapdragover'], e, { 24657 x: pos[0], 24658 y: pos[1] 24659 }); 24660 } 24661 24662 r.hoverData.last = near; 24663 } 24664 24665 if (down) { 24666 if (isOverThresholdDrag) { 24667 // then we can take action 24668 if (cy.boxSelectionEnabled() && multSelKeyDown) { 24669 // then selection overrides 24670 if (down && down.grabbed()) { 24671 freeDraggedElements(draggedElements); 24672 down.emit('freeon'); 24673 draggedElements.emit('free'); 24674 24675 if (r.dragData.didDrag) { 24676 down.emit('dragfreeon'); 24677 draggedElements.emit('dragfree'); 24678 } 24679 } 24680 24681 goIntoBoxMode(); 24682 } else if (down && down.grabbed() && r.nodeIsDraggable(down)) { 24683 // drag node 24684 var justStartedDrag = !r.dragData.didDrag; 24685 24686 if (justStartedDrag) { 24687 r.redrawHint('eles', true); 24688 } 24689 24690 r.dragData.didDrag = true; // indicate that we actually did drag the node 24691 24692 var toTrigger = cy.collection(); // now, add the elements to the drag layer if not done already 24693 24694 if (!r.hoverData.draggingEles) { 24695 addNodesToDrag(draggedElements, { 24696 inDragLayer: true 24697 }); 24698 } 24699 24700 var totalShift = { 24701 x: 0, 24702 y: 0 24703 }; 24704 24705 if (number(disp[0]) && number(disp[1])) { 24706 totalShift.x += disp[0]; 24707 totalShift.y += disp[1]; 24708 24709 if (justStartedDrag) { 24710 var dragDelta = r.hoverData.dragDelta; 24711 24712 if (dragDelta && number(dragDelta[0]) && number(dragDelta[1])) { 24713 totalShift.x += dragDelta[0]; 24714 totalShift.y += dragDelta[1]; 24715 } 24716 } 24717 } 24718 24719 for (var i = 0; i < draggedElements.length; i++) { 24720 var dEle = draggedElements[i]; 24721 24722 if (r.nodeIsDraggable(dEle) && dEle.grabbed()) { 24723 toTrigger.merge(dEle); 24724 } 24725 } 24726 24727 r.hoverData.draggingEles = true; 24728 toTrigger.silentShift(totalShift).emit('position drag'); 24729 r.redrawHint('drag', true); 24730 r.redraw(); 24731 } 24732 } else { 24733 // otherwise save drag delta for when we actually start dragging so the relative grab pos is constant 24734 updateDragDelta(); 24735 } 24736 } // prevent the dragging from triggering text selection on the page 24737 24738 24739 preventDefault = true; 24740 } 24741 24742 select[2] = pos[0]; 24743 select[3] = pos[1]; 24744 24745 if (preventDefault) { 24746 if (e.stopPropagation) e.stopPropagation(); 24747 if (e.preventDefault) e.preventDefault(); 24748 return false; 24749 } 24750 }, false); 24751 r.registerBinding(window, 'mouseup', function mouseupHandler(e) { 24752 // eslint-disable-line no-undef 24753 var capture = r.hoverData.capture; 24754 24755 if (!capture) { 24756 return; 24757 } 24758 24759 r.hoverData.capture = false; 24760 var cy = r.cy; 24761 var pos = r.projectIntoViewport(e.clientX, e.clientY); 24762 var select = r.selection; 24763 var near = r.findNearestElement(pos[0], pos[1], true, false); 24764 var draggedElements = r.dragData.possibleDragElements; 24765 var down = r.hoverData.down; 24766 var multSelKeyDown = isMultSelKeyDown(e); 24767 24768 if (r.data.bgActivePosistion) { 24769 r.redrawHint('select', true); 24770 r.redraw(); 24771 } 24772 24773 r.hoverData.tapholdCancelled = true; 24774 r.data.bgActivePosistion = undefined; // not active bg now 24775 24776 if (down) { 24777 down.unactivate(); 24778 } 24779 24780 if (r.hoverData.which === 3) { 24781 var cxtEvt = { 24782 originalEvent: e, 24783 type: 'cxttapend', 24784 position: { 24785 x: pos[0], 24786 y: pos[1] 24787 } 24788 }; 24789 24790 if (down) { 24791 down.emit(cxtEvt); 24792 } else { 24793 cy.emit(cxtEvt); 24794 } 24795 24796 if (!r.hoverData.cxtDragged) { 24797 var cxtTap = { 24798 originalEvent: e, 24799 type: 'cxttap', 24800 position: { 24801 x: pos[0], 24802 y: pos[1] 24803 } 24804 }; 24805 24806 if (down) { 24807 down.emit(cxtTap); 24808 } else { 24809 cy.emit(cxtTap); 24810 } 24811 } 24812 24813 r.hoverData.cxtDragged = false; 24814 r.hoverData.which = null; 24815 } else if (r.hoverData.which === 1) { 24816 triggerEvents(near, ['mouseup', 'tapend', 'vmouseup'], e, { 24817 x: pos[0], 24818 y: pos[1] 24819 }); 24820 24821 if (!r.dragData.didDrag // didn't move a node around 24822 && !r.hoverData.dragged // didn't pan 24823 && !r.hoverData.selecting // not box selection 24824 && !r.hoverData.isOverThresholdDrag // didn't move too much 24825 ) { 24826 triggerEvents(down, ['click', 'tap', 'vclick'], e, { 24827 x: pos[0], 24828 y: pos[1] 24829 }); 24830 } // Deselect all elements if nothing is currently under the mouse cursor and we aren't dragging something 24831 24832 24833 if (down == null && // not mousedown on node 24834 !r.dragData.didDrag // didn't move the node around 24835 && !r.hoverData.selecting // not box selection 24836 && !r.hoverData.dragged // didn't pan 24837 && !isMultSelKeyDown(e)) { 24838 cy.$(isSelected).unselect(['tapunselect']); 24839 24840 if (draggedElements.length > 0) { 24841 r.redrawHint('eles', true); 24842 } 24843 24844 r.dragData.possibleDragElements = draggedElements = cy.collection(); 24845 } // Single selection 24846 24847 24848 if (near == down && !r.dragData.didDrag && !r.hoverData.selecting) { 24849 if (near != null && near._private.selectable) { 24850 if (r.hoverData.dragging) ; else if (cy.selectionType() === 'additive' || multSelKeyDown) { 24851 if (near.selected()) { 24852 near.unselect(['tapunselect']); 24853 } else { 24854 near.select(['tapselect']); 24855 } 24856 } else { 24857 if (!multSelKeyDown) { 24858 cy.$(isSelected).unmerge(near).unselect(['tapunselect']); 24859 near.select(['tapselect']); 24860 } 24861 } 24862 24863 r.redrawHint('eles', true); 24864 } 24865 } 24866 24867 if (r.hoverData.selecting) { 24868 var box = cy.collection(r.getAllInBox(select[0], select[1], select[2], select[3])); 24869 r.redrawHint('select', true); 24870 24871 if (box.length > 0) { 24872 r.redrawHint('eles', true); 24873 } 24874 24875 cy.emit({ 24876 type: 'boxend', 24877 originalEvent: e, 24878 position: { 24879 x: pos[0], 24880 y: pos[1] 24881 } 24882 }); 24883 24884 var eleWouldBeSelected = function eleWouldBeSelected(ele) { 24885 return ele.selectable() && !ele.selected(); 24886 }; 24887 24888 if (cy.selectionType() === 'additive') { 24889 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 24890 } else { 24891 if (!multSelKeyDown) { 24892 cy.$(isSelected).unmerge(box).unselect(); 24893 } 24894 24895 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 24896 } // always need redraw in case eles unselectable 24897 24898 24899 r.redraw(); 24900 } // Cancel drag pan 24901 24902 24903 if (r.hoverData.dragging) { 24904 r.hoverData.dragging = false; 24905 r.redrawHint('select', true); 24906 r.redrawHint('eles', true); 24907 r.redraw(); 24908 } 24909 24910 if (!select[4]) { 24911 r.redrawHint('drag', true); 24912 r.redrawHint('eles', true); 24913 var downWasGrabbed = down && down.grabbed(); 24914 freeDraggedElements(draggedElements); 24915 24916 if (downWasGrabbed) { 24917 down.emit('freeon'); 24918 draggedElements.emit('free'); 24919 24920 if (r.dragData.didDrag) { 24921 down.emit('dragfreeon'); 24922 draggedElements.emit('dragfree'); 24923 } 24924 } 24925 } 24926 } // else not right mouse 24927 24928 24929 select[4] = 0; 24930 r.hoverData.down = null; 24931 r.hoverData.cxtStarted = false; 24932 r.hoverData.draggingEles = false; 24933 r.hoverData.selecting = false; 24934 r.hoverData.isOverThresholdDrag = false; 24935 r.dragData.didDrag = false; 24936 r.hoverData.dragged = false; 24937 r.hoverData.dragDelta = []; 24938 r.hoverData.mdownPos = null; 24939 r.hoverData.mdownGPos = null; 24940 }, false); 24941 24942 var wheelHandler = function wheelHandler(e) { 24943 if (r.scrollingPage) { 24944 return; 24945 } // while scrolling, ignore wheel-to-zoom 24946 24947 24948 var cy = r.cy; 24949 var pos = r.projectIntoViewport(e.clientX, e.clientY); 24950 var rpos = [pos[0] * cy.zoom() + cy.pan().x, pos[1] * cy.zoom() + cy.pan().y]; 24951 24952 if (r.hoverData.draggingEles || r.hoverData.dragging || r.hoverData.cxtStarted || inBoxSelection()) { 24953 // if pan dragging or cxt dragging, wheel movements make no zoom 24954 e.preventDefault(); 24955 return; 24956 } 24957 24958 if (cy.panningEnabled() && cy.userPanningEnabled() && cy.zoomingEnabled() && cy.userZoomingEnabled()) { 24959 e.preventDefault(); 24960 r.data.wheelZooming = true; 24961 clearTimeout(r.data.wheelTimeout); 24962 r.data.wheelTimeout = setTimeout(function () { 24963 r.data.wheelZooming = false; 24964 r.redrawHint('eles', true); 24965 r.redraw(); 24966 }, 150); 24967 var diff; 24968 24969 if (e.deltaY != null) { 24970 diff = e.deltaY / -250; 24971 } else if (e.wheelDeltaY != null) { 24972 diff = e.wheelDeltaY / 1000; 24973 } else { 24974 diff = e.wheelDelta / 1000; 24975 } 24976 24977 diff = diff * r.wheelSensitivity; 24978 var needsWheelFix = e.deltaMode === 1; 24979 24980 if (needsWheelFix) { 24981 // fixes slow wheel events on ff/linux and ff/windows 24982 diff *= 33; 24983 } 24984 24985 cy.zoom({ 24986 level: cy.zoom() * Math.pow(10, diff), 24987 renderedPosition: { 24988 x: rpos[0], 24989 y: rpos[1] 24990 } 24991 }); 24992 } 24993 }; // Functions to help with whether mouse wheel should trigger zooming 24994 // -- 24995 24996 24997 r.registerBinding(r.container, 'wheel', wheelHandler, true); // disable nonstandard wheel events 24998 // r.registerBinding(r.container, 'mousewheel', wheelHandler, true); 24999 // r.registerBinding(r.container, 'DOMMouseScroll', wheelHandler, true); 25000 // r.registerBinding(r.container, 'MozMousePixelScroll', wheelHandler, true); // older firefox 25001 25002 r.registerBinding(window, 'scroll', function scrollHandler(e) { 25003 // eslint-disable-line no-unused-vars 25004 r.scrollingPage = true; 25005 clearTimeout(r.scrollingPageTimeout); 25006 r.scrollingPageTimeout = setTimeout(function () { 25007 r.scrollingPage = false; 25008 }, 250); 25009 }, true); // Functions to help with handling mouseout/mouseover on the Cytoscape container 25010 // Handle mouseout on Cytoscape container 25011 25012 r.registerBinding(r.container, 'mouseout', function mouseOutHandler(e) { 25013 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25014 r.cy.emit({ 25015 originalEvent: e, 25016 type: 'mouseout', 25017 position: { 25018 x: pos[0], 25019 y: pos[1] 25020 } 25021 }); 25022 }, false); 25023 r.registerBinding(r.container, 'mouseover', function mouseOverHandler(e) { 25024 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25025 r.cy.emit({ 25026 originalEvent: e, 25027 type: 'mouseover', 25028 position: { 25029 x: pos[0], 25030 y: pos[1] 25031 } 25032 }); 25033 }, false); 25034 var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom 25035 25036 var distance1, distance1Sq; // initial distance between finger 1 and finger 2 for pinch-to-zoom 25037 25038 var center1, modelCenter1; // center point on start pinch to zoom 25039 25040 var offsetLeft, offsetTop; 25041 var containerWidth, containerHeight; 25042 var twoFingersStartInside; 25043 25044 var distance = function distance(x1, y1, x2, y2) { 25045 return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); 25046 }; 25047 25048 var distanceSq = function distanceSq(x1, y1, x2, y2) { 25049 return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); 25050 }; 25051 25052 var touchstartHandler; 25053 r.registerBinding(r.container, 'touchstart', touchstartHandler = function touchstartHandler(e) { 25054 if (!eventInContainer(e)) { 25055 return; 25056 } 25057 25058 blurActiveDomElement(); 25059 r.touchData.capture = true; 25060 r.data.bgActivePosistion = undefined; 25061 var cy = r.cy; 25062 var now = r.touchData.now; 25063 var earlier = r.touchData.earlier; 25064 25065 if (e.touches[0]) { 25066 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25067 now[0] = pos[0]; 25068 now[1] = pos[1]; 25069 } 25070 25071 if (e.touches[1]) { 25072 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25073 now[2] = pos[0]; 25074 now[3] = pos[1]; 25075 } 25076 25077 if (e.touches[2]) { 25078 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25079 now[4] = pos[0]; 25080 now[5] = pos[1]; 25081 } // record starting points for pinch-to-zoom 25082 25083 25084 if (e.touches[1]) { 25085 r.touchData.singleTouchMoved = true; 25086 freeDraggedElements(r.dragData.touchDragEles); 25087 var offsets = r.findContainerClientCoords(); 25088 offsetLeft = offsets[0]; 25089 offsetTop = offsets[1]; 25090 containerWidth = offsets[2]; 25091 containerHeight = offsets[3]; 25092 f1x1 = e.touches[0].clientX - offsetLeft; 25093 f1y1 = e.touches[0].clientY - offsetTop; 25094 f2x1 = e.touches[1].clientX - offsetLeft; 25095 f2y1 = e.touches[1].clientY - offsetTop; 25096 twoFingersStartInside = 0 <= f1x1 && f1x1 <= containerWidth && 0 <= f2x1 && f2x1 <= containerWidth && 0 <= f1y1 && f1y1 <= containerHeight && 0 <= f2y1 && f2y1 <= containerHeight; 25097 var pan = cy.pan(); 25098 var zoom = cy.zoom(); 25099 distance1 = distance(f1x1, f1y1, f2x1, f2y1); 25100 distance1Sq = distanceSq(f1x1, f1y1, f2x1, f2y1); 25101 center1 = [(f1x1 + f2x1) / 2, (f1y1 + f2y1) / 2]; 25102 modelCenter1 = [(center1[0] - pan.x) / zoom, (center1[1] - pan.y) / zoom]; // consider context tap 25103 25104 var cxtDistThreshold = 200; 25105 var cxtDistThresholdSq = cxtDistThreshold * cxtDistThreshold; 25106 25107 if (distance1Sq < cxtDistThresholdSq && !e.touches[2]) { 25108 var near1 = r.findNearestElement(now[0], now[1], true, true); 25109 var near2 = r.findNearestElement(now[2], now[3], true, true); 25110 25111 if (near1 && near1.isNode()) { 25112 near1.activate().emit({ 25113 originalEvent: e, 25114 type: 'cxttapstart', 25115 position: { 25116 x: now[0], 25117 y: now[1] 25118 } 25119 }); 25120 r.touchData.start = near1; 25121 } else if (near2 && near2.isNode()) { 25122 near2.activate().emit({ 25123 originalEvent: e, 25124 type: 'cxttapstart', 25125 position: { 25126 x: now[0], 25127 y: now[1] 25128 } 25129 }); 25130 r.touchData.start = near2; 25131 } else { 25132 cy.emit({ 25133 originalEvent: e, 25134 type: 'cxttapstart', 25135 position: { 25136 x: now[0], 25137 y: now[1] 25138 } 25139 }); 25140 } 25141 25142 if (r.touchData.start) { 25143 r.touchData.start._private.grabbed = false; 25144 } 25145 25146 r.touchData.cxt = true; 25147 r.touchData.cxtDragged = false; 25148 r.data.bgActivePosistion = undefined; 25149 r.redraw(); 25150 return; 25151 } 25152 } 25153 25154 if (e.touches[2]) { 25155 // ignore 25156 // safari on ios pans the page otherwise (normally you should be able to preventdefault on touchmove...) 25157 if (cy.boxSelectionEnabled()) { 25158 e.preventDefault(); 25159 } 25160 } else if (e.touches[1]) ; else if (e.touches[0]) { 25161 var nears = r.findNearestElements(now[0], now[1], true, true); 25162 var near = nears[0]; 25163 25164 if (near != null) { 25165 near.activate(); 25166 r.touchData.start = near; 25167 r.touchData.starts = nears; 25168 25169 if (r.nodeIsGrabbable(near)) { 25170 var draggedEles = r.dragData.touchDragEles = cy.collection(); 25171 var selectedNodes = null; 25172 r.redrawHint('eles', true); 25173 r.redrawHint('drag', true); 25174 25175 if (near.selected()) { 25176 // reset drag elements, since near will be added again 25177 selectedNodes = cy.$(function (ele) { 25178 return ele.selected() && r.nodeIsGrabbable(ele); 25179 }); 25180 addNodesToDrag(selectedNodes, { 25181 addToList: draggedEles 25182 }); 25183 } else { 25184 addNodeToDrag(near, { 25185 addToList: draggedEles 25186 }); 25187 } 25188 25189 setGrabTarget(near); 25190 25191 var makeEvent = function makeEvent(type) { 25192 return { 25193 originalEvent: e, 25194 type: type, 25195 position: { 25196 x: now[0], 25197 y: now[1] 25198 } 25199 }; 25200 }; 25201 25202 near.emit(makeEvent('grabon')); 25203 25204 if (selectedNodes) { 25205 selectedNodes.forEach(function (n) { 25206 n.emit(makeEvent('grab')); 25207 }); 25208 } else { 25209 near.emit(makeEvent('grab')); 25210 } 25211 } 25212 } 25213 25214 triggerEvents(near, ['touchstart', 'tapstart', 'vmousedown'], e, { 25215 x: now[0], 25216 y: now[1] 25217 }); 25218 25219 if (near == null) { 25220 r.data.bgActivePosistion = { 25221 x: pos[0], 25222 y: pos[1] 25223 }; 25224 r.redrawHint('select', true); 25225 r.redraw(); 25226 } // Tap, taphold 25227 // ----- 25228 25229 25230 r.touchData.singleTouchMoved = false; 25231 r.touchData.singleTouchStartTime = +new Date(); 25232 clearTimeout(r.touchData.tapholdTimeout); 25233 r.touchData.tapholdTimeout = setTimeout(function () { 25234 if (r.touchData.singleTouchMoved === false && !r.pinching // if pinching, then taphold unselect shouldn't take effect 25235 && !r.touchData.selecting // box selection shouldn't allow taphold through 25236 ) { 25237 triggerEvents(r.touchData.start, ['taphold'], e, { 25238 x: now[0], 25239 y: now[1] 25240 }); 25241 } 25242 }, r.tapholdDuration); 25243 } 25244 25245 if (e.touches.length >= 1) { 25246 var sPos = r.touchData.startPosition = []; 25247 25248 for (var i = 0; i < now.length; i++) { 25249 sPos[i] = earlier[i] = now[i]; 25250 } 25251 25252 var touch0 = e.touches[0]; 25253 r.touchData.startGPosition = [touch0.clientX, touch0.clientY]; 25254 } 25255 }, false); 25256 var touchmoveHandler; 25257 r.registerBinding(window, 'touchmove', touchmoveHandler = function touchmoveHandler(e) { 25258 // eslint-disable-line no-undef 25259 var capture = r.touchData.capture; 25260 25261 if (!capture && !eventInContainer(e)) { 25262 return; 25263 } 25264 25265 var select = r.selection; 25266 var cy = r.cy; 25267 var now = r.touchData.now; 25268 var earlier = r.touchData.earlier; 25269 var zoom = cy.zoom(); 25270 25271 if (e.touches[0]) { 25272 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25273 now[0] = pos[0]; 25274 now[1] = pos[1]; 25275 } 25276 25277 if (e.touches[1]) { 25278 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25279 now[2] = pos[0]; 25280 now[3] = pos[1]; 25281 } 25282 25283 if (e.touches[2]) { 25284 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25285 now[4] = pos[0]; 25286 now[5] = pos[1]; 25287 } 25288 25289 var startGPos = r.touchData.startGPosition; 25290 var isOverThresholdDrag; 25291 25292 if (capture && e.touches[0] && startGPos) { 25293 var disp = []; 25294 25295 for (var j = 0; j < now.length; j++) { 25296 disp[j] = now[j] - earlier[j]; 25297 } 25298 25299 var dx = e.touches[0].clientX - startGPos[0]; 25300 var dx2 = dx * dx; 25301 var dy = e.touches[0].clientY - startGPos[1]; 25302 var dy2 = dy * dy; 25303 var dist2 = dx2 + dy2; 25304 isOverThresholdDrag = dist2 >= r.touchTapThreshold2; 25305 } // context swipe cancelling 25306 25307 25308 if (capture && r.touchData.cxt) { 25309 e.preventDefault(); 25310 var f1x2 = e.touches[0].clientX - offsetLeft, 25311 f1y2 = e.touches[0].clientY - offsetTop; 25312 var f2x2 = e.touches[1].clientX - offsetLeft, 25313 f2y2 = e.touches[1].clientY - offsetTop; // var distance2 = distance( f1x2, f1y2, f2x2, f2y2 ); 25314 25315 var distance2Sq = distanceSq(f1x2, f1y2, f2x2, f2y2); 25316 var factorSq = distance2Sq / distance1Sq; 25317 var distThreshold = 150; 25318 var distThresholdSq = distThreshold * distThreshold; 25319 var factorThreshold = 1.5; 25320 var factorThresholdSq = factorThreshold * factorThreshold; // cancel ctx gestures if the distance b/t the fingers increases 25321 25322 if (factorSq >= factorThresholdSq || distance2Sq >= distThresholdSq) { 25323 r.touchData.cxt = false; 25324 r.data.bgActivePosistion = undefined; 25325 r.redrawHint('select', true); 25326 var cxtEvt = { 25327 originalEvent: e, 25328 type: 'cxttapend', 25329 position: { 25330 x: now[0], 25331 y: now[1] 25332 } 25333 }; 25334 25335 if (r.touchData.start) { 25336 r.touchData.start.unactivate().emit(cxtEvt); 25337 r.touchData.start = null; 25338 } else { 25339 cy.emit(cxtEvt); 25340 } 25341 } 25342 } // context swipe 25343 25344 25345 if (capture && r.touchData.cxt) { 25346 var cxtEvt = { 25347 originalEvent: e, 25348 type: 'cxtdrag', 25349 position: { 25350 x: now[0], 25351 y: now[1] 25352 } 25353 }; 25354 r.data.bgActivePosistion = undefined; 25355 r.redrawHint('select', true); 25356 25357 if (r.touchData.start) { 25358 r.touchData.start.emit(cxtEvt); 25359 } else { 25360 cy.emit(cxtEvt); 25361 } 25362 25363 if (r.touchData.start) { 25364 r.touchData.start._private.grabbed = false; 25365 } 25366 25367 r.touchData.cxtDragged = true; 25368 var near = r.findNearestElement(now[0], now[1], true, true); 25369 25370 if (!r.touchData.cxtOver || near !== r.touchData.cxtOver) { 25371 if (r.touchData.cxtOver) { 25372 r.touchData.cxtOver.emit({ 25373 originalEvent: e, 25374 type: 'cxtdragout', 25375 position: { 25376 x: now[0], 25377 y: now[1] 25378 } 25379 }); 25380 } 25381 25382 r.touchData.cxtOver = near; 25383 25384 if (near) { 25385 near.emit({ 25386 originalEvent: e, 25387 type: 'cxtdragover', 25388 position: { 25389 x: now[0], 25390 y: now[1] 25391 } 25392 }); 25393 } 25394 } // box selection 25395 25396 } else if (capture && e.touches[2] && cy.boxSelectionEnabled()) { 25397 e.preventDefault(); 25398 r.data.bgActivePosistion = undefined; 25399 this.lastThreeTouch = +new Date(); 25400 25401 if (!r.touchData.selecting) { 25402 cy.emit({ 25403 originalEvent: e, 25404 type: 'boxstart', 25405 position: { 25406 x: now[0], 25407 y: now[1] 25408 } 25409 }); 25410 } 25411 25412 r.touchData.selecting = true; 25413 r.touchData.didSelect = true; 25414 select[4] = 1; 25415 25416 if (!select || select.length === 0 || select[0] === undefined) { 25417 select[0] = (now[0] + now[2] + now[4]) / 3; 25418 select[1] = (now[1] + now[3] + now[5]) / 3; 25419 select[2] = (now[0] + now[2] + now[4]) / 3 + 1; 25420 select[3] = (now[1] + now[3] + now[5]) / 3 + 1; 25421 } else { 25422 select[2] = (now[0] + now[2] + now[4]) / 3; 25423 select[3] = (now[1] + now[3] + now[5]) / 3; 25424 } 25425 25426 r.redrawHint('select', true); 25427 r.redraw(); // pinch to zoom 25428 } else if (capture && e.touches[1] && !r.touchData.didSelect // don't allow box selection to degrade to pinch-to-zoom 25429 && cy.zoomingEnabled() && cy.panningEnabled() && cy.userZoomingEnabled() && cy.userPanningEnabled()) { 25430 // two fingers => pinch to zoom 25431 e.preventDefault(); 25432 r.data.bgActivePosistion = undefined; 25433 r.redrawHint('select', true); 25434 var draggedEles = r.dragData.touchDragEles; 25435 25436 if (draggedEles) { 25437 r.redrawHint('drag', true); 25438 25439 for (var i = 0; i < draggedEles.length; i++) { 25440 var de_p = draggedEles[i]._private; 25441 de_p.grabbed = false; 25442 de_p.rscratch.inDragLayer = false; 25443 } 25444 } 25445 25446 var _start = r.touchData.start; // (x2, y2) for fingers 1 and 2 25447 25448 var f1x2 = e.touches[0].clientX - offsetLeft, 25449 f1y2 = e.touches[0].clientY - offsetTop; 25450 var f2x2 = e.touches[1].clientX - offsetLeft, 25451 f2y2 = e.touches[1].clientY - offsetTop; 25452 var distance2 = distance(f1x2, f1y2, f2x2, f2y2); // var distance2Sq = distanceSq( f1x2, f1y2, f2x2, f2y2 ); 25453 // var factor = Math.sqrt( distance2Sq ) / Math.sqrt( distance1Sq ); 25454 25455 var factor = distance2 / distance1; 25456 25457 if (twoFingersStartInside) { 25458 // delta finger1 25459 var df1x = f1x2 - f1x1; 25460 var df1y = f1y2 - f1y1; // delta finger 2 25461 25462 var df2x = f2x2 - f2x1; 25463 var df2y = f2y2 - f2y1; // translation is the normalised vector of the two fingers movement 25464 // i.e. so pinching cancels out and moving together pans 25465 25466 var tx = (df1x + df2x) / 2; 25467 var ty = (df1y + df2y) / 2; // now calculate the zoom 25468 25469 var zoom1 = cy.zoom(); 25470 var zoom2 = zoom1 * factor; 25471 var pan1 = cy.pan(); // the model center point converted to the current rendered pos 25472 25473 var ctrx = modelCenter1[0] * zoom1 + pan1.x; 25474 var ctry = modelCenter1[1] * zoom1 + pan1.y; 25475 var pan2 = { 25476 x: -zoom2 / zoom1 * (ctrx - pan1.x - tx) + ctrx, 25477 y: -zoom2 / zoom1 * (ctry - pan1.y - ty) + ctry 25478 }; // remove dragged eles 25479 25480 if (_start && _start.active()) { 25481 var draggedEles = r.dragData.touchDragEles; 25482 freeDraggedElements(draggedEles); 25483 r.redrawHint('drag', true); 25484 r.redrawHint('eles', true); 25485 25486 _start.unactivate().emit('freeon'); 25487 25488 draggedEles.emit('free'); 25489 25490 if (r.dragData.didDrag) { 25491 _start.emit('dragfreeon'); 25492 25493 draggedEles.emit('dragfree'); 25494 } 25495 } 25496 25497 cy.viewport({ 25498 zoom: zoom2, 25499 pan: pan2, 25500 cancelOnFailedZoom: true 25501 }); 25502 distance1 = distance2; 25503 f1x1 = f1x2; 25504 f1y1 = f1y2; 25505 f2x1 = f2x2; 25506 f2y1 = f2y2; 25507 r.pinching = true; 25508 } // Re-project 25509 25510 25511 if (e.touches[0]) { 25512 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25513 now[0] = pos[0]; 25514 now[1] = pos[1]; 25515 } 25516 25517 if (e.touches[1]) { 25518 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25519 now[2] = pos[0]; 25520 now[3] = pos[1]; 25521 } 25522 25523 if (e.touches[2]) { 25524 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25525 now[4] = pos[0]; 25526 now[5] = pos[1]; 25527 } 25528 } else if (e.touches[0] && !r.touchData.didSelect // don't allow box selection to degrade to single finger events like panning 25529 ) { 25530 var start = r.touchData.start; 25531 var last = r.touchData.last; 25532 var near; 25533 25534 if (!r.hoverData.draggingEles && !r.swipePanning) { 25535 near = r.findNearestElement(now[0], now[1], true, true); 25536 } 25537 25538 if (capture && start != null) { 25539 e.preventDefault(); 25540 } // dragging nodes 25541 25542 25543 if (capture && start != null && r.nodeIsDraggable(start)) { 25544 if (isOverThresholdDrag) { 25545 // then dragging can happen 25546 var draggedEles = r.dragData.touchDragEles; 25547 var justStartedDrag = !r.dragData.didDrag; 25548 25549 if (justStartedDrag) { 25550 addNodesToDrag(draggedEles, { 25551 inDragLayer: true 25552 }); 25553 } 25554 25555 r.dragData.didDrag = true; 25556 var totalShift = { 25557 x: 0, 25558 y: 0 25559 }; 25560 25561 if (number(disp[0]) && number(disp[1])) { 25562 totalShift.x += disp[0]; 25563 totalShift.y += disp[1]; 25564 25565 if (justStartedDrag) { 25566 r.redrawHint('eles', true); 25567 var dragDelta = r.touchData.dragDelta; 25568 25569 if (dragDelta && number(dragDelta[0]) && number(dragDelta[1])) { 25570 totalShift.x += dragDelta[0]; 25571 totalShift.y += dragDelta[1]; 25572 } 25573 } 25574 } 25575 25576 r.hoverData.draggingEles = true; 25577 draggedEles.silentShift(totalShift).emit('position drag'); 25578 r.redrawHint('drag', true); 25579 25580 if (r.touchData.startPosition[0] == earlier[0] && r.touchData.startPosition[1] == earlier[1]) { 25581 r.redrawHint('eles', true); 25582 } 25583 25584 r.redraw(); 25585 } else { 25586 // otherise keep track of drag delta for later 25587 var dragDelta = r.touchData.dragDelta = r.touchData.dragDelta || []; 25588 25589 if (dragDelta.length === 0) { 25590 dragDelta.push(disp[0]); 25591 dragDelta.push(disp[1]); 25592 } else { 25593 dragDelta[0] += disp[0]; 25594 dragDelta[1] += disp[1]; 25595 } 25596 } 25597 } // touchmove 25598 25599 25600 { 25601 triggerEvents(start || near, ['touchmove', 'tapdrag', 'vmousemove'], e, { 25602 x: now[0], 25603 y: now[1] 25604 }); 25605 25606 if ((!start || !start.grabbed()) && near != last) { 25607 if (last) { 25608 last.emit({ 25609 originalEvent: e, 25610 type: 'tapdragout', 25611 position: { 25612 x: now[0], 25613 y: now[1] 25614 } 25615 }); 25616 } 25617 25618 if (near) { 25619 near.emit({ 25620 originalEvent: e, 25621 type: 'tapdragover', 25622 position: { 25623 x: now[0], 25624 y: now[1] 25625 } 25626 }); 25627 } 25628 } 25629 25630 r.touchData.last = near; 25631 } // check to cancel taphold 25632 25633 if (capture) { 25634 for (var i = 0; i < now.length; i++) { 25635 if (now[i] && r.touchData.startPosition[i] && isOverThresholdDrag) { 25636 r.touchData.singleTouchMoved = true; 25637 } 25638 } 25639 } // panning 25640 25641 25642 if (capture && (start == null || start.pannable()) && cy.panningEnabled() && cy.userPanningEnabled()) { 25643 var allowPassthrough = allowPanningPassthrough(start, r.touchData.starts); 25644 25645 if (allowPassthrough) { 25646 e.preventDefault(); 25647 25648 if (!r.data.bgActivePosistion) { 25649 r.data.bgActivePosistion = array2point(r.touchData.startPosition); 25650 } 25651 25652 if (r.swipePanning) { 25653 cy.panBy({ 25654 x: disp[0] * zoom, 25655 y: disp[1] * zoom 25656 }); 25657 } else if (isOverThresholdDrag) { 25658 r.swipePanning = true; 25659 cy.panBy({ 25660 x: dx * zoom, 25661 y: dy * zoom 25662 }); 25663 25664 if (start) { 25665 start.unactivate(); 25666 r.redrawHint('select', true); 25667 r.touchData.start = null; 25668 } 25669 } 25670 } // Re-project 25671 25672 25673 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25674 now[0] = pos[0]; 25675 now[1] = pos[1]; 25676 } 25677 } 25678 25679 for (var j = 0; j < now.length; j++) { 25680 earlier[j] = now[j]; 25681 } // the active bg indicator should be removed when making a swipe that is neither for dragging nodes or panning 25682 25683 25684 if (capture && e.touches.length > 0 && !r.hoverData.draggingEles && !r.swipePanning && r.data.bgActivePosistion != null) { 25685 r.data.bgActivePosistion = undefined; 25686 r.redrawHint('select', true); 25687 r.redraw(); 25688 } 25689 }, false); 25690 var touchcancelHandler; 25691 r.registerBinding(window, 'touchcancel', touchcancelHandler = function touchcancelHandler(e) { 25692 // eslint-disable-line no-unused-vars 25693 var start = r.touchData.start; 25694 r.touchData.capture = false; 25695 25696 if (start) { 25697 start.unactivate(); 25698 } 25699 }); 25700 var touchendHandler; 25701 r.registerBinding(window, 'touchend', touchendHandler = function touchendHandler(e) { 25702 // eslint-disable-line no-unused-vars 25703 var start = r.touchData.start; 25704 var capture = r.touchData.capture; 25705 25706 if (capture) { 25707 if (e.touches.length === 0) { 25708 r.touchData.capture = false; 25709 } 25710 25711 e.preventDefault(); 25712 } else { 25713 return; 25714 } 25715 25716 var select = r.selection; 25717 r.swipePanning = false; 25718 r.hoverData.draggingEles = false; 25719 var cy = r.cy; 25720 var zoom = cy.zoom(); 25721 var now = r.touchData.now; 25722 var earlier = r.touchData.earlier; 25723 25724 if (e.touches[0]) { 25725 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25726 now[0] = pos[0]; 25727 now[1] = pos[1]; 25728 } 25729 25730 if (e.touches[1]) { 25731 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25732 now[2] = pos[0]; 25733 now[3] = pos[1]; 25734 } 25735 25736 if (e.touches[2]) { 25737 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25738 now[4] = pos[0]; 25739 now[5] = pos[1]; 25740 } 25741 25742 if (start) { 25743 start.unactivate(); 25744 } 25745 25746 var ctxTapend; 25747 25748 if (r.touchData.cxt) { 25749 ctxTapend = { 25750 originalEvent: e, 25751 type: 'cxttapend', 25752 position: { 25753 x: now[0], 25754 y: now[1] 25755 } 25756 }; 25757 25758 if (start) { 25759 start.emit(ctxTapend); 25760 } else { 25761 cy.emit(ctxTapend); 25762 } 25763 25764 if (!r.touchData.cxtDragged) { 25765 var ctxTap = { 25766 originalEvent: e, 25767 type: 'cxttap', 25768 position: { 25769 x: now[0], 25770 y: now[1] 25771 } 25772 }; 25773 25774 if (start) { 25775 start.emit(ctxTap); 25776 } else { 25777 cy.emit(ctxTap); 25778 } 25779 } 25780 25781 if (r.touchData.start) { 25782 r.touchData.start._private.grabbed = false; 25783 } 25784 25785 r.touchData.cxt = false; 25786 r.touchData.start = null; 25787 r.redraw(); 25788 return; 25789 } // no more box selection if we don't have three fingers 25790 25791 25792 if (!e.touches[2] && cy.boxSelectionEnabled() && r.touchData.selecting) { 25793 r.touchData.selecting = false; 25794 var box = cy.collection(r.getAllInBox(select[0], select[1], select[2], select[3])); 25795 select[0] = undefined; 25796 select[1] = undefined; 25797 select[2] = undefined; 25798 select[3] = undefined; 25799 select[4] = 0; 25800 r.redrawHint('select', true); 25801 cy.emit({ 25802 type: 'boxend', 25803 originalEvent: e, 25804 position: { 25805 x: now[0], 25806 y: now[1] 25807 } 25808 }); 25809 25810 var eleWouldBeSelected = function eleWouldBeSelected(ele) { 25811 return ele.selectable() && !ele.selected(); 25812 }; 25813 25814 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 25815 25816 if (box.nonempty()) { 25817 r.redrawHint('eles', true); 25818 } 25819 25820 r.redraw(); 25821 } 25822 25823 if (start != null) { 25824 start.unactivate(); 25825 } 25826 25827 if (e.touches[2]) { 25828 r.data.bgActivePosistion = undefined; 25829 r.redrawHint('select', true); 25830 } else if (e.touches[1]) ; else if (e.touches[0]) ; else if (!e.touches[0]) { 25831 r.data.bgActivePosistion = undefined; 25832 r.redrawHint('select', true); 25833 var draggedEles = r.dragData.touchDragEles; 25834 25835 if (start != null) { 25836 var startWasGrabbed = start._private.grabbed; 25837 freeDraggedElements(draggedEles); 25838 r.redrawHint('drag', true); 25839 r.redrawHint('eles', true); 25840 25841 if (startWasGrabbed) { 25842 start.emit('freeon'); 25843 draggedEles.emit('free'); 25844 25845 if (r.dragData.didDrag) { 25846 start.emit('dragfreeon'); 25847 draggedEles.emit('dragfree'); 25848 } 25849 } 25850 25851 triggerEvents(start, ['touchend', 'tapend', 'vmouseup', 'tapdragout'], e, { 25852 x: now[0], 25853 y: now[1] 25854 }); 25855 start.unactivate(); 25856 r.touchData.start = null; 25857 } else { 25858 var near = r.findNearestElement(now[0], now[1], true, true); 25859 triggerEvents(near, ['touchend', 'tapend', 'vmouseup', 'tapdragout'], e, { 25860 x: now[0], 25861 y: now[1] 25862 }); 25863 } 25864 25865 var dx = r.touchData.startPosition[0] - now[0]; 25866 var dx2 = dx * dx; 25867 var dy = r.touchData.startPosition[1] - now[1]; 25868 var dy2 = dy * dy; 25869 var dist2 = dx2 + dy2; 25870 var rdist2 = dist2 * zoom * zoom; // Tap event, roughly same as mouse click event for touch 25871 25872 if (!r.touchData.singleTouchMoved) { 25873 if (!start) { 25874 cy.$(':selected').unselect(['tapunselect']); 25875 } 25876 25877 triggerEvents(start, ['tap', 'vclick'], e, { 25878 x: now[0], 25879 y: now[1] 25880 }); 25881 } // Prepare to select the currently touched node, only if it hasn't been dragged past a certain distance 25882 25883 25884 if (start != null && !r.dragData.didDrag // didn't drag nodes around 25885 && start._private.selectable && rdist2 < r.touchTapThreshold2 && !r.pinching // pinch to zoom should not affect selection 25886 ) { 25887 if (cy.selectionType() === 'single') { 25888 cy.$(isSelected).unmerge(start).unselect(['tapunselect']); 25889 start.select(['tapselect']); 25890 } else { 25891 if (start.selected()) { 25892 start.unselect(['tapunselect']); 25893 } else { 25894 start.select(['tapselect']); 25895 } 25896 } 25897 25898 r.redrawHint('eles', true); 25899 } 25900 25901 r.touchData.singleTouchMoved = true; 25902 } 25903 25904 for (var j = 0; j < now.length; j++) { 25905 earlier[j] = now[j]; 25906 } 25907 25908 r.dragData.didDrag = false; // reset for next touchstart 25909 25910 if (e.touches.length === 0) { 25911 r.touchData.dragDelta = []; 25912 r.touchData.startPosition = null; 25913 r.touchData.startGPosition = null; 25914 r.touchData.didSelect = false; 25915 } 25916 25917 if (e.touches.length < 2) { 25918 if (e.touches.length === 1) { 25919 // the old start global pos'n may not be the same finger that remains 25920 r.touchData.startGPosition = [e.touches[0].clientX, e.touches[0].clientY]; 25921 } 25922 25923 r.pinching = false; 25924 r.redrawHint('eles', true); 25925 r.redraw(); 25926 } //r.redraw(); 25927 25928 }, false); // fallback compatibility layer for ms pointer events 25929 25930 if (typeof TouchEvent === 'undefined') { 25931 var pointers = []; 25932 25933 var makeTouch = function makeTouch(e) { 25934 return { 25935 clientX: e.clientX, 25936 clientY: e.clientY, 25937 force: 1, 25938 identifier: e.pointerId, 25939 pageX: e.pageX, 25940 pageY: e.pageY, 25941 radiusX: e.width / 2, 25942 radiusY: e.height / 2, 25943 screenX: e.screenX, 25944 screenY: e.screenY, 25945 target: e.target 25946 }; 25947 }; 25948 25949 var makePointer = function makePointer(e) { 25950 return { 25951 event: e, 25952 touch: makeTouch(e) 25953 }; 25954 }; 25955 25956 var addPointer = function addPointer(e) { 25957 pointers.push(makePointer(e)); 25958 }; 25959 25960 var removePointer = function removePointer(e) { 25961 for (var i = 0; i < pointers.length; i++) { 25962 var p = pointers[i]; 25963 25964 if (p.event.pointerId === e.pointerId) { 25965 pointers.splice(i, 1); 25966 return; 25967 } 25968 } 25969 }; 25970 25971 var updatePointer = function updatePointer(e) { 25972 var p = pointers.filter(function (p) { 25973 return p.event.pointerId === e.pointerId; 25974 })[0]; 25975 p.event = e; 25976 p.touch = makeTouch(e); 25977 }; 25978 25979 var addTouchesToEvent = function addTouchesToEvent(e) { 25980 e.touches = pointers.map(function (p) { 25981 return p.touch; 25982 }); 25983 }; 25984 25985 var pointerIsMouse = function pointerIsMouse(e) { 25986 return e.pointerType === 'mouse' || e.pointerType === 4; 25987 }; 25988 25989 r.registerBinding(r.container, 'pointerdown', function (e) { 25990 if (pointerIsMouse(e)) { 25991 return; 25992 } // mouse already handled 25993 25994 25995 e.preventDefault(); 25996 addPointer(e); 25997 addTouchesToEvent(e); 25998 touchstartHandler(e); 25999 }); 26000 r.registerBinding(r.container, 'pointerup', function (e) { 26001 if (pointerIsMouse(e)) { 26002 return; 26003 } // mouse already handled 26004 26005 26006 removePointer(e); 26007 addTouchesToEvent(e); 26008 touchendHandler(e); 26009 }); 26010 r.registerBinding(r.container, 'pointercancel', function (e) { 26011 if (pointerIsMouse(e)) { 26012 return; 26013 } // mouse already handled 26014 26015 26016 removePointer(e); 26017 addTouchesToEvent(e); 26018 touchcancelHandler(e); 26019 }); 26020 r.registerBinding(r.container, 'pointermove', function (e) { 26021 if (pointerIsMouse(e)) { 26022 return; 26023 } // mouse already handled 26024 26025 26026 e.preventDefault(); 26027 updatePointer(e); 26028 addTouchesToEvent(e); 26029 touchmoveHandler(e); 26030 }); 26031 } 26032 }; 26033 26034 var BRp$d = {}; 26035 26036 BRp$d.generatePolygon = function (name, points) { 26037 return this.nodeShapes[name] = { 26038 renderer: this, 26039 name: name, 26040 points: points, 26041 draw: function draw(context, centerX, centerY, width, height) { 26042 this.renderer.nodeShapeImpl('polygon', context, centerX, centerY, width, height, this.points); 26043 }, 26044 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26045 return polygonIntersectLine(x, y, this.points, nodeX, nodeY, width / 2, height / 2, padding); 26046 }, 26047 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26048 return pointInsidePolygon(x, y, this.points, centerX, centerY, width, height, [0, -1], padding); 26049 } 26050 }; 26051 }; 26052 26053 BRp$d.generateEllipse = function () { 26054 return this.nodeShapes['ellipse'] = { 26055 renderer: this, 26056 name: 'ellipse', 26057 draw: function draw(context, centerX, centerY, width, height) { 26058 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26059 }, 26060 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26061 return intersectLineEllipse(x, y, nodeX, nodeY, width / 2 + padding, height / 2 + padding); 26062 }, 26063 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26064 return checkInEllipse(x, y, width, height, centerX, centerY, padding); 26065 } 26066 }; 26067 }; 26068 26069 BRp$d.generateRoundPolygon = function (name, points) { 26070 // Pre-compute control points 26071 // Since these points depend on the radius length (which in turns depend on the width/height of the node) we will only pre-compute 26072 // the unit vectors. 26073 // For simplicity the layout will be: 26074 // [ p0, UnitVectorP0P1, p1, UniVectorP1P2, ..., pn, UnitVectorPnP0 ] 26075 var allPoints = new Array(points.length * 2); 26076 26077 for (var i = 0; i < points.length / 2; i++) { 26078 var sourceIndex = i * 2; 26079 var destIndex = void 0; 26080 26081 if (i < points.length / 2 - 1) { 26082 destIndex = (i + 1) * 2; 26083 } else { 26084 destIndex = 0; 26085 } 26086 26087 allPoints[i * 4] = points[sourceIndex]; 26088 allPoints[i * 4 + 1] = points[sourceIndex + 1]; 26089 var xDest = points[destIndex] - points[sourceIndex]; 26090 var yDest = points[destIndex + 1] - points[sourceIndex + 1]; 26091 var norm = Math.sqrt(xDest * xDest + yDest * yDest); 26092 allPoints[i * 4 + 2] = xDest / norm; 26093 allPoints[i * 4 + 3] = yDest / norm; 26094 } 26095 26096 return this.nodeShapes[name] = { 26097 renderer: this, 26098 name: name, 26099 points: allPoints, 26100 draw: function draw(context, centerX, centerY, width, height) { 26101 this.renderer.nodeShapeImpl('round-polygon', context, centerX, centerY, width, height, this.points); 26102 }, 26103 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26104 return roundPolygonIntersectLine(x, y, this.points, nodeX, nodeY, width, height); 26105 }, 26106 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26107 return pointInsideRoundPolygon(x, y, this.points, centerX, centerY, width, height); 26108 } 26109 }; 26110 }; 26111 26112 BRp$d.generateRoundRectangle = function () { 26113 return this.nodeShapes['round-rectangle'] = this.nodeShapes['roundrectangle'] = { 26114 renderer: this, 26115 name: 'round-rectangle', 26116 points: generateUnitNgonPointsFitToSquare(4, 0), 26117 draw: function draw(context, centerX, centerY, width, height) { 26118 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26119 }, 26120 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26121 return roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding); 26122 }, 26123 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26124 var cornerRadius = getRoundRectangleRadius(width, height); 26125 var diam = cornerRadius * 2; // Check hBox 26126 26127 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - diam, [0, -1], padding)) { 26128 return true; 26129 } // Check vBox 26130 26131 26132 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - diam, height, [0, -1], padding)) { 26133 return true; 26134 } // Check top left quarter circle 26135 26136 26137 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY - height / 2 + cornerRadius, padding)) { 26138 return true; 26139 } // Check top right quarter circle 26140 26141 26142 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY - height / 2 + cornerRadius, padding)) { 26143 return true; 26144 } // Check bottom right quarter circle 26145 26146 26147 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26148 return true; 26149 } // Check bottom left quarter circle 26150 26151 26152 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26153 return true; 26154 } 26155 26156 return false; 26157 } 26158 }; 26159 }; 26160 26161 BRp$d.generateCutRectangle = function () { 26162 return this.nodeShapes['cut-rectangle'] = this.nodeShapes['cutrectangle'] = { 26163 renderer: this, 26164 name: 'cut-rectangle', 26165 cornerLength: getCutRectangleCornerLength(), 26166 points: generateUnitNgonPointsFitToSquare(4, 0), 26167 draw: function draw(context, centerX, centerY, width, height) { 26168 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26169 }, 26170 generateCutTrianglePts: function generateCutTrianglePts(width, height, centerX, centerY) { 26171 var cl = this.cornerLength; 26172 var hh = height / 2; 26173 var hw = width / 2; 26174 var xBegin = centerX - hw; 26175 var xEnd = centerX + hw; 26176 var yBegin = centerY - hh; 26177 var yEnd = centerY + hh; // points are in clockwise order, inner (imaginary) triangle pt on [4, 5] 26178 26179 return { 26180 topLeft: [xBegin, yBegin + cl, xBegin + cl, yBegin, xBegin + cl, yBegin + cl], 26181 topRight: [xEnd - cl, yBegin, xEnd, yBegin + cl, xEnd - cl, yBegin + cl], 26182 bottomRight: [xEnd, yEnd - cl, xEnd - cl, yEnd, xEnd - cl, yEnd - cl], 26183 bottomLeft: [xBegin + cl, yEnd, xBegin, yEnd - cl, xBegin + cl, yEnd - cl] 26184 }; 26185 }, 26186 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26187 var cPts = this.generateCutTrianglePts(width + 2 * padding, height + 2 * padding, nodeX, nodeY); 26188 var pts = [].concat.apply([], [cPts.topLeft.splice(0, 4), cPts.topRight.splice(0, 4), cPts.bottomRight.splice(0, 4), cPts.bottomLeft.splice(0, 4)]); 26189 return polygonIntersectLine(x, y, pts, nodeX, nodeY); 26190 }, 26191 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26192 // Check hBox 26193 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - 2 * this.cornerLength, [0, -1], padding)) { 26194 return true; 26195 } // Check vBox 26196 26197 26198 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - 2 * this.cornerLength, height, [0, -1], padding)) { 26199 return true; 26200 } 26201 26202 var cutTrianglePts = this.generateCutTrianglePts(width, height, centerX, centerY); 26203 return pointInsidePolygonPoints(x, y, cutTrianglePts.topLeft) || pointInsidePolygonPoints(x, y, cutTrianglePts.topRight) || pointInsidePolygonPoints(x, y, cutTrianglePts.bottomRight) || pointInsidePolygonPoints(x, y, cutTrianglePts.bottomLeft); 26204 } 26205 }; 26206 }; 26207 26208 BRp$d.generateBarrel = function () { 26209 return this.nodeShapes['barrel'] = { 26210 renderer: this, 26211 name: 'barrel', 26212 points: generateUnitNgonPointsFitToSquare(4, 0), 26213 draw: function draw(context, centerX, centerY, width, height) { 26214 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26215 }, 26216 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26217 // use two fixed t values for the bezier curve approximation 26218 var t0 = 0.15; 26219 var t1 = 0.5; 26220 var t2 = 0.85; 26221 var bPts = this.generateBarrelBezierPts(width + 2 * padding, height + 2 * padding, nodeX, nodeY); 26222 26223 var approximateBarrelCurvePts = function approximateBarrelCurvePts(pts) { 26224 // approximate curve pts based on the two t values 26225 var m0 = qbezierPtAt({ 26226 x: pts[0], 26227 y: pts[1] 26228 }, { 26229 x: pts[2], 26230 y: pts[3] 26231 }, { 26232 x: pts[4], 26233 y: pts[5] 26234 }, t0); 26235 var m1 = qbezierPtAt({ 26236 x: pts[0], 26237 y: pts[1] 26238 }, { 26239 x: pts[2], 26240 y: pts[3] 26241 }, { 26242 x: pts[4], 26243 y: pts[5] 26244 }, t1); 26245 var m2 = qbezierPtAt({ 26246 x: pts[0], 26247 y: pts[1] 26248 }, { 26249 x: pts[2], 26250 y: pts[3] 26251 }, { 26252 x: pts[4], 26253 y: pts[5] 26254 }, t2); 26255 return [pts[0], pts[1], m0.x, m0.y, m1.x, m1.y, m2.x, m2.y, pts[4], pts[5]]; 26256 }; 26257 26258 var pts = [].concat(approximateBarrelCurvePts(bPts.topLeft), approximateBarrelCurvePts(bPts.topRight), approximateBarrelCurvePts(bPts.bottomRight), approximateBarrelCurvePts(bPts.bottomLeft)); 26259 return polygonIntersectLine(x, y, pts, nodeX, nodeY); 26260 }, 26261 generateBarrelBezierPts: function generateBarrelBezierPts(width, height, centerX, centerY) { 26262 var hh = height / 2; 26263 var hw = width / 2; 26264 var xBegin = centerX - hw; 26265 var xEnd = centerX + hw; 26266 var yBegin = centerY - hh; 26267 var yEnd = centerY + hh; 26268 var curveConstants = getBarrelCurveConstants(width, height); 26269 var hOffset = curveConstants.heightOffset; 26270 var wOffset = curveConstants.widthOffset; 26271 var ctrlPtXOffset = curveConstants.ctrlPtOffsetPct * width; // points are in clockwise order, inner (imaginary) control pt on [4, 5] 26272 26273 var pts = { 26274 topLeft: [xBegin, yBegin + hOffset, xBegin + ctrlPtXOffset, yBegin, xBegin + wOffset, yBegin], 26275 topRight: [xEnd - wOffset, yBegin, xEnd - ctrlPtXOffset, yBegin, xEnd, yBegin + hOffset], 26276 bottomRight: [xEnd, yEnd - hOffset, xEnd - ctrlPtXOffset, yEnd, xEnd - wOffset, yEnd], 26277 bottomLeft: [xBegin + wOffset, yEnd, xBegin + ctrlPtXOffset, yEnd, xBegin, yEnd - hOffset] 26278 }; 26279 pts.topLeft.isTop = true; 26280 pts.topRight.isTop = true; 26281 pts.bottomLeft.isBottom = true; 26282 pts.bottomRight.isBottom = true; 26283 return pts; 26284 }, 26285 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26286 var curveConstants = getBarrelCurveConstants(width, height); 26287 var hOffset = curveConstants.heightOffset; 26288 var wOffset = curveConstants.widthOffset; // Check hBox 26289 26290 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - 2 * hOffset, [0, -1], padding)) { 26291 return true; 26292 } // Check vBox 26293 26294 26295 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - 2 * wOffset, height, [0, -1], padding)) { 26296 return true; 26297 } 26298 26299 var barrelCurvePts = this.generateBarrelBezierPts(width, height, centerX, centerY); 26300 26301 var getCurveT = function getCurveT(x, y, curvePts) { 26302 var x0 = curvePts[4]; 26303 var x1 = curvePts[2]; 26304 var x2 = curvePts[0]; 26305 var y0 = curvePts[5]; // var y1 = curvePts[ 3 ]; 26306 26307 var y2 = curvePts[1]; 26308 var xMin = Math.min(x0, x2); 26309 var xMax = Math.max(x0, x2); 26310 var yMin = Math.min(y0, y2); 26311 var yMax = Math.max(y0, y2); 26312 26313 if (xMin <= x && x <= xMax && yMin <= y && y <= yMax) { 26314 var coeff = bezierPtsToQuadCoeff(x0, x1, x2); 26315 var roots = solveQuadratic(coeff[0], coeff[1], coeff[2], x); 26316 var validRoots = roots.filter(function (r) { 26317 return 0 <= r && r <= 1; 26318 }); 26319 26320 if (validRoots.length > 0) { 26321 return validRoots[0]; 26322 } 26323 } 26324 26325 return null; 26326 }; 26327 26328 var curveRegions = Object.keys(barrelCurvePts); 26329 26330 for (var i = 0; i < curveRegions.length; i++) { 26331 var corner = curveRegions[i]; 26332 var cornerPts = barrelCurvePts[corner]; 26333 var t = getCurveT(x, y, cornerPts); 26334 26335 if (t == null) { 26336 continue; 26337 } 26338 26339 var y0 = cornerPts[5]; 26340 var y1 = cornerPts[3]; 26341 var y2 = cornerPts[1]; 26342 var bezY = qbezierAt(y0, y1, y2, t); 26343 26344 if (cornerPts.isTop && bezY <= y) { 26345 return true; 26346 } 26347 26348 if (cornerPts.isBottom && y <= bezY) { 26349 return true; 26350 } 26351 } 26352 26353 return false; 26354 } 26355 }; 26356 }; 26357 26358 BRp$d.generateBottomRoundrectangle = function () { 26359 return this.nodeShapes['bottom-round-rectangle'] = this.nodeShapes['bottomroundrectangle'] = { 26360 renderer: this, 26361 name: 'bottom-round-rectangle', 26362 points: generateUnitNgonPointsFitToSquare(4, 0), 26363 draw: function draw(context, centerX, centerY, width, height) { 26364 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26365 }, 26366 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26367 var topStartX = nodeX - (width / 2 + padding); 26368 var topStartY = nodeY - (height / 2 + padding); 26369 var topEndY = topStartY; 26370 var topEndX = nodeX + (width / 2 + padding); 26371 var topIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); 26372 26373 if (topIntersections.length > 0) { 26374 return topIntersections; 26375 } 26376 26377 return roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding); 26378 }, 26379 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26380 var cornerRadius = getRoundRectangleRadius(width, height); 26381 var diam = 2 * cornerRadius; // Check hBox 26382 26383 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - diam, [0, -1], padding)) { 26384 return true; 26385 } // Check vBox 26386 26387 26388 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - diam, height, [0, -1], padding)) { 26389 return true; 26390 } // check non-rounded top side 26391 26392 26393 var outerWidth = width / 2 + 2 * padding; 26394 var outerHeight = height / 2 + 2 * padding; 26395 var points = [centerX - outerWidth, centerY - outerHeight, centerX - outerWidth, centerY, centerX + outerWidth, centerY, centerX + outerWidth, centerY - outerHeight]; 26396 26397 if (pointInsidePolygonPoints(x, y, points)) { 26398 return true; 26399 } // Check bottom right quarter circle 26400 26401 26402 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26403 return true; 26404 } // Check bottom left quarter circle 26405 26406 26407 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26408 return true; 26409 } 26410 26411 return false; 26412 } 26413 }; 26414 }; 26415 26416 BRp$d.registerNodeShapes = function () { 26417 var nodeShapes = this.nodeShapes = {}; 26418 var renderer = this; 26419 this.generateEllipse(); 26420 this.generatePolygon('triangle', generateUnitNgonPointsFitToSquare(3, 0)); 26421 this.generateRoundPolygon('round-triangle', generateUnitNgonPointsFitToSquare(3, 0)); 26422 this.generatePolygon('rectangle', generateUnitNgonPointsFitToSquare(4, 0)); 26423 nodeShapes['square'] = nodeShapes['rectangle']; 26424 this.generateRoundRectangle(); 26425 this.generateCutRectangle(); 26426 this.generateBarrel(); 26427 this.generateBottomRoundrectangle(); 26428 { 26429 var diamondPoints = [0, 1, 1, 0, 0, -1, -1, 0]; 26430 this.generatePolygon('diamond', diamondPoints); 26431 this.generateRoundPolygon('round-diamond', diamondPoints); 26432 } 26433 this.generatePolygon('pentagon', generateUnitNgonPointsFitToSquare(5, 0)); 26434 this.generateRoundPolygon('round-pentagon', generateUnitNgonPointsFitToSquare(5, 0)); 26435 this.generatePolygon('hexagon', generateUnitNgonPointsFitToSquare(6, 0)); 26436 this.generateRoundPolygon('round-hexagon', generateUnitNgonPointsFitToSquare(6, 0)); 26437 this.generatePolygon('heptagon', generateUnitNgonPointsFitToSquare(7, 0)); 26438 this.generateRoundPolygon('round-heptagon', generateUnitNgonPointsFitToSquare(7, 0)); 26439 this.generatePolygon('octagon', generateUnitNgonPointsFitToSquare(8, 0)); 26440 this.generateRoundPolygon('round-octagon', generateUnitNgonPointsFitToSquare(8, 0)); 26441 var star5Points = new Array(20); 26442 { 26443 var outerPoints = generateUnitNgonPoints(5, 0); 26444 var innerPoints = generateUnitNgonPoints(5, Math.PI / 5); // Outer radius is 1; inner radius of star is smaller 26445 26446 var innerRadius = 0.5 * (3 - Math.sqrt(5)); 26447 innerRadius *= 1.57; 26448 26449 for (var i = 0; i < innerPoints.length / 2; i++) { 26450 innerPoints[i * 2] *= innerRadius; 26451 innerPoints[i * 2 + 1] *= innerRadius; 26452 } 26453 26454 for (var i = 0; i < 20 / 4; i++) { 26455 star5Points[i * 4] = outerPoints[i * 2]; 26456 star5Points[i * 4 + 1] = outerPoints[i * 2 + 1]; 26457 star5Points[i * 4 + 2] = innerPoints[i * 2]; 26458 star5Points[i * 4 + 3] = innerPoints[i * 2 + 1]; 26459 } 26460 } 26461 star5Points = fitPolygonToSquare(star5Points); 26462 this.generatePolygon('star', star5Points); 26463 this.generatePolygon('vee', [-1, -1, 0, -0.333, 1, -1, 0, 1]); 26464 this.generatePolygon('rhomboid', [-1, -1, 0.333, -1, 1, 1, -0.333, 1]); 26465 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]); 26466 { 26467 var tagPoints = [-1, -1, 0.25, -1, 1, 0, 0.25, 1, -1, 1]; 26468 this.generatePolygon('tag', tagPoints); 26469 this.generateRoundPolygon('round-tag', tagPoints); 26470 } 26471 26472 nodeShapes.makePolygon = function (points) { 26473 // use caching on user-specified polygons so they are as fast as native shapes 26474 var key = points.join('$'); 26475 var name = 'polygon-' + key; 26476 var shape; 26477 26478 if (shape = this[name]) { 26479 // got cached shape 26480 return shape; 26481 } // create and cache new shape 26482 26483 26484 return renderer.generatePolygon(name, points); 26485 }; 26486 }; 26487 26488 var BRp$e = {}; 26489 26490 BRp$e.timeToRender = function () { 26491 return this.redrawTotalTime / this.redrawCount; 26492 }; 26493 26494 BRp$e.redraw = function (options) { 26495 options = options || staticEmptyObject(); 26496 var r = this; 26497 26498 if (r.averageRedrawTime === undefined) { 26499 r.averageRedrawTime = 0; 26500 } 26501 26502 if (r.lastRedrawTime === undefined) { 26503 r.lastRedrawTime = 0; 26504 } 26505 26506 if (r.lastDrawTime === undefined) { 26507 r.lastDrawTime = 0; 26508 } 26509 26510 r.requestedFrame = true; 26511 r.renderOptions = options; 26512 }; 26513 26514 BRp$e.beforeRender = function (fn, priority) { 26515 // the renderer can't add tick callbacks when destroyed 26516 if (this.destroyed) { 26517 return; 26518 } 26519 26520 if (priority == null) { 26521 error('Priority is not optional for beforeRender'); 26522 } 26523 26524 var cbs = this.beforeRenderCallbacks; 26525 cbs.push({ 26526 fn: fn, 26527 priority: priority 26528 }); // higher priority callbacks executed first 26529 26530 cbs.sort(function (a, b) { 26531 return b.priority - a.priority; 26532 }); 26533 }; 26534 26535 var beforeRenderCallbacks = function beforeRenderCallbacks(r, willDraw, startTime) { 26536 var cbs = r.beforeRenderCallbacks; 26537 26538 for (var i = 0; i < cbs.length; i++) { 26539 cbs[i].fn(willDraw, startTime); 26540 } 26541 }; 26542 26543 BRp$e.startRenderLoop = function () { 26544 var r = this; 26545 var cy = r.cy; 26546 26547 if (r.renderLoopStarted) { 26548 return; 26549 } else { 26550 r.renderLoopStarted = true; 26551 } 26552 26553 var renderFn = function renderFn(requestTime) { 26554 if (r.destroyed) { 26555 return; 26556 } 26557 26558 if (cy.batching()) ; else if (r.requestedFrame && !r.skipFrame) { 26559 beforeRenderCallbacks(r, true, requestTime); 26560 var startTime = performanceNow(); 26561 r.render(r.renderOptions); 26562 var endTime = r.lastDrawTime = performanceNow(); 26563 26564 if (r.averageRedrawTime === undefined) { 26565 r.averageRedrawTime = endTime - startTime; 26566 } 26567 26568 if (r.redrawCount === undefined) { 26569 r.redrawCount = 0; 26570 } 26571 26572 r.redrawCount++; 26573 26574 if (r.redrawTotalTime === undefined) { 26575 r.redrawTotalTime = 0; 26576 } 26577 26578 var duration = endTime - startTime; 26579 r.redrawTotalTime += duration; 26580 r.lastRedrawTime = duration; // use a weighted average with a bias from the previous average so we don't spike so easily 26581 26582 r.averageRedrawTime = r.averageRedrawTime / 2 + duration / 2; 26583 r.requestedFrame = false; 26584 } else { 26585 beforeRenderCallbacks(r, false, requestTime); 26586 } 26587 26588 r.skipFrame = false; 26589 requestAnimationFrame(renderFn); 26590 }; 26591 26592 requestAnimationFrame(renderFn); 26593 }; 26594 26595 var BaseRenderer = function BaseRenderer(options) { 26596 this.init(options); 26597 }; 26598 26599 var BR = BaseRenderer; 26600 var BRp$f = BR.prototype; 26601 BRp$f.clientFunctions = ['redrawHint', 'render', 'renderTo', 'matchCanvasSize', 'nodeShapeImpl', 'arrowShapeImpl']; 26602 26603 BRp$f.init = function (options) { 26604 var r = this; 26605 r.options = options; 26606 r.cy = options.cy; 26607 var ctr = r.container = options.cy.container(); // prepend a stylesheet in the head such that 26608 26609 if (window$1) { 26610 var document = window$1.document; 26611 var head = document.head; 26612 var stylesheetId = '__________cytoscape_stylesheet'; 26613 var className = '__________cytoscape_container'; 26614 var stylesheetAlreadyExists = document.getElementById(stylesheetId) != null; 26615 26616 if (ctr.className.indexOf(className) < 0) { 26617 ctr.className = (ctr.className || '') + ' ' + className; 26618 } 26619 26620 if (!stylesheetAlreadyExists) { 26621 var stylesheet = document.createElement('style'); 26622 stylesheet.id = stylesheetId; 26623 stylesheet.innerHTML = '.' + className + ' { position: relative; }'; 26624 head.insertBefore(stylesheet, head.children[0]); // first so lowest priority 26625 } 26626 26627 var computedStyle = window$1.getComputedStyle(ctr); 26628 var position = computedStyle.getPropertyValue('position'); 26629 26630 if (position === 'static') { 26631 warn('A Cytoscape container has style position:static and so can not use UI extensions properly'); 26632 } 26633 } 26634 26635 r.selection = [undefined, undefined, undefined, undefined, 0]; // Coordinates for selection box, plus enabled flag 26636 26637 r.bezierProjPcts = [0.05, 0.225, 0.4, 0.5, 0.6, 0.775, 0.95]; //--Pointer-related data 26638 26639 r.hoverData = { 26640 down: null, 26641 last: null, 26642 downTime: null, 26643 triggerMode: null, 26644 dragging: false, 26645 initialPan: [null, null], 26646 capture: false 26647 }; 26648 r.dragData = { 26649 possibleDragElements: [] 26650 }; 26651 r.touchData = { 26652 start: null, 26653 capture: false, 26654 // These 3 fields related to tap, taphold events 26655 startPosition: [null, null, null, null, null, null], 26656 singleTouchStartTime: null, 26657 singleTouchMoved: true, 26658 now: [null, null, null, null, null, null], 26659 earlier: [null, null, null, null, null, null] 26660 }; 26661 r.redraws = 0; 26662 r.showFps = options.showFps; 26663 r.debug = options.debug; 26664 r.hideEdgesOnViewport = options.hideEdgesOnViewport; 26665 r.textureOnViewport = options.textureOnViewport; 26666 r.wheelSensitivity = options.wheelSensitivity; 26667 r.motionBlurEnabled = options.motionBlur; // on by default 26668 26669 r.forcedPixelRatio = number(options.pixelRatio) ? options.pixelRatio : null; 26670 r.motionBlur = options.motionBlur; // for initial kick off 26671 26672 r.motionBlurOpacity = options.motionBlurOpacity; 26673 r.motionBlurTransparency = 1 - r.motionBlurOpacity; 26674 r.motionBlurPxRatio = 1; 26675 r.mbPxRBlurry = 1; //0.8; 26676 26677 r.minMbLowQualFrames = 4; 26678 r.fullQualityMb = false; 26679 r.clearedForMotionBlur = []; 26680 r.desktopTapThreshold = options.desktopTapThreshold; 26681 r.desktopTapThreshold2 = options.desktopTapThreshold * options.desktopTapThreshold; 26682 r.touchTapThreshold = options.touchTapThreshold; 26683 r.touchTapThreshold2 = options.touchTapThreshold * options.touchTapThreshold; 26684 r.tapholdDuration = 500; 26685 r.bindings = []; 26686 r.beforeRenderCallbacks = []; 26687 r.beforeRenderPriorities = { 26688 // higher priority execs before lower one 26689 animations: 400, 26690 eleCalcs: 300, 26691 eleTxrDeq: 200, 26692 lyrTxrDeq: 150, 26693 lyrTxrSkip: 100 26694 }; 26695 r.registerNodeShapes(); 26696 r.registerArrowShapes(); 26697 r.registerCalculationListeners(); 26698 }; 26699 26700 BRp$f.notify = function (eventName, eles) { 26701 var r = this; 26702 var cy = r.cy; // the renderer can't be notified after it's destroyed 26703 26704 if (this.destroyed) { 26705 return; 26706 } 26707 26708 if (eventName === 'init') { 26709 r.load(); 26710 return; 26711 } 26712 26713 if (eventName === 'destroy') { 26714 r.destroy(); 26715 return; 26716 } 26717 26718 if (eventName === 'add' || eventName === 'remove' || eventName === 'move' && cy.hasCompoundNodes() || eventName === 'load' || eventName === 'zorder' || eventName === 'mount') { 26719 r.invalidateCachedZSortedEles(); 26720 } 26721 26722 if (eventName === 'viewport') { 26723 r.redrawHint('select', true); 26724 } 26725 26726 if (eventName === 'load' || eventName === 'resize' || eventName === 'mount') { 26727 r.invalidateContainerClientCoordsCache(); 26728 r.matchCanvasSize(r.container); 26729 } 26730 26731 r.redrawHint('eles', true); 26732 r.redrawHint('drag', true); 26733 this.startRenderLoop(); 26734 this.redraw(); 26735 }; 26736 26737 BRp$f.destroy = function () { 26738 var r = this; 26739 r.destroyed = true; 26740 r.cy.stopAnimationLoop(); 26741 26742 for (var i = 0; i < r.bindings.length; i++) { 26743 var binding = r.bindings[i]; 26744 var b = binding; 26745 var tgt = b.target; 26746 (tgt.off || tgt.removeEventListener).apply(tgt, b.args); 26747 } 26748 26749 r.bindings = []; 26750 r.beforeRenderCallbacks = []; 26751 r.onUpdateEleCalcsFns = []; 26752 26753 if (r.removeObserver) { 26754 r.removeObserver.disconnect(); 26755 } 26756 26757 if (r.styleObserver) { 26758 r.styleObserver.disconnect(); 26759 } 26760 26761 if (r.resizeObserver) { 26762 r.resizeObserver.disconnect(); 26763 } 26764 26765 if (r.labelCalcDiv) { 26766 try { 26767 document.body.removeChild(r.labelCalcDiv); // eslint-disable-line no-undef 26768 } catch (e) {// ie10 issue #1014 26769 } 26770 } 26771 }; 26772 26773 BRp$f.isHeadless = function () { 26774 return false; 26775 }; 26776 26777 [BRp, BRp$a, BRp$b, BRp$c, BRp$d, BRp$e].forEach(function (props) { 26778 extend(BRp$f, props); 26779 }); 26780 26781 var fullFpsTime = 1000 / 60; // assume 60 frames per second 26782 26783 var defs = { 26784 setupDequeueing: function setupDequeueing(opts) { 26785 return function setupDequeueingImpl() { 26786 var self = this; 26787 var r = this.renderer; 26788 26789 if (self.dequeueingSetup) { 26790 return; 26791 } else { 26792 self.dequeueingSetup = true; 26793 } 26794 26795 var queueRedraw = util(function () { 26796 r.redrawHint('eles', true); 26797 r.redrawHint('drag', true); 26798 r.redraw(); 26799 }, opts.deqRedrawThreshold); 26800 26801 var dequeue = function dequeue(willDraw, frameStartTime) { 26802 var startTime = performanceNow(); 26803 var avgRenderTime = r.averageRedrawTime; 26804 var renderTime = r.lastRedrawTime; 26805 var deqd = []; 26806 var extent = r.cy.extent(); 26807 var pixelRatio = r.getPixelRatio(); // if we aren't in a tick that causes a draw, then the rendered style 26808 // queue won't automatically be flushed before dequeueing starts 26809 26810 if (!willDraw) { 26811 r.flushRenderedStyleQueue(); 26812 } 26813 26814 while (true) { 26815 // eslint-disable-line no-constant-condition 26816 var now = performanceNow(); 26817 var duration = now - startTime; 26818 var frameDuration = now - frameStartTime; 26819 26820 if (renderTime < fullFpsTime) { 26821 // if we're rendering faster than the ideal fps, then do dequeueing 26822 // during all of the remaining frame time 26823 var timeAvailable = fullFpsTime - (willDraw ? avgRenderTime : 0); 26824 26825 if (frameDuration >= opts.deqFastCost * timeAvailable) { 26826 break; 26827 } 26828 } else { 26829 if (willDraw) { 26830 if (duration >= opts.deqCost * renderTime || duration >= opts.deqAvgCost * avgRenderTime) { 26831 break; 26832 } 26833 } else if (frameDuration >= opts.deqNoDrawCost * fullFpsTime) { 26834 break; 26835 } 26836 } 26837 26838 var thisDeqd = opts.deq(self, pixelRatio, extent); 26839 26840 if (thisDeqd.length > 0) { 26841 for (var i = 0; i < thisDeqd.length; i++) { 26842 deqd.push(thisDeqd[i]); 26843 } 26844 } else { 26845 break; 26846 } 26847 } // callbacks on dequeue 26848 26849 26850 if (deqd.length > 0) { 26851 opts.onDeqd(self, deqd); 26852 26853 if (!willDraw && opts.shouldRedraw(self, deqd, pixelRatio, extent)) { 26854 queueRedraw(); 26855 } 26856 } 26857 }; 26858 26859 var priority = opts.priority || noop; 26860 r.beforeRender(dequeue, priority(self)); 26861 }; 26862 } 26863 }; 26864 26865 // Uses keys so elements may share the same cache. 26866 26867 var ElementTextureCacheLookup = 26868 /*#__PURE__*/ 26869 function () { 26870 function ElementTextureCacheLookup(getKey) { 26871 var doesEleInvalidateKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : falsify; 26872 26873 _classCallCheck(this, ElementTextureCacheLookup); 26874 26875 this.idsByKey = new Map$1(); 26876 this.keyForId = new Map$1(); 26877 this.cachesByLvl = new Map$1(); 26878 this.lvls = []; 26879 this.getKey = getKey; 26880 this.doesEleInvalidateKey = doesEleInvalidateKey; 26881 } 26882 26883 _createClass(ElementTextureCacheLookup, [{ 26884 key: "getIdsFor", 26885 value: function getIdsFor(key) { 26886 if (key == null) { 26887 error("Can not get id list for null key"); 26888 } 26889 26890 var idsByKey = this.idsByKey; 26891 var ids = this.idsByKey.get(key); 26892 26893 if (!ids) { 26894 ids = new Set$1(); 26895 idsByKey.set(key, ids); 26896 } 26897 26898 return ids; 26899 } 26900 }, { 26901 key: "addIdForKey", 26902 value: function addIdForKey(key, id) { 26903 if (key != null) { 26904 this.getIdsFor(key).add(id); 26905 } 26906 } 26907 }, { 26908 key: "deleteIdForKey", 26909 value: function deleteIdForKey(key, id) { 26910 if (key != null) { 26911 this.getIdsFor(key)["delete"](id); 26912 } 26913 } 26914 }, { 26915 key: "getNumberOfIdsForKey", 26916 value: function getNumberOfIdsForKey(key) { 26917 if (key == null) { 26918 return 0; 26919 } else { 26920 return this.getIdsFor(key).size; 26921 } 26922 } 26923 }, { 26924 key: "updateKeyMappingFor", 26925 value: function updateKeyMappingFor(ele) { 26926 var id = ele.id(); 26927 var prevKey = this.keyForId.get(id); 26928 var currKey = this.getKey(ele); 26929 this.deleteIdForKey(prevKey, id); 26930 this.addIdForKey(currKey, id); 26931 this.keyForId.set(id, currKey); 26932 } 26933 }, { 26934 key: "deleteKeyMappingFor", 26935 value: function deleteKeyMappingFor(ele) { 26936 var id = ele.id(); 26937 var prevKey = this.keyForId.get(id); 26938 this.deleteIdForKey(prevKey, id); 26939 this.keyForId["delete"](id); 26940 } 26941 }, { 26942 key: "keyHasChangedFor", 26943 value: function keyHasChangedFor(ele) { 26944 var id = ele.id(); 26945 var prevKey = this.keyForId.get(id); 26946 var newKey = this.getKey(ele); 26947 return prevKey !== newKey; 26948 } 26949 }, { 26950 key: "isInvalid", 26951 value: function isInvalid(ele) { 26952 return this.keyHasChangedFor(ele) || this.doesEleInvalidateKey(ele); 26953 } 26954 }, { 26955 key: "getCachesAt", 26956 value: function getCachesAt(lvl) { 26957 var cachesByLvl = this.cachesByLvl, 26958 lvls = this.lvls; 26959 var caches = cachesByLvl.get(lvl); 26960 26961 if (!caches) { 26962 caches = new Map$1(); 26963 cachesByLvl.set(lvl, caches); 26964 lvls.push(lvl); 26965 } 26966 26967 return caches; 26968 } 26969 }, { 26970 key: "getCache", 26971 value: function getCache(key, lvl) { 26972 return this.getCachesAt(lvl).get(key); 26973 } 26974 }, { 26975 key: "get", 26976 value: function get(ele, lvl) { 26977 var key = this.getKey(ele); 26978 var cache = this.getCache(key, lvl); // getting for an element may need to add to the id list b/c eles can share keys 26979 26980 if (cache != null) { 26981 this.updateKeyMappingFor(ele); 26982 } 26983 26984 return cache; 26985 } 26986 }, { 26987 key: "getForCachedKey", 26988 value: function getForCachedKey(ele, lvl) { 26989 var key = this.keyForId.get(ele.id()); // n.b. use cached key, not newly computed key 26990 26991 var cache = this.getCache(key, lvl); 26992 return cache; 26993 } 26994 }, { 26995 key: "hasCache", 26996 value: function hasCache(key, lvl) { 26997 return this.getCachesAt(lvl).has(key); 26998 } 26999 }, { 27000 key: "has", 27001 value: function has(ele, lvl) { 27002 var key = this.getKey(ele); 27003 return this.hasCache(key, lvl); 27004 } 27005 }, { 27006 key: "setCache", 27007 value: function setCache(key, lvl, cache) { 27008 cache.key = key; 27009 this.getCachesAt(lvl).set(key, cache); 27010 } 27011 }, { 27012 key: "set", 27013 value: function set(ele, lvl, cache) { 27014 var key = this.getKey(ele); 27015 this.setCache(key, lvl, cache); 27016 this.updateKeyMappingFor(ele); 27017 } 27018 }, { 27019 key: "deleteCache", 27020 value: function deleteCache(key, lvl) { 27021 this.getCachesAt(lvl)["delete"](key); 27022 } 27023 }, { 27024 key: "delete", 27025 value: function _delete(ele, lvl) { 27026 var key = this.getKey(ele); 27027 this.deleteCache(key, lvl); 27028 } 27029 }, { 27030 key: "invalidateKey", 27031 value: function invalidateKey(key) { 27032 var _this = this; 27033 27034 this.lvls.forEach(function (lvl) { 27035 return _this.deleteCache(key, lvl); 27036 }); 27037 } // returns true if no other eles reference the invalidated cache (n.b. other eles may need the cache with the same key) 27038 27039 }, { 27040 key: "invalidate", 27041 value: function invalidate(ele) { 27042 var id = ele.id(); 27043 var key = this.keyForId.get(id); // n.b. use stored key rather than current (potential key) 27044 27045 this.deleteKeyMappingFor(ele); 27046 var entireKeyInvalidated = this.doesEleInvalidateKey(ele); 27047 27048 if (entireKeyInvalidated) { 27049 // clear mapping for current key 27050 this.invalidateKey(key); 27051 } 27052 27053 return entireKeyInvalidated || this.getNumberOfIdsForKey(key) === 0; 27054 } 27055 }]); 27056 27057 return ElementTextureCacheLookup; 27058 }(); 27059 27060 var minTxrH = 25; // the size of the texture cache for small height eles (special case) 27061 27062 var txrStepH = 50; // the min size of the regular cache, and the size it increases with each step up 27063 27064 var minLvl = -4; // when scaling smaller than that we don't need to re-render 27065 27066 var maxLvl = 3; // when larger than this scale just render directly (caching is not helpful) 27067 27068 var maxZoom = 7.99; // beyond this zoom level, layered textures are not used 27069 27070 var eleTxrSpacing = 8; // spacing between elements on textures to avoid blitting overlaps 27071 27072 var defTxrWidth = 1024; // default/minimum texture width 27073 27074 var maxTxrW = 1024; // the maximum width of a texture 27075 27076 var maxTxrH = 1024; // the maximum height of a texture 27077 27078 var minUtility = 0.2; // if usage of texture is less than this, it is retired 27079 27080 var maxFullness = 0.8; // fullness of texture after which queue removal is checked 27081 27082 var maxFullnessChecks = 10; // dequeued after this many checks 27083 27084 var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 27085 27086 var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time 27087 27088 var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 27089 27090 var deqFastCost = 0.9; // % of frame time to be used when >60fps 27091 27092 var deqRedrawThreshold = 100; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 27093 27094 var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch 27095 27096 var getTxrReasons = { 27097 dequeue: 'dequeue', 27098 downscale: 'downscale', 27099 highQuality: 'highQuality' 27100 }; 27101 var initDefaults = defaults({ 27102 getKey: null, 27103 doesEleInvalidateKey: falsify, 27104 drawElement: null, 27105 getBoundingBox: null, 27106 getRotationPoint: null, 27107 getRotationOffset: null, 27108 isVisible: trueify, 27109 allowEdgeTxrCaching: true, 27110 allowParentTxrCaching: true 27111 }); 27112 27113 var ElementTextureCache = function ElementTextureCache(renderer, initOptions) { 27114 var self = this; 27115 self.renderer = renderer; 27116 self.onDequeues = []; 27117 var opts = initDefaults(initOptions); 27118 extend(self, opts); 27119 self.lookup = new ElementTextureCacheLookup(opts.getKey, opts.doesEleInvalidateKey); 27120 self.setupDequeueing(); 27121 }; 27122 27123 var ETCp = ElementTextureCache.prototype; 27124 ETCp.reasons = getTxrReasons; // the list of textures in which new subtextures for elements can be placed 27125 27126 ETCp.getTextureQueue = function (txrH) { 27127 var self = this; 27128 self.eleImgCaches = self.eleImgCaches || {}; 27129 return self.eleImgCaches[txrH] = self.eleImgCaches[txrH] || []; 27130 }; // the list of usused textures which can be recycled (in use in texture queue) 27131 27132 27133 ETCp.getRetiredTextureQueue = function (txrH) { 27134 var self = this; 27135 var rtxtrQs = self.eleImgCaches.retired = self.eleImgCaches.retired || {}; 27136 var rtxtrQ = rtxtrQs[txrH] = rtxtrQs[txrH] || []; 27137 return rtxtrQ; 27138 }; // queue of element draw requests at different scale levels 27139 27140 27141 ETCp.getElementQueue = function () { 27142 var self = this; 27143 var q = self.eleCacheQueue = self.eleCacheQueue || new Heap(function (a, b) { 27144 return b.reqs - a.reqs; 27145 }); 27146 return q; 27147 }; // queue of element draw requests at different scale levels (element id lookup) 27148 27149 27150 ETCp.getElementKeyToQueue = function () { 27151 var self = this; 27152 var k2q = self.eleKeyToCacheQueue = self.eleKeyToCacheQueue || {}; 27153 return k2q; 27154 }; 27155 27156 ETCp.getElement = function (ele, bb, pxRatio, lvl, reason) { 27157 var self = this; 27158 var r = this.renderer; 27159 var zoom = r.cy.zoom(); 27160 var lookup = this.lookup; 27161 27162 if (bb.w === 0 || bb.h === 0 || isNaN(bb.w) || isNaN(bb.h) || !ele.visible()) { 27163 return null; 27164 } 27165 27166 if (!self.allowEdgeTxrCaching && ele.isEdge() || !self.allowParentTxrCaching && ele.isParent()) { 27167 return null; 27168 } 27169 27170 if (lvl == null) { 27171 lvl = Math.ceil(log2(zoom * pxRatio)); 27172 } 27173 27174 if (lvl < minLvl) { 27175 lvl = minLvl; 27176 } else if (zoom >= maxZoom || lvl > maxLvl) { 27177 return null; 27178 } 27179 27180 var scale = Math.pow(2, lvl); 27181 var eleScaledH = bb.h * scale; 27182 var eleScaledW = bb.w * scale; 27183 var scaledLabelShown = r.eleTextBiggerThanMin(ele, scale); 27184 27185 if (!this.isVisible(ele, scaledLabelShown)) { 27186 return null; 27187 } 27188 27189 var eleCache = lookup.get(ele, lvl); // if this get was on an unused/invalidated cache, then restore the texture usage metric 27190 27191 if (eleCache && eleCache.invalidated) { 27192 eleCache.invalidated = false; 27193 eleCache.texture.invalidatedWidth -= eleCache.width; 27194 } 27195 27196 if (eleCache) { 27197 return eleCache; 27198 } 27199 27200 var txrH; // which texture height this ele belongs to 27201 27202 if (eleScaledH <= minTxrH) { 27203 txrH = minTxrH; 27204 } else if (eleScaledH <= txrStepH) { 27205 txrH = txrStepH; 27206 } else { 27207 txrH = Math.ceil(eleScaledH / txrStepH) * txrStepH; 27208 } 27209 27210 if (eleScaledH > maxTxrH || eleScaledW > maxTxrW) { 27211 return null; // caching large elements is not efficient 27212 } 27213 27214 var txrQ = self.getTextureQueue(txrH); // first try the second last one in case it has space at the end 27215 27216 var txr = txrQ[txrQ.length - 2]; 27217 27218 var addNewTxr = function addNewTxr() { 27219 return self.recycleTexture(txrH, eleScaledW) || self.addTexture(txrH, eleScaledW); 27220 }; // try the last one if there is no second last one 27221 27222 27223 if (!txr) { 27224 txr = txrQ[txrQ.length - 1]; 27225 } // if the last one doesn't exist, we need a first one 27226 27227 27228 if (!txr) { 27229 txr = addNewTxr(); 27230 } // if there's no room in the current texture, we need a new one 27231 27232 27233 if (txr.width - txr.usedWidth < eleScaledW) { 27234 txr = addNewTxr(); 27235 } 27236 27237 var scalableFrom = function scalableFrom(otherCache) { 27238 return otherCache && otherCache.scaledLabelShown === scaledLabelShown; 27239 }; 27240 27241 var deqing = reason && reason === getTxrReasons.dequeue; 27242 var highQualityReq = reason && reason === getTxrReasons.highQuality; 27243 var downscaleReq = reason && reason === getTxrReasons.downscale; 27244 var higherCache; // the nearest cache with a higher level 27245 27246 for (var l = lvl + 1; l <= maxLvl; l++) { 27247 var c = lookup.get(ele, l); 27248 27249 if (c) { 27250 higherCache = c; 27251 break; 27252 } 27253 } 27254 27255 var oneUpCache = higherCache && higherCache.level === lvl + 1 ? higherCache : null; 27256 27257 var downscale = function downscale() { 27258 txr.context.drawImage(oneUpCache.texture.canvas, oneUpCache.x, 0, oneUpCache.width, oneUpCache.height, txr.usedWidth, 0, eleScaledW, eleScaledH); 27259 }; // reset ele area in texture 27260 27261 27262 txr.context.setTransform(1, 0, 0, 1, 0, 0); 27263 txr.context.clearRect(txr.usedWidth, 0, eleScaledW, txrH); 27264 27265 if (scalableFrom(oneUpCache)) { 27266 // then we can relatively cheaply rescale the existing image w/o rerendering 27267 downscale(); 27268 } else if (scalableFrom(higherCache)) { 27269 // then use the higher cache for now and queue the next level down 27270 // to cheaply scale towards the smaller level 27271 if (highQualityReq) { 27272 for (var _l = higherCache.level; _l > lvl; _l--) { 27273 oneUpCache = self.getElement(ele, bb, pxRatio, _l, getTxrReasons.downscale); 27274 } 27275 27276 downscale(); 27277 } else { 27278 self.queueElement(ele, higherCache.level - 1); 27279 return higherCache; 27280 } 27281 } else { 27282 var lowerCache; // the nearest cache with a lower level 27283 27284 if (!deqing && !highQualityReq && !downscaleReq) { 27285 for (var _l2 = lvl - 1; _l2 >= minLvl; _l2--) { 27286 var _c = lookup.get(ele, _l2); 27287 27288 if (_c) { 27289 lowerCache = _c; 27290 break; 27291 } 27292 } 27293 } 27294 27295 if (scalableFrom(lowerCache)) { 27296 // then use the lower quality cache for now and queue the better one for later 27297 self.queueElement(ele, lvl); 27298 return lowerCache; 27299 } 27300 27301 txr.context.translate(txr.usedWidth, 0); 27302 txr.context.scale(scale, scale); 27303 this.drawElement(txr.context, ele, bb, scaledLabelShown, false); 27304 txr.context.scale(1 / scale, 1 / scale); 27305 txr.context.translate(-txr.usedWidth, 0); 27306 } 27307 27308 eleCache = { 27309 x: txr.usedWidth, 27310 texture: txr, 27311 level: lvl, 27312 scale: scale, 27313 width: eleScaledW, 27314 height: eleScaledH, 27315 scaledLabelShown: scaledLabelShown 27316 }; 27317 txr.usedWidth += Math.ceil(eleScaledW + eleTxrSpacing); 27318 txr.eleCaches.push(eleCache); 27319 lookup.set(ele, lvl, eleCache); 27320 self.checkTextureFullness(txr); 27321 return eleCache; 27322 }; 27323 27324 ETCp.invalidateElements = function (eles) { 27325 for (var i = 0; i < eles.length; i++) { 27326 this.invalidateElement(eles[i]); 27327 } 27328 }; 27329 27330 ETCp.invalidateElement = function (ele) { 27331 var self = this; 27332 var lookup = self.lookup; 27333 var caches = []; 27334 var invalid = lookup.isInvalid(ele); 27335 27336 if (!invalid) { 27337 return; // override the invalidation request if the element key has not changed 27338 } 27339 27340 for (var lvl = minLvl; lvl <= maxLvl; lvl++) { 27341 var cache = lookup.getForCachedKey(ele, lvl); 27342 27343 if (cache) { 27344 caches.push(cache); 27345 } 27346 } 27347 27348 var noOtherElesUseCache = lookup.invalidate(ele); 27349 27350 if (noOtherElesUseCache) { 27351 for (var i = 0; i < caches.length; i++) { 27352 var _cache = caches[i]; 27353 var txr = _cache.texture; // remove space from the texture it belongs to 27354 27355 txr.invalidatedWidth += _cache.width; // mark the cache as invalidated 27356 27357 _cache.invalidated = true; // retire the texture if its utility is low 27358 27359 self.checkTextureUtility(txr); 27360 } 27361 } // remove from queue since the old req was for the old state 27362 27363 27364 self.removeFromQueue(ele); 27365 }; 27366 27367 ETCp.checkTextureUtility = function (txr) { 27368 // invalidate all entries in the cache if the cache size is small 27369 if (txr.invalidatedWidth >= minUtility * txr.width) { 27370 this.retireTexture(txr); 27371 } 27372 }; 27373 27374 ETCp.checkTextureFullness = function (txr) { 27375 // if texture has been mostly filled and passed over several times, remove 27376 // it from the queue so we don't need to waste time looking at it to put new things 27377 var self = this; 27378 var txrQ = self.getTextureQueue(txr.height); 27379 27380 if (txr.usedWidth / txr.width > maxFullness && txr.fullnessChecks >= maxFullnessChecks) { 27381 removeFromArray(txrQ, txr); 27382 } else { 27383 txr.fullnessChecks++; 27384 } 27385 }; 27386 27387 ETCp.retireTexture = function (txr) { 27388 var self = this; 27389 var txrH = txr.height; 27390 var txrQ = self.getTextureQueue(txrH); 27391 var lookup = this.lookup; // retire the texture from the active / searchable queue: 27392 27393 removeFromArray(txrQ, txr); 27394 txr.retired = true; // remove the refs from the eles to the caches: 27395 27396 var eleCaches = txr.eleCaches; 27397 27398 for (var i = 0; i < eleCaches.length; i++) { 27399 var eleCache = eleCaches[i]; 27400 lookup.deleteCache(eleCache.key, eleCache.level); 27401 } 27402 27403 clearArray(eleCaches); // add the texture to a retired queue so it can be recycled in future: 27404 27405 var rtxtrQ = self.getRetiredTextureQueue(txrH); 27406 rtxtrQ.push(txr); 27407 }; 27408 27409 ETCp.addTexture = function (txrH, minW) { 27410 var self = this; 27411 var txrQ = self.getTextureQueue(txrH); 27412 var txr = {}; 27413 txrQ.push(txr); 27414 txr.eleCaches = []; 27415 txr.height = txrH; 27416 txr.width = Math.max(defTxrWidth, minW); 27417 txr.usedWidth = 0; 27418 txr.invalidatedWidth = 0; 27419 txr.fullnessChecks = 0; 27420 txr.canvas = self.renderer.makeOffscreenCanvas(txr.width, txr.height); 27421 txr.context = txr.canvas.getContext('2d'); 27422 return txr; 27423 }; 27424 27425 ETCp.recycleTexture = function (txrH, minW) { 27426 var self = this; 27427 var txrQ = self.getTextureQueue(txrH); 27428 var rtxtrQ = self.getRetiredTextureQueue(txrH); 27429 27430 for (var i = 0; i < rtxtrQ.length; i++) { 27431 var txr = rtxtrQ[i]; 27432 27433 if (txr.width >= minW) { 27434 txr.retired = false; 27435 txr.usedWidth = 0; 27436 txr.invalidatedWidth = 0; 27437 txr.fullnessChecks = 0; 27438 clearArray(txr.eleCaches); 27439 txr.context.setTransform(1, 0, 0, 1, 0, 0); 27440 txr.context.clearRect(0, 0, txr.width, txr.height); 27441 removeFromArray(rtxtrQ, txr); 27442 txrQ.push(txr); 27443 return txr; 27444 } 27445 } 27446 }; 27447 27448 ETCp.queueElement = function (ele, lvl) { 27449 var self = this; 27450 var q = self.getElementQueue(); 27451 var k2q = self.getElementKeyToQueue(); 27452 var key = this.getKey(ele); 27453 var existingReq = k2q[key]; 27454 27455 if (existingReq) { 27456 // use the max lvl b/c in between lvls are cheap to make 27457 existingReq.level = Math.max(existingReq.level, lvl); 27458 existingReq.eles.merge(ele); 27459 existingReq.reqs++; 27460 q.updateItem(existingReq); 27461 } else { 27462 var req = { 27463 eles: ele.spawn().merge(ele), 27464 level: lvl, 27465 reqs: 1, 27466 key: key 27467 }; 27468 q.push(req); 27469 k2q[key] = req; 27470 } 27471 }; 27472 27473 ETCp.dequeue = function (pxRatio 27474 /*, extent*/ 27475 ) { 27476 var self = this; 27477 var q = self.getElementQueue(); 27478 var k2q = self.getElementKeyToQueue(); 27479 var dequeued = []; 27480 var lookup = self.lookup; 27481 27482 for (var i = 0; i < maxDeqSize; i++) { 27483 if (q.size() > 0) { 27484 var req = q.pop(); 27485 var key = req.key; 27486 var ele = req.eles[0]; // all eles have the same key 27487 27488 var cacheExists = lookup.hasCache(ele, req.level); // clear out the key to req lookup 27489 27490 k2q[key] = null; // dequeueing isn't necessary with an existing cache 27491 27492 if (cacheExists) { 27493 continue; 27494 } 27495 27496 dequeued.push(req); 27497 var bb = self.getBoundingBox(ele); 27498 self.getElement(ele, bb, pxRatio, req.level, getTxrReasons.dequeue); 27499 } else { 27500 break; 27501 } 27502 } 27503 27504 return dequeued; 27505 }; 27506 27507 ETCp.removeFromQueue = function (ele) { 27508 var self = this; 27509 var q = self.getElementQueue(); 27510 var k2q = self.getElementKeyToQueue(); 27511 var key = this.getKey(ele); 27512 var req = k2q[key]; 27513 27514 if (req != null) { 27515 if (req.eles.length === 1) { 27516 // remove if last ele in the req 27517 // bring to front of queue 27518 req.reqs = MAX_INT; 27519 q.updateItem(req); 27520 q.pop(); // remove from queue 27521 27522 k2q[key] = null; // remove from lookup map 27523 } else { 27524 // otherwise just remove ele from req 27525 req.eles.unmerge(ele); 27526 } 27527 } 27528 }; 27529 27530 ETCp.onDequeue = function (fn) { 27531 this.onDequeues.push(fn); 27532 }; 27533 27534 ETCp.offDequeue = function (fn) { 27535 removeFromArray(this.onDequeues, fn); 27536 }; 27537 27538 ETCp.setupDequeueing = defs.setupDequeueing({ 27539 deqRedrawThreshold: deqRedrawThreshold, 27540 deqCost: deqCost, 27541 deqAvgCost: deqAvgCost, 27542 deqNoDrawCost: deqNoDrawCost, 27543 deqFastCost: deqFastCost, 27544 deq: function deq(self, pxRatio, extent) { 27545 return self.dequeue(pxRatio, extent); 27546 }, 27547 onDeqd: function onDeqd(self, deqd) { 27548 for (var i = 0; i < self.onDequeues.length; i++) { 27549 var fn = self.onDequeues[i]; 27550 fn(deqd); 27551 } 27552 }, 27553 shouldRedraw: function shouldRedraw(self, deqd, pxRatio, extent) { 27554 for (var i = 0; i < deqd.length; i++) { 27555 var eles = deqd[i].eles; 27556 27557 for (var j = 0; j < eles.length; j++) { 27558 var bb = eles[j].boundingBox(); 27559 27560 if (boundingBoxesIntersect(bb, extent)) { 27561 return true; 27562 } 27563 } 27564 } 27565 27566 return false; 27567 }, 27568 priority: function priority(self) { 27569 return self.renderer.beforeRenderPriorities.eleTxrDeq; 27570 } 27571 }); 27572 27573 var defNumLayers = 1; // default number of layers to use 27574 27575 var minLvl$1 = -4; // when scaling smaller than that we don't need to re-render 27576 27577 var maxLvl$1 = 2; // when larger than this scale just render directly (caching is not helpful) 27578 27579 var maxZoom$1 = 3.99; // beyond this zoom level, layered textures are not used 27580 27581 var deqRedrawThreshold$1 = 50; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 27582 27583 var refineEleDebounceTime = 50; // time to debounce sharper ele texture updates 27584 27585 var deqCost$1 = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 27586 27587 var deqAvgCost$1 = 0.1; // % of add'l rendering cost compared to average overall redraw time 27588 27589 var deqNoDrawCost$1 = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 27590 27591 var deqFastCost$1 = 0.9; // % of frame time to be used when >60fps 27592 27593 var maxDeqSize$1 = 1; // number of eles to dequeue and render at higher texture in each batch 27594 27595 var invalidThreshold = 250; // time threshold for disabling b/c of invalidations 27596 27597 var maxLayerArea = 4000 * 4000; // layers can't be bigger than this 27598 27599 var useHighQualityEleTxrReqs = true; // whether to use high quality ele txr requests (generally faster and cheaper in the longterm) 27600 // var log = function(){ console.log.apply( console, arguments ); }; 27601 27602 var LayeredTextureCache = function LayeredTextureCache(renderer) { 27603 var self = this; 27604 var r = self.renderer = renderer; 27605 var cy = r.cy; 27606 self.layersByLevel = {}; // e.g. 2 => [ layer1, layer2, ..., layerN ] 27607 27608 self.firstGet = true; 27609 self.lastInvalidationTime = performanceNow() - 2 * invalidThreshold; 27610 self.skipping = false; 27611 self.eleTxrDeqs = cy.collection(); 27612 self.scheduleElementRefinement = util(function () { 27613 self.refineElementTextures(self.eleTxrDeqs); 27614 self.eleTxrDeqs.unmerge(self.eleTxrDeqs); 27615 }, refineEleDebounceTime); 27616 r.beforeRender(function (willDraw, now) { 27617 if (now - self.lastInvalidationTime <= invalidThreshold) { 27618 self.skipping = true; 27619 } else { 27620 self.skipping = false; 27621 } 27622 }, r.beforeRenderPriorities.lyrTxrSkip); 27623 27624 var qSort = function qSort(a, b) { 27625 return b.reqs - a.reqs; 27626 }; 27627 27628 self.layersQueue = new Heap(qSort); 27629 self.setupDequeueing(); 27630 }; 27631 27632 var LTCp = LayeredTextureCache.prototype; 27633 var layerIdPool = 0; 27634 var MAX_INT$1 = Math.pow(2, 53) - 1; 27635 27636 LTCp.makeLayer = function (bb, lvl) { 27637 var scale = Math.pow(2, lvl); 27638 var w = Math.ceil(bb.w * scale); 27639 var h = Math.ceil(bb.h * scale); 27640 var canvas = this.renderer.makeOffscreenCanvas(w, h); 27641 var layer = { 27642 id: layerIdPool = ++layerIdPool % MAX_INT$1, 27643 bb: bb, 27644 level: lvl, 27645 width: w, 27646 height: h, 27647 canvas: canvas, 27648 context: canvas.getContext('2d'), 27649 eles: [], 27650 elesQueue: [], 27651 reqs: 0 27652 }; // log('make layer %s with w %s and h %s and lvl %s', layer.id, layer.width, layer.height, layer.level); 27653 27654 var cxt = layer.context; 27655 var dx = -layer.bb.x1; 27656 var dy = -layer.bb.y1; // do the transform on creation to save cycles (it's the same for all eles) 27657 27658 cxt.scale(scale, scale); 27659 cxt.translate(dx, dy); 27660 return layer; 27661 }; 27662 27663 LTCp.getLayers = function (eles, pxRatio, lvl) { 27664 var self = this; 27665 var r = self.renderer; 27666 var cy = r.cy; 27667 var zoom = cy.zoom(); 27668 var firstGet = self.firstGet; 27669 self.firstGet = false; // log('--\nget layers with %s eles', eles.length); 27670 //log eles.map(function(ele){ return ele.id() }) ); 27671 27672 if (lvl == null) { 27673 lvl = Math.ceil(log2(zoom * pxRatio)); 27674 27675 if (lvl < minLvl$1) { 27676 lvl = minLvl$1; 27677 } else if (zoom >= maxZoom$1 || lvl > maxLvl$1) { 27678 return null; 27679 } 27680 } 27681 27682 self.validateLayersElesOrdering(lvl, eles); 27683 var layersByLvl = self.layersByLevel; 27684 var scale = Math.pow(2, lvl); 27685 var layers = layersByLvl[lvl] = layersByLvl[lvl] || []; 27686 var bb; 27687 var lvlComplete = self.levelIsComplete(lvl, eles); 27688 var tmpLayers; 27689 27690 var checkTempLevels = function checkTempLevels() { 27691 var canUseAsTmpLvl = function canUseAsTmpLvl(l) { 27692 self.validateLayersElesOrdering(l, eles); 27693 27694 if (self.levelIsComplete(l, eles)) { 27695 tmpLayers = layersByLvl[l]; 27696 return true; 27697 } 27698 }; 27699 27700 var checkLvls = function checkLvls(dir) { 27701 if (tmpLayers) { 27702 return; 27703 } 27704 27705 for (var l = lvl + dir; minLvl$1 <= l && l <= maxLvl$1; l += dir) { 27706 if (canUseAsTmpLvl(l)) { 27707 break; 27708 } 27709 } 27710 }; 27711 27712 checkLvls(+1); 27713 checkLvls(-1); // remove the invalid layers; they will be replaced as needed later in this function 27714 27715 for (var i = layers.length - 1; i >= 0; i--) { 27716 var layer = layers[i]; 27717 27718 if (layer.invalid) { 27719 removeFromArray(layers, layer); 27720 } 27721 } 27722 }; 27723 27724 if (!lvlComplete) { 27725 // if the current level is incomplete, then use the closest, best quality layerset temporarily 27726 // and later queue the current layerset so we can get the proper quality level soon 27727 checkTempLevels(); 27728 } else { 27729 // log('level complete, using existing layers\n--'); 27730 return layers; 27731 } 27732 27733 var getBb = function getBb() { 27734 if (!bb) { 27735 bb = makeBoundingBox(); 27736 27737 for (var i = 0; i < eles.length; i++) { 27738 updateBoundingBox(bb, eles[i].boundingBox()); 27739 } 27740 } 27741 27742 return bb; 27743 }; 27744 27745 var makeLayer = function makeLayer(opts) { 27746 opts = opts || {}; 27747 var after = opts.after; 27748 getBb(); 27749 var area = bb.w * scale * (bb.h * scale); 27750 27751 if (area > maxLayerArea) { 27752 return null; 27753 } 27754 27755 var layer = self.makeLayer(bb, lvl); 27756 27757 if (after != null) { 27758 var index = layers.indexOf(after) + 1; 27759 layers.splice(index, 0, layer); 27760 } else if (opts.insert === undefined || opts.insert) { 27761 // no after specified => first layer made so put at start 27762 layers.unshift(layer); 27763 } // if( tmpLayers ){ 27764 //self.queueLayer( layer ); 27765 // } 27766 27767 27768 return layer; 27769 }; 27770 27771 if (self.skipping && !firstGet) { 27772 // log('skip layers'); 27773 return null; 27774 } // log('do layers'); 27775 27776 27777 var layer = null; 27778 var maxElesPerLayer = eles.length / defNumLayers; 27779 var allowLazyQueueing = !firstGet; 27780 27781 for (var i = 0; i < eles.length; i++) { 27782 var ele = eles[i]; 27783 var rs = ele._private.rscratch; 27784 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; // log('look at ele', ele.id()); 27785 27786 var existingLayer = caches[lvl]; 27787 27788 if (existingLayer) { 27789 // reuse layer for later eles 27790 // log('reuse layer for', ele.id()); 27791 layer = existingLayer; 27792 continue; 27793 } 27794 27795 if (!layer || layer.eles.length >= maxElesPerLayer || !boundingBoxInBoundingBox(layer.bb, ele.boundingBox())) { 27796 // log('make new layer for ele %s', ele.id()); 27797 layer = makeLayer({ 27798 insert: true, 27799 after: layer 27800 }); // if now layer can be built then we can't use layers at this level 27801 27802 if (!layer) { 27803 return null; 27804 } // log('new layer with id %s', layer.id); 27805 27806 } 27807 27808 if (tmpLayers || allowLazyQueueing) { 27809 // log('queue ele %s in layer %s', ele.id(), layer.id); 27810 self.queueLayer(layer, ele); 27811 } else { 27812 // log('draw ele %s in layer %s', ele.id(), layer.id); 27813 self.drawEleInLayer(layer, ele, lvl, pxRatio); 27814 } 27815 27816 layer.eles.push(ele); 27817 caches[lvl] = layer; 27818 } // log('--'); 27819 27820 27821 if (tmpLayers) { 27822 // then we only queued the current layerset and can't draw it yet 27823 return tmpLayers; 27824 } 27825 27826 if (allowLazyQueueing) { 27827 // log('lazy queue level', lvl); 27828 return null; 27829 } 27830 27831 return layers; 27832 }; // a layer may want to use an ele cache of a higher level to avoid blurriness 27833 // so the layer level might not equal the ele level 27834 27835 27836 LTCp.getEleLevelForLayerLevel = function (lvl, pxRatio) { 27837 return lvl; 27838 }; 27839 27840 LTCp.drawEleInLayer = function (layer, ele, lvl, pxRatio) { 27841 var self = this; 27842 var r = this.renderer; 27843 var context = layer.context; 27844 var bb = ele.boundingBox(); 27845 27846 if (bb.w === 0 || bb.h === 0 || !ele.visible()) { 27847 return; 27848 } 27849 27850 lvl = self.getEleLevelForLayerLevel(lvl, pxRatio); 27851 27852 { 27853 r.setImgSmoothing(context, false); 27854 } 27855 27856 { 27857 r.drawCachedElement(context, ele, null, null, lvl, useHighQualityEleTxrReqs); 27858 } 27859 27860 { 27861 r.setImgSmoothing(context, true); 27862 } 27863 }; 27864 27865 LTCp.levelIsComplete = function (lvl, eles) { 27866 var self = this; 27867 var layers = self.layersByLevel[lvl]; 27868 27869 if (!layers || layers.length === 0) { 27870 return false; 27871 } 27872 27873 var numElesInLayers = 0; 27874 27875 for (var i = 0; i < layers.length; i++) { 27876 var layer = layers[i]; // if there are any eles needed to be drawn yet, the level is not complete 27877 27878 if (layer.reqs > 0) { 27879 return false; 27880 } // if the layer is invalid, the level is not complete 27881 27882 27883 if (layer.invalid) { 27884 return false; 27885 } 27886 27887 numElesInLayers += layer.eles.length; 27888 } // we should have exactly the number of eles passed in to be complete 27889 27890 27891 if (numElesInLayers !== eles.length) { 27892 return false; 27893 } 27894 27895 return true; 27896 }; 27897 27898 LTCp.validateLayersElesOrdering = function (lvl, eles) { 27899 var layers = this.layersByLevel[lvl]; 27900 27901 if (!layers) { 27902 return; 27903 } // if in a layer the eles are not in the same order, then the layer is invalid 27904 // (i.e. there is an ele in between the eles in the layer) 27905 27906 27907 for (var i = 0; i < layers.length; i++) { 27908 var layer = layers[i]; 27909 var offset = -1; // find the offset 27910 27911 for (var j = 0; j < eles.length; j++) { 27912 if (layer.eles[0] === eles[j]) { 27913 offset = j; 27914 break; 27915 } 27916 } 27917 27918 if (offset < 0) { 27919 // then the layer has nonexistant elements and is invalid 27920 this.invalidateLayer(layer); 27921 continue; 27922 } // the eles in the layer must be in the same continuous order, else the layer is invalid 27923 27924 27925 var o = offset; 27926 27927 for (var j = 0; j < layer.eles.length; j++) { 27928 if (layer.eles[j] !== eles[o + j]) { 27929 // log('invalidate based on ordering', layer.id); 27930 this.invalidateLayer(layer); 27931 break; 27932 } 27933 } 27934 } 27935 }; 27936 27937 LTCp.updateElementsInLayers = function (eles, update) { 27938 var self = this; 27939 var isEles = element(eles[0]); // collect udpated elements (cascaded from the layers) and update each 27940 // layer itself along the way 27941 27942 for (var i = 0; i < eles.length; i++) { 27943 var req = isEles ? null : eles[i]; 27944 var ele = isEles ? eles[i] : eles[i].ele; 27945 var rs = ele._private.rscratch; 27946 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; 27947 27948 for (var l = minLvl$1; l <= maxLvl$1; l++) { 27949 var layer = caches[l]; 27950 27951 if (!layer) { 27952 continue; 27953 } // if update is a request from the ele cache, then it affects only 27954 // the matching level 27955 27956 27957 if (req && self.getEleLevelForLayerLevel(layer.level) !== req.level) { 27958 continue; 27959 } 27960 27961 update(layer, ele, req); 27962 } 27963 } 27964 }; 27965 27966 LTCp.haveLayers = function () { 27967 var self = this; 27968 var haveLayers = false; 27969 27970 for (var l = minLvl$1; l <= maxLvl$1; l++) { 27971 var layers = self.layersByLevel[l]; 27972 27973 if (layers && layers.length > 0) { 27974 haveLayers = true; 27975 break; 27976 } 27977 } 27978 27979 return haveLayers; 27980 }; 27981 27982 LTCp.invalidateElements = function (eles) { 27983 var self = this; 27984 27985 if (eles.length === 0) { 27986 return; 27987 } 27988 27989 self.lastInvalidationTime = performanceNow(); // log('update invalidate layer time from eles'); 27990 27991 if (eles.length === 0 || !self.haveLayers()) { 27992 return; 27993 } 27994 27995 self.updateElementsInLayers(eles, function invalAssocLayers(layer, ele, req) { 27996 self.invalidateLayer(layer); 27997 }); 27998 }; 27999 28000 LTCp.invalidateLayer = function (layer) { 28001 // log('update invalidate layer time'); 28002 this.lastInvalidationTime = performanceNow(); 28003 28004 if (layer.invalid) { 28005 return; 28006 } // save cycles 28007 28008 28009 var lvl = layer.level; 28010 var eles = layer.eles; 28011 var layers = this.layersByLevel[lvl]; // log('invalidate layer', layer.id ); 28012 28013 removeFromArray(layers, layer); // layer.eles = []; 28014 28015 layer.elesQueue = []; 28016 layer.invalid = true; 28017 28018 if (layer.replacement) { 28019 layer.replacement.invalid = true; 28020 } 28021 28022 for (var i = 0; i < eles.length; i++) { 28023 var caches = eles[i]._private.rscratch.imgLayerCaches; 28024 28025 if (caches) { 28026 caches[lvl] = null; 28027 } 28028 } 28029 }; 28030 28031 LTCp.refineElementTextures = function (eles) { 28032 var self = this; // log('refine', eles.length); 28033 28034 self.updateElementsInLayers(eles, function refineEachEle(layer, ele, req) { 28035 var rLyr = layer.replacement; 28036 28037 if (!rLyr) { 28038 rLyr = layer.replacement = self.makeLayer(layer.bb, layer.level); 28039 rLyr.replaces = layer; 28040 rLyr.eles = layer.eles; // log('make replacement layer %s for %s with level %s', rLyr.id, layer.id, rLyr.level); 28041 } 28042 28043 if (!rLyr.reqs) { 28044 for (var i = 0; i < rLyr.eles.length; i++) { 28045 self.queueLayer(rLyr, rLyr.eles[i]); 28046 } // log('queue replacement layer refinement', rLyr.id); 28047 28048 } 28049 }); 28050 }; 28051 28052 LTCp.enqueueElementRefinement = function (ele) { 28053 28054 this.eleTxrDeqs.merge(ele); 28055 this.scheduleElementRefinement(); 28056 }; 28057 28058 LTCp.queueLayer = function (layer, ele) { 28059 var self = this; 28060 var q = self.layersQueue; 28061 var elesQ = layer.elesQueue; 28062 var hasId = elesQ.hasId = elesQ.hasId || {}; // if a layer is going to be replaced, queuing is a waste of time 28063 28064 if (layer.replacement) { 28065 return; 28066 } 28067 28068 if (ele) { 28069 if (hasId[ele.id()]) { 28070 return; 28071 } 28072 28073 elesQ.push(ele); 28074 hasId[ele.id()] = true; 28075 } 28076 28077 if (layer.reqs) { 28078 layer.reqs++; 28079 q.updateItem(layer); 28080 } else { 28081 layer.reqs = 1; 28082 q.push(layer); 28083 } 28084 }; 28085 28086 LTCp.dequeue = function (pxRatio) { 28087 var self = this; 28088 var q = self.layersQueue; 28089 var deqd = []; 28090 var eleDeqs = 0; 28091 28092 while (eleDeqs < maxDeqSize$1) { 28093 if (q.size() === 0) { 28094 break; 28095 } 28096 28097 var layer = q.peek(); // if a layer has been or will be replaced, then don't waste time with it 28098 28099 if (layer.replacement) { 28100 // log('layer %s in queue skipped b/c it already has a replacement', layer.id); 28101 q.pop(); 28102 continue; 28103 } // if this is a replacement layer that has been superceded, then forget it 28104 28105 28106 if (layer.replaces && layer !== layer.replaces.replacement) { 28107 // log('layer is no longer the most uptodate replacement; dequeued', layer.id) 28108 q.pop(); 28109 continue; 28110 } 28111 28112 if (layer.invalid) { 28113 // log('replacement layer %s is invalid; dequeued', layer.id); 28114 q.pop(); 28115 continue; 28116 } 28117 28118 var ele = layer.elesQueue.shift(); 28119 28120 if (ele) { 28121 // log('dequeue layer %s', layer.id); 28122 self.drawEleInLayer(layer, ele, layer.level, pxRatio); 28123 eleDeqs++; 28124 } 28125 28126 if (deqd.length === 0) { 28127 // we need only one entry in deqd to queue redrawing etc 28128 deqd.push(true); 28129 } // if the layer has all its eles done, then remove from the queue 28130 28131 28132 if (layer.elesQueue.length === 0) { 28133 q.pop(); 28134 layer.reqs = 0; // log('dequeue of layer %s complete', layer.id); 28135 // when a replacement layer is dequeued, it replaces the old layer in the level 28136 28137 if (layer.replaces) { 28138 self.applyLayerReplacement(layer); 28139 } 28140 28141 self.requestRedraw(); 28142 } 28143 } 28144 28145 return deqd; 28146 }; 28147 28148 LTCp.applyLayerReplacement = function (layer) { 28149 var self = this; 28150 var layersInLevel = self.layersByLevel[layer.level]; 28151 var replaced = layer.replaces; 28152 var index = layersInLevel.indexOf(replaced); // if the replaced layer is not in the active list for the level, then replacing 28153 // refs would be a mistake (i.e. overwriting the true active layer) 28154 28155 if (index < 0 || replaced.invalid) { 28156 // log('replacement layer would have no effect', layer.id); 28157 return; 28158 } 28159 28160 layersInLevel[index] = layer; // replace level ref 28161 // replace refs in eles 28162 28163 for (var i = 0; i < layer.eles.length; i++) { 28164 var _p = layer.eles[i]._private; 28165 var cache = _p.imgLayerCaches = _p.imgLayerCaches || {}; 28166 28167 if (cache) { 28168 cache[layer.level] = layer; 28169 } 28170 } // log('apply replacement layer %s over %s', layer.id, replaced.id); 28171 28172 28173 self.requestRedraw(); 28174 }; 28175 28176 LTCp.requestRedraw = util(function () { 28177 var r = this.renderer; 28178 r.redrawHint('eles', true); 28179 r.redrawHint('drag', true); 28180 r.redraw(); 28181 }, 100); 28182 LTCp.setupDequeueing = defs.setupDequeueing({ 28183 deqRedrawThreshold: deqRedrawThreshold$1, 28184 deqCost: deqCost$1, 28185 deqAvgCost: deqAvgCost$1, 28186 deqNoDrawCost: deqNoDrawCost$1, 28187 deqFastCost: deqFastCost$1, 28188 deq: function deq(self, pxRatio) { 28189 return self.dequeue(pxRatio); 28190 }, 28191 onDeqd: noop, 28192 shouldRedraw: trueify, 28193 priority: function priority(self) { 28194 return self.renderer.beforeRenderPriorities.lyrTxrDeq; 28195 } 28196 }); 28197 28198 var CRp = {}; 28199 var impl; 28200 28201 function polygon(context, points) { 28202 for (var i = 0; i < points.length; i++) { 28203 var pt = points[i]; 28204 context.lineTo(pt.x, pt.y); 28205 } 28206 } 28207 28208 function triangleBackcurve(context, points, controlPoint) { 28209 var firstPt; 28210 28211 for (var i = 0; i < points.length; i++) { 28212 var pt = points[i]; 28213 28214 if (i === 0) { 28215 firstPt = pt; 28216 } 28217 28218 context.lineTo(pt.x, pt.y); 28219 } 28220 28221 context.quadraticCurveTo(controlPoint.x, controlPoint.y, firstPt.x, firstPt.y); 28222 } 28223 28224 function triangleTee(context, trianglePoints, teePoints) { 28225 if (context.beginPath) { 28226 context.beginPath(); 28227 } 28228 28229 var triPts = trianglePoints; 28230 28231 for (var i = 0; i < triPts.length; i++) { 28232 var pt = triPts[i]; 28233 context.lineTo(pt.x, pt.y); 28234 } 28235 28236 var teePts = teePoints; 28237 var firstTeePt = teePoints[0]; 28238 context.moveTo(firstTeePt.x, firstTeePt.y); 28239 28240 for (var i = 1; i < teePts.length; i++) { 28241 var pt = teePts[i]; 28242 context.lineTo(pt.x, pt.y); 28243 } 28244 28245 if (context.closePath) { 28246 context.closePath(); 28247 } 28248 } 28249 28250 function circle(context, rx, ry, r) { 28251 context.arc(rx, ry, r, 0, Math.PI * 2, false); 28252 } 28253 28254 CRp.arrowShapeImpl = function (name) { 28255 return (impl || (impl = { 28256 'polygon': polygon, 28257 'triangle-backcurve': triangleBackcurve, 28258 'triangle-tee': triangleTee, 28259 'triangle-cross': triangleTee, 28260 'circle': circle 28261 }))[name]; 28262 }; 28263 28264 var CRp$1 = {}; 28265 28266 CRp$1.drawElement = function (context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity) { 28267 var r = this; 28268 28269 if (ele.isNode()) { 28270 r.drawNode(context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity); 28271 } else { 28272 r.drawEdge(context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity); 28273 } 28274 }; 28275 28276 CRp$1.drawElementOverlay = function (context, ele) { 28277 var r = this; 28278 28279 if (ele.isNode()) { 28280 r.drawNodeOverlay(context, ele); 28281 } else { 28282 r.drawEdgeOverlay(context, ele); 28283 } 28284 }; 28285 28286 CRp$1.drawCachedElementPortion = function (context, ele, eleTxrCache, pxRatio, lvl, reason, getRotation, getOpacity) { 28287 var r = this; 28288 var bb = eleTxrCache.getBoundingBox(ele); 28289 28290 if (bb.w === 0 || bb.h === 0) { 28291 return; 28292 } // ignore zero size case 28293 28294 28295 var eleCache = eleTxrCache.getElement(ele, bb, pxRatio, lvl, reason); 28296 28297 if (eleCache != null) { 28298 var opacity = getOpacity(r, ele); 28299 28300 if (opacity === 0) { 28301 return; 28302 } 28303 28304 var theta = getRotation(r, ele); 28305 var x1 = bb.x1, 28306 y1 = bb.y1, 28307 w = bb.w, 28308 h = bb.h; 28309 var x, y, sx, sy, smooth; 28310 28311 if (theta !== 0) { 28312 var rotPt = eleTxrCache.getRotationPoint(ele); 28313 sx = rotPt.x; 28314 sy = rotPt.y; 28315 context.translate(sx, sy); 28316 context.rotate(theta); 28317 smooth = r.getImgSmoothing(context); 28318 28319 if (!smooth) { 28320 r.setImgSmoothing(context, true); 28321 } 28322 28323 var off = eleTxrCache.getRotationOffset(ele); 28324 x = off.x; 28325 y = off.y; 28326 } else { 28327 x = x1; 28328 y = y1; 28329 } 28330 28331 var oldGlobalAlpha; 28332 28333 if (opacity !== 1) { 28334 oldGlobalAlpha = context.globalAlpha; 28335 context.globalAlpha = oldGlobalAlpha * opacity; 28336 } 28337 28338 context.drawImage(eleCache.texture.canvas, eleCache.x, 0, eleCache.width, eleCache.height, x, y, w, h); 28339 28340 if (opacity !== 1) { 28341 context.globalAlpha = oldGlobalAlpha; 28342 } 28343 28344 if (theta !== 0) { 28345 context.rotate(-theta); 28346 context.translate(-sx, -sy); 28347 28348 if (!smooth) { 28349 r.setImgSmoothing(context, false); 28350 } 28351 } 28352 } else { 28353 eleTxrCache.drawElement(context, ele); // direct draw fallback 28354 } 28355 }; 28356 28357 var getZeroRotation = function getZeroRotation() { 28358 return 0; 28359 }; 28360 28361 var getLabelRotation = function getLabelRotation(r, ele) { 28362 return r.getTextAngle(ele, null); 28363 }; 28364 28365 var getSourceLabelRotation = function getSourceLabelRotation(r, ele) { 28366 return r.getTextAngle(ele, 'source'); 28367 }; 28368 28369 var getTargetLabelRotation = function getTargetLabelRotation(r, ele) { 28370 return r.getTextAngle(ele, 'target'); 28371 }; 28372 28373 var getOpacity = function getOpacity(r, ele) { 28374 return ele.effectiveOpacity(); 28375 }; 28376 28377 var getTextOpacity = function getTextOpacity(e, ele) { 28378 return ele.pstyle('text-opacity').pfValue * ele.effectiveOpacity(); 28379 }; 28380 28381 CRp$1.drawCachedElement = function (context, ele, pxRatio, extent, lvl, requestHighQuality) { 28382 var r = this; 28383 var _r$data = r.data, 28384 eleTxrCache = _r$data.eleTxrCache, 28385 lblTxrCache = _r$data.lblTxrCache, 28386 slbTxrCache = _r$data.slbTxrCache, 28387 tlbTxrCache = _r$data.tlbTxrCache; 28388 var bb = ele.boundingBox(); 28389 var reason = requestHighQuality === true ? eleTxrCache.reasons.highQuality : null; 28390 28391 if (bb.w === 0 || bb.h === 0 || !ele.visible()) { 28392 return; 28393 } 28394 28395 if (!extent || boundingBoxesIntersect(bb, extent)) { 28396 var isEdge = ele.isEdge(); 28397 28398 var badLine = ele.element()._private.rscratch.badLine; 28399 28400 r.drawCachedElementPortion(context, ele, eleTxrCache, pxRatio, lvl, reason, getZeroRotation, getOpacity); 28401 28402 if (!isEdge || !badLine) { 28403 r.drawCachedElementPortion(context, ele, lblTxrCache, pxRatio, lvl, reason, getLabelRotation, getTextOpacity); 28404 } 28405 28406 if (isEdge && !badLine) { 28407 r.drawCachedElementPortion(context, ele, slbTxrCache, pxRatio, lvl, reason, getSourceLabelRotation, getTextOpacity); 28408 r.drawCachedElementPortion(context, ele, tlbTxrCache, pxRatio, lvl, reason, getTargetLabelRotation, getTextOpacity); 28409 } 28410 28411 r.drawElementOverlay(context, ele); 28412 } 28413 }; 28414 28415 CRp$1.drawElements = function (context, eles) { 28416 var r = this; 28417 28418 for (var i = 0; i < eles.length; i++) { 28419 var ele = eles[i]; 28420 r.drawElement(context, ele); 28421 } 28422 }; 28423 28424 CRp$1.drawCachedElements = function (context, eles, pxRatio, extent) { 28425 var r = this; 28426 28427 for (var i = 0; i < eles.length; i++) { 28428 var ele = eles[i]; 28429 r.drawCachedElement(context, ele, pxRatio, extent); 28430 } 28431 }; 28432 28433 CRp$1.drawCachedNodes = function (context, eles, pxRatio, extent) { 28434 var r = this; 28435 28436 for (var i = 0; i < eles.length; i++) { 28437 var ele = eles[i]; 28438 28439 if (!ele.isNode()) { 28440 continue; 28441 } 28442 28443 r.drawCachedElement(context, ele, pxRatio, extent); 28444 } 28445 }; 28446 28447 CRp$1.drawLayeredElements = function (context, eles, pxRatio, extent) { 28448 var r = this; 28449 var layers = r.data.lyrTxrCache.getLayers(eles, pxRatio); 28450 28451 if (layers) { 28452 for (var i = 0; i < layers.length; i++) { 28453 var layer = layers[i]; 28454 var bb = layer.bb; 28455 28456 if (bb.w === 0 || bb.h === 0) { 28457 continue; 28458 } 28459 28460 context.drawImage(layer.canvas, bb.x1, bb.y1, bb.w, bb.h); 28461 } 28462 } else { 28463 // fall back on plain caching if no layers 28464 r.drawCachedElements(context, eles, pxRatio, extent); 28465 } 28466 }; 28467 28468 /* global Path2D */ 28469 var CRp$2 = {}; 28470 28471 CRp$2.drawEdge = function (context, edge, shiftToOriginWithBb) { 28472 var drawLabel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 28473 var shouldDrawOverlay = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 28474 var shouldDrawOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 28475 var r = this; 28476 var rs = edge._private.rscratch; 28477 28478 if (shouldDrawOpacity && !edge.visible()) { 28479 return; 28480 } // if bezier ctrl pts can not be calculated, then die 28481 28482 28483 if (rs.badLine || rs.allpts == null || isNaN(rs.allpts[0])) { 28484 // isNaN in case edge is impossible and browser bugs (e.g. safari) 28485 return; 28486 } 28487 28488 var bb; 28489 28490 if (shiftToOriginWithBb) { 28491 bb = shiftToOriginWithBb; 28492 context.translate(-bb.x1, -bb.y1); 28493 } 28494 28495 var opacity = shouldDrawOpacity ? edge.pstyle('opacity').value : 1; 28496 var lineStyle = edge.pstyle('line-style').value; 28497 var edgeWidth = edge.pstyle('width').pfValue; 28498 var lineCap = edge.pstyle('line-cap').value; 28499 28500 var drawLine = function drawLine() { 28501 var strokeOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : opacity; 28502 context.lineWidth = edgeWidth; 28503 context.lineCap = lineCap; 28504 r.eleStrokeStyle(context, edge, strokeOpacity); 28505 r.drawEdgePath(edge, context, rs.allpts, lineStyle); 28506 context.lineCap = 'butt'; // reset for other drawing functions 28507 }; 28508 28509 var drawOverlay = function drawOverlay() { 28510 if (!shouldDrawOverlay) { 28511 return; 28512 } 28513 28514 r.drawEdgeOverlay(context, edge); 28515 }; 28516 28517 var drawArrows = function drawArrows() { 28518 var arrowOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : opacity; 28519 r.drawArrowheads(context, edge, arrowOpacity); 28520 }; 28521 28522 var drawText = function drawText() { 28523 r.drawElementText(context, edge, null, drawLabel); 28524 }; 28525 28526 context.lineJoin = 'round'; 28527 var ghost = edge.pstyle('ghost').value === 'yes'; 28528 28529 if (ghost) { 28530 var gx = edge.pstyle('ghost-offset-x').pfValue; 28531 var gy = edge.pstyle('ghost-offset-y').pfValue; 28532 var ghostOpacity = edge.pstyle('ghost-opacity').value; 28533 var effectiveGhostOpacity = opacity * ghostOpacity; 28534 context.translate(gx, gy); 28535 drawLine(effectiveGhostOpacity); 28536 drawArrows(effectiveGhostOpacity); 28537 context.translate(-gx, -gy); 28538 } 28539 28540 drawLine(); 28541 drawArrows(); 28542 drawOverlay(); 28543 drawText(); 28544 28545 if (shiftToOriginWithBb) { 28546 context.translate(bb.x1, bb.y1); 28547 } 28548 }; 28549 28550 CRp$2.drawEdgeOverlay = function (context, edge) { 28551 if (!edge.visible()) { 28552 return; 28553 } 28554 28555 var overlayOpacity = edge.pstyle('overlay-opacity').value; 28556 28557 if (overlayOpacity === 0) { 28558 return; 28559 } 28560 28561 var r = this; 28562 var usePaths = r.usePaths(); 28563 var rs = edge._private.rscratch; 28564 var overlayPadding = edge.pstyle('overlay-padding').pfValue; 28565 var overlayWidth = 2 * overlayPadding; 28566 var overlayColor = edge.pstyle('overlay-color').value; 28567 context.lineWidth = overlayWidth; 28568 28569 if (rs.edgeType === 'self' && !usePaths) { 28570 context.lineCap = 'butt'; 28571 } else { 28572 context.lineCap = 'round'; 28573 } 28574 28575 r.colorStrokeStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); 28576 r.drawEdgePath(edge, context, rs.allpts, 'solid'); 28577 }; 28578 28579 CRp$2.drawEdgePath = function (edge, context, pts, type) { 28580 var rs = edge._private.rscratch; 28581 var canvasCxt = context; 28582 var path; 28583 var pathCacheHit = false; 28584 var usePaths = this.usePaths(); 28585 var lineDashPattern = edge.pstyle('line-dash-pattern').pfValue; 28586 var lineDashOffset = edge.pstyle('line-dash-offset').pfValue; 28587 28588 if (usePaths) { 28589 var pathCacheKey = pts.join('$'); 28590 var keyMatches = rs.pathCacheKey && rs.pathCacheKey === pathCacheKey; 28591 28592 if (keyMatches) { 28593 path = context = rs.pathCache; 28594 pathCacheHit = true; 28595 } else { 28596 path = context = new Path2D(); 28597 rs.pathCacheKey = pathCacheKey; 28598 rs.pathCache = path; 28599 } 28600 } 28601 28602 if (canvasCxt.setLineDash) { 28603 // for very outofdate browsers 28604 switch (type) { 28605 case 'dotted': 28606 canvasCxt.setLineDash([1, 1]); 28607 break; 28608 28609 case 'dashed': 28610 canvasCxt.setLineDash(lineDashPattern); 28611 canvasCxt.lineDashOffset = lineDashOffset; 28612 break; 28613 28614 case 'solid': 28615 canvasCxt.setLineDash([]); 28616 break; 28617 } 28618 } 28619 28620 if (!pathCacheHit && !rs.badLine) { 28621 if (context.beginPath) { 28622 context.beginPath(); 28623 } 28624 28625 context.moveTo(pts[0], pts[1]); 28626 28627 switch (rs.edgeType) { 28628 case 'bezier': 28629 case 'self': 28630 case 'compound': 28631 case 'multibezier': 28632 for (var i = 2; i + 3 < pts.length; i += 4) { 28633 context.quadraticCurveTo(pts[i], pts[i + 1], pts[i + 2], pts[i + 3]); 28634 } 28635 28636 break; 28637 28638 case 'straight': 28639 case 'segments': 28640 case 'haystack': 28641 for (var _i = 2; _i + 1 < pts.length; _i += 2) { 28642 context.lineTo(pts[_i], pts[_i + 1]); 28643 } 28644 28645 break; 28646 } 28647 } 28648 28649 context = canvasCxt; 28650 28651 if (usePaths) { 28652 context.stroke(path); 28653 } else { 28654 context.stroke(); 28655 } // reset any line dashes 28656 28657 28658 if (context.setLineDash) { 28659 // for very outofdate browsers 28660 context.setLineDash([]); 28661 } 28662 }; 28663 28664 CRp$2.drawArrowheads = function (context, edge, opacity) { 28665 var rs = edge._private.rscratch; 28666 var isHaystack = rs.edgeType === 'haystack'; 28667 28668 if (!isHaystack) { 28669 this.drawArrowhead(context, edge, 'source', rs.arrowStartX, rs.arrowStartY, rs.srcArrowAngle, opacity); 28670 } 28671 28672 this.drawArrowhead(context, edge, 'mid-target', rs.midX, rs.midY, rs.midtgtArrowAngle, opacity); 28673 this.drawArrowhead(context, edge, 'mid-source', rs.midX, rs.midY, rs.midsrcArrowAngle, opacity); 28674 28675 if (!isHaystack) { 28676 this.drawArrowhead(context, edge, 'target', rs.arrowEndX, rs.arrowEndY, rs.tgtArrowAngle, opacity); 28677 } 28678 }; 28679 28680 CRp$2.drawArrowhead = function (context, edge, prefix, x, y, angle, opacity) { 28681 if (isNaN(x) || x == null || isNaN(y) || y == null || isNaN(angle) || angle == null) { 28682 return; 28683 } 28684 28685 var self = this; 28686 var arrowShape = edge.pstyle(prefix + '-arrow-shape').value; 28687 28688 if (arrowShape === 'none') { 28689 return; 28690 } 28691 28692 var arrowClearFill = edge.pstyle(prefix + '-arrow-fill').value === 'hollow' ? 'both' : 'filled'; 28693 var arrowFill = edge.pstyle(prefix + '-arrow-fill').value; 28694 var edgeWidth = edge.pstyle('width').pfValue; 28695 var edgeOpacity = edge.pstyle('opacity').value; 28696 28697 if (opacity === undefined) { 28698 opacity = edgeOpacity; 28699 } 28700 28701 var gco = context.globalCompositeOperation; 28702 28703 if (opacity !== 1 || arrowFill === 'hollow') { 28704 // then extra clear is needed 28705 context.globalCompositeOperation = 'destination-out'; 28706 self.colorFillStyle(context, 255, 255, 255, 1); 28707 self.colorStrokeStyle(context, 255, 255, 255, 1); 28708 self.drawArrowShape(edge, context, arrowClearFill, edgeWidth, arrowShape, x, y, angle); 28709 context.globalCompositeOperation = gco; 28710 } // otherwise, the opaque arrow clears it for free :) 28711 28712 28713 var color = edge.pstyle(prefix + '-arrow-color').value; 28714 self.colorFillStyle(context, color[0], color[1], color[2], opacity); 28715 self.colorStrokeStyle(context, color[0], color[1], color[2], opacity); 28716 self.drawArrowShape(edge, context, arrowFill, edgeWidth, arrowShape, x, y, angle); 28717 }; 28718 28719 CRp$2.drawArrowShape = function (edge, context, fill, edgeWidth, shape, x, y, angle) { 28720 var r = this; 28721 var usePaths = this.usePaths() && shape !== 'triangle-cross'; 28722 var pathCacheHit = false; 28723 var path; 28724 var canvasContext = context; 28725 var translation = { 28726 x: x, 28727 y: y 28728 }; 28729 var scale = edge.pstyle('arrow-scale').value; 28730 var size = this.getArrowWidth(edgeWidth, scale); 28731 var shapeImpl = r.arrowShapes[shape]; 28732 28733 if (usePaths) { 28734 var cache = r.arrowPathCache = r.arrowPathCache || []; 28735 var key = hashString(shape); 28736 var cachedPath = cache[key]; 28737 28738 if (cachedPath != null) { 28739 path = context = cachedPath; 28740 pathCacheHit = true; 28741 } else { 28742 path = context = new Path2D(); 28743 cache[key] = path; 28744 } 28745 } 28746 28747 if (!pathCacheHit) { 28748 if (context.beginPath) { 28749 context.beginPath(); 28750 } 28751 28752 if (usePaths) { 28753 // store in the path cache with values easily manipulated later 28754 shapeImpl.draw(context, 1, 0, { 28755 x: 0, 28756 y: 0 28757 }, 1); 28758 } else { 28759 shapeImpl.draw(context, size, angle, translation, edgeWidth); 28760 } 28761 28762 if (context.closePath) { 28763 context.closePath(); 28764 } 28765 } 28766 28767 context = canvasContext; 28768 28769 if (usePaths) { 28770 // set transform to arrow position/orientation 28771 context.translate(x, y); 28772 context.rotate(angle); 28773 context.scale(size, size); 28774 } 28775 28776 if (fill === 'filled' || fill === 'both') { 28777 if (usePaths) { 28778 context.fill(path); 28779 } else { 28780 context.fill(); 28781 } 28782 } 28783 28784 if (fill === 'hollow' || fill === 'both') { 28785 context.lineWidth = (shapeImpl.matchEdgeWidth ? edgeWidth : 1) / (usePaths ? size : 1); 28786 context.lineJoin = 'miter'; 28787 28788 if (usePaths) { 28789 context.stroke(path); 28790 } else { 28791 context.stroke(); 28792 } 28793 } 28794 28795 if (usePaths) { 28796 // reset transform by applying inverse 28797 context.scale(1 / size, 1 / size); 28798 context.rotate(-angle); 28799 context.translate(-x, -y); 28800 } 28801 }; 28802 28803 var CRp$3 = {}; 28804 28805 CRp$3.safeDrawImage = function (context, img, ix, iy, iw, ih, x, y, w, h) { 28806 // detect problematic cases for old browsers with bad images (cheaper than try-catch) 28807 if (iw <= 0 || ih <= 0 || w <= 0 || h <= 0) { 28808 return; 28809 } 28810 28811 context.drawImage(img, ix, iy, iw, ih, x, y, w, h); 28812 }; 28813 28814 CRp$3.drawInscribedImage = function (context, img, node, index, nodeOpacity) { 28815 var r = this; 28816 var pos = node.position(); 28817 var nodeX = pos.x; 28818 var nodeY = pos.y; 28819 var styleObj = node.cy().style(); 28820 var getIndexedStyle = styleObj.getIndexedStyle.bind(styleObj); 28821 var fit = getIndexedStyle(node, 'background-fit', 'value', index); 28822 var repeat = getIndexedStyle(node, 'background-repeat', 'value', index); 28823 var nodeW = node.width(); 28824 var nodeH = node.height(); 28825 var paddingX2 = node.padding() * 2; 28826 var nodeTW = nodeW + (getIndexedStyle(node, 'background-width-relative-to', 'value', index) === 'inner' ? 0 : paddingX2); 28827 var nodeTH = nodeH + (getIndexedStyle(node, 'background-height-relative-to', 'value', index) === 'inner' ? 0 : paddingX2); 28828 var rs = node._private.rscratch; 28829 var clip = getIndexedStyle(node, 'background-clip', 'value', index); 28830 var shouldClip = clip === 'node'; 28831 var imgOpacity = getIndexedStyle(node, 'background-image-opacity', 'value', index) * nodeOpacity; 28832 var imgW = img.width || img.cachedW; 28833 var imgH = img.height || img.cachedH; // workaround for broken browsers like ie 28834 28835 if (null == imgW || null == imgH) { 28836 document.body.appendChild(img); // eslint-disable-line no-undef 28837 28838 imgW = img.cachedW = img.width || img.offsetWidth; 28839 imgH = img.cachedH = img.height || img.offsetHeight; 28840 document.body.removeChild(img); // eslint-disable-line no-undef 28841 } 28842 28843 var w = imgW; 28844 var h = imgH; 28845 28846 if (getIndexedStyle(node, 'background-width', 'value', index) !== 'auto') { 28847 if (getIndexedStyle(node, 'background-width', 'units', index) === '%') { 28848 w = getIndexedStyle(node, 'background-width', 'pfValue', index) * nodeTW; 28849 } else { 28850 w = getIndexedStyle(node, 'background-width', 'pfValue', index); 28851 } 28852 } 28853 28854 if (getIndexedStyle(node, 'background-height', 'value', index) !== 'auto') { 28855 if (getIndexedStyle(node, 'background-height', 'units', index) === '%') { 28856 h = getIndexedStyle(node, 'background-height', 'pfValue', index) * nodeTH; 28857 } else { 28858 h = getIndexedStyle(node, 'background-height', 'pfValue', index); 28859 } 28860 } 28861 28862 if (w === 0 || h === 0) { 28863 return; // no point in drawing empty image (and chrome is broken in this case) 28864 } 28865 28866 if (fit === 'contain') { 28867 var scale = Math.min(nodeTW / w, nodeTH / h); 28868 w *= scale; 28869 h *= scale; 28870 } else if (fit === 'cover') { 28871 var scale = Math.max(nodeTW / w, nodeTH / h); 28872 w *= scale; 28873 h *= scale; 28874 } 28875 28876 var x = nodeX - nodeTW / 2; // left 28877 28878 var posXUnits = getIndexedStyle(node, 'background-position-x', 'units', index); 28879 var posXPfVal = getIndexedStyle(node, 'background-position-x', 'pfValue', index); 28880 28881 if (posXUnits === '%') { 28882 x += (nodeTW - w) * posXPfVal; 28883 } else { 28884 x += posXPfVal; 28885 } 28886 28887 var offXUnits = getIndexedStyle(node, 'background-offset-x', 'units', index); 28888 var offXPfVal = getIndexedStyle(node, 'background-offset-x', 'pfValue', index); 28889 28890 if (offXUnits === '%') { 28891 x += (nodeTW - w) * offXPfVal; 28892 } else { 28893 x += offXPfVal; 28894 } 28895 28896 var y = nodeY - nodeTH / 2; // top 28897 28898 var posYUnits = getIndexedStyle(node, 'background-position-y', 'units', index); 28899 var posYPfVal = getIndexedStyle(node, 'background-position-y', 'pfValue', index); 28900 28901 if (posYUnits === '%') { 28902 y += (nodeTH - h) * posYPfVal; 28903 } else { 28904 y += posYPfVal; 28905 } 28906 28907 var offYUnits = getIndexedStyle(node, 'background-offset-y', 'units', index); 28908 var offYPfVal = getIndexedStyle(node, 'background-offset-y', 'pfValue', index); 28909 28910 if (offYUnits === '%') { 28911 y += (nodeTH - h) * offYPfVal; 28912 } else { 28913 y += offYPfVal; 28914 } 28915 28916 if (rs.pathCache) { 28917 x -= nodeX; 28918 y -= nodeY; 28919 nodeX = 0; 28920 nodeY = 0; 28921 } 28922 28923 var gAlpha = context.globalAlpha; 28924 context.globalAlpha = imgOpacity; 28925 28926 if (repeat === 'no-repeat') { 28927 if (shouldClip) { 28928 context.save(); 28929 28930 if (rs.pathCache) { 28931 context.clip(rs.pathCache); 28932 } else { 28933 r.nodeShapes[r.getNodeShape(node)].draw(context, nodeX, nodeY, nodeTW, nodeTH); 28934 context.clip(); 28935 } 28936 } 28937 28938 r.safeDrawImage(context, img, 0, 0, imgW, imgH, x, y, w, h); 28939 28940 if (shouldClip) { 28941 context.restore(); 28942 } 28943 } else { 28944 var pattern = context.createPattern(img, repeat); 28945 context.fillStyle = pattern; 28946 r.nodeShapes[r.getNodeShape(node)].draw(context, nodeX, nodeY, nodeTW, nodeTH); 28947 context.translate(x, y); 28948 context.fill(); 28949 context.translate(-x, -y); 28950 } 28951 28952 context.globalAlpha = gAlpha; 28953 }; 28954 28955 var CRp$4 = {}; 28956 28957 CRp$4.eleTextBiggerThanMin = function (ele, scale) { 28958 if (!scale) { 28959 var zoom = ele.cy().zoom(); 28960 var pxRatio = this.getPixelRatio(); 28961 var lvl = Math.ceil(log2(zoom * pxRatio)); // the effective texture level 28962 28963 scale = Math.pow(2, lvl); 28964 } 28965 28966 var computedSize = ele.pstyle('font-size').pfValue * scale; 28967 var minSize = ele.pstyle('min-zoomed-font-size').pfValue; 28968 28969 if (computedSize < minSize) { 28970 return false; 28971 } 28972 28973 return true; 28974 }; 28975 28976 CRp$4.drawElementText = function (context, ele, shiftToOriginWithBb, force, prefix) { 28977 var useEleOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 28978 var r = this; 28979 28980 if (force == null) { 28981 if (useEleOpacity && !r.eleTextBiggerThanMin(ele)) { 28982 return; 28983 } 28984 } else if (force === false) { 28985 return; 28986 } 28987 28988 if (ele.isNode()) { 28989 var label = ele.pstyle('label'); 28990 28991 if (!label || !label.value) { 28992 return; 28993 } 28994 28995 var justification = r.getLabelJustification(ele); 28996 context.textAlign = justification; 28997 context.textBaseline = 'bottom'; 28998 } else { 28999 var badLine = ele.element()._private.rscratch.badLine; 29000 29001 var _label = ele.pstyle('label'); 29002 29003 var srcLabel = ele.pstyle('source-label'); 29004 var tgtLabel = ele.pstyle('target-label'); 29005 29006 if (badLine || (!_label || !_label.value) && (!srcLabel || !srcLabel.value) && (!tgtLabel || !tgtLabel.value)) { 29007 return; 29008 } 29009 29010 context.textAlign = 'center'; 29011 context.textBaseline = 'bottom'; 29012 } 29013 29014 var applyRotation = !shiftToOriginWithBb; 29015 var bb; 29016 29017 if (shiftToOriginWithBb) { 29018 bb = shiftToOriginWithBb; 29019 context.translate(-bb.x1, -bb.y1); 29020 } 29021 29022 if (prefix == null) { 29023 r.drawText(context, ele, null, applyRotation, useEleOpacity); 29024 29025 if (ele.isEdge()) { 29026 r.drawText(context, ele, 'source', applyRotation, useEleOpacity); 29027 r.drawText(context, ele, 'target', applyRotation, useEleOpacity); 29028 } 29029 } else { 29030 r.drawText(context, ele, prefix, applyRotation, useEleOpacity); 29031 } 29032 29033 if (shiftToOriginWithBb) { 29034 context.translate(bb.x1, bb.y1); 29035 } 29036 }; 29037 29038 CRp$4.getFontCache = function (context) { 29039 var cache; 29040 this.fontCaches = this.fontCaches || []; 29041 29042 for (var i = 0; i < this.fontCaches.length; i++) { 29043 cache = this.fontCaches[i]; 29044 29045 if (cache.context === context) { 29046 return cache; 29047 } 29048 } 29049 29050 cache = { 29051 context: context 29052 }; 29053 this.fontCaches.push(cache); 29054 return cache; 29055 }; // set up canvas context with font 29056 // returns transformed text string 29057 29058 29059 CRp$4.setupTextStyle = function (context, ele) { 29060 var useEleOpacity = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 29061 // Font style 29062 var labelStyle = ele.pstyle('font-style').strValue; 29063 var labelSize = ele.pstyle('font-size').pfValue + 'px'; 29064 var labelFamily = ele.pstyle('font-family').strValue; 29065 var labelWeight = ele.pstyle('font-weight').strValue; 29066 var opacity = useEleOpacity ? ele.effectiveOpacity() * ele.pstyle('text-opacity').value : 1; 29067 var outlineOpacity = ele.pstyle('text-outline-opacity').value * opacity; 29068 var color = ele.pstyle('color').value; 29069 var outlineColor = ele.pstyle('text-outline-color').value; 29070 context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily; 29071 context.lineJoin = 'round'; // so text outlines aren't jagged 29072 29073 this.colorFillStyle(context, color[0], color[1], color[2], opacity); 29074 this.colorStrokeStyle(context, outlineColor[0], outlineColor[1], outlineColor[2], outlineOpacity); 29075 }; // TODO ensure re-used 29076 29077 29078 function roundRect(ctx, x, y, width, height) { 29079 var radius = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 5; 29080 ctx.beginPath(); 29081 ctx.moveTo(x + radius, y); 29082 ctx.lineTo(x + width - radius, y); 29083 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 29084 ctx.lineTo(x + width, y + height - radius); 29085 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 29086 ctx.lineTo(x + radius, y + height); 29087 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 29088 ctx.lineTo(x, y + radius); 29089 ctx.quadraticCurveTo(x, y, x + radius, y); 29090 ctx.closePath(); 29091 ctx.fill(); 29092 } 29093 29094 CRp$4.getTextAngle = function (ele, prefix) { 29095 var theta; 29096 var _p = ele._private; 29097 var rscratch = _p.rscratch; 29098 var pdash = prefix ? prefix + '-' : ''; 29099 var rotation = ele.pstyle(pdash + 'text-rotation'); 29100 var textAngle = getPrefixedProperty(rscratch, 'labelAngle', prefix); 29101 29102 if (rotation.strValue === 'autorotate') { 29103 theta = ele.isEdge() ? textAngle : 0; 29104 } else if (rotation.strValue === 'none') { 29105 theta = 0; 29106 } else { 29107 theta = rotation.pfValue; 29108 } 29109 29110 return theta; 29111 }; 29112 29113 CRp$4.drawText = function (context, ele, prefix) { 29114 var applyRotation = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 29115 var useEleOpacity = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 29116 var _p = ele._private; 29117 var rscratch = _p.rscratch; 29118 var parentOpacity = useEleOpacity ? ele.effectiveOpacity() : 1; 29119 29120 if (useEleOpacity && (parentOpacity === 0 || ele.pstyle('text-opacity').value === 0)) { 29121 return; 29122 } // use 'main' as an alias for the main label (i.e. null prefix) 29123 29124 29125 if (prefix === 'main') { 29126 prefix = null; 29127 } 29128 29129 var textX = getPrefixedProperty(rscratch, 'labelX', prefix); 29130 var textY = getPrefixedProperty(rscratch, 'labelY', prefix); 29131 var orgTextX, orgTextY; // used for rotation 29132 29133 var text = this.getLabelText(ele, prefix); 29134 29135 if (text != null && text !== '' && !isNaN(textX) && !isNaN(textY)) { 29136 this.setupTextStyle(context, ele, useEleOpacity); 29137 var pdash = prefix ? prefix + '-' : ''; 29138 var textW = getPrefixedProperty(rscratch, 'labelWidth', prefix); 29139 var textH = getPrefixedProperty(rscratch, 'labelHeight', prefix); 29140 var marginX = ele.pstyle(pdash + 'text-margin-x').pfValue; 29141 var marginY = ele.pstyle(pdash + 'text-margin-y').pfValue; 29142 var isEdge = ele.isEdge(); 29143 var halign = ele.pstyle('text-halign').value; 29144 var valign = ele.pstyle('text-valign').value; 29145 29146 if (isEdge) { 29147 halign = 'center'; 29148 valign = 'center'; 29149 } 29150 29151 textX += marginX; 29152 textY += marginY; 29153 var theta; 29154 29155 if (!applyRotation) { 29156 theta = 0; 29157 } else { 29158 theta = this.getTextAngle(ele, prefix); 29159 } 29160 29161 if (theta !== 0) { 29162 orgTextX = textX; 29163 orgTextY = textY; 29164 context.translate(orgTextX, orgTextY); 29165 context.rotate(theta); 29166 textX = 0; 29167 textY = 0; 29168 } 29169 29170 switch (valign) { 29171 case 'top': 29172 break; 29173 29174 case 'center': 29175 textY += textH / 2; 29176 break; 29177 29178 case 'bottom': 29179 textY += textH; 29180 break; 29181 } 29182 29183 var backgroundOpacity = ele.pstyle('text-background-opacity').value; 29184 var borderOpacity = ele.pstyle('text-border-opacity').value; 29185 var textBorderWidth = ele.pstyle('text-border-width').pfValue; 29186 var backgroundPadding = ele.pstyle('text-background-padding').pfValue; 29187 29188 if (backgroundOpacity > 0 || textBorderWidth > 0 && borderOpacity > 0) { 29189 var bgX = textX - backgroundPadding; 29190 29191 switch (halign) { 29192 case 'left': 29193 bgX -= textW; 29194 break; 29195 29196 case 'center': 29197 bgX -= textW / 2; 29198 break; 29199 } 29200 29201 var bgY = textY - textH - backgroundPadding; 29202 var bgW = textW + 2 * backgroundPadding; 29203 var bgH = textH + 2 * backgroundPadding; 29204 29205 if (backgroundOpacity > 0) { 29206 var textFill = context.fillStyle; 29207 var textBackgroundColor = ele.pstyle('text-background-color').value; 29208 context.fillStyle = 'rgba(' + textBackgroundColor[0] + ',' + textBackgroundColor[1] + ',' + textBackgroundColor[2] + ',' + backgroundOpacity * parentOpacity + ')'; 29209 var styleShape = ele.pstyle('text-background-shape').strValue; 29210 29211 if (styleShape.indexOf('round') === 0) { 29212 roundRect(context, bgX, bgY, bgW, bgH, 2); 29213 } else { 29214 context.fillRect(bgX, bgY, bgW, bgH); 29215 } 29216 29217 context.fillStyle = textFill; 29218 } 29219 29220 if (textBorderWidth > 0 && borderOpacity > 0) { 29221 var textStroke = context.strokeStyle; 29222 var textLineWidth = context.lineWidth; 29223 var textBorderColor = ele.pstyle('text-border-color').value; 29224 var textBorderStyle = ele.pstyle('text-border-style').value; 29225 context.strokeStyle = 'rgba(' + textBorderColor[0] + ',' + textBorderColor[1] + ',' + textBorderColor[2] + ',' + borderOpacity * parentOpacity + ')'; 29226 context.lineWidth = textBorderWidth; 29227 29228 if (context.setLineDash) { 29229 // for very outofdate browsers 29230 switch (textBorderStyle) { 29231 case 'dotted': 29232 context.setLineDash([1, 1]); 29233 break; 29234 29235 case 'dashed': 29236 context.setLineDash([4, 2]); 29237 break; 29238 29239 case 'double': 29240 context.lineWidth = textBorderWidth / 4; // 50% reserved for white between the two borders 29241 29242 context.setLineDash([]); 29243 break; 29244 29245 case 'solid': 29246 context.setLineDash([]); 29247 break; 29248 } 29249 } 29250 29251 context.strokeRect(bgX, bgY, bgW, bgH); 29252 29253 if (textBorderStyle === 'double') { 29254 var whiteWidth = textBorderWidth / 2; 29255 context.strokeRect(bgX + whiteWidth, bgY + whiteWidth, bgW - whiteWidth * 2, bgH - whiteWidth * 2); 29256 } 29257 29258 if (context.setLineDash) { 29259 // for very outofdate browsers 29260 context.setLineDash([]); 29261 } 29262 29263 context.lineWidth = textLineWidth; 29264 context.strokeStyle = textStroke; 29265 } 29266 } 29267 29268 var lineWidth = 2 * ele.pstyle('text-outline-width').pfValue; // *2 b/c the stroke is drawn centred on the middle 29269 29270 if (lineWidth > 0) { 29271 context.lineWidth = lineWidth; 29272 } 29273 29274 if (ele.pstyle('text-wrap').value === 'wrap') { 29275 var lines = getPrefixedProperty(rscratch, 'labelWrapCachedLines', prefix); 29276 var lineHeight = getPrefixedProperty(rscratch, 'labelLineHeight', prefix); 29277 var halfTextW = textW / 2; 29278 var justification = this.getLabelJustification(ele); 29279 29280 if (justification === 'auto') ; else if (halign === 'left') { 29281 // auto justification : right 29282 if (justification === 'left') { 29283 textX += -textW; 29284 } else if (justification === 'center') { 29285 textX += -halfTextW; 29286 } // else same as auto 29287 29288 } else if (halign === 'center') { 29289 // auto justfication : center 29290 if (justification === 'left') { 29291 textX += -halfTextW; 29292 } else if (justification === 'right') { 29293 textX += halfTextW; 29294 } // else same as auto 29295 29296 } else if (halign === 'right') { 29297 // auto justification : left 29298 if (justification === 'center') { 29299 textX += halfTextW; 29300 } else if (justification === 'right') { 29301 textX += textW; 29302 } // else same as auto 29303 29304 } 29305 29306 switch (valign) { 29307 case 'top': 29308 textY -= (lines.length - 1) * lineHeight; 29309 break; 29310 29311 case 'center': 29312 case 'bottom': 29313 textY -= (lines.length - 1) * lineHeight; 29314 break; 29315 } 29316 29317 for (var l = 0; l < lines.length; l++) { 29318 if (lineWidth > 0) { 29319 context.strokeText(lines[l], textX, textY); 29320 } 29321 29322 context.fillText(lines[l], textX, textY); 29323 textY += lineHeight; 29324 } 29325 } else { 29326 if (lineWidth > 0) { 29327 context.strokeText(text, textX, textY); 29328 } 29329 29330 context.fillText(text, textX, textY); 29331 } 29332 29333 if (theta !== 0) { 29334 context.rotate(-theta); 29335 context.translate(-orgTextX, -orgTextY); 29336 } 29337 } 29338 }; 29339 29340 /* global Path2D */ 29341 var CRp$5 = {}; 29342 29343 CRp$5.drawNode = function (context, node, shiftToOriginWithBb) { 29344 var drawLabel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 29345 var shouldDrawOverlay = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 29346 var shouldDrawOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 29347 var r = this; 29348 var nodeWidth, nodeHeight; 29349 var _p = node._private; 29350 var rs = _p.rscratch; 29351 var pos = node.position(); 29352 29353 if (!number(pos.x) || !number(pos.y)) { 29354 return; // can't draw node with undefined position 29355 } 29356 29357 if (shouldDrawOpacity && !node.visible()) { 29358 return; 29359 } 29360 29361 var eleOpacity = shouldDrawOpacity ? node.effectiveOpacity() : 1; 29362 var usePaths = r.usePaths(); 29363 var path; 29364 var pathCacheHit = false; 29365 var padding = node.padding(); 29366 nodeWidth = node.width() + 2 * padding; 29367 nodeHeight = node.height() + 2 * padding; // 29368 // setup shift 29369 29370 var bb; 29371 29372 if (shiftToOriginWithBb) { 29373 bb = shiftToOriginWithBb; 29374 context.translate(-bb.x1, -bb.y1); 29375 } // 29376 // load bg image 29377 29378 29379 var bgImgProp = node.pstyle('background-image'); 29380 var urls = bgImgProp.value; 29381 var urlDefined = new Array(urls.length); 29382 var image = new Array(urls.length); 29383 var numImages = 0; 29384 29385 for (var i = 0; i < urls.length; i++) { 29386 var url = urls[i]; 29387 var defd = urlDefined[i] = url != null && url !== 'none'; 29388 29389 if (defd) { 29390 var bgImgCrossOrigin = node.cy().style().getIndexedStyle(node, 'background-image-crossorigin', 'value', i); 29391 numImages++; // get image, and if not loaded then ask to redraw when later loaded 29392 29393 image[i] = r.getCachedImage(url, bgImgCrossOrigin, function () { 29394 _p.backgroundTimestamp = Date.now(); 29395 node.emitAndNotify('background'); 29396 }); 29397 } 29398 } // 29399 // setup styles 29400 29401 29402 var darkness = node.pstyle('background-blacken').value; 29403 var borderWidth = node.pstyle('border-width').pfValue; 29404 var bgOpacity = node.pstyle('background-opacity').value * eleOpacity; 29405 var borderColor = node.pstyle('border-color').value; 29406 var borderStyle = node.pstyle('border-style').value; 29407 var borderOpacity = node.pstyle('border-opacity').value * eleOpacity; 29408 context.lineJoin = 'miter'; // so borders are square with the node shape 29409 29410 var setupShapeColor = function setupShapeColor() { 29411 var bgOpy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : bgOpacity; 29412 r.eleFillStyle(context, node, bgOpy); 29413 }; 29414 29415 var setupBorderColor = function setupBorderColor() { 29416 var bdrOpy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : borderOpacity; 29417 r.colorStrokeStyle(context, borderColor[0], borderColor[1], borderColor[2], bdrOpy); 29418 }; // 29419 // setup shape 29420 29421 29422 var styleShape = node.pstyle('shape').strValue; 29423 var shapePts = node.pstyle('shape-polygon-points').pfValue; 29424 29425 if (usePaths) { 29426 context.translate(pos.x, pos.y); 29427 var pathCache = r.nodePathCache = r.nodePathCache || []; 29428 var key = hashStrings(styleShape === 'polygon' ? styleShape + ',' + shapePts.join(',') : styleShape, '' + nodeHeight, '' + nodeWidth); 29429 var cachedPath = pathCache[key]; 29430 29431 if (cachedPath != null) { 29432 path = cachedPath; 29433 pathCacheHit = true; 29434 rs.pathCache = path; 29435 } else { 29436 path = new Path2D(); 29437 pathCache[key] = rs.pathCache = path; 29438 } 29439 } 29440 29441 var drawShape = function drawShape() { 29442 if (!pathCacheHit) { 29443 var npos = pos; 29444 29445 if (usePaths) { 29446 npos = { 29447 x: 0, 29448 y: 0 29449 }; 29450 } 29451 29452 r.nodeShapes[r.getNodeShape(node)].draw(path || context, npos.x, npos.y, nodeWidth, nodeHeight); 29453 } 29454 29455 if (usePaths) { 29456 context.fill(path); 29457 } else { 29458 context.fill(); 29459 } 29460 }; 29461 29462 var drawImages = function drawImages() { 29463 var nodeOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : eleOpacity; 29464 var prevBging = _p.backgrounding; 29465 var totalCompleted = 0; 29466 29467 for (var _i = 0; _i < image.length; _i++) { 29468 if (urlDefined[_i] && image[_i].complete && !image[_i].error) { 29469 totalCompleted++; 29470 r.drawInscribedImage(context, image[_i], node, _i, nodeOpacity); 29471 } 29472 } 29473 29474 _p.backgrounding = !(totalCompleted === numImages); 29475 29476 if (prevBging !== _p.backgrounding) { 29477 // update style b/c :backgrounding state changed 29478 node.updateStyle(false); 29479 } 29480 }; 29481 29482 var drawPie = function drawPie() { 29483 var redrawShape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 29484 var pieOpacity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : eleOpacity; 29485 29486 if (r.hasPie(node)) { 29487 r.drawPie(context, node, pieOpacity); // redraw/restore path if steps after pie need it 29488 29489 if (redrawShape) { 29490 if (!usePaths) { 29491 r.nodeShapes[r.getNodeShape(node)].draw(context, pos.x, pos.y, nodeWidth, nodeHeight); 29492 } 29493 } 29494 } 29495 }; 29496 29497 var darken = function darken() { 29498 var darkenOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : eleOpacity; 29499 var opacity = (darkness > 0 ? darkness : -darkness) * darkenOpacity; 29500 var c = darkness > 0 ? 0 : 255; 29501 29502 if (darkness !== 0) { 29503 r.colorFillStyle(context, c, c, c, opacity); 29504 29505 if (usePaths) { 29506 context.fill(path); 29507 } else { 29508 context.fill(); 29509 } 29510 } 29511 }; 29512 29513 var drawBorder = function drawBorder() { 29514 if (borderWidth > 0) { 29515 context.lineWidth = borderWidth; 29516 context.lineCap = 'butt'; 29517 29518 if (context.setLineDash) { 29519 // for very outofdate browsers 29520 switch (borderStyle) { 29521 case 'dotted': 29522 context.setLineDash([1, 1]); 29523 break; 29524 29525 case 'dashed': 29526 context.setLineDash([4, 2]); 29527 break; 29528 29529 case 'solid': 29530 case 'double': 29531 context.setLineDash([]); 29532 break; 29533 } 29534 } 29535 29536 if (usePaths) { 29537 context.stroke(path); 29538 } else { 29539 context.stroke(); 29540 } 29541 29542 if (borderStyle === 'double') { 29543 context.lineWidth = borderWidth / 3; 29544 var gco = context.globalCompositeOperation; 29545 context.globalCompositeOperation = 'destination-out'; 29546 29547 if (usePaths) { 29548 context.stroke(path); 29549 } else { 29550 context.stroke(); 29551 } 29552 29553 context.globalCompositeOperation = gco; 29554 } // reset in case we changed the border style 29555 29556 29557 if (context.setLineDash) { 29558 // for very outofdate browsers 29559 context.setLineDash([]); 29560 } 29561 } 29562 }; 29563 29564 var drawOverlay = function drawOverlay() { 29565 if (shouldDrawOverlay) { 29566 r.drawNodeOverlay(context, node, pos, nodeWidth, nodeHeight); 29567 } 29568 }; 29569 29570 var drawText = function drawText() { 29571 r.drawElementText(context, node, null, drawLabel); 29572 }; 29573 29574 var ghost = node.pstyle('ghost').value === 'yes'; 29575 29576 if (ghost) { 29577 var gx = node.pstyle('ghost-offset-x').pfValue; 29578 var gy = node.pstyle('ghost-offset-y').pfValue; 29579 var ghostOpacity = node.pstyle('ghost-opacity').value; 29580 var effGhostOpacity = ghostOpacity * eleOpacity; 29581 context.translate(gx, gy); 29582 setupShapeColor(ghostOpacity * bgOpacity); 29583 drawShape(); 29584 drawImages(effGhostOpacity); 29585 drawPie(darkness !== 0 || borderWidth !== 0); 29586 darken(effGhostOpacity); 29587 setupBorderColor(ghostOpacity * borderOpacity); 29588 drawBorder(); 29589 context.translate(-gx, -gy); 29590 } 29591 29592 setupShapeColor(); 29593 drawShape(); 29594 drawImages(); 29595 drawPie(darkness !== 0 || borderWidth !== 0); 29596 darken(); 29597 setupBorderColor(); 29598 drawBorder(); 29599 29600 if (usePaths) { 29601 context.translate(-pos.x, -pos.y); 29602 } 29603 29604 drawText(); 29605 drawOverlay(); // 29606 // clean up shift 29607 29608 if (shiftToOriginWithBb) { 29609 context.translate(bb.x1, bb.y1); 29610 } 29611 }; 29612 29613 CRp$5.drawNodeOverlay = function (context, node, pos, nodeWidth, nodeHeight) { 29614 var r = this; 29615 29616 if (!node.visible()) { 29617 return; 29618 } 29619 29620 var overlayPadding = node.pstyle('overlay-padding').pfValue; 29621 var overlayOpacity = node.pstyle('overlay-opacity').value; 29622 var overlayColor = node.pstyle('overlay-color').value; 29623 29624 if (overlayOpacity > 0) { 29625 pos = pos || node.position(); 29626 29627 if (nodeWidth == null || nodeHeight == null) { 29628 var padding = node.padding(); 29629 nodeWidth = node.width() + 2 * padding; 29630 nodeHeight = node.height() + 2 * padding; 29631 } 29632 29633 r.colorFillStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); 29634 r.nodeShapes['roundrectangle'].draw(context, pos.x, pos.y, nodeWidth + overlayPadding * 2, nodeHeight + overlayPadding * 2); 29635 context.fill(); 29636 } 29637 }; // does the node have at least one pie piece? 29638 29639 29640 CRp$5.hasPie = function (node) { 29641 node = node[0]; // ensure ele ref 29642 29643 return node._private.hasPie; 29644 }; 29645 29646 CRp$5.drawPie = function (context, node, nodeOpacity, pos) { 29647 node = node[0]; // ensure ele ref 29648 29649 pos = pos || node.position(); 29650 var cyStyle = node.cy().style(); 29651 var pieSize = node.pstyle('pie-size'); 29652 var x = pos.x; 29653 var y = pos.y; 29654 var nodeW = node.width(); 29655 var nodeH = node.height(); 29656 var radius = Math.min(nodeW, nodeH) / 2; // must fit in node 29657 29658 var lastPercent = 0; // what % to continue drawing pie slices from on [0, 1] 29659 29660 var usePaths = this.usePaths(); 29661 29662 if (usePaths) { 29663 x = 0; 29664 y = 0; 29665 } 29666 29667 if (pieSize.units === '%') { 29668 radius = radius * pieSize.pfValue; 29669 } else if (pieSize.pfValue !== undefined) { 29670 radius = pieSize.pfValue / 2; 29671 } 29672 29673 for (var i = 1; i <= cyStyle.pieBackgroundN; i++) { 29674 // 1..N 29675 var size = node.pstyle('pie-' + i + '-background-size').value; 29676 var color = node.pstyle('pie-' + i + '-background-color').value; 29677 var opacity = node.pstyle('pie-' + i + '-background-opacity').value * nodeOpacity; 29678 var percent = size / 100; // map integer range [0, 100] to [0, 1] 29679 // percent can't push beyond 1 29680 29681 if (percent + lastPercent > 1) { 29682 percent = 1 - lastPercent; 29683 } 29684 29685 var angleStart = 1.5 * Math.PI + 2 * Math.PI * lastPercent; // start at 12 o'clock and go clockwise 29686 29687 var angleDelta = 2 * Math.PI * percent; 29688 var angleEnd = angleStart + angleDelta; // ignore if 29689 // - zero size 29690 // - we're already beyond the full circle 29691 // - adding the current slice would go beyond the full circle 29692 29693 if (size === 0 || lastPercent >= 1 || lastPercent + percent > 1) { 29694 continue; 29695 } 29696 29697 context.beginPath(); 29698 context.moveTo(x, y); 29699 context.arc(x, y, radius, angleStart, angleEnd); 29700 context.closePath(); 29701 this.colorFillStyle(context, color[0], color[1], color[2], opacity); 29702 context.fill(); 29703 lastPercent += percent; 29704 } 29705 }; 29706 29707 var CRp$6 = {}; 29708 var motionBlurDelay = 100; // var isFirefox = typeof InstallTrigger !== 'undefined'; 29709 29710 CRp$6.getPixelRatio = function () { 29711 var context = this.data.contexts[0]; 29712 29713 if (this.forcedPixelRatio != null) { 29714 return this.forcedPixelRatio; 29715 } 29716 29717 var backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; 29718 return (window.devicePixelRatio || 1) / backingStore; // eslint-disable-line no-undef 29719 }; 29720 29721 CRp$6.paintCache = function (context) { 29722 var caches = this.paintCaches = this.paintCaches || []; 29723 var needToCreateCache = true; 29724 var cache; 29725 29726 for (var i = 0; i < caches.length; i++) { 29727 cache = caches[i]; 29728 29729 if (cache.context === context) { 29730 needToCreateCache = false; 29731 break; 29732 } 29733 } 29734 29735 if (needToCreateCache) { 29736 cache = { 29737 context: context 29738 }; 29739 caches.push(cache); 29740 } 29741 29742 return cache; 29743 }; 29744 29745 CRp$6.createGradientStyleFor = function (context, shapeStyleName, ele, fill, opacity) { 29746 var gradientStyle; 29747 var usePaths = this.usePaths(); 29748 var colors = ele.pstyle(shapeStyleName + '-gradient-stop-colors').value, 29749 positions = ele.pstyle(shapeStyleName + '-gradient-stop-positions').pfValue; 29750 29751 if (fill === 'radial-gradient') { 29752 if (ele.isEdge()) { 29753 var start = ele.sourceEndpoint(), 29754 end = ele.targetEndpoint(), 29755 mid = ele.midpoint(); 29756 var d1 = dist(start, mid); 29757 var d2 = dist(end, mid); 29758 gradientStyle = context.createRadialGradient(mid.x, mid.y, 0, mid.x, mid.y, Math.max(d1, d2)); 29759 } else { 29760 var pos = usePaths ? { 29761 x: 0, 29762 y: 0 29763 } : ele.position(), 29764 width = ele.paddedWidth(), 29765 height = ele.paddedHeight(); 29766 gradientStyle = context.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, Math.max(width, height)); 29767 } 29768 } else { 29769 if (ele.isEdge()) { 29770 var _start = ele.sourceEndpoint(), 29771 _end = ele.targetEndpoint(); 29772 29773 gradientStyle = context.createLinearGradient(_start.x, _start.y, _end.x, _end.y); 29774 } else { 29775 var _pos = usePaths ? { 29776 x: 0, 29777 y: 0 29778 } : ele.position(), 29779 _width = ele.paddedWidth(), 29780 _height = ele.paddedHeight(), 29781 halfWidth = _width / 2, 29782 halfHeight = _height / 2; 29783 29784 var direction = ele.pstyle('background-gradient-direction').value; 29785 29786 switch (direction) { 29787 case 'to-bottom': 29788 gradientStyle = context.createLinearGradient(_pos.x, _pos.y - halfHeight, _pos.x, _pos.y + halfHeight); 29789 break; 29790 29791 case 'to-top': 29792 gradientStyle = context.createLinearGradient(_pos.x, _pos.y + halfHeight, _pos.x, _pos.y - halfHeight); 29793 break; 29794 29795 case 'to-left': 29796 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y, _pos.x - halfWidth, _pos.y); 29797 break; 29798 29799 case 'to-right': 29800 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y, _pos.x + halfWidth, _pos.y); 29801 break; 29802 29803 case 'to-bottom-right': 29804 case 'to-right-bottom': 29805 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y - halfHeight, _pos.x + halfWidth, _pos.y + halfHeight); 29806 break; 29807 29808 case 'to-top-right': 29809 case 'to-right-top': 29810 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y + halfHeight, _pos.x + halfWidth, _pos.y - halfHeight); 29811 break; 29812 29813 case 'to-bottom-left': 29814 case 'to-left-bottom': 29815 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y - halfHeight, _pos.x - halfWidth, _pos.y + halfHeight); 29816 break; 29817 29818 case 'to-top-left': 29819 case 'to-left-top': 29820 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y + halfHeight, _pos.x - halfWidth, _pos.y - halfHeight); 29821 break; 29822 } 29823 } 29824 } 29825 29826 if (!gradientStyle) return null; // invalid gradient style 29827 29828 var hasPositions = positions.length === colors.length; 29829 var length = colors.length; 29830 29831 for (var i = 0; i < length; i++) { 29832 gradientStyle.addColorStop(hasPositions ? positions[i] : i / (length - 1), 'rgba(' + colors[i][0] + ',' + colors[i][1] + ',' + colors[i][2] + ',' + opacity + ')'); 29833 } 29834 29835 return gradientStyle; 29836 }; 29837 29838 CRp$6.gradientFillStyle = function (context, ele, fill, opacity) { 29839 var gradientStyle = this.createGradientStyleFor(context, 'background', ele, fill, opacity); 29840 if (!gradientStyle) return null; // error 29841 29842 context.fillStyle = gradientStyle; 29843 }; 29844 29845 CRp$6.colorFillStyle = function (context, r, g, b, a) { 29846 context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; // turn off for now, seems context does its own caching 29847 // var cache = this.paintCache(context); 29848 // var fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 29849 // if( cache.fillStyle !== fillStyle ){ 29850 // context.fillStyle = cache.fillStyle = fillStyle; 29851 // } 29852 }; 29853 29854 CRp$6.eleFillStyle = function (context, ele, opacity) { 29855 var backgroundFill = ele.pstyle('background-fill').value; 29856 29857 if (backgroundFill === 'linear-gradient' || backgroundFill === 'radial-gradient') { 29858 this.gradientFillStyle(context, ele, backgroundFill, opacity); 29859 } else { 29860 var backgroundColor = ele.pstyle('background-color').value; 29861 this.colorFillStyle(context, backgroundColor[0], backgroundColor[1], backgroundColor[2], opacity); 29862 } 29863 }; 29864 29865 CRp$6.gradientStrokeStyle = function (context, ele, fill, opacity) { 29866 var gradientStyle = this.createGradientStyleFor(context, 'line', ele, fill, opacity); 29867 if (!gradientStyle) return null; // error 29868 29869 context.strokeStyle = gradientStyle; 29870 }; 29871 29872 CRp$6.colorStrokeStyle = function (context, r, g, b, a) { 29873 context.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; // turn off for now, seems context does its own caching 29874 // var cache = this.paintCache(context); 29875 // var strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 29876 // if( cache.strokeStyle !== strokeStyle ){ 29877 // context.strokeStyle = cache.strokeStyle = strokeStyle; 29878 // } 29879 }; 29880 29881 CRp$6.eleStrokeStyle = function (context, ele, opacity) { 29882 var lineFill = ele.pstyle('line-fill').value; 29883 29884 if (lineFill === 'linear-gradient' || lineFill === 'radial-gradient') { 29885 this.gradientStrokeStyle(context, ele, lineFill, opacity); 29886 } else { 29887 var lineColor = ele.pstyle('line-color').value; 29888 this.colorStrokeStyle(context, lineColor[0], lineColor[1], lineColor[2], opacity); 29889 } 29890 }; // Resize canvas 29891 29892 29893 CRp$6.matchCanvasSize = function (container) { 29894 var r = this; 29895 var data = r.data; 29896 var bb = r.findContainerClientCoords(); 29897 var width = bb[2]; 29898 var height = bb[3]; 29899 var pixelRatio = r.getPixelRatio(); 29900 var mbPxRatio = r.motionBlurPxRatio; 29901 29902 if (container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE] || container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]) { 29903 pixelRatio = mbPxRatio; 29904 } 29905 29906 var canvasWidth = width * pixelRatio; 29907 var canvasHeight = height * pixelRatio; 29908 var canvas; 29909 29910 if (canvasWidth === r.canvasWidth && canvasHeight === r.canvasHeight) { 29911 return; // save cycles if same 29912 } 29913 29914 r.fontCaches = null; // resizing resets the style 29915 29916 var canvasContainer = data.canvasContainer; 29917 canvasContainer.style.width = width + 'px'; 29918 canvasContainer.style.height = height + 'px'; 29919 29920 for (var i = 0; i < r.CANVAS_LAYERS; i++) { 29921 canvas = data.canvases[i]; 29922 canvas.width = canvasWidth; 29923 canvas.height = canvasHeight; 29924 canvas.style.width = width + 'px'; 29925 canvas.style.height = height + 'px'; 29926 } 29927 29928 for (var i = 0; i < r.BUFFER_COUNT; i++) { 29929 canvas = data.bufferCanvases[i]; 29930 canvas.width = canvasWidth; 29931 canvas.height = canvasHeight; 29932 canvas.style.width = width + 'px'; 29933 canvas.style.height = height + 'px'; 29934 } 29935 29936 r.textureMult = 1; 29937 29938 if (pixelRatio <= 1) { 29939 canvas = data.bufferCanvases[r.TEXTURE_BUFFER]; 29940 r.textureMult = 2; 29941 canvas.width = canvasWidth * r.textureMult; 29942 canvas.height = canvasHeight * r.textureMult; 29943 } 29944 29945 r.canvasWidth = canvasWidth; 29946 r.canvasHeight = canvasHeight; 29947 }; 29948 29949 CRp$6.renderTo = function (cxt, zoom, pan, pxRatio) { 29950 this.render({ 29951 forcedContext: cxt, 29952 forcedZoom: zoom, 29953 forcedPan: pan, 29954 drawAllLayers: true, 29955 forcedPxRatio: pxRatio 29956 }); 29957 }; 29958 29959 CRp$6.render = function (options) { 29960 options = options || staticEmptyObject(); 29961 var forcedContext = options.forcedContext; 29962 var drawAllLayers = options.drawAllLayers; 29963 var drawOnlyNodeLayer = options.drawOnlyNodeLayer; 29964 var forcedZoom = options.forcedZoom; 29965 var forcedPan = options.forcedPan; 29966 var r = this; 29967 var pixelRatio = options.forcedPxRatio === undefined ? this.getPixelRatio() : options.forcedPxRatio; 29968 var cy = r.cy; 29969 var data = r.data; 29970 var needDraw = data.canvasNeedsRedraw; 29971 var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming); 29972 var motionBlur = options.motionBlur !== undefined ? options.motionBlur : r.motionBlur; 29973 var mbPxRatio = r.motionBlurPxRatio; 29974 var hasCompoundNodes = cy.hasCompoundNodes(); 29975 var inNodeDragGesture = r.hoverData.draggingEles; 29976 var inBoxSelection = r.hoverData.selecting || r.touchData.selecting ? true : false; 29977 motionBlur = motionBlur && !forcedContext && r.motionBlurEnabled && !inBoxSelection; 29978 var motionBlurFadeEffect = motionBlur; 29979 29980 if (!forcedContext) { 29981 if (r.prevPxRatio !== pixelRatio) { 29982 r.invalidateContainerClientCoordsCache(); 29983 r.matchCanvasSize(r.container); 29984 r.redrawHint('eles', true); 29985 r.redrawHint('drag', true); 29986 } 29987 29988 r.prevPxRatio = pixelRatio; 29989 } 29990 29991 if (!forcedContext && r.motionBlurTimeout) { 29992 clearTimeout(r.motionBlurTimeout); 29993 } 29994 29995 if (motionBlur) { 29996 if (r.mbFrames == null) { 29997 r.mbFrames = 0; 29998 } 29999 30000 r.mbFrames++; 30001 30002 if (r.mbFrames < 3) { 30003 // need several frames before even high quality motionblur 30004 motionBlurFadeEffect = false; 30005 } // go to lower quality blurry frames when several m/b frames have been rendered (avoids flashing) 30006 30007 30008 if (r.mbFrames > r.minMbLowQualFrames) { 30009 //r.fullQualityMb = false; 30010 r.motionBlurPxRatio = r.mbPxRBlurry; 30011 } 30012 } 30013 30014 if (r.clearingMotionBlur) { 30015 r.motionBlurPxRatio = 1; 30016 } // b/c drawToContext() may be async w.r.t. redraw(), keep track of last texture frame 30017 // because a rogue async texture frame would clear needDraw 30018 30019 30020 if (r.textureDrawLastFrame && !textureDraw) { 30021 needDraw[r.NODE] = true; 30022 needDraw[r.SELECT_BOX] = true; 30023 } 30024 30025 var style = cy.style(); 30026 var zoom = cy.zoom(); 30027 var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom; 30028 var pan = cy.pan(); 30029 var effectivePan = { 30030 x: pan.x, 30031 y: pan.y 30032 }; 30033 var vp = { 30034 zoom: zoom, 30035 pan: { 30036 x: pan.x, 30037 y: pan.y 30038 } 30039 }; 30040 var prevVp = r.prevViewport; 30041 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) 30042 30043 if (!viewportIsDiff && !(inNodeDragGesture && !hasCompoundNodes)) { 30044 r.motionBlurPxRatio = 1; 30045 } 30046 30047 if (forcedPan) { 30048 effectivePan = forcedPan; 30049 } // apply pixel ratio 30050 30051 30052 effectiveZoom *= pixelRatio; 30053 effectivePan.x *= pixelRatio; 30054 effectivePan.y *= pixelRatio; 30055 var eles = r.getCachedZSortedEles(); 30056 30057 function mbclear(context, x, y, w, h) { 30058 var gco = context.globalCompositeOperation; 30059 context.globalCompositeOperation = 'destination-out'; 30060 r.colorFillStyle(context, 255, 255, 255, r.motionBlurTransparency); 30061 context.fillRect(x, y, w, h); 30062 context.globalCompositeOperation = gco; 30063 } 30064 30065 function setContextTransform(context, clear) { 30066 var ePan, eZoom, w, h; 30067 30068 if (!r.clearingMotionBlur && (context === data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] || context === data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG])) { 30069 ePan = { 30070 x: pan.x * mbPxRatio, 30071 y: pan.y * mbPxRatio 30072 }; 30073 eZoom = zoom * mbPxRatio; 30074 w = r.canvasWidth * mbPxRatio; 30075 h = r.canvasHeight * mbPxRatio; 30076 } else { 30077 ePan = effectivePan; 30078 eZoom = effectiveZoom; 30079 w = r.canvasWidth; 30080 h = r.canvasHeight; 30081 } 30082 30083 context.setTransform(1, 0, 0, 1, 0, 0); 30084 30085 if (clear === 'motionBlur') { 30086 mbclear(context, 0, 0, w, h); 30087 } else if (!forcedContext && (clear === undefined || clear)) { 30088 context.clearRect(0, 0, w, h); 30089 } 30090 30091 if (!drawAllLayers) { 30092 context.translate(ePan.x, ePan.y); 30093 context.scale(eZoom, eZoom); 30094 } 30095 30096 if (forcedPan) { 30097 context.translate(forcedPan.x, forcedPan.y); 30098 } 30099 30100 if (forcedZoom) { 30101 context.scale(forcedZoom, forcedZoom); 30102 } 30103 } 30104 30105 if (!textureDraw) { 30106 r.textureDrawLastFrame = false; 30107 } 30108 30109 if (textureDraw) { 30110 r.textureDrawLastFrame = true; 30111 30112 if (!r.textureCache) { 30113 r.textureCache = {}; 30114 r.textureCache.bb = cy.mutableElements().boundingBox(); 30115 r.textureCache.texture = r.data.bufferCanvases[r.TEXTURE_BUFFER]; 30116 var cxt = r.data.bufferContexts[r.TEXTURE_BUFFER]; 30117 cxt.setTransform(1, 0, 0, 1, 0, 0); 30118 cxt.clearRect(0, 0, r.canvasWidth * r.textureMult, r.canvasHeight * r.textureMult); 30119 r.render({ 30120 forcedContext: cxt, 30121 drawOnlyNodeLayer: true, 30122 forcedPxRatio: pixelRatio * r.textureMult 30123 }); 30124 var vp = r.textureCache.viewport = { 30125 zoom: cy.zoom(), 30126 pan: cy.pan(), 30127 width: r.canvasWidth, 30128 height: r.canvasHeight 30129 }; 30130 vp.mpan = { 30131 x: (0 - vp.pan.x) / vp.zoom, 30132 y: (0 - vp.pan.y) / vp.zoom 30133 }; 30134 } 30135 30136 needDraw[r.DRAG] = false; 30137 needDraw[r.NODE] = false; 30138 var context = data.contexts[r.NODE]; 30139 var texture = r.textureCache.texture; 30140 var vp = r.textureCache.viewport; 30141 context.setTransform(1, 0, 0, 1, 0, 0); 30142 30143 if (motionBlur) { 30144 mbclear(context, 0, 0, vp.width, vp.height); 30145 } else { 30146 context.clearRect(0, 0, vp.width, vp.height); 30147 } 30148 30149 var outsideBgColor = style.core('outside-texture-bg-color').value; 30150 var outsideBgOpacity = style.core('outside-texture-bg-opacity').value; 30151 r.colorFillStyle(context, outsideBgColor[0], outsideBgColor[1], outsideBgColor[2], outsideBgOpacity); 30152 context.fillRect(0, 0, vp.width, vp.height); 30153 var zoom = cy.zoom(); 30154 setContextTransform(context, false); 30155 context.clearRect(vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio); 30156 context.drawImage(texture, vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio); 30157 } else if (r.textureOnViewport && !forcedContext) { 30158 // clear the cache since we don't need it 30159 r.textureCache = null; 30160 } 30161 30162 var extent = cy.extent(); 30163 var vpManip = r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming || r.hoverData.draggingEles || r.cy.animated(); 30164 var hideEdges = r.hideEdgesOnViewport && vpManip; 30165 var needMbClear = []; 30166 needMbClear[r.NODE] = !needDraw[r.NODE] && motionBlur && !r.clearedForMotionBlur[r.NODE] || r.clearingMotionBlur; 30167 30168 if (needMbClear[r.NODE]) { 30169 r.clearedForMotionBlur[r.NODE] = true; 30170 } 30171 30172 needMbClear[r.DRAG] = !needDraw[r.DRAG] && motionBlur && !r.clearedForMotionBlur[r.DRAG] || r.clearingMotionBlur; 30173 30174 if (needMbClear[r.DRAG]) { 30175 r.clearedForMotionBlur[r.DRAG] = true; 30176 } 30177 30178 if (needDraw[r.NODE] || drawAllLayers || drawOnlyNodeLayer || needMbClear[r.NODE]) { 30179 var useBuffer = motionBlur && !needMbClear[r.NODE] && mbPxRatio !== 1; 30180 var context = forcedContext || (useBuffer ? r.data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] : data.contexts[r.NODE]); 30181 var clear = motionBlur && !useBuffer ? 'motionBlur' : undefined; 30182 setContextTransform(context, clear); 30183 30184 if (hideEdges) { 30185 r.drawCachedNodes(context, eles.nondrag, pixelRatio, extent); 30186 } else { 30187 r.drawLayeredElements(context, eles.nondrag, pixelRatio, extent); 30188 } 30189 30190 if (r.debug) { 30191 r.drawDebugPoints(context, eles.nondrag); 30192 } 30193 30194 if (!drawAllLayers && !motionBlur) { 30195 needDraw[r.NODE] = false; 30196 } 30197 } 30198 30199 if (!drawOnlyNodeLayer && (needDraw[r.DRAG] || drawAllLayers || needMbClear[r.DRAG])) { 30200 var useBuffer = motionBlur && !needMbClear[r.DRAG] && mbPxRatio !== 1; 30201 var context = forcedContext || (useBuffer ? r.data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG] : data.contexts[r.DRAG]); 30202 setContextTransform(context, motionBlur && !useBuffer ? 'motionBlur' : undefined); 30203 30204 if (hideEdges) { 30205 r.drawCachedNodes(context, eles.drag, pixelRatio, extent); 30206 } else { 30207 r.drawCachedElements(context, eles.drag, pixelRatio, extent); 30208 } 30209 30210 if (r.debug) { 30211 r.drawDebugPoints(context, eles.drag); 30212 } 30213 30214 if (!drawAllLayers && !motionBlur) { 30215 needDraw[r.DRAG] = false; 30216 } 30217 } 30218 30219 if (r.showFps || !drawOnlyNodeLayer && needDraw[r.SELECT_BOX] && !drawAllLayers) { 30220 var context = forcedContext || data.contexts[r.SELECT_BOX]; 30221 setContextTransform(context); 30222 30223 if (r.selection[4] == 1 && (r.hoverData.selecting || r.touchData.selecting)) { 30224 var zoom = r.cy.zoom(); 30225 var borderWidth = style.core('selection-box-border-width').value / zoom; 30226 context.lineWidth = borderWidth; 30227 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 + ')'; 30228 context.fillRect(r.selection[0], r.selection[1], r.selection[2] - r.selection[0], r.selection[3] - r.selection[1]); 30229 30230 if (borderWidth > 0) { 30231 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 + ')'; 30232 context.strokeRect(r.selection[0], r.selection[1], r.selection[2] - r.selection[0], r.selection[3] - r.selection[1]); 30233 } 30234 } 30235 30236 if (data.bgActivePosistion && !r.hoverData.selecting) { 30237 var zoom = r.cy.zoom(); 30238 var pos = data.bgActivePosistion; 30239 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 + ')'; 30240 context.beginPath(); 30241 context.arc(pos.x, pos.y, style.core('active-bg-size').pfValue / zoom, 0, 2 * Math.PI); 30242 context.fill(); 30243 } 30244 30245 var timeToRender = r.lastRedrawTime; 30246 30247 if (r.showFps && timeToRender) { 30248 timeToRender = Math.round(timeToRender); 30249 var fps = Math.round(1000 / timeToRender); 30250 context.setTransform(1, 0, 0, 1, 0, 0); 30251 context.fillStyle = 'rgba(255, 0, 0, 0.75)'; 30252 context.strokeStyle = 'rgba(255, 0, 0, 0.75)'; 30253 context.lineWidth = 1; 30254 context.fillText('1 frame = ' + timeToRender + ' ms = ' + fps + ' fps', 0, 20); 30255 var maxFps = 60; 30256 context.strokeRect(0, 30, 250, 20); 30257 context.fillRect(0, 30, 250 * Math.min(fps / maxFps, 1), 20); 30258 } 30259 30260 if (!drawAllLayers) { 30261 needDraw[r.SELECT_BOX] = false; 30262 } 30263 } // motionblur: blit rendered blurry frames 30264 30265 30266 if (motionBlur && mbPxRatio !== 1) { 30267 var cxtNode = data.contexts[r.NODE]; 30268 var txtNode = r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE]; 30269 var cxtDrag = data.contexts[r.DRAG]; 30270 var txtDrag = r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]; 30271 30272 var drawMotionBlur = function drawMotionBlur(cxt, txt, needClear) { 30273 cxt.setTransform(1, 0, 0, 1, 0, 0); 30274 30275 if (needClear || !motionBlurFadeEffect) { 30276 cxt.clearRect(0, 0, r.canvasWidth, r.canvasHeight); 30277 } else { 30278 mbclear(cxt, 0, 0, r.canvasWidth, r.canvasHeight); 30279 } 30280 30281 var pxr = mbPxRatio; 30282 cxt.drawImage(txt, // img 30283 0, 0, // sx, sy 30284 r.canvasWidth * pxr, r.canvasHeight * pxr, // sw, sh 30285 0, 0, // x, y 30286 r.canvasWidth, r.canvasHeight // w, h 30287 ); 30288 }; 30289 30290 if (needDraw[r.NODE] || needMbClear[r.NODE]) { 30291 drawMotionBlur(cxtNode, txtNode, needMbClear[r.NODE]); 30292 needDraw[r.NODE] = false; 30293 } 30294 30295 if (needDraw[r.DRAG] || needMbClear[r.DRAG]) { 30296 drawMotionBlur(cxtDrag, txtDrag, needMbClear[r.DRAG]); 30297 needDraw[r.DRAG] = false; 30298 } 30299 } 30300 30301 r.prevViewport = vp; 30302 30303 if (r.clearingMotionBlur) { 30304 r.clearingMotionBlur = false; 30305 r.motionBlurCleared = true; 30306 r.motionBlur = true; 30307 } 30308 30309 if (motionBlur) { 30310 r.motionBlurTimeout = setTimeout(function () { 30311 r.motionBlurTimeout = null; 30312 r.clearedForMotionBlur[r.NODE] = false; 30313 r.clearedForMotionBlur[r.DRAG] = false; 30314 r.motionBlur = false; 30315 r.clearingMotionBlur = !textureDraw; 30316 r.mbFrames = 0; 30317 needDraw[r.NODE] = true; 30318 needDraw[r.DRAG] = true; 30319 r.redraw(); 30320 }, motionBlurDelay); 30321 } 30322 30323 if (!forcedContext) { 30324 cy.emit('render'); 30325 } 30326 }; 30327 30328 var CRp$7 = {}; // @O Polygon drawing 30329 30330 CRp$7.drawPolygonPath = function (context, x, y, width, height, points) { 30331 var halfW = width / 2; 30332 var halfH = height / 2; 30333 30334 if (context.beginPath) { 30335 context.beginPath(); 30336 } 30337 30338 context.moveTo(x + halfW * points[0], y + halfH * points[1]); 30339 30340 for (var i = 1; i < points.length / 2; i++) { 30341 context.lineTo(x + halfW * points[i * 2], y + halfH * points[i * 2 + 1]); 30342 } 30343 30344 context.closePath(); 30345 }; 30346 30347 CRp$7.drawRoundPolygonPath = function (context, x, y, width, height, points) { 30348 var halfW = width / 2; 30349 var halfH = height / 2; 30350 var cornerRadius = getRoundPolygonRadius(width, height); 30351 30352 if (context.beginPath) { 30353 context.beginPath(); 30354 } 30355 30356 for (var _i = 0; _i < points.length / 4; _i++) { 30357 var sourceUv = void 0, 30358 destUv = void 0; 30359 30360 if (_i === 0) { 30361 sourceUv = points.length - 2; 30362 } else { 30363 sourceUv = _i * 4 - 2; 30364 } 30365 30366 destUv = _i * 4 + 2; 30367 var px = x + halfW * points[_i * 4]; 30368 var py = y + halfH * points[_i * 4 + 1]; 30369 var cosTheta = -points[sourceUv] * points[destUv] - points[sourceUv + 1] * points[destUv + 1]; 30370 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 30371 var cp0x = px - offset * points[sourceUv]; 30372 var cp0y = py - offset * points[sourceUv + 1]; 30373 var cp1x = px + offset * points[destUv]; 30374 var cp1y = py + offset * points[destUv + 1]; 30375 30376 if (_i === 0) { 30377 context.moveTo(cp0x, cp0y); 30378 } else { 30379 context.lineTo(cp0x, cp0y); 30380 } 30381 30382 context.arcTo(px, py, cp1x, cp1y, cornerRadius); 30383 } 30384 30385 context.closePath(); 30386 }; // Round rectangle drawing 30387 30388 30389 CRp$7.drawRoundRectanglePath = function (context, x, y, width, height) { 30390 var halfWidth = width / 2; 30391 var halfHeight = height / 2; 30392 var cornerRadius = getRoundRectangleRadius(width, height); 30393 30394 if (context.beginPath) { 30395 context.beginPath(); 30396 } // Start at top middle 30397 30398 30399 context.moveTo(x, y - halfHeight); // Arc from middle top to right side 30400 30401 context.arcTo(x + halfWidth, y - halfHeight, x + halfWidth, y, cornerRadius); // Arc from right side to bottom 30402 30403 context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius); // Arc from bottom to left side 30404 30405 context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius); // Arc from left side to topBorder 30406 30407 context.arcTo(x - halfWidth, y - halfHeight, x, y - halfHeight, cornerRadius); // Join line 30408 30409 context.lineTo(x, y - halfHeight); 30410 context.closePath(); 30411 }; 30412 30413 CRp$7.drawBottomRoundRectanglePath = function (context, x, y, width, height) { 30414 var halfWidth = width / 2; 30415 var halfHeight = height / 2; 30416 var cornerRadius = getRoundRectangleRadius(width, height); 30417 30418 if (context.beginPath) { 30419 context.beginPath(); 30420 } // Start at top middle 30421 30422 30423 context.moveTo(x, y - halfHeight); 30424 context.lineTo(x + halfWidth, y - halfHeight); 30425 context.lineTo(x + halfWidth, y); 30426 context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius); 30427 context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius); 30428 context.lineTo(x - halfWidth, y - halfHeight); 30429 context.lineTo(x, y - halfHeight); 30430 context.closePath(); 30431 }; 30432 30433 CRp$7.drawCutRectanglePath = function (context, x, y, width, height) { 30434 var halfWidth = width / 2; 30435 var halfHeight = height / 2; 30436 var cornerLength = getCutRectangleCornerLength(); 30437 30438 if (context.beginPath) { 30439 context.beginPath(); 30440 } 30441 30442 context.moveTo(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.lineTo(x + halfWidth - cornerLength, y + halfHeight); 30447 context.lineTo(x - halfWidth + cornerLength, y + halfHeight); 30448 context.lineTo(x - halfWidth, y + halfHeight - cornerLength); 30449 context.lineTo(x - halfWidth, y - halfHeight + cornerLength); 30450 context.closePath(); 30451 }; 30452 30453 CRp$7.drawBarrelPath = function (context, x, y, width, height) { 30454 var halfWidth = width / 2; 30455 var halfHeight = height / 2; 30456 var xBegin = x - halfWidth; 30457 var xEnd = x + halfWidth; 30458 var yBegin = y - halfHeight; 30459 var yEnd = y + halfHeight; 30460 var barrelCurveConstants = getBarrelCurveConstants(width, height); 30461 var wOffset = barrelCurveConstants.widthOffset; 30462 var hOffset = barrelCurveConstants.heightOffset; 30463 var ctrlPtXOffset = barrelCurveConstants.ctrlPtOffsetPct * wOffset; 30464 30465 if (context.beginPath) { 30466 context.beginPath(); 30467 } 30468 30469 context.moveTo(xBegin, yBegin + hOffset); 30470 context.lineTo(xBegin, yEnd - hOffset); 30471 context.quadraticCurveTo(xBegin + ctrlPtXOffset, yEnd, xBegin + wOffset, yEnd); 30472 context.lineTo(xEnd - wOffset, yEnd); 30473 context.quadraticCurveTo(xEnd - ctrlPtXOffset, yEnd, xEnd, yEnd - hOffset); 30474 context.lineTo(xEnd, yBegin + hOffset); 30475 context.quadraticCurveTo(xEnd - ctrlPtXOffset, yBegin, xEnd - wOffset, yBegin); 30476 context.lineTo(xBegin + wOffset, yBegin); 30477 context.quadraticCurveTo(xBegin + ctrlPtXOffset, yBegin, xBegin, yBegin + hOffset); 30478 context.closePath(); 30479 }; 30480 30481 var sin0 = Math.sin(0); 30482 var cos0 = Math.cos(0); 30483 var sin = {}; 30484 var cos = {}; 30485 var ellipseStepSize = Math.PI / 40; 30486 30487 for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize) { 30488 sin[i] = Math.sin(i); 30489 cos[i] = Math.cos(i); 30490 } 30491 30492 CRp$7.drawEllipsePath = function (context, centerX, centerY, width, height) { 30493 if (context.beginPath) { 30494 context.beginPath(); 30495 } 30496 30497 if (context.ellipse) { 30498 context.ellipse(centerX, centerY, width / 2, height / 2, 0, 0, 2 * Math.PI); 30499 } else { 30500 var xPos, yPos; 30501 var rw = width / 2; 30502 var rh = height / 2; 30503 30504 for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize) { 30505 xPos = centerX - rw * sin[i] * sin0 + rw * cos[i] * cos0; 30506 yPos = centerY + rh * cos[i] * sin0 + rh * sin[i] * cos0; 30507 30508 if (i === 0) { 30509 context.moveTo(xPos, yPos); 30510 } else { 30511 context.lineTo(xPos, yPos); 30512 } 30513 } 30514 } 30515 30516 context.closePath(); 30517 }; 30518 30519 /* global atob, ArrayBuffer, Uint8Array, Blob */ 30520 var CRp$8 = {}; 30521 30522 CRp$8.createBuffer = function (w, h) { 30523 var buffer = document.createElement('canvas'); // eslint-disable-line no-undef 30524 30525 buffer.width = w; 30526 buffer.height = h; 30527 return [buffer, buffer.getContext('2d')]; 30528 }; 30529 30530 CRp$8.bufferCanvasImage = function (options) { 30531 var cy = this.cy; 30532 var eles = cy.mutableElements(); 30533 var bb = eles.boundingBox(); 30534 var ctrRect = this.findContainerClientCoords(); 30535 var width = options.full ? Math.ceil(bb.w) : ctrRect[2]; 30536 var height = options.full ? Math.ceil(bb.h) : ctrRect[3]; 30537 var specdMaxDims = number(options.maxWidth) || number(options.maxHeight); 30538 var pxRatio = this.getPixelRatio(); 30539 var scale = 1; 30540 30541 if (options.scale !== undefined) { 30542 width *= options.scale; 30543 height *= options.scale; 30544 scale = options.scale; 30545 } else if (specdMaxDims) { 30546 var maxScaleW = Infinity; 30547 var maxScaleH = Infinity; 30548 30549 if (number(options.maxWidth)) { 30550 maxScaleW = scale * options.maxWidth / width; 30551 } 30552 30553 if (number(options.maxHeight)) { 30554 maxScaleH = scale * options.maxHeight / height; 30555 } 30556 30557 scale = Math.min(maxScaleW, maxScaleH); 30558 width *= scale; 30559 height *= scale; 30560 } 30561 30562 if (!specdMaxDims) { 30563 width *= pxRatio; 30564 height *= pxRatio; 30565 scale *= pxRatio; 30566 } 30567 30568 var buffCanvas = document.createElement('canvas'); // eslint-disable-line no-undef 30569 30570 buffCanvas.width = width; 30571 buffCanvas.height = height; 30572 buffCanvas.style.width = width + 'px'; 30573 buffCanvas.style.height = height + 'px'; 30574 var buffCxt = buffCanvas.getContext('2d'); // Rasterize the layers, but only if container has nonzero size 30575 30576 if (width > 0 && height > 0) { 30577 buffCxt.clearRect(0, 0, width, height); 30578 buffCxt.globalCompositeOperation = 'source-over'; 30579 var zsortedEles = this.getCachedZSortedEles(); 30580 30581 if (options.full) { 30582 // draw the full bounds of the graph 30583 buffCxt.translate(-bb.x1 * scale, -bb.y1 * scale); 30584 buffCxt.scale(scale, scale); 30585 this.drawElements(buffCxt, zsortedEles); 30586 buffCxt.scale(1 / scale, 1 / scale); 30587 buffCxt.translate(bb.x1 * scale, bb.y1 * scale); 30588 } else { 30589 // draw the current view 30590 var pan = cy.pan(); 30591 var translation = { 30592 x: pan.x * scale, 30593 y: pan.y * scale 30594 }; 30595 scale *= cy.zoom(); 30596 buffCxt.translate(translation.x, translation.y); 30597 buffCxt.scale(scale, scale); 30598 this.drawElements(buffCxt, zsortedEles); 30599 buffCxt.scale(1 / scale, 1 / scale); 30600 buffCxt.translate(-translation.x, -translation.y); 30601 } // need to fill bg at end like this in order to fill cleared transparent pixels in jpgs 30602 30603 30604 if (options.bg) { 30605 buffCxt.globalCompositeOperation = 'destination-over'; 30606 buffCxt.fillStyle = options.bg; 30607 buffCxt.rect(0, 0, width, height); 30608 buffCxt.fill(); 30609 } 30610 } 30611 30612 return buffCanvas; 30613 }; 30614 30615 function b64ToBlob(b64, mimeType) { 30616 var bytes = atob(b64); 30617 var buff = new ArrayBuffer(bytes.length); 30618 var buffUint8 = new Uint8Array(buff); 30619 30620 for (var i = 0; i < bytes.length; i++) { 30621 buffUint8[i] = bytes.charCodeAt(i); 30622 } 30623 30624 return new Blob([buff], { 30625 type: mimeType 30626 }); 30627 } 30628 30629 function b64UriToB64(b64uri) { 30630 var i = b64uri.indexOf(','); 30631 return b64uri.substr(i + 1); 30632 } 30633 30634 function output(options, canvas, mimeType) { 30635 var getB64Uri = function getB64Uri() { 30636 return canvas.toDataURL(mimeType, options.quality); 30637 }; 30638 30639 switch (options.output) { 30640 case 'blob-promise': 30641 return new Promise$1(function (resolve, reject) { 30642 try { 30643 canvas.toBlob(function (blob) { 30644 if (blob != null) { 30645 resolve(blob); 30646 } else { 30647 reject(new Error('`canvas.toBlob()` sent a null value in its callback')); 30648 } 30649 }, mimeType, options.quality); 30650 } catch (err) { 30651 reject(err); 30652 } 30653 }); 30654 30655 case 'blob': 30656 return b64ToBlob(b64UriToB64(getB64Uri()), mimeType); 30657 30658 case 'base64': 30659 return b64UriToB64(getB64Uri()); 30660 30661 case 'base64uri': 30662 default: 30663 return getB64Uri(); 30664 } 30665 } 30666 30667 CRp$8.png = function (options) { 30668 return output(options, this.bufferCanvasImage(options), 'image/png'); 30669 }; 30670 30671 CRp$8.jpg = function (options) { 30672 return output(options, this.bufferCanvasImage(options), 'image/jpeg'); 30673 }; 30674 30675 var CRp$9 = {}; 30676 30677 CRp$9.nodeShapeImpl = function (name, context, centerX, centerY, width, height, points) { 30678 switch (name) { 30679 case 'ellipse': 30680 return this.drawEllipsePath(context, centerX, centerY, width, height); 30681 30682 case 'polygon': 30683 return this.drawPolygonPath(context, centerX, centerY, width, height, points); 30684 30685 case 'round-polygon': 30686 return this.drawRoundPolygonPath(context, centerX, centerY, width, height, points); 30687 30688 case 'roundrectangle': 30689 case 'round-rectangle': 30690 return this.drawRoundRectanglePath(context, centerX, centerY, width, height); 30691 30692 case 'cutrectangle': 30693 case 'cut-rectangle': 30694 return this.drawCutRectanglePath(context, centerX, centerY, width, height); 30695 30696 case 'bottomroundrectangle': 30697 case 'bottom-round-rectangle': 30698 return this.drawBottomRoundRectanglePath(context, centerX, centerY, width, height); 30699 30700 case 'barrel': 30701 return this.drawBarrelPath(context, centerX, centerY, width, height); 30702 } 30703 }; 30704 30705 var CR = CanvasRenderer; 30706 var CRp$a = CanvasRenderer.prototype; 30707 CRp$a.CANVAS_LAYERS = 3; // 30708 30709 CRp$a.SELECT_BOX = 0; 30710 CRp$a.DRAG = 1; 30711 CRp$a.NODE = 2; 30712 CRp$a.BUFFER_COUNT = 3; // 30713 30714 CRp$a.TEXTURE_BUFFER = 0; 30715 CRp$a.MOTIONBLUR_BUFFER_NODE = 1; 30716 CRp$a.MOTIONBLUR_BUFFER_DRAG = 2; 30717 30718 function CanvasRenderer(options) { 30719 var r = this; 30720 r.data = { 30721 canvases: new Array(CRp$a.CANVAS_LAYERS), 30722 contexts: new Array(CRp$a.CANVAS_LAYERS), 30723 canvasNeedsRedraw: new Array(CRp$a.CANVAS_LAYERS), 30724 bufferCanvases: new Array(CRp$a.BUFFER_COUNT), 30725 bufferContexts: new Array(CRp$a.CANVAS_LAYERS) 30726 }; 30727 var tapHlOffAttr = '-webkit-tap-highlight-color'; 30728 var tapHlOffStyle = 'rgba(0,0,0,0)'; 30729 r.data.canvasContainer = document.createElement('div'); // eslint-disable-line no-undef 30730 30731 var containerStyle = r.data.canvasContainer.style; 30732 r.data.canvasContainer.style[tapHlOffAttr] = tapHlOffStyle; 30733 containerStyle.position = 'relative'; 30734 containerStyle.zIndex = '0'; 30735 containerStyle.overflow = 'hidden'; 30736 var container = options.cy.container(); 30737 container.appendChild(r.data.canvasContainer); 30738 container.style[tapHlOffAttr] = tapHlOffStyle; 30739 var styleMap = { 30740 '-webkit-user-select': 'none', 30741 '-moz-user-select': '-moz-none', 30742 'user-select': 'none', 30743 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', 30744 'outline-style': 'none' 30745 }; 30746 30747 if (ms()) { 30748 styleMap['-ms-touch-action'] = 'none'; 30749 styleMap['touch-action'] = 'none'; 30750 } 30751 30752 for (var i = 0; i < CRp$a.CANVAS_LAYERS; i++) { 30753 var canvas = r.data.canvases[i] = document.createElement('canvas'); // eslint-disable-line no-undef 30754 30755 r.data.contexts[i] = canvas.getContext('2d'); 30756 Object.keys(styleMap).forEach(function (k) { 30757 canvas.style[k] = styleMap[k]; 30758 }); 30759 canvas.style.position = 'absolute'; 30760 canvas.setAttribute('data-id', 'layer' + i); 30761 canvas.style.zIndex = String(CRp$a.CANVAS_LAYERS - i); 30762 r.data.canvasContainer.appendChild(canvas); 30763 r.data.canvasNeedsRedraw[i] = false; 30764 } 30765 30766 r.data.topCanvas = r.data.canvases[0]; 30767 r.data.canvases[CRp$a.NODE].setAttribute('data-id', 'layer' + CRp$a.NODE + '-node'); 30768 r.data.canvases[CRp$a.SELECT_BOX].setAttribute('data-id', 'layer' + CRp$a.SELECT_BOX + '-selectbox'); 30769 r.data.canvases[CRp$a.DRAG].setAttribute('data-id', 'layer' + CRp$a.DRAG + '-drag'); 30770 30771 for (var i = 0; i < CRp$a.BUFFER_COUNT; i++) { 30772 r.data.bufferCanvases[i] = document.createElement('canvas'); // eslint-disable-line no-undef 30773 30774 r.data.bufferContexts[i] = r.data.bufferCanvases[i].getContext('2d'); 30775 r.data.bufferCanvases[i].style.position = 'absolute'; 30776 r.data.bufferCanvases[i].setAttribute('data-id', 'buffer' + i); 30777 r.data.bufferCanvases[i].style.zIndex = String(-i - 1); 30778 r.data.bufferCanvases[i].style.visibility = 'hidden'; //r.data.canvasContainer.appendChild(r.data.bufferCanvases[i]); 30779 } 30780 30781 r.pathsEnabled = true; 30782 var emptyBb = makeBoundingBox(); 30783 30784 var getBoxCenter = function getBoxCenter(bb) { 30785 return { 30786 x: (bb.x1 + bb.x2) / 2, 30787 y: (bb.y1 + bb.y2) / 2 30788 }; 30789 }; 30790 30791 var getCenterOffset = function getCenterOffset(bb) { 30792 return { 30793 x: -bb.w / 2, 30794 y: -bb.h / 2 30795 }; 30796 }; 30797 30798 var backgroundTimestampHasChanged = function backgroundTimestampHasChanged(ele) { 30799 var _p = ele[0]._private; 30800 var same = _p.oldBackgroundTimestamp === _p.backgroundTimestamp; 30801 return !same; 30802 }; 30803 30804 var getStyleKey = function getStyleKey(ele) { 30805 return ele[0]._private.nodeKey; 30806 }; 30807 30808 var getLabelKey = function getLabelKey(ele) { 30809 return ele[0]._private.labelStyleKey; 30810 }; 30811 30812 var getSourceLabelKey = function getSourceLabelKey(ele) { 30813 return ele[0]._private.sourceLabelStyleKey; 30814 }; 30815 30816 var getTargetLabelKey = function getTargetLabelKey(ele) { 30817 return ele[0]._private.targetLabelStyleKey; 30818 }; 30819 30820 var drawElement = function drawElement(context, ele, bb, scaledLabelShown, useEleOpacity) { 30821 return r.drawElement(context, ele, bb, false, false, useEleOpacity); 30822 }; 30823 30824 var drawLabel = function drawLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 30825 return r.drawElementText(context, ele, bb, scaledLabelShown, 'main', useEleOpacity); 30826 }; 30827 30828 var drawSourceLabel = function drawSourceLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 30829 return r.drawElementText(context, ele, bb, scaledLabelShown, 'source', useEleOpacity); 30830 }; 30831 30832 var drawTargetLabel = function drawTargetLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 30833 return r.drawElementText(context, ele, bb, scaledLabelShown, 'target', useEleOpacity); 30834 }; 30835 30836 var getElementBox = function getElementBox(ele) { 30837 ele.boundingBox(); 30838 return ele[0]._private.bodyBounds; 30839 }; 30840 30841 var getLabelBox = function getLabelBox(ele) { 30842 ele.boundingBox(); 30843 return ele[0]._private.labelBounds.main || emptyBb; 30844 }; 30845 30846 var getSourceLabelBox = function getSourceLabelBox(ele) { 30847 ele.boundingBox(); 30848 return ele[0]._private.labelBounds.source || emptyBb; 30849 }; 30850 30851 var getTargetLabelBox = function getTargetLabelBox(ele) { 30852 ele.boundingBox(); 30853 return ele[0]._private.labelBounds.target || emptyBb; 30854 }; 30855 30856 var isLabelVisibleAtScale = function isLabelVisibleAtScale(ele, scaledLabelShown) { 30857 return scaledLabelShown; 30858 }; 30859 30860 var getElementRotationPoint = function getElementRotationPoint(ele) { 30861 return getBoxCenter(getElementBox(ele)); 30862 }; 30863 30864 var addTextMargin = function addTextMargin(prefix, pt, ele) { 30865 var pre = prefix ? prefix + '-' : ''; 30866 return { 30867 x: pt.x + ele.pstyle(pre + 'text-margin-x').pfValue, 30868 y: pt.y + ele.pstyle(pre + 'text-margin-y').pfValue 30869 }; 30870 }; 30871 30872 var getRsPt = function getRsPt(ele, x, y) { 30873 var rs = ele[0]._private.rscratch; 30874 return { 30875 x: rs[x], 30876 y: rs[y] 30877 }; 30878 }; 30879 30880 var getLabelRotationPoint = function getLabelRotationPoint(ele) { 30881 return addTextMargin('', getRsPt(ele, 'labelX', 'labelY'), ele); 30882 }; 30883 30884 var getSourceLabelRotationPoint = function getSourceLabelRotationPoint(ele) { 30885 return addTextMargin('source', getRsPt(ele, 'sourceLabelX', 'sourceLabelY'), ele); 30886 }; 30887 30888 var getTargetLabelRotationPoint = function getTargetLabelRotationPoint(ele) { 30889 return addTextMargin('target', getRsPt(ele, 'targetLabelX', 'targetLabelY'), ele); 30890 }; 30891 30892 var getElementRotationOffset = function getElementRotationOffset(ele) { 30893 return getCenterOffset(getElementBox(ele)); 30894 }; 30895 30896 var getSourceLabelRotationOffset = function getSourceLabelRotationOffset(ele) { 30897 return getCenterOffset(getSourceLabelBox(ele)); 30898 }; 30899 30900 var getTargetLabelRotationOffset = function getTargetLabelRotationOffset(ele) { 30901 return getCenterOffset(getTargetLabelBox(ele)); 30902 }; 30903 30904 var getLabelRotationOffset = function getLabelRotationOffset(ele) { 30905 var bb = getLabelBox(ele); 30906 var p = getCenterOffset(getLabelBox(ele)); 30907 30908 if (ele.isNode()) { 30909 switch (ele.pstyle('text-halign').value) { 30910 case 'left': 30911 p.x = -bb.w; 30912 break; 30913 30914 case 'right': 30915 p.x = 0; 30916 break; 30917 } 30918 30919 switch (ele.pstyle('text-valign').value) { 30920 case 'top': 30921 p.y = -bb.h; 30922 break; 30923 30924 case 'bottom': 30925 p.y = 0; 30926 break; 30927 } 30928 } 30929 30930 return p; 30931 }; 30932 30933 var eleTxrCache = r.data.eleTxrCache = new ElementTextureCache(r, { 30934 getKey: getStyleKey, 30935 doesEleInvalidateKey: backgroundTimestampHasChanged, 30936 drawElement: drawElement, 30937 getBoundingBox: getElementBox, 30938 getRotationPoint: getElementRotationPoint, 30939 getRotationOffset: getElementRotationOffset, 30940 allowEdgeTxrCaching: false, 30941 allowParentTxrCaching: false 30942 }); 30943 var lblTxrCache = r.data.lblTxrCache = new ElementTextureCache(r, { 30944 getKey: getLabelKey, 30945 drawElement: drawLabel, 30946 getBoundingBox: getLabelBox, 30947 getRotationPoint: getLabelRotationPoint, 30948 getRotationOffset: getLabelRotationOffset, 30949 isVisible: isLabelVisibleAtScale 30950 }); 30951 var slbTxrCache = r.data.slbTxrCache = new ElementTextureCache(r, { 30952 getKey: getSourceLabelKey, 30953 drawElement: drawSourceLabel, 30954 getBoundingBox: getSourceLabelBox, 30955 getRotationPoint: getSourceLabelRotationPoint, 30956 getRotationOffset: getSourceLabelRotationOffset, 30957 isVisible: isLabelVisibleAtScale 30958 }); 30959 var tlbTxrCache = r.data.tlbTxrCache = new ElementTextureCache(r, { 30960 getKey: getTargetLabelKey, 30961 drawElement: drawTargetLabel, 30962 getBoundingBox: getTargetLabelBox, 30963 getRotationPoint: getTargetLabelRotationPoint, 30964 getRotationOffset: getTargetLabelRotationOffset, 30965 isVisible: isLabelVisibleAtScale 30966 }); 30967 var lyrTxrCache = r.data.lyrTxrCache = new LayeredTextureCache(r); 30968 r.onUpdateEleCalcs(function invalidateTextureCaches(willDraw, eles) { 30969 // each cache should check for sub-key diff to see that the update affects that cache particularly 30970 eleTxrCache.invalidateElements(eles); 30971 lblTxrCache.invalidateElements(eles); 30972 slbTxrCache.invalidateElements(eles); 30973 tlbTxrCache.invalidateElements(eles); // any change invalidates the layers 30974 30975 lyrTxrCache.invalidateElements(eles); // update the old bg timestamp so diffs can be done in the ele txr caches 30976 30977 for (var _i = 0; _i < eles.length; _i++) { 30978 var _p = eles[_i]._private; 30979 _p.oldBackgroundTimestamp = _p.backgroundTimestamp; 30980 } 30981 }); 30982 30983 var refineInLayers = function refineInLayers(reqs) { 30984 for (var i = 0; i < reqs.length; i++) { 30985 lyrTxrCache.enqueueElementRefinement(reqs[i].ele); 30986 } 30987 }; 30988 30989 eleTxrCache.onDequeue(refineInLayers); 30990 lblTxrCache.onDequeue(refineInLayers); 30991 slbTxrCache.onDequeue(refineInLayers); 30992 tlbTxrCache.onDequeue(refineInLayers); 30993 } 30994 30995 CRp$a.redrawHint = function (group, bool) { 30996 var r = this; 30997 30998 switch (group) { 30999 case 'eles': 31000 r.data.canvasNeedsRedraw[CRp$a.NODE] = bool; 31001 break; 31002 31003 case 'drag': 31004 r.data.canvasNeedsRedraw[CRp$a.DRAG] = bool; 31005 break; 31006 31007 case 'select': 31008 r.data.canvasNeedsRedraw[CRp$a.SELECT_BOX] = bool; 31009 break; 31010 } 31011 }; // whether to use Path2D caching for drawing 31012 31013 31014 var pathsImpld = typeof Path2D !== 'undefined'; 31015 31016 CRp$a.path2dEnabled = function (on) { 31017 if (on === undefined) { 31018 return this.pathsEnabled; 31019 } 31020 31021 this.pathsEnabled = on ? true : false; 31022 }; 31023 31024 CRp$a.usePaths = function () { 31025 return pathsImpld && this.pathsEnabled; 31026 }; 31027 31028 CRp$a.setImgSmoothing = function (context, bool) { 31029 if (context.imageSmoothingEnabled != null) { 31030 context.imageSmoothingEnabled = bool; 31031 } else { 31032 context.webkitImageSmoothingEnabled = bool; 31033 context.mozImageSmoothingEnabled = bool; 31034 context.msImageSmoothingEnabled = bool; 31035 } 31036 }; 31037 31038 CRp$a.getImgSmoothing = function (context) { 31039 if (context.imageSmoothingEnabled != null) { 31040 return context.imageSmoothingEnabled; 31041 } else { 31042 return context.webkitImageSmoothingEnabled || context.mozImageSmoothingEnabled || context.msImageSmoothingEnabled; 31043 } 31044 }; 31045 31046 CRp$a.makeOffscreenCanvas = function (width, height) { 31047 var canvas; 31048 31049 if ((typeof OffscreenCanvas === "undefined" ? "undefined" : _typeof(OffscreenCanvas)) !== ( "undefined" )) { 31050 canvas = new OffscreenCanvas(width, height); 31051 } else { 31052 canvas = document.createElement('canvas'); // eslint-disable-line no-undef 31053 31054 canvas.width = width; 31055 canvas.height = height; 31056 } 31057 31058 return canvas; 31059 }; 31060 31061 [CRp, CRp$1, CRp$2, CRp$3, CRp$4, CRp$5, CRp$6, CRp$7, CRp$8, CRp$9].forEach(function (props) { 31062 extend(CRp$a, props); 31063 }); 31064 31065 var renderer = [{ 31066 name: 'null', 31067 impl: NullRenderer 31068 }, { 31069 name: 'base', 31070 impl: BR 31071 }, { 31072 name: 'canvas', 31073 impl: CR 31074 }]; 31075 31076 var incExts = [{ 31077 type: 'layout', 31078 extensions: layout 31079 }, { 31080 type: 'renderer', 31081 extensions: renderer 31082 }]; 31083 31084 var extensions = {}; // registered modules for extensions, indexed by name 31085 31086 var modules = {}; 31087 31088 function setExtension(type, name, registrant) { 31089 var ext = registrant; 31090 31091 var overrideErr = function overrideErr(field) { 31092 error('Can not register `' + name + '` for `' + type + '` since `' + field + '` already exists in the prototype and can not be overridden'); 31093 }; 31094 31095 if (type === 'core') { 31096 if (Core.prototype[name]) { 31097 return overrideErr(name); 31098 } else { 31099 Core.prototype[name] = registrant; 31100 } 31101 } else if (type === 'collection') { 31102 if (Collection.prototype[name]) { 31103 return overrideErr(name); 31104 } else { 31105 Collection.prototype[name] = registrant; 31106 } 31107 } else if (type === 'layout') { 31108 // fill in missing layout functions in the prototype 31109 var Layout = function Layout(options) { 31110 this.options = options; 31111 registrant.call(this, options); // make sure layout has _private for use w/ std apis like .on() 31112 31113 if (!plainObject(this._private)) { 31114 this._private = {}; 31115 } 31116 31117 this._private.cy = options.cy; 31118 this._private.listeners = []; 31119 this.createEmitter(); 31120 }; 31121 31122 var layoutProto = Layout.prototype = Object.create(registrant.prototype); 31123 var optLayoutFns = []; 31124 31125 for (var i = 0; i < optLayoutFns.length; i++) { 31126 var fnName = optLayoutFns[i]; 31127 31128 layoutProto[fnName] = layoutProto[fnName] || function () { 31129 return this; 31130 }; 31131 } // either .start() or .run() is defined, so autogen the other 31132 31133 31134 if (layoutProto.start && !layoutProto.run) { 31135 layoutProto.run = function () { 31136 this.start(); 31137 return this; 31138 }; 31139 } else if (!layoutProto.start && layoutProto.run) { 31140 layoutProto.start = function () { 31141 this.run(); 31142 return this; 31143 }; 31144 } 31145 31146 var regStop = registrant.prototype.stop; 31147 31148 layoutProto.stop = function () { 31149 var opts = this.options; 31150 31151 if (opts && opts.animate) { 31152 var anis = this.animations; 31153 31154 if (anis) { 31155 for (var _i = 0; _i < anis.length; _i++) { 31156 anis[_i].stop(); 31157 } 31158 } 31159 } 31160 31161 if (regStop) { 31162 regStop.call(this); 31163 } else { 31164 this.emit('layoutstop'); 31165 } 31166 31167 return this; 31168 }; 31169 31170 if (!layoutProto.destroy) { 31171 layoutProto.destroy = function () { 31172 return this; 31173 }; 31174 } 31175 31176 layoutProto.cy = function () { 31177 return this._private.cy; 31178 }; 31179 31180 var getCy = function getCy(layout) { 31181 return layout._private.cy; 31182 }; 31183 31184 var emitterOpts = { 31185 addEventFields: function addEventFields(layout, evt) { 31186 evt.layout = layout; 31187 evt.cy = getCy(layout); 31188 evt.target = layout; 31189 }, 31190 bubble: function bubble() { 31191 return true; 31192 }, 31193 parent: function parent(layout) { 31194 return getCy(layout); 31195 } 31196 }; 31197 extend(layoutProto, { 31198 createEmitter: function createEmitter() { 31199 this._private.emitter = new Emitter(emitterOpts, this); 31200 return this; 31201 }, 31202 emitter: function emitter() { 31203 return this._private.emitter; 31204 }, 31205 on: function on(evt, cb) { 31206 this.emitter().on(evt, cb); 31207 return this; 31208 }, 31209 one: function one(evt, cb) { 31210 this.emitter().one(evt, cb); 31211 return this; 31212 }, 31213 once: function once(evt, cb) { 31214 this.emitter().one(evt, cb); 31215 return this; 31216 }, 31217 removeListener: function removeListener(evt, cb) { 31218 this.emitter().removeListener(evt, cb); 31219 return this; 31220 }, 31221 removeAllListeners: function removeAllListeners() { 31222 this.emitter().removeAllListeners(); 31223 return this; 31224 }, 31225 emit: function emit(evt, params) { 31226 this.emitter().emit(evt, params); 31227 return this; 31228 } 31229 }); 31230 define$3.eventAliasesOn(layoutProto); 31231 ext = Layout; // replace with our wrapped layout 31232 } else if (type === 'renderer' && name !== 'null' && name !== 'base') { 31233 // user registered renderers inherit from base 31234 var BaseRenderer = getExtension('renderer', 'base'); 31235 var bProto = BaseRenderer.prototype; 31236 var RegistrantRenderer = registrant; 31237 var rProto = registrant.prototype; 31238 31239 var Renderer = function Renderer() { 31240 BaseRenderer.apply(this, arguments); 31241 RegistrantRenderer.apply(this, arguments); 31242 }; 31243 31244 var proto = Renderer.prototype; 31245 31246 for (var pName in bProto) { 31247 var pVal = bProto[pName]; 31248 var existsInR = rProto[pName] != null; 31249 31250 if (existsInR) { 31251 return overrideErr(pName); 31252 } 31253 31254 proto[pName] = pVal; // take impl from base 31255 } 31256 31257 for (var _pName in rProto) { 31258 proto[_pName] = rProto[_pName]; // take impl from registrant 31259 } 31260 31261 bProto.clientFunctions.forEach(function (name) { 31262 proto[name] = proto[name] || function () { 31263 error('Renderer does not implement `renderer.' + name + '()` on its prototype'); 31264 }; 31265 }); 31266 ext = Renderer; 31267 } 31268 31269 return setMap({ 31270 map: extensions, 31271 keys: [type, name], 31272 value: ext 31273 }); 31274 } 31275 31276 function getExtension(type, name) { 31277 return getMap({ 31278 map: extensions, 31279 keys: [type, name] 31280 }); 31281 } 31282 31283 function setModule(type, name, moduleType, moduleName, registrant) { 31284 return setMap({ 31285 map: modules, 31286 keys: [type, name, moduleType, moduleName], 31287 value: registrant 31288 }); 31289 } 31290 31291 function getModule(type, name, moduleType, moduleName) { 31292 return getMap({ 31293 map: modules, 31294 keys: [type, name, moduleType, moduleName] 31295 }); 31296 } 31297 31298 var extension = function extension() { 31299 // e.g. extension('renderer', 'svg') 31300 if (arguments.length === 2) { 31301 return getExtension.apply(null, arguments); 31302 } // e.g. extension('renderer', 'svg', { ... }) 31303 else if (arguments.length === 3) { 31304 return setExtension.apply(null, arguments); 31305 } // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse') 31306 else if (arguments.length === 4) { 31307 return getModule.apply(null, arguments); 31308 } // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... }) 31309 else if (arguments.length === 5) { 31310 return setModule.apply(null, arguments); 31311 } else { 31312 error('Invalid extension access syntax'); 31313 } 31314 }; // allows a core instance to access extensions internally 31315 31316 31317 Core.prototype.extension = extension; // included extensions 31318 31319 incExts.forEach(function (group) { 31320 group.extensions.forEach(function (ext) { 31321 setExtension(group.type, ext.name, ext.impl); 31322 }); 31323 }); 31324 31325 // (useful for init) 31326 31327 var Stylesheet = function Stylesheet() { 31328 if (!(this instanceof Stylesheet)) { 31329 return new Stylesheet(); 31330 } 31331 31332 this.length = 0; 31333 }; 31334 31335 var sheetfn = Stylesheet.prototype; 31336 31337 sheetfn.instanceString = function () { 31338 return 'stylesheet'; 31339 }; // just store the selector to be parsed later 31340 31341 31342 sheetfn.selector = function (selector) { 31343 var i = this.length++; 31344 this[i] = { 31345 selector: selector, 31346 properties: [] 31347 }; 31348 return this; // chaining 31349 }; // just store the property to be parsed later 31350 31351 31352 sheetfn.css = function (name, value) { 31353 var i = this.length - 1; 31354 31355 if (string(name)) { 31356 this[i].properties.push({ 31357 name: name, 31358 value: value 31359 }); 31360 } else if (plainObject(name)) { 31361 var map = name; 31362 var propNames = Object.keys(map); 31363 31364 for (var j = 0; j < propNames.length; j++) { 31365 var key = propNames[j]; 31366 var mapVal = map[key]; 31367 31368 if (mapVal == null) { 31369 continue; 31370 } 31371 31372 var prop = Style.properties[key] || Style.properties[dash2camel(key)]; 31373 31374 if (prop == null) { 31375 continue; 31376 } 31377 31378 var _name = prop.name; 31379 var _value = mapVal; 31380 this[i].properties.push({ 31381 name: _name, 31382 value: _value 31383 }); 31384 } 31385 } 31386 31387 return this; // chaining 31388 }; 31389 31390 sheetfn.style = sheetfn.css; // generate a real style object from the dummy stylesheet 31391 31392 sheetfn.generateStyle = function (cy) { 31393 var style = new Style(cy); 31394 return this.appendToStyle(style); 31395 }; // append a dummy stylesheet object on a real style object 31396 31397 31398 sheetfn.appendToStyle = function (style) { 31399 for (var i = 0; i < this.length; i++) { 31400 var context = this[i]; 31401 var selector = context.selector; 31402 var props = context.properties; 31403 style.selector(selector); // apply selector 31404 31405 for (var j = 0; j < props.length; j++) { 31406 var prop = props[j]; 31407 style.css(prop.name, prop.value); // apply property 31408 } 31409 } 31410 31411 return style; 31412 }; 31413 31414 var version = "3.13.3"; 31415 31416 var cytoscape = function cytoscape(options) { 31417 // if no options specified, use default 31418 if (options === undefined) { 31419 options = {}; 31420 } // create instance 31421 31422 31423 if (plainObject(options)) { 31424 return new Core(options); 31425 } // allow for registration of extensions 31426 else if (string(options)) { 31427 return extension.apply(extension, arguments); 31428 } 31429 }; // e.g. cytoscape.use( require('cytoscape-foo'), bar ) 31430 31431 31432 cytoscape.use = function (ext) { 31433 var args = Array.prototype.slice.call(arguments, 1); // args to pass to ext 31434 31435 args.unshift(cytoscape); // cytoscape is first arg to ext 31436 31437 ext.apply(null, args); 31438 return this; 31439 }; 31440 31441 cytoscape.warnings = function (bool) { 31442 return warnings(bool); 31443 }; // replaced by build system 31444 31445 31446 cytoscape.version = version; // expose public apis (mostly for extensions) 31447 31448 cytoscape.stylesheet = cytoscape.Stylesheet = Stylesheet; 31449 31450 module.exports = cytoscape;