github.com/mweagle/Sparta@v1.15.0/resources/describe/cytoscape.js/dist/cytoscape.umd.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 (function (global, factory) { 24 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 25 typeof define === 'function' && define.amd ? define(factory) : 26 (global = global || self, global.cytoscape = factory()); 27 }(this, (function () { 'use strict'; 28 29 function _typeof(obj) { 30 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 31 _typeof = function (obj) { 32 return typeof obj; 33 }; 34 } else { 35 _typeof = function (obj) { 36 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 37 }; 38 } 39 40 return _typeof(obj); 41 } 42 43 function _classCallCheck(instance, Constructor) { 44 if (!(instance instanceof Constructor)) { 45 throw new TypeError("Cannot call a class as a function"); 46 } 47 } 48 49 function _defineProperties(target, props) { 50 for (var i = 0; i < props.length; i++) { 51 var descriptor = props[i]; 52 descriptor.enumerable = descriptor.enumerable || false; 53 descriptor.configurable = true; 54 if ("value" in descriptor) descriptor.writable = true; 55 Object.defineProperty(target, descriptor.key, descriptor); 56 } 57 } 58 59 function _createClass(Constructor, protoProps, staticProps) { 60 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 61 if (staticProps) _defineProperties(Constructor, staticProps); 62 return Constructor; 63 } 64 65 function _defineProperty(obj, key, value) { 66 if (key in obj) { 67 Object.defineProperty(obj, key, { 68 value: value, 69 enumerable: true, 70 configurable: true, 71 writable: true 72 }); 73 } else { 74 obj[key] = value; 75 } 76 77 return obj; 78 } 79 80 function _slicedToArray(arr, i) { 81 return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); 82 } 83 84 function _arrayWithHoles(arr) { 85 if (Array.isArray(arr)) return arr; 86 } 87 88 function _iterableToArrayLimit(arr, i) { 89 var _arr = []; 90 var _n = true; 91 var _d = false; 92 var _e = undefined; 93 94 try { 95 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 96 _arr.push(_s.value); 97 98 if (i && _arr.length === i) break; 99 } 100 } catch (err) { 101 _d = true; 102 _e = err; 103 } finally { 104 try { 105 if (!_n && _i["return"] != null) _i["return"](); 106 } finally { 107 if (_d) throw _e; 108 } 109 } 110 111 return _arr; 112 } 113 114 function _nonIterableRest() { 115 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 116 } 117 118 var window$1 = typeof window === 'undefined' ? null : window; // eslint-disable-line no-undef 119 120 var navigator = window$1 ? window$1.navigator : null; 121 var document$1 = window$1 ? window$1.document : null; 122 123 var typeofstr = _typeof(''); 124 125 var typeofobj = _typeof({}); 126 127 var typeoffn = _typeof(function () {}); 128 129 var typeofhtmlele = typeof HTMLElement === "undefined" ? "undefined" : _typeof(HTMLElement); 130 131 var instanceStr = function instanceStr(obj) { 132 return obj && obj.instanceString && fn(obj.instanceString) ? obj.instanceString() : null; 133 }; 134 135 var string = function string(obj) { 136 return obj != null && _typeof(obj) == typeofstr; 137 }; 138 var fn = function fn(obj) { 139 return obj != null && _typeof(obj) === typeoffn; 140 }; 141 var array = function array(obj) { 142 return Array.isArray ? Array.isArray(obj) : obj != null && obj instanceof Array; 143 }; 144 var plainObject = function plainObject(obj) { 145 return obj != null && _typeof(obj) === typeofobj && !array(obj) && obj.constructor === Object; 146 }; 147 var object = function object(obj) { 148 return obj != null && _typeof(obj) === typeofobj; 149 }; 150 var number = function number(obj) { 151 return obj != null && _typeof(obj) === _typeof(1) && !isNaN(obj); 152 }; 153 var integer = function integer(obj) { 154 return number(obj) && Math.floor(obj) === obj; 155 }; 156 var htmlElement = function htmlElement(obj) { 157 if ('undefined' === typeofhtmlele) { 158 return undefined; 159 } else { 160 return null != obj && obj instanceof HTMLElement; 161 } 162 }; 163 var elementOrCollection = function elementOrCollection(obj) { 164 return element(obj) || collection(obj); 165 }; 166 var element = function element(obj) { 167 return instanceStr(obj) === 'collection' && obj._private.single; 168 }; 169 var collection = function collection(obj) { 170 return instanceStr(obj) === 'collection' && !obj._private.single; 171 }; 172 var core = function core(obj) { 173 return instanceStr(obj) === 'core'; 174 }; 175 var stylesheet = function stylesheet(obj) { 176 return instanceStr(obj) === 'stylesheet'; 177 }; 178 var event = function event(obj) { 179 return instanceStr(obj) === 'event'; 180 }; 181 var emptyString = function emptyString(obj) { 182 if (obj === undefined || obj === null) { 183 // null is empty 184 return true; 185 } else if (obj === '' || obj.match(/^\s+$/)) { 186 return true; // empty string is empty 187 } 188 189 return false; // otherwise, we don't know what we've got 190 }; 191 var domElement = function domElement(obj) { 192 if (typeof HTMLElement === 'undefined') { 193 return false; // we're not in a browser so it doesn't matter 194 } else { 195 return obj instanceof HTMLElement; 196 } 197 }; 198 var boundingBox = function boundingBox(obj) { 199 return plainObject(obj) && number(obj.x1) && number(obj.x2) && number(obj.y1) && number(obj.y2); 200 }; 201 var promise = function promise(obj) { 202 return object(obj) && fn(obj.then); 203 }; 204 var ms = function ms() { 205 return navigator && navigator.userAgent.match(/msie|trident|edge/i); 206 }; // probably a better way to detect this... 207 208 var memoize = function memoize(fn, keyFn) { 209 if (!keyFn) { 210 keyFn = function keyFn() { 211 if (arguments.length === 1) { 212 return arguments[0]; 213 } else if (arguments.length === 0) { 214 return 'undefined'; 215 } 216 217 var args = []; 218 219 for (var i = 0; i < arguments.length; i++) { 220 args.push(arguments[i]); 221 } 222 223 return args.join('$'); 224 }; 225 } 226 227 var memoizedFn = function memoizedFn() { 228 var self = this; 229 var args = arguments; 230 var ret; 231 var k = keyFn.apply(self, args); 232 var cache = memoizedFn.cache; 233 234 if (!(ret = cache[k])) { 235 ret = cache[k] = fn.apply(self, args); 236 } 237 238 return ret; 239 }; 240 241 memoizedFn.cache = {}; 242 return memoizedFn; 243 }; 244 245 var camel2dash = memoize(function (str) { 246 return str.replace(/([A-Z])/g, function (v) { 247 return '-' + v.toLowerCase(); 248 }); 249 }); 250 var dash2camel = memoize(function (str) { 251 return str.replace(/(-\w)/g, function (v) { 252 return v[1].toUpperCase(); 253 }); 254 }); 255 var prependCamel = memoize(function (prefix, str) { 256 return prefix + str[0].toUpperCase() + str.substring(1); 257 }, function (prefix, str) { 258 return prefix + '$' + str; 259 }); 260 var capitalize = function capitalize(str) { 261 if (emptyString(str)) { 262 return str; 263 } 264 265 return str.charAt(0).toUpperCase() + str.substring(1); 266 }; 267 268 var number$1 = '(?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))'; 269 var rgba = 'rgb[a]?\\((' + number$1 + '[%]?)\\s*,\\s*(' + number$1 + '[%]?)\\s*,\\s*(' + number$1 + '[%]?)(?:\\s*,\\s*(' + number$1 + '))?\\)'; 270 var rgbaNoBackRefs = 'rgb[a]?\\((?:' + number$1 + '[%]?)\\s*,\\s*(?:' + number$1 + '[%]?)\\s*,\\s*(?:' + number$1 + '[%]?)(?:\\s*,\\s*(?:' + number$1 + '))?\\)'; 271 var hsla = 'hsl[a]?\\((' + number$1 + ')\\s*,\\s*(' + number$1 + '[%])\\s*,\\s*(' + number$1 + '[%])(?:\\s*,\\s*(' + number$1 + '))?\\)'; 272 var hslaNoBackRefs = 'hsl[a]?\\((?:' + number$1 + ')\\s*,\\s*(?:' + number$1 + '[%])\\s*,\\s*(?:' + number$1 + '[%])(?:\\s*,\\s*(?:' + number$1 + '))?\\)'; 273 var hex3 = '\\#[0-9a-fA-F]{3}'; 274 var hex6 = '\\#[0-9a-fA-F]{6}'; 275 276 var ascending = function ascending(a, b) { 277 if (a < b) { 278 return -1; 279 } else if (a > b) { 280 return 1; 281 } else { 282 return 0; 283 } 284 }; 285 var descending = function descending(a, b) { 286 return -1 * ascending(a, b); 287 }; 288 289 var extend = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { 290 var args = arguments; 291 292 for (var i = 1; i < args.length; i++) { 293 var obj = args[i]; 294 295 if (obj == null) { 296 continue; 297 } 298 299 var keys = Object.keys(obj); 300 301 for (var j = 0; j < keys.length; j++) { 302 var k = keys[j]; 303 tgt[k] = obj[k]; 304 } 305 } 306 307 return tgt; 308 }; 309 310 var hex2tuple = function hex2tuple(hex) { 311 if (!(hex.length === 4 || hex.length === 7) || hex[0] !== '#') { 312 return; 313 } 314 315 var shortHex = hex.length === 4; 316 var r, g, b; 317 var base = 16; 318 319 if (shortHex) { 320 r = parseInt(hex[1] + hex[1], base); 321 g = parseInt(hex[2] + hex[2], base); 322 b = parseInt(hex[3] + hex[3], base); 323 } else { 324 r = parseInt(hex[1] + hex[2], base); 325 g = parseInt(hex[3] + hex[4], base); 326 b = parseInt(hex[5] + hex[6], base); 327 } 328 329 return [r, g, b]; 330 }; // get [r, g, b, a] from hsl(0, 0, 0) or hsla(0, 0, 0, 0) 331 332 var hsl2tuple = function hsl2tuple(hsl) { 333 var ret; 334 var h, s, l, a, r, g, b; 335 336 function hue2rgb(p, q, t) { 337 if (t < 0) t += 1; 338 if (t > 1) t -= 1; 339 if (t < 1 / 6) return p + (q - p) * 6 * t; 340 if (t < 1 / 2) return q; 341 if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 342 return p; 343 } 344 345 var m = new RegExp('^' + hsla + '$').exec(hsl); 346 347 if (m) { 348 // get hue 349 h = parseInt(m[1]); 350 351 if (h < 0) { 352 h = (360 - -1 * h % 360) % 360; 353 } else if (h > 360) { 354 h = h % 360; 355 } 356 357 h /= 360; // normalise on [0, 1] 358 359 s = parseFloat(m[2]); 360 361 if (s < 0 || s > 100) { 362 return; 363 } // saturation is [0, 100] 364 365 366 s = s / 100; // normalise on [0, 1] 367 368 l = parseFloat(m[3]); 369 370 if (l < 0 || l > 100) { 371 return; 372 } // lightness is [0, 100] 373 374 375 l = l / 100; // normalise on [0, 1] 376 377 a = m[4]; 378 379 if (a !== undefined) { 380 a = parseFloat(a); 381 382 if (a < 0 || a > 1) { 383 return; 384 } // alpha is [0, 1] 385 386 } // now, convert to rgb 387 // code from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript 388 389 390 if (s === 0) { 391 r = g = b = Math.round(l * 255); // achromatic 392 } else { 393 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 394 var p = 2 * l - q; 395 r = Math.round(255 * hue2rgb(p, q, h + 1 / 3)); 396 g = Math.round(255 * hue2rgb(p, q, h)); 397 b = Math.round(255 * hue2rgb(p, q, h - 1 / 3)); 398 } 399 400 ret = [r, g, b, a]; 401 } 402 403 return ret; 404 }; // get [r, g, b, a] from rgb(0, 0, 0) or rgba(0, 0, 0, 0) 405 406 var rgb2tuple = function rgb2tuple(rgb) { 407 var ret; 408 var m = new RegExp('^' + rgba + '$').exec(rgb); 409 410 if (m) { 411 ret = []; 412 var isPct = []; 413 414 for (var i = 1; i <= 3; i++) { 415 var channel = m[i]; 416 417 if (channel[channel.length - 1] === '%') { 418 isPct[i] = true; 419 } 420 421 channel = parseFloat(channel); 422 423 if (isPct[i]) { 424 channel = channel / 100 * 255; // normalise to [0, 255] 425 } 426 427 if (channel < 0 || channel > 255) { 428 return; 429 } // invalid channel value 430 431 432 ret.push(Math.floor(channel)); 433 } 434 435 var atLeastOneIsPct = isPct[1] || isPct[2] || isPct[3]; 436 var allArePct = isPct[1] && isPct[2] && isPct[3]; 437 438 if (atLeastOneIsPct && !allArePct) { 439 return; 440 } // must all be percent values if one is 441 442 443 var alpha = m[4]; 444 445 if (alpha !== undefined) { 446 alpha = parseFloat(alpha); 447 448 if (alpha < 0 || alpha > 1) { 449 return; 450 } // invalid alpha value 451 452 453 ret.push(alpha); 454 } 455 } 456 457 return ret; 458 }; 459 var colorname2tuple = function colorname2tuple(color) { 460 return colors[color.toLowerCase()]; 461 }; 462 var color2tuple = function color2tuple(color) { 463 return (array(color) ? color : null) || colorname2tuple(color) || hex2tuple(color) || rgb2tuple(color) || hsl2tuple(color); 464 }; 465 var colors = { 466 // special colour names 467 transparent: [0, 0, 0, 0], 468 // NB alpha === 0 469 // regular colours 470 aliceblue: [240, 248, 255], 471 antiquewhite: [250, 235, 215], 472 aqua: [0, 255, 255], 473 aquamarine: [127, 255, 212], 474 azure: [240, 255, 255], 475 beige: [245, 245, 220], 476 bisque: [255, 228, 196], 477 black: [0, 0, 0], 478 blanchedalmond: [255, 235, 205], 479 blue: [0, 0, 255], 480 blueviolet: [138, 43, 226], 481 brown: [165, 42, 42], 482 burlywood: [222, 184, 135], 483 cadetblue: [95, 158, 160], 484 chartreuse: [127, 255, 0], 485 chocolate: [210, 105, 30], 486 coral: [255, 127, 80], 487 cornflowerblue: [100, 149, 237], 488 cornsilk: [255, 248, 220], 489 crimson: [220, 20, 60], 490 cyan: [0, 255, 255], 491 darkblue: [0, 0, 139], 492 darkcyan: [0, 139, 139], 493 darkgoldenrod: [184, 134, 11], 494 darkgray: [169, 169, 169], 495 darkgreen: [0, 100, 0], 496 darkgrey: [169, 169, 169], 497 darkkhaki: [189, 183, 107], 498 darkmagenta: [139, 0, 139], 499 darkolivegreen: [85, 107, 47], 500 darkorange: [255, 140, 0], 501 darkorchid: [153, 50, 204], 502 darkred: [139, 0, 0], 503 darksalmon: [233, 150, 122], 504 darkseagreen: [143, 188, 143], 505 darkslateblue: [72, 61, 139], 506 darkslategray: [47, 79, 79], 507 darkslategrey: [47, 79, 79], 508 darkturquoise: [0, 206, 209], 509 darkviolet: [148, 0, 211], 510 deeppink: [255, 20, 147], 511 deepskyblue: [0, 191, 255], 512 dimgray: [105, 105, 105], 513 dimgrey: [105, 105, 105], 514 dodgerblue: [30, 144, 255], 515 firebrick: [178, 34, 34], 516 floralwhite: [255, 250, 240], 517 forestgreen: [34, 139, 34], 518 fuchsia: [255, 0, 255], 519 gainsboro: [220, 220, 220], 520 ghostwhite: [248, 248, 255], 521 gold: [255, 215, 0], 522 goldenrod: [218, 165, 32], 523 gray: [128, 128, 128], 524 grey: [128, 128, 128], 525 green: [0, 128, 0], 526 greenyellow: [173, 255, 47], 527 honeydew: [240, 255, 240], 528 hotpink: [255, 105, 180], 529 indianred: [205, 92, 92], 530 indigo: [75, 0, 130], 531 ivory: [255, 255, 240], 532 khaki: [240, 230, 140], 533 lavender: [230, 230, 250], 534 lavenderblush: [255, 240, 245], 535 lawngreen: [124, 252, 0], 536 lemonchiffon: [255, 250, 205], 537 lightblue: [173, 216, 230], 538 lightcoral: [240, 128, 128], 539 lightcyan: [224, 255, 255], 540 lightgoldenrodyellow: [250, 250, 210], 541 lightgray: [211, 211, 211], 542 lightgreen: [144, 238, 144], 543 lightgrey: [211, 211, 211], 544 lightpink: [255, 182, 193], 545 lightsalmon: [255, 160, 122], 546 lightseagreen: [32, 178, 170], 547 lightskyblue: [135, 206, 250], 548 lightslategray: [119, 136, 153], 549 lightslategrey: [119, 136, 153], 550 lightsteelblue: [176, 196, 222], 551 lightyellow: [255, 255, 224], 552 lime: [0, 255, 0], 553 limegreen: [50, 205, 50], 554 linen: [250, 240, 230], 555 magenta: [255, 0, 255], 556 maroon: [128, 0, 0], 557 mediumaquamarine: [102, 205, 170], 558 mediumblue: [0, 0, 205], 559 mediumorchid: [186, 85, 211], 560 mediumpurple: [147, 112, 219], 561 mediumseagreen: [60, 179, 113], 562 mediumslateblue: [123, 104, 238], 563 mediumspringgreen: [0, 250, 154], 564 mediumturquoise: [72, 209, 204], 565 mediumvioletred: [199, 21, 133], 566 midnightblue: [25, 25, 112], 567 mintcream: [245, 255, 250], 568 mistyrose: [255, 228, 225], 569 moccasin: [255, 228, 181], 570 navajowhite: [255, 222, 173], 571 navy: [0, 0, 128], 572 oldlace: [253, 245, 230], 573 olive: [128, 128, 0], 574 olivedrab: [107, 142, 35], 575 orange: [255, 165, 0], 576 orangered: [255, 69, 0], 577 orchid: [218, 112, 214], 578 palegoldenrod: [238, 232, 170], 579 palegreen: [152, 251, 152], 580 paleturquoise: [175, 238, 238], 581 palevioletred: [219, 112, 147], 582 papayawhip: [255, 239, 213], 583 peachpuff: [255, 218, 185], 584 peru: [205, 133, 63], 585 pink: [255, 192, 203], 586 plum: [221, 160, 221], 587 powderblue: [176, 224, 230], 588 purple: [128, 0, 128], 589 red: [255, 0, 0], 590 rosybrown: [188, 143, 143], 591 royalblue: [65, 105, 225], 592 saddlebrown: [139, 69, 19], 593 salmon: [250, 128, 114], 594 sandybrown: [244, 164, 96], 595 seagreen: [46, 139, 87], 596 seashell: [255, 245, 238], 597 sienna: [160, 82, 45], 598 silver: [192, 192, 192], 599 skyblue: [135, 206, 235], 600 slateblue: [106, 90, 205], 601 slategray: [112, 128, 144], 602 slategrey: [112, 128, 144], 603 snow: [255, 250, 250], 604 springgreen: [0, 255, 127], 605 steelblue: [70, 130, 180], 606 tan: [210, 180, 140], 607 teal: [0, 128, 128], 608 thistle: [216, 191, 216], 609 tomato: [255, 99, 71], 610 turquoise: [64, 224, 208], 611 violet: [238, 130, 238], 612 wheat: [245, 222, 179], 613 white: [255, 255, 255], 614 whitesmoke: [245, 245, 245], 615 yellow: [255, 255, 0], 616 yellowgreen: [154, 205, 50] 617 }; 618 619 var setMap = function setMap(options) { 620 var obj = options.map; 621 var keys = options.keys; 622 var l = keys.length; 623 624 for (var i = 0; i < l; i++) { 625 var key = keys[i]; 626 627 if (plainObject(key)) { 628 throw Error('Tried to set map with object key'); 629 } 630 631 if (i < keys.length - 1) { 632 // extend the map if necessary 633 if (obj[key] == null) { 634 obj[key] = {}; 635 } 636 637 obj = obj[key]; 638 } else { 639 // set the value 640 obj[key] = options.value; 641 } 642 } 643 }; // gets the value in a map even if it's not built in places 644 645 var getMap = function getMap(options) { 646 var obj = options.map; 647 var keys = options.keys; 648 var l = keys.length; 649 650 for (var i = 0; i < l; i++) { 651 var key = keys[i]; 652 653 if (plainObject(key)) { 654 throw Error('Tried to get map with object key'); 655 } 656 657 obj = obj[key]; 658 659 if (obj == null) { 660 return obj; 661 } 662 } 663 664 return obj; 665 }; // deletes the entry in the map 666 667 var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 668 669 function createCommonjsModule(fn, module) { 670 return module = { exports: {} }, fn(module, module.exports), module.exports; 671 } 672 673 /** 674 * lodash (Custom Build) <https://lodash.com/> 675 * Build: `lodash modularize exports="npm" -o ./` 676 * Copyright jQuery Foundation and other contributors <https://jquery.org/> 677 * Released under MIT license <https://lodash.com/license> 678 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> 679 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 680 */ 681 682 /** Used as the `TypeError` message for "Functions" methods. */ 683 var FUNC_ERROR_TEXT = 'Expected a function'; 684 685 /** Used as references for various `Number` constants. */ 686 var NAN = 0 / 0; 687 688 /** `Object#toString` result references. */ 689 var symbolTag = '[object Symbol]'; 690 691 /** Used to match leading and trailing whitespace. */ 692 var reTrim = /^\s+|\s+$/g; 693 694 /** Used to detect bad signed hexadecimal string values. */ 695 var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; 696 697 /** Used to detect binary string values. */ 698 var reIsBinary = /^0b[01]+$/i; 699 700 /** Used to detect octal string values. */ 701 var reIsOctal = /^0o[0-7]+$/i; 702 703 /** Built-in method references without a dependency on `root`. */ 704 var freeParseInt = parseInt; 705 706 /** Detect free variable `global` from Node.js. */ 707 var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; 708 709 /** Detect free variable `self`. */ 710 var freeSelf = typeof self == 'object' && self && self.Object === Object && self; 711 712 /** Used as a reference to the global object. */ 713 var root = freeGlobal || freeSelf || Function('return this')(); 714 715 /** Used for built-in method references. */ 716 var objectProto = Object.prototype; 717 718 /** 719 * Used to resolve the 720 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) 721 * of values. 722 */ 723 var objectToString = objectProto.toString; 724 725 /* Built-in method references for those with the same name as other `lodash` methods. */ 726 var nativeMax = Math.max, 727 nativeMin = Math.min; 728 729 /** 730 * Gets the timestamp of the number of milliseconds that have elapsed since 731 * the Unix epoch (1 January 1970 00:00:00 UTC). 732 * 733 * @static 734 * @memberOf _ 735 * @since 2.4.0 736 * @category Date 737 * @returns {number} Returns the timestamp. 738 * @example 739 * 740 * _.defer(function(stamp) { 741 * console.log(_.now() - stamp); 742 * }, _.now()); 743 * // => Logs the number of milliseconds it took for the deferred invocation. 744 */ 745 var now = function() { 746 return root.Date.now(); 747 }; 748 749 /** 750 * Creates a debounced function that delays invoking `func` until after `wait` 751 * milliseconds have elapsed since the last time the debounced function was 752 * invoked. The debounced function comes with a `cancel` method to cancel 753 * delayed `func` invocations and a `flush` method to immediately invoke them. 754 * Provide `options` to indicate whether `func` should be invoked on the 755 * leading and/or trailing edge of the `wait` timeout. The `func` is invoked 756 * with the last arguments provided to the debounced function. Subsequent 757 * calls to the debounced function return the result of the last `func` 758 * invocation. 759 * 760 * **Note:** If `leading` and `trailing` options are `true`, `func` is 761 * invoked on the trailing edge of the timeout only if the debounced function 762 * is invoked more than once during the `wait` timeout. 763 * 764 * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred 765 * until to the next tick, similar to `setTimeout` with a timeout of `0`. 766 * 767 * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) 768 * for details over the differences between `_.debounce` and `_.throttle`. 769 * 770 * @static 771 * @memberOf _ 772 * @since 0.1.0 773 * @category Function 774 * @param {Function} func The function to debounce. 775 * @param {number} [wait=0] The number of milliseconds to delay. 776 * @param {Object} [options={}] The options object. 777 * @param {boolean} [options.leading=false] 778 * Specify invoking on the leading edge of the timeout. 779 * @param {number} [options.maxWait] 780 * The maximum time `func` is allowed to be delayed before it's invoked. 781 * @param {boolean} [options.trailing=true] 782 * Specify invoking on the trailing edge of the timeout. 783 * @returns {Function} Returns the new debounced function. 784 * @example 785 * 786 * // Avoid costly calculations while the window size is in flux. 787 * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); 788 * 789 * // Invoke `sendMail` when clicked, debouncing subsequent calls. 790 * jQuery(element).on('click', _.debounce(sendMail, 300, { 791 * 'leading': true, 792 * 'trailing': false 793 * })); 794 * 795 * // Ensure `batchLog` is invoked once after 1 second of debounced calls. 796 * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); 797 * var source = new EventSource('/stream'); 798 * jQuery(source).on('message', debounced); 799 * 800 * // Cancel the trailing debounced invocation. 801 * jQuery(window).on('popstate', debounced.cancel); 802 */ 803 function debounce(func, wait, options) { 804 var lastArgs, 805 lastThis, 806 maxWait, 807 result, 808 timerId, 809 lastCallTime, 810 lastInvokeTime = 0, 811 leading = false, 812 maxing = false, 813 trailing = true; 814 815 if (typeof func != 'function') { 816 throw new TypeError(FUNC_ERROR_TEXT); 817 } 818 wait = toNumber(wait) || 0; 819 if (isObject(options)) { 820 leading = !!options.leading; 821 maxing = 'maxWait' in options; 822 maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; 823 trailing = 'trailing' in options ? !!options.trailing : trailing; 824 } 825 826 function invokeFunc(time) { 827 var args = lastArgs, 828 thisArg = lastThis; 829 830 lastArgs = lastThis = undefined; 831 lastInvokeTime = time; 832 result = func.apply(thisArg, args); 833 return result; 834 } 835 836 function leadingEdge(time) { 837 // Reset any `maxWait` timer. 838 lastInvokeTime = time; 839 // Start the timer for the trailing edge. 840 timerId = setTimeout(timerExpired, wait); 841 // Invoke the leading edge. 842 return leading ? invokeFunc(time) : result; 843 } 844 845 function remainingWait(time) { 846 var timeSinceLastCall = time - lastCallTime, 847 timeSinceLastInvoke = time - lastInvokeTime, 848 result = wait - timeSinceLastCall; 849 850 return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; 851 } 852 853 function shouldInvoke(time) { 854 var timeSinceLastCall = time - lastCallTime, 855 timeSinceLastInvoke = time - lastInvokeTime; 856 857 // Either this is the first call, activity has stopped and we're at the 858 // trailing edge, the system time has gone backwards and we're treating 859 // it as the trailing edge, or we've hit the `maxWait` limit. 860 return (lastCallTime === undefined || (timeSinceLastCall >= wait) || 861 (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); 862 } 863 864 function timerExpired() { 865 var time = now(); 866 if (shouldInvoke(time)) { 867 return trailingEdge(time); 868 } 869 // Restart the timer. 870 timerId = setTimeout(timerExpired, remainingWait(time)); 871 } 872 873 function trailingEdge(time) { 874 timerId = undefined; 875 876 // Only invoke if we have `lastArgs` which means `func` has been 877 // debounced at least once. 878 if (trailing && lastArgs) { 879 return invokeFunc(time); 880 } 881 lastArgs = lastThis = undefined; 882 return result; 883 } 884 885 function cancel() { 886 if (timerId !== undefined) { 887 clearTimeout(timerId); 888 } 889 lastInvokeTime = 0; 890 lastArgs = lastCallTime = lastThis = timerId = undefined; 891 } 892 893 function flush() { 894 return timerId === undefined ? result : trailingEdge(now()); 895 } 896 897 function debounced() { 898 var time = now(), 899 isInvoking = shouldInvoke(time); 900 901 lastArgs = arguments; 902 lastThis = this; 903 lastCallTime = time; 904 905 if (isInvoking) { 906 if (timerId === undefined) { 907 return leadingEdge(lastCallTime); 908 } 909 if (maxing) { 910 // Handle invocations in a tight loop. 911 timerId = setTimeout(timerExpired, wait); 912 return invokeFunc(lastCallTime); 913 } 914 } 915 if (timerId === undefined) { 916 timerId = setTimeout(timerExpired, wait); 917 } 918 return result; 919 } 920 debounced.cancel = cancel; 921 debounced.flush = flush; 922 return debounced; 923 } 924 925 /** 926 * Checks if `value` is the 927 * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) 928 * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 929 * 930 * @static 931 * @memberOf _ 932 * @since 0.1.0 933 * @category Lang 934 * @param {*} value The value to check. 935 * @returns {boolean} Returns `true` if `value` is an object, else `false`. 936 * @example 937 * 938 * _.isObject({}); 939 * // => true 940 * 941 * _.isObject([1, 2, 3]); 942 * // => true 943 * 944 * _.isObject(_.noop); 945 * // => true 946 * 947 * _.isObject(null); 948 * // => false 949 */ 950 function isObject(value) { 951 var type = typeof value; 952 return !!value && (type == 'object' || type == 'function'); 953 } 954 955 /** 956 * Checks if `value` is object-like. A value is object-like if it's not `null` 957 * and has a `typeof` result of "object". 958 * 959 * @static 960 * @memberOf _ 961 * @since 4.0.0 962 * @category Lang 963 * @param {*} value The value to check. 964 * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 965 * @example 966 * 967 * _.isObjectLike({}); 968 * // => true 969 * 970 * _.isObjectLike([1, 2, 3]); 971 * // => true 972 * 973 * _.isObjectLike(_.noop); 974 * // => false 975 * 976 * _.isObjectLike(null); 977 * // => false 978 */ 979 function isObjectLike(value) { 980 return !!value && typeof value == 'object'; 981 } 982 983 /** 984 * Checks if `value` is classified as a `Symbol` primitive or object. 985 * 986 * @static 987 * @memberOf _ 988 * @since 4.0.0 989 * @category Lang 990 * @param {*} value The value to check. 991 * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. 992 * @example 993 * 994 * _.isSymbol(Symbol.iterator); 995 * // => true 996 * 997 * _.isSymbol('abc'); 998 * // => false 999 */ 1000 function isSymbol(value) { 1001 return typeof value == 'symbol' || 1002 (isObjectLike(value) && objectToString.call(value) == symbolTag); 1003 } 1004 1005 /** 1006 * Converts `value` to a number. 1007 * 1008 * @static 1009 * @memberOf _ 1010 * @since 4.0.0 1011 * @category Lang 1012 * @param {*} value The value to process. 1013 * @returns {number} Returns the number. 1014 * @example 1015 * 1016 * _.toNumber(3.2); 1017 * // => 3.2 1018 * 1019 * _.toNumber(Number.MIN_VALUE); 1020 * // => 5e-324 1021 * 1022 * _.toNumber(Infinity); 1023 * // => Infinity 1024 * 1025 * _.toNumber('3.2'); 1026 * // => 3.2 1027 */ 1028 function toNumber(value) { 1029 if (typeof value == 'number') { 1030 return value; 1031 } 1032 if (isSymbol(value)) { 1033 return NAN; 1034 } 1035 if (isObject(value)) { 1036 var other = typeof value.valueOf == 'function' ? value.valueOf() : value; 1037 value = isObject(other) ? (other + '') : other; 1038 } 1039 if (typeof value != 'string') { 1040 return value === 0 ? value : +value; 1041 } 1042 value = value.replace(reTrim, ''); 1043 var isBinary = reIsBinary.test(value); 1044 return (isBinary || reIsOctal.test(value)) 1045 ? freeParseInt(value.slice(2), isBinary ? 2 : 8) 1046 : (reIsBadHex.test(value) ? NAN : +value); 1047 } 1048 1049 var lodash_debounce = debounce; 1050 1051 var performance = window$1 ? window$1.performance : null; 1052 var pnow = performance && performance.now ? function () { 1053 return performance.now(); 1054 } : function () { 1055 return Date.now(); 1056 }; 1057 1058 var raf = function () { 1059 if (window$1) { 1060 if (window$1.requestAnimationFrame) { 1061 return function (fn) { 1062 window$1.requestAnimationFrame(fn); 1063 }; 1064 } else if (window$1.mozRequestAnimationFrame) { 1065 return function (fn) { 1066 window$1.mozRequestAnimationFrame(fn); 1067 }; 1068 } else if (window$1.webkitRequestAnimationFrame) { 1069 return function (fn) { 1070 window$1.webkitRequestAnimationFrame(fn); 1071 }; 1072 } else if (window$1.msRequestAnimationFrame) { 1073 return function (fn) { 1074 window$1.msRequestAnimationFrame(fn); 1075 }; 1076 } 1077 } 1078 1079 return function (fn) { 1080 if (fn) { 1081 setTimeout(function () { 1082 fn(pnow()); 1083 }, 1000 / 60); 1084 } 1085 }; 1086 }(); 1087 1088 var requestAnimationFrame = function requestAnimationFrame(fn) { 1089 return raf(fn); 1090 }; 1091 var performanceNow = pnow; 1092 1093 var DEFAULT_SEED = 5381; 1094 var hashIterableInts = function hashIterableInts(iterator) { 1095 var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_SEED; 1096 // djb2/string-hash 1097 var hash = seed; 1098 var entry; 1099 1100 for (;;) { 1101 entry = iterator.next(); 1102 1103 if (entry.done) { 1104 break; 1105 } 1106 1107 hash = (hash << 5) + hash + entry.value | 0; 1108 } 1109 1110 return hash; 1111 }; 1112 var hashInt = function hashInt(num) { 1113 var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_SEED; 1114 // djb2/string-hash 1115 return (seed << 5) + seed + num | 0; 1116 }; 1117 var hashIntsArray = function hashIntsArray(ints, seed) { 1118 var entry = { 1119 value: 0, 1120 done: false 1121 }; 1122 var i = 0; 1123 var length = ints.length; 1124 var iterator = { 1125 next: function next() { 1126 if (i < length) { 1127 entry.value = ints[i++]; 1128 } else { 1129 entry.done = true; 1130 } 1131 1132 return entry; 1133 } 1134 }; 1135 return hashIterableInts(iterator, seed); 1136 }; 1137 var hashString = function hashString(str, seed) { 1138 var entry = { 1139 value: 0, 1140 done: false 1141 }; 1142 var i = 0; 1143 var length = str.length; 1144 var iterator = { 1145 next: function next() { 1146 if (i < length) { 1147 entry.value = str.charCodeAt(i++); 1148 } else { 1149 entry.done = true; 1150 } 1151 1152 return entry; 1153 } 1154 }; 1155 return hashIterableInts(iterator, seed); 1156 }; 1157 var hashStrings = function hashStrings() { 1158 return hashStringsArray(arguments); 1159 }; 1160 var hashStringsArray = function hashStringsArray(strs) { 1161 var hash; 1162 1163 for (var i = 0; i < strs.length; i++) { 1164 var str = strs[i]; 1165 1166 if (i === 0) { 1167 hash = hashString(str); 1168 } else { 1169 hash = hashString(str, hash); 1170 } 1171 } 1172 1173 return hash; 1174 }; 1175 1176 /*global console */ 1177 var warningsEnabled = true; 1178 var warnSupported = console.warn != null; // eslint-disable-line no-console 1179 1180 var traceSupported = console.trace != null; // eslint-disable-line no-console 1181 1182 var MAX_INT = Number.MAX_SAFE_INTEGER || 9007199254740991; 1183 var trueify = function trueify() { 1184 return true; 1185 }; 1186 var falsify = function falsify() { 1187 return false; 1188 }; 1189 var zeroify = function zeroify() { 1190 return 0; 1191 }; 1192 var noop = function noop() {}; 1193 var error = function error(msg) { 1194 throw new Error(msg); 1195 }; 1196 var warnings = function warnings(enabled) { 1197 if (enabled !== undefined) { 1198 warningsEnabled = !!enabled; 1199 } else { 1200 return warningsEnabled; 1201 } 1202 }; 1203 var warn = function warn(msg) { 1204 /* eslint-disable no-console */ 1205 if (!warnings()) { 1206 return; 1207 } 1208 1209 if (warnSupported) { 1210 console.warn(msg); 1211 } else { 1212 console.log(msg); 1213 1214 if (traceSupported) { 1215 console.trace(); 1216 } 1217 } 1218 }; 1219 /* eslint-enable */ 1220 1221 var clone = function clone(obj) { 1222 return extend({}, obj); 1223 }; // gets a shallow copy of the argument 1224 1225 var copy = function copy(obj) { 1226 if (obj == null) { 1227 return obj; 1228 } 1229 1230 if (array(obj)) { 1231 return obj.slice(); 1232 } else if (plainObject(obj)) { 1233 return clone(obj); 1234 } else { 1235 return obj; 1236 } 1237 }; 1238 var copyArray = function copyArray(arr) { 1239 return arr.slice(); 1240 }; 1241 var uuid = function uuid(a, b 1242 /* placeholders */ 1243 ) { 1244 for ( // loop :) 1245 b = a = ''; // b - result , a - numeric letiable 1246 a++ < 36; // 1247 b += a * 51 & 52 // if "a" is not 9 or 14 or 19 or 24 1248 ? // return a random number or 4 1249 (a ^ 15 // if "a" is not 15 1250 ? // genetate a random number from 0 to 15 1251 8 ^ Math.random() * (a ^ 20 ? 16 : 4) // unless "a" is 20, in which case a random number from 8 to 11 1252 : 4 // otherwise 4 1253 ).toString(16) : '-' // in other cases (if "a" is 9,14,19,24) insert "-" 1254 ) { 1255 } 1256 1257 return b; 1258 }; 1259 var _staticEmptyObject = {}; 1260 var staticEmptyObject = function staticEmptyObject() { 1261 return _staticEmptyObject; 1262 }; 1263 var defaults = function defaults(_defaults) { 1264 var keys = Object.keys(_defaults); 1265 return function (opts) { 1266 var filledOpts = {}; 1267 1268 for (var i = 0; i < keys.length; i++) { 1269 var key = keys[i]; 1270 var optVal = opts == null ? undefined : opts[key]; 1271 filledOpts[key] = optVal === undefined ? _defaults[key] : optVal; 1272 } 1273 1274 return filledOpts; 1275 }; 1276 }; 1277 var removeFromArray = function removeFromArray(arr, ele, manyCopies) { 1278 for (var i = arr.length; i >= 0; i--) { 1279 if (arr[i] === ele) { 1280 arr.splice(i, 1); 1281 1282 if (!manyCopies) { 1283 break; 1284 } 1285 } 1286 } 1287 }; 1288 var clearArray = function clearArray(arr) { 1289 arr.splice(0, arr.length); 1290 }; 1291 var push = function push(arr, otherArr) { 1292 for (var i = 0; i < otherArr.length; i++) { 1293 var el = otherArr[i]; 1294 arr.push(el); 1295 } 1296 }; 1297 var getPrefixedProperty = function getPrefixedProperty(obj, propName, prefix) { 1298 if (prefix) { 1299 propName = prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth 1300 } 1301 1302 return obj[propName]; 1303 }; 1304 var setPrefixedProperty = function setPrefixedProperty(obj, propName, prefix, value) { 1305 if (prefix) { 1306 propName = prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth 1307 } 1308 1309 obj[propName] = value; 1310 }; 1311 1312 /* global Map */ 1313 var ObjectMap = 1314 /*#__PURE__*/ 1315 function () { 1316 function ObjectMap() { 1317 _classCallCheck(this, ObjectMap); 1318 1319 this._obj = {}; 1320 } 1321 1322 _createClass(ObjectMap, [{ 1323 key: "set", 1324 value: function set(key, val) { 1325 this._obj[key] = val; 1326 return this; 1327 } 1328 }, { 1329 key: "delete", 1330 value: function _delete(key) { 1331 this._obj[key] = undefined; 1332 return this; 1333 } 1334 }, { 1335 key: "clear", 1336 value: function clear() { 1337 this._obj = {}; 1338 } 1339 }, { 1340 key: "has", 1341 value: function has(key) { 1342 return this._obj[key] !== undefined; 1343 } 1344 }, { 1345 key: "get", 1346 value: function get(key) { 1347 return this._obj[key]; 1348 } 1349 }]); 1350 1351 return ObjectMap; 1352 }(); 1353 1354 var Map$1 = typeof Map !== 'undefined' ? Map : ObjectMap; 1355 1356 /* global Set */ 1357 var undef = "undefined" ; 1358 1359 var ObjectSet = 1360 /*#__PURE__*/ 1361 function () { 1362 function ObjectSet(arrayOrObjectSet) { 1363 _classCallCheck(this, ObjectSet); 1364 1365 this._obj = Object.create(null); 1366 this.size = 0; 1367 1368 if (arrayOrObjectSet != null) { 1369 var arr; 1370 1371 if (arrayOrObjectSet.instanceString != null && arrayOrObjectSet.instanceString() === this.instanceString()) { 1372 arr = arrayOrObjectSet.toArray(); 1373 } else { 1374 arr = arrayOrObjectSet; 1375 } 1376 1377 for (var i = 0; i < arr.length; i++) { 1378 this.add(arr[i]); 1379 } 1380 } 1381 } 1382 1383 _createClass(ObjectSet, [{ 1384 key: "instanceString", 1385 value: function instanceString() { 1386 return 'set'; 1387 } 1388 }, { 1389 key: "add", 1390 value: function add(val) { 1391 var o = this._obj; 1392 1393 if (o[val] !== 1) { 1394 o[val] = 1; 1395 this.size++; 1396 } 1397 } 1398 }, { 1399 key: "delete", 1400 value: function _delete(val) { 1401 var o = this._obj; 1402 1403 if (o[val] === 1) { 1404 o[val] = 0; 1405 this.size--; 1406 } 1407 } 1408 }, { 1409 key: "clear", 1410 value: function clear() { 1411 this._obj = Object.create(null); 1412 } 1413 }, { 1414 key: "has", 1415 value: function has(val) { 1416 return this._obj[val] === 1; 1417 } 1418 }, { 1419 key: "toArray", 1420 value: function toArray() { 1421 var _this = this; 1422 1423 return Object.keys(this._obj).filter(function (key) { 1424 return _this.has(key); 1425 }); 1426 } 1427 }, { 1428 key: "forEach", 1429 value: function forEach(callback, thisArg) { 1430 return this.toArray().forEach(callback, thisArg); 1431 } 1432 }]); 1433 1434 return ObjectSet; 1435 }(); 1436 1437 var Set$1 = (typeof Set === "undefined" ? "undefined" : _typeof(Set)) !== undef ? Set : ObjectSet; 1438 1439 var Element = function Element(cy, params, restore) { 1440 restore = restore === undefined || restore ? true : false; 1441 1442 if (cy === undefined || params === undefined || !core(cy)) { 1443 error('An element must have a core reference and parameters set'); 1444 return; 1445 } 1446 1447 var group = params.group; // try to automatically infer the group if unspecified 1448 1449 if (group == null) { 1450 if (params.data && params.data.source != null && params.data.target != null) { 1451 group = 'edges'; 1452 } else { 1453 group = 'nodes'; 1454 } 1455 } // validate group 1456 1457 1458 if (group !== 'nodes' && group !== 'edges') { 1459 error('An element must be of type `nodes` or `edges`; you specified `' + group + '`'); 1460 return; 1461 } // make the element array-like, just like a collection 1462 1463 1464 this.length = 1; 1465 this[0] = this; // NOTE: when something is added here, add also to ele.json() 1466 1467 var _p = this._private = { 1468 cy: cy, 1469 single: true, 1470 // indicates this is an element 1471 data: params.data || {}, 1472 // data object 1473 position: params.position || { 1474 x: 0, 1475 y: 0 1476 }, 1477 // (x, y) position pair 1478 autoWidth: undefined, 1479 // width and height of nodes calculated by the renderer when set to special 'auto' value 1480 autoHeight: undefined, 1481 autoPadding: undefined, 1482 compoundBoundsClean: false, 1483 // whether the compound dimensions need to be recalculated the next time dimensions are read 1484 listeners: [], 1485 // array of bound listeners 1486 group: group, 1487 // string; 'nodes' or 'edges' 1488 style: {}, 1489 // properties as set by the style 1490 rstyle: {}, 1491 // properties for style sent from the renderer to the core 1492 styleCxts: [], 1493 // applied style contexts from the styler 1494 styleKeys: {}, 1495 // per-group keys of style property values 1496 removed: true, 1497 // whether it's inside the vis; true if removed (set true here since we call restore) 1498 selected: params.selected ? true : false, 1499 // whether it's selected 1500 selectable: params.selectable === undefined ? true : params.selectable ? true : false, 1501 // whether it's selectable 1502 locked: params.locked ? true : false, 1503 // whether the element is locked (cannot be moved) 1504 grabbed: false, 1505 // whether the element is grabbed by the mouse; renderer sets this privately 1506 grabbable: params.grabbable === undefined ? true : params.grabbable ? true : false, 1507 // whether the element can be grabbed 1508 pannable: params.pannable === undefined ? group === 'edges' ? true : false : params.pannable ? true : false, 1509 // whether the element has passthrough panning enabled 1510 active: false, 1511 // whether the element is active from user interaction 1512 classes: new Set$1(), 1513 // map ( className => true ) 1514 animation: { 1515 // object for currently-running animations 1516 current: [], 1517 queue: [] 1518 }, 1519 rscratch: {}, 1520 // object in which the renderer can store information 1521 scratch: params.scratch || {}, 1522 // scratch objects 1523 edges: [], 1524 // array of connected edges 1525 children: [], 1526 // array of children 1527 parent: null, 1528 // parent ref 1529 traversalCache: {}, 1530 // cache of output of traversal functions 1531 backgrounding: false, 1532 // whether background images are loading 1533 bbCache: null, 1534 // cache of the current bounding box 1535 bbCacheShift: { 1536 x: 0, 1537 y: 0 1538 }, 1539 // shift applied to cached bb to be applied on next get 1540 bodyBounds: null, 1541 // bounds cache of element body, w/o overlay 1542 overlayBounds: null, 1543 // bounds cache of element body, including overlay 1544 labelBounds: { 1545 // bounds cache of labels 1546 all: null, 1547 source: null, 1548 target: null, 1549 main: null 1550 }, 1551 arrowBounds: { 1552 // bounds cache of edge arrows 1553 source: null, 1554 target: null, 1555 'mid-source': null, 1556 'mid-target': null 1557 } 1558 }; 1559 1560 if (_p.position.x == null) { 1561 _p.position.x = 0; 1562 } 1563 1564 if (_p.position.y == null) { 1565 _p.position.y = 0; 1566 } // renderedPosition overrides if specified 1567 1568 1569 if (params.renderedPosition) { 1570 var rpos = params.renderedPosition; 1571 var pan = cy.pan(); 1572 var zoom = cy.zoom(); 1573 _p.position = { 1574 x: (rpos.x - pan.x) / zoom, 1575 y: (rpos.y - pan.y) / zoom 1576 }; 1577 } 1578 1579 var classes = []; 1580 1581 if (array(params.classes)) { 1582 classes = params.classes; 1583 } else if (string(params.classes)) { 1584 classes = params.classes.split(/\s+/); 1585 } 1586 1587 for (var i = 0, l = classes.length; i < l; i++) { 1588 var cls = classes[i]; 1589 1590 if (!cls || cls === '') { 1591 continue; 1592 } 1593 1594 _p.classes.add(cls); 1595 } 1596 1597 this.createEmitter(); 1598 var bypass = params.style || params.css; 1599 1600 if (bypass) { 1601 warn('Setting a `style` bypass at element creation is deprecated'); 1602 this.style(bypass); 1603 } 1604 1605 if (restore === undefined || restore) { 1606 this.restore(); 1607 } 1608 }; 1609 1610 var defineSearch = function defineSearch(params) { 1611 params = { 1612 bfs: params.bfs || !params.dfs, 1613 dfs: params.dfs || !params.bfs 1614 }; // from pseudocode on wikipedia 1615 1616 return function searchFn(roots, fn$1, directed) { 1617 var options; 1618 1619 if (plainObject(roots) && !elementOrCollection(roots)) { 1620 options = roots; 1621 roots = options.roots || options.root; 1622 fn$1 = options.visit; 1623 directed = options.directed; 1624 } 1625 1626 directed = arguments.length === 2 && !fn(fn$1) ? fn$1 : directed; 1627 fn$1 = fn(fn$1) ? fn$1 : function () {}; 1628 var cy = this._private.cy; 1629 var v = roots = string(roots) ? this.filter(roots) : roots; 1630 var Q = []; 1631 var connectedNodes = []; 1632 var connectedBy = {}; 1633 var id2depth = {}; 1634 var V = {}; 1635 var j = 0; 1636 var found; 1637 1638 var _this$byGroup = this.byGroup(), 1639 nodes = _this$byGroup.nodes, 1640 edges = _this$byGroup.edges; // enqueue v 1641 1642 1643 for (var i = 0; i < v.length; i++) { 1644 var vi = v[i]; 1645 var viId = vi.id(); 1646 1647 if (vi.isNode()) { 1648 Q.unshift(vi); 1649 1650 if (params.bfs) { 1651 V[viId] = true; 1652 connectedNodes.push(vi); 1653 } 1654 1655 id2depth[viId] = 0; 1656 } 1657 } 1658 1659 var _loop2 = function _loop2() { 1660 var v = params.bfs ? Q.shift() : Q.pop(); 1661 var vId = v.id(); 1662 1663 if (params.dfs) { 1664 if (V[vId]) { 1665 return "continue"; 1666 } 1667 1668 V[vId] = true; 1669 connectedNodes.push(v); 1670 } 1671 1672 var depth = id2depth[vId]; 1673 var prevEdge = connectedBy[vId]; 1674 var src = prevEdge != null ? prevEdge.source() : null; 1675 var tgt = prevEdge != null ? prevEdge.target() : null; 1676 var prevNode = prevEdge == null ? undefined : v.same(src) ? tgt[0] : src[0]; 1677 var ret = void 0; 1678 ret = fn$1(v, prevEdge, prevNode, j++, depth); 1679 1680 if (ret === true) { 1681 found = v; 1682 return "break"; 1683 } 1684 1685 if (ret === false) { 1686 return "break"; 1687 } 1688 1689 var vwEdges = v.connectedEdges().filter(function (e) { 1690 return (!directed || e.source().same(v)) && edges.has(e); 1691 }); 1692 1693 for (var _i2 = 0; _i2 < vwEdges.length; _i2++) { 1694 var e = vwEdges[_i2]; 1695 var w = e.connectedNodes().filter(function (n) { 1696 return !n.same(v) && nodes.has(n); 1697 }); 1698 var wId = w.id(); 1699 1700 if (w.length !== 0 && !V[wId]) { 1701 w = w[0]; 1702 Q.push(w); 1703 1704 if (params.bfs) { 1705 V[wId] = true; 1706 connectedNodes.push(w); 1707 } 1708 1709 connectedBy[wId] = e; 1710 id2depth[wId] = id2depth[vId] + 1; 1711 } 1712 } 1713 }; 1714 1715 _loop: while (Q.length !== 0) { 1716 var _ret = _loop2(); 1717 1718 switch (_ret) { 1719 case "continue": 1720 continue; 1721 1722 case "break": 1723 break _loop; 1724 } 1725 } 1726 1727 var connectedEles = cy.collection(); 1728 1729 for (var _i = 0; _i < connectedNodes.length; _i++) { 1730 var node = connectedNodes[_i]; 1731 var edge = connectedBy[node.id()]; 1732 1733 if (edge != null) { 1734 connectedEles.merge(edge); 1735 } 1736 1737 connectedEles.merge(node); 1738 } 1739 1740 return { 1741 path: cy.collection(connectedEles), 1742 found: cy.collection(found) 1743 }; 1744 }; 1745 }; // search, spanning trees, etc 1746 1747 1748 var elesfn = { 1749 breadthFirstSearch: defineSearch({ 1750 bfs: true 1751 }), 1752 depthFirstSearch: defineSearch({ 1753 dfs: true 1754 }) 1755 }; // nice, short mathemathical alias 1756 1757 elesfn.bfs = elesfn.breadthFirstSearch; 1758 elesfn.dfs = elesfn.depthFirstSearch; 1759 1760 var heap = createCommonjsModule(function (module, exports) { 1761 // Generated by CoffeeScript 1.8.0 1762 (function() { 1763 var Heap, defaultCmp, floor, heapify, heappop, heappush, heappushpop, heapreplace, insort, min, nlargest, nsmallest, updateItem, _siftdown, _siftup; 1764 1765 floor = Math.floor, min = Math.min; 1766 1767 1768 /* 1769 Default comparison function to be used 1770 */ 1771 1772 defaultCmp = function(x, y) { 1773 if (x < y) { 1774 return -1; 1775 } 1776 if (x > y) { 1777 return 1; 1778 } 1779 return 0; 1780 }; 1781 1782 1783 /* 1784 Insert item x in list a, and keep it sorted assuming a is sorted. 1785 1786 If x is already in a, insert it to the right of the rightmost x. 1787 1788 Optional args lo (default 0) and hi (default a.length) bound the slice 1789 of a to be searched. 1790 */ 1791 1792 insort = function(a, x, lo, hi, cmp) { 1793 var mid; 1794 if (lo == null) { 1795 lo = 0; 1796 } 1797 if (cmp == null) { 1798 cmp = defaultCmp; 1799 } 1800 if (lo < 0) { 1801 throw new Error('lo must be non-negative'); 1802 } 1803 if (hi == null) { 1804 hi = a.length; 1805 } 1806 while (lo < hi) { 1807 mid = floor((lo + hi) / 2); 1808 if (cmp(x, a[mid]) < 0) { 1809 hi = mid; 1810 } else { 1811 lo = mid + 1; 1812 } 1813 } 1814 return ([].splice.apply(a, [lo, lo - lo].concat(x)), x); 1815 }; 1816 1817 1818 /* 1819 Push item onto heap, maintaining the heap invariant. 1820 */ 1821 1822 heappush = function(array, item, cmp) { 1823 if (cmp == null) { 1824 cmp = defaultCmp; 1825 } 1826 array.push(item); 1827 return _siftdown(array, 0, array.length - 1, cmp); 1828 }; 1829 1830 1831 /* 1832 Pop the smallest item off the heap, maintaining the heap invariant. 1833 */ 1834 1835 heappop = function(array, cmp) { 1836 var lastelt, returnitem; 1837 if (cmp == null) { 1838 cmp = defaultCmp; 1839 } 1840 lastelt = array.pop(); 1841 if (array.length) { 1842 returnitem = array[0]; 1843 array[0] = lastelt; 1844 _siftup(array, 0, cmp); 1845 } else { 1846 returnitem = lastelt; 1847 } 1848 return returnitem; 1849 }; 1850 1851 1852 /* 1853 Pop and return the current smallest value, and add the new item. 1854 1855 This is more efficient than heappop() followed by heappush(), and can be 1856 more appropriate when using a fixed size heap. Note that the value 1857 returned may be larger than item! That constrains reasonable use of 1858 this routine unless written as part of a conditional replacement: 1859 if item > array[0] 1860 item = heapreplace(array, item) 1861 */ 1862 1863 heapreplace = function(array, item, cmp) { 1864 var returnitem; 1865 if (cmp == null) { 1866 cmp = defaultCmp; 1867 } 1868 returnitem = array[0]; 1869 array[0] = item; 1870 _siftup(array, 0, cmp); 1871 return returnitem; 1872 }; 1873 1874 1875 /* 1876 Fast version of a heappush followed by a heappop. 1877 */ 1878 1879 heappushpop = function(array, item, cmp) { 1880 var _ref; 1881 if (cmp == null) { 1882 cmp = defaultCmp; 1883 } 1884 if (array.length && cmp(array[0], item) < 0) { 1885 _ref = [array[0], item], item = _ref[0], array[0] = _ref[1]; 1886 _siftup(array, 0, cmp); 1887 } 1888 return item; 1889 }; 1890 1891 1892 /* 1893 Transform list into a heap, in-place, in O(array.length) time. 1894 */ 1895 1896 heapify = function(array, cmp) { 1897 var i, _i, _len, _ref1, _results, _results1; 1898 if (cmp == null) { 1899 cmp = defaultCmp; 1900 } 1901 _ref1 = (function() { 1902 _results1 = []; 1903 for (var _j = 0, _ref = floor(array.length / 2); 0 <= _ref ? _j < _ref : _j > _ref; 0 <= _ref ? _j++ : _j--){ _results1.push(_j); } 1904 return _results1; 1905 }).apply(this).reverse(); 1906 _results = []; 1907 for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 1908 i = _ref1[_i]; 1909 _results.push(_siftup(array, i, cmp)); 1910 } 1911 return _results; 1912 }; 1913 1914 1915 /* 1916 Update the position of the given item in the heap. 1917 This function should be called every time the item is being modified. 1918 */ 1919 1920 updateItem = function(array, item, cmp) { 1921 var pos; 1922 if (cmp == null) { 1923 cmp = defaultCmp; 1924 } 1925 pos = array.indexOf(item); 1926 if (pos === -1) { 1927 return; 1928 } 1929 _siftdown(array, 0, pos, cmp); 1930 return _siftup(array, pos, cmp); 1931 }; 1932 1933 1934 /* 1935 Find the n largest elements in a dataset. 1936 */ 1937 1938 nlargest = function(array, n, cmp) { 1939 var elem, result, _i, _len, _ref; 1940 if (cmp == null) { 1941 cmp = defaultCmp; 1942 } 1943 result = array.slice(0, n); 1944 if (!result.length) { 1945 return result; 1946 } 1947 heapify(result, cmp); 1948 _ref = array.slice(n); 1949 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1950 elem = _ref[_i]; 1951 heappushpop(result, elem, cmp); 1952 } 1953 return result.sort(cmp).reverse(); 1954 }; 1955 1956 1957 /* 1958 Find the n smallest elements in a dataset. 1959 */ 1960 1961 nsmallest = function(array, n, cmp) { 1962 var elem, i, los, result, _i, _j, _len, _ref, _ref1, _results; 1963 if (cmp == null) { 1964 cmp = defaultCmp; 1965 } 1966 if (n * 10 <= array.length) { 1967 result = array.slice(0, n).sort(cmp); 1968 if (!result.length) { 1969 return result; 1970 } 1971 los = result[result.length - 1]; 1972 _ref = array.slice(n); 1973 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1974 elem = _ref[_i]; 1975 if (cmp(elem, los) < 0) { 1976 insort(result, elem, 0, null, cmp); 1977 result.pop(); 1978 los = result[result.length - 1]; 1979 } 1980 } 1981 return result; 1982 } 1983 heapify(array, cmp); 1984 _results = []; 1985 for (i = _j = 0, _ref1 = min(n, array.length); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { 1986 _results.push(heappop(array, cmp)); 1987 } 1988 return _results; 1989 }; 1990 1991 _siftdown = function(array, startpos, pos, cmp) { 1992 var newitem, parent, parentpos; 1993 if (cmp == null) { 1994 cmp = defaultCmp; 1995 } 1996 newitem = array[pos]; 1997 while (pos > startpos) { 1998 parentpos = (pos - 1) >> 1; 1999 parent = array[parentpos]; 2000 if (cmp(newitem, parent) < 0) { 2001 array[pos] = parent; 2002 pos = parentpos; 2003 continue; 2004 } 2005 break; 2006 } 2007 return array[pos] = newitem; 2008 }; 2009 2010 _siftup = function(array, pos, cmp) { 2011 var childpos, endpos, newitem, rightpos, startpos; 2012 if (cmp == null) { 2013 cmp = defaultCmp; 2014 } 2015 endpos = array.length; 2016 startpos = pos; 2017 newitem = array[pos]; 2018 childpos = 2 * pos + 1; 2019 while (childpos < endpos) { 2020 rightpos = childpos + 1; 2021 if (rightpos < endpos && !(cmp(array[childpos], array[rightpos]) < 0)) { 2022 childpos = rightpos; 2023 } 2024 array[pos] = array[childpos]; 2025 pos = childpos; 2026 childpos = 2 * pos + 1; 2027 } 2028 array[pos] = newitem; 2029 return _siftdown(array, startpos, pos, cmp); 2030 }; 2031 2032 Heap = (function() { 2033 Heap.push = heappush; 2034 2035 Heap.pop = heappop; 2036 2037 Heap.replace = heapreplace; 2038 2039 Heap.pushpop = heappushpop; 2040 2041 Heap.heapify = heapify; 2042 2043 Heap.updateItem = updateItem; 2044 2045 Heap.nlargest = nlargest; 2046 2047 Heap.nsmallest = nsmallest; 2048 2049 function Heap(cmp) { 2050 this.cmp = cmp != null ? cmp : defaultCmp; 2051 this.nodes = []; 2052 } 2053 2054 Heap.prototype.push = function(x) { 2055 return heappush(this.nodes, x, this.cmp); 2056 }; 2057 2058 Heap.prototype.pop = function() { 2059 return heappop(this.nodes, this.cmp); 2060 }; 2061 2062 Heap.prototype.peek = function() { 2063 return this.nodes[0]; 2064 }; 2065 2066 Heap.prototype.contains = function(x) { 2067 return this.nodes.indexOf(x) !== -1; 2068 }; 2069 2070 Heap.prototype.replace = function(x) { 2071 return heapreplace(this.nodes, x, this.cmp); 2072 }; 2073 2074 Heap.prototype.pushpop = function(x) { 2075 return heappushpop(this.nodes, x, this.cmp); 2076 }; 2077 2078 Heap.prototype.heapify = function() { 2079 return heapify(this.nodes, this.cmp); 2080 }; 2081 2082 Heap.prototype.updateItem = function(x) { 2083 return updateItem(this.nodes, x, this.cmp); 2084 }; 2085 2086 Heap.prototype.clear = function() { 2087 return this.nodes = []; 2088 }; 2089 2090 Heap.prototype.empty = function() { 2091 return this.nodes.length === 0; 2092 }; 2093 2094 Heap.prototype.size = function() { 2095 return this.nodes.length; 2096 }; 2097 2098 Heap.prototype.clone = function() { 2099 var heap; 2100 heap = new Heap(); 2101 heap.nodes = this.nodes.slice(0); 2102 return heap; 2103 }; 2104 2105 Heap.prototype.toArray = function() { 2106 return this.nodes.slice(0); 2107 }; 2108 2109 Heap.prototype.insert = Heap.prototype.push; 2110 2111 Heap.prototype.top = Heap.prototype.peek; 2112 2113 Heap.prototype.front = Heap.prototype.peek; 2114 2115 Heap.prototype.has = Heap.prototype.contains; 2116 2117 Heap.prototype.copy = Heap.prototype.clone; 2118 2119 return Heap; 2120 2121 })(); 2122 2123 (function(root, factory) { 2124 { 2125 return module.exports = factory(); 2126 } 2127 })(this, function() { 2128 return Heap; 2129 }); 2130 2131 }).call(commonjsGlobal); 2132 }); 2133 2134 var heap$1 = heap; 2135 2136 var dijkstraDefaults = defaults({ 2137 root: null, 2138 weight: function weight(edge) { 2139 return 1; 2140 }, 2141 directed: false 2142 }); 2143 var elesfn$1 = { 2144 dijkstra: function dijkstra(options) { 2145 if (!plainObject(options)) { 2146 var args = arguments; 2147 options = { 2148 root: args[0], 2149 weight: args[1], 2150 directed: args[2] 2151 }; 2152 } 2153 2154 var _dijkstraDefaults = dijkstraDefaults(options), 2155 root = _dijkstraDefaults.root, 2156 weight = _dijkstraDefaults.weight, 2157 directed = _dijkstraDefaults.directed; 2158 2159 var eles = this; 2160 var weightFn = weight; 2161 var source = string(root) ? this.filter(root)[0] : root[0]; 2162 var dist = {}; 2163 var prev = {}; 2164 var knownDist = {}; 2165 2166 var _this$byGroup = this.byGroup(), 2167 nodes = _this$byGroup.nodes, 2168 edges = _this$byGroup.edges; 2169 2170 edges.unmergeBy(function (ele) { 2171 return ele.isLoop(); 2172 }); 2173 2174 var getDist = function getDist(node) { 2175 return dist[node.id()]; 2176 }; 2177 2178 var setDist = function setDist(node, d) { 2179 dist[node.id()] = d; 2180 Q.updateItem(node); 2181 }; 2182 2183 var Q = new heap$1(function (a, b) { 2184 return getDist(a) - getDist(b); 2185 }); 2186 2187 for (var i = 0; i < nodes.length; i++) { 2188 var node = nodes[i]; 2189 dist[node.id()] = node.same(source) ? 0 : Infinity; 2190 Q.push(node); 2191 } 2192 2193 var distBetween = function distBetween(u, v) { 2194 var uvs = (directed ? u.edgesTo(v) : u.edgesWith(v)).intersect(edges); 2195 var smallestDistance = Infinity; 2196 var smallestEdge; 2197 2198 for (var _i = 0; _i < uvs.length; _i++) { 2199 var edge = uvs[_i]; 2200 2201 var _weight = weightFn(edge); 2202 2203 if (_weight < smallestDistance || !smallestEdge) { 2204 smallestDistance = _weight; 2205 smallestEdge = edge; 2206 } 2207 } 2208 2209 return { 2210 edge: smallestEdge, 2211 dist: smallestDistance 2212 }; 2213 }; 2214 2215 while (Q.size() > 0) { 2216 var u = Q.pop(); 2217 var smalletsDist = getDist(u); 2218 var uid = u.id(); 2219 knownDist[uid] = smalletsDist; 2220 2221 if (smalletsDist === Infinity) { 2222 continue; 2223 } 2224 2225 var neighbors = u.neighborhood().intersect(nodes); 2226 2227 for (var _i2 = 0; _i2 < neighbors.length; _i2++) { 2228 var v = neighbors[_i2]; 2229 var vid = v.id(); 2230 var vDist = distBetween(u, v); 2231 var alt = smalletsDist + vDist.dist; 2232 2233 if (alt < getDist(v)) { 2234 setDist(v, alt); 2235 prev[vid] = { 2236 node: u, 2237 edge: vDist.edge 2238 }; 2239 } 2240 } // for 2241 2242 } // while 2243 2244 2245 return { 2246 distanceTo: function distanceTo(node) { 2247 var target = string(node) ? nodes.filter(node)[0] : node[0]; 2248 return knownDist[target.id()]; 2249 }, 2250 pathTo: function pathTo(node) { 2251 var target = string(node) ? nodes.filter(node)[0] : node[0]; 2252 var S = []; 2253 var u = target; 2254 var uid = u.id(); 2255 2256 if (target.length > 0) { 2257 S.unshift(target); 2258 2259 while (prev[uid]) { 2260 var p = prev[uid]; 2261 S.unshift(p.edge); 2262 S.unshift(p.node); 2263 u = p.node; 2264 uid = u.id(); 2265 } 2266 } 2267 2268 return eles.spawn(S); 2269 } 2270 }; 2271 } 2272 }; 2273 2274 var elesfn$2 = { 2275 // kruskal's algorithm (finds min spanning tree, assuming undirected graph) 2276 // implemented from pseudocode from wikipedia 2277 kruskal: function kruskal(weightFn) { 2278 weightFn = weightFn || function (edge) { 2279 return 1; 2280 }; 2281 2282 var _this$byGroup = this.byGroup(), 2283 nodes = _this$byGroup.nodes, 2284 edges = _this$byGroup.edges; 2285 2286 var numNodes = nodes.length; 2287 var forest = new Array(numNodes); 2288 var A = nodes; // assumes byGroup() creates new collections that can be safely mutated 2289 2290 var findSetIndex = function findSetIndex(ele) { 2291 for (var i = 0; i < forest.length; i++) { 2292 var eles = forest[i]; 2293 2294 if (eles.has(ele)) { 2295 return i; 2296 } 2297 } 2298 }; // start with one forest per node 2299 2300 2301 for (var i = 0; i < numNodes; i++) { 2302 forest[i] = this.spawn(nodes[i]); 2303 } 2304 2305 var S = edges.sort(function (a, b) { 2306 return weightFn(a) - weightFn(b); 2307 }); 2308 2309 for (var _i = 0; _i < S.length; _i++) { 2310 var edge = S[_i]; 2311 var u = edge.source()[0]; 2312 var v = edge.target()[0]; 2313 var setUIndex = findSetIndex(u); 2314 var setVIndex = findSetIndex(v); 2315 var setU = forest[setUIndex]; 2316 var setV = forest[setVIndex]; 2317 2318 if (setUIndex !== setVIndex) { 2319 A.merge(edge); // combine forests for u and v 2320 2321 setU.merge(setV); 2322 forest.splice(setVIndex, 1); 2323 } 2324 } 2325 2326 return A; 2327 } 2328 }; 2329 2330 var aStarDefaults = defaults({ 2331 root: null, 2332 goal: null, 2333 weight: function weight(edge) { 2334 return 1; 2335 }, 2336 heuristic: function heuristic(edge) { 2337 return 0; 2338 }, 2339 directed: false 2340 }); 2341 var elesfn$3 = { 2342 // Implemented from pseudocode from wikipedia 2343 aStar: function aStar(options) { 2344 var cy = this.cy(); 2345 2346 var _aStarDefaults = aStarDefaults(options), 2347 root = _aStarDefaults.root, 2348 goal = _aStarDefaults.goal, 2349 heuristic = _aStarDefaults.heuristic, 2350 directed = _aStarDefaults.directed, 2351 weight = _aStarDefaults.weight; 2352 2353 root = cy.collection(root)[0]; 2354 goal = cy.collection(goal)[0]; 2355 var sid = root.id(); 2356 var tid = goal.id(); 2357 var gScore = {}; 2358 var fScore = {}; 2359 var closedSetIds = {}; 2360 var openSet = new heap$1(function (a, b) { 2361 return fScore[a.id()] - fScore[b.id()]; 2362 }); 2363 var openSetIds = new Set$1(); 2364 var cameFrom = {}; 2365 var cameFromEdge = {}; 2366 2367 var addToOpenSet = function addToOpenSet(ele, id) { 2368 openSet.push(ele); 2369 openSetIds.add(id); 2370 }; 2371 2372 var cMin, cMinId; 2373 2374 var popFromOpenSet = function popFromOpenSet() { 2375 cMin = openSet.pop(); 2376 cMinId = cMin.id(); 2377 openSetIds["delete"](cMinId); 2378 }; 2379 2380 var isInOpenSet = function isInOpenSet(id) { 2381 return openSetIds.has(id); 2382 }; 2383 2384 addToOpenSet(root, sid); 2385 gScore[sid] = 0; 2386 fScore[sid] = heuristic(root); // Counter 2387 2388 var steps = 0; // Main loop 2389 2390 while (openSet.size() > 0) { 2391 popFromOpenSet(); 2392 steps++; // If we've found our goal, then we are done 2393 2394 if (cMinId === tid) { 2395 var path = []; 2396 var pathNode = goal; 2397 var pathNodeId = tid; 2398 var pathEdge = cameFromEdge[pathNodeId]; 2399 2400 for (;;) { 2401 path.unshift(pathNode); 2402 2403 if (pathEdge != null) { 2404 path.unshift(pathEdge); 2405 } 2406 2407 pathNode = cameFrom[pathNodeId]; 2408 2409 if (pathNode == null) { 2410 break; 2411 } 2412 2413 pathNodeId = pathNode.id(); 2414 pathEdge = cameFromEdge[pathNodeId]; 2415 } 2416 2417 return { 2418 found: true, 2419 distance: gScore[cMinId], 2420 path: this.spawn(path), 2421 steps: steps 2422 }; 2423 } // Add cMin to processed nodes 2424 2425 2426 closedSetIds[cMinId] = true; // Update scores for neighbors of cMin 2427 // Take into account if graph is directed or not 2428 2429 var vwEdges = cMin._private.edges; 2430 2431 for (var i = 0; i < vwEdges.length; i++) { 2432 var e = vwEdges[i]; // edge must be in set of calling eles 2433 2434 if (!this.hasElementWithId(e.id())) { 2435 continue; 2436 } // cMin must be the source of edge if directed 2437 2438 2439 if (directed && e.data('source') !== cMinId) { 2440 continue; 2441 } 2442 2443 var wSrc = e.source(); 2444 var wTgt = e.target(); 2445 var w = wSrc.id() !== cMinId ? wSrc : wTgt; 2446 var wid = w.id(); // node must be in set of calling eles 2447 2448 if (!this.hasElementWithId(wid)) { 2449 continue; 2450 } // if node is in closedSet, ignore it 2451 2452 2453 if (closedSetIds[wid]) { 2454 continue; 2455 } // New tentative score for node w 2456 2457 2458 var tempScore = gScore[cMinId] + weight(e); // Update gScore for node w if: 2459 // w not present in openSet 2460 // OR 2461 // tentative gScore is less than previous value 2462 // w not in openSet 2463 2464 if (!isInOpenSet(wid)) { 2465 gScore[wid] = tempScore; 2466 fScore[wid] = tempScore + heuristic(w); 2467 addToOpenSet(w, wid); 2468 cameFrom[wid] = cMin; 2469 cameFromEdge[wid] = e; 2470 continue; 2471 } // w already in openSet, but with greater gScore 2472 2473 2474 if (tempScore < gScore[wid]) { 2475 gScore[wid] = tempScore; 2476 fScore[wid] = tempScore + heuristic(w); 2477 cameFrom[wid] = cMin; 2478 } 2479 } // End of neighbors update 2480 2481 } // End of main loop 2482 // If we've reached here, then we've not reached our goal 2483 2484 2485 return { 2486 found: false, 2487 distance: undefined, 2488 path: undefined, 2489 steps: steps 2490 }; 2491 } 2492 }; // elesfn 2493 2494 var floydWarshallDefaults = defaults({ 2495 weight: function weight(edge) { 2496 return 1; 2497 }, 2498 directed: false 2499 }); 2500 var elesfn$4 = { 2501 // Implemented from pseudocode from wikipedia 2502 floydWarshall: function floydWarshall(options) { 2503 var cy = this.cy(); 2504 2505 var _floydWarshallDefault = floydWarshallDefaults(options), 2506 weight = _floydWarshallDefault.weight, 2507 directed = _floydWarshallDefault.directed; 2508 2509 var weightFn = weight; 2510 2511 var _this$byGroup = this.byGroup(), 2512 nodes = _this$byGroup.nodes, 2513 edges = _this$byGroup.edges; 2514 2515 var N = nodes.length; 2516 var Nsq = N * N; 2517 2518 var indexOf = function indexOf(node) { 2519 return nodes.indexOf(node); 2520 }; 2521 2522 var atIndex = function atIndex(i) { 2523 return nodes[i]; 2524 }; // Initialize distance matrix 2525 2526 2527 var dist = new Array(Nsq); 2528 2529 for (var n = 0; n < Nsq; n++) { 2530 var j = n % N; 2531 var i = (n - j) / N; 2532 2533 if (i === j) { 2534 dist[n] = 0; 2535 } else { 2536 dist[n] = Infinity; 2537 } 2538 } // Initialize matrix used for path reconstruction 2539 // Initialize distance matrix 2540 2541 2542 var next = new Array(Nsq); 2543 var edgeNext = new Array(Nsq); // Process edges 2544 2545 for (var _i = 0; _i < edges.length; _i++) { 2546 var edge = edges[_i]; 2547 var src = edge.source()[0]; 2548 var tgt = edge.target()[0]; 2549 2550 if (src === tgt) { 2551 continue; 2552 } // exclude loops 2553 2554 2555 var s = indexOf(src); 2556 var t = indexOf(tgt); 2557 var st = s * N + t; // source to target index 2558 2559 var _weight = weightFn(edge); // Check if already process another edge between same 2 nodes 2560 2561 2562 if (dist[st] > _weight) { 2563 dist[st] = _weight; 2564 next[st] = t; 2565 edgeNext[st] = edge; 2566 } // If undirected graph, process 'reversed' edge 2567 2568 2569 if (!directed) { 2570 var ts = t * N + s; // target to source index 2571 2572 if (!directed && dist[ts] > _weight) { 2573 dist[ts] = _weight; 2574 next[ts] = s; 2575 edgeNext[ts] = edge; 2576 } 2577 } 2578 } // Main loop 2579 2580 2581 for (var k = 0; k < N; k++) { 2582 for (var _i2 = 0; _i2 < N; _i2++) { 2583 var ik = _i2 * N + k; 2584 2585 for (var _j = 0; _j < N; _j++) { 2586 var ij = _i2 * N + _j; 2587 var kj = k * N + _j; 2588 2589 if (dist[ik] + dist[kj] < dist[ij]) { 2590 dist[ij] = dist[ik] + dist[kj]; 2591 next[ij] = next[ik]; 2592 } 2593 } 2594 } 2595 } 2596 2597 var getArgEle = function getArgEle(ele) { 2598 return (string(ele) ? cy.filter(ele) : ele)[0]; 2599 }; 2600 2601 var indexOfArgEle = function indexOfArgEle(ele) { 2602 return indexOf(getArgEle(ele)); 2603 }; 2604 2605 var res = { 2606 distance: function distance(from, to) { 2607 var i = indexOfArgEle(from); 2608 var j = indexOfArgEle(to); 2609 return dist[i * N + j]; 2610 }, 2611 path: function path(from, to) { 2612 var i = indexOfArgEle(from); 2613 var j = indexOfArgEle(to); 2614 var fromNode = atIndex(i); 2615 2616 if (i === j) { 2617 return fromNode.collection(); 2618 } 2619 2620 if (next[i * N + j] == null) { 2621 return cy.collection(); 2622 } 2623 2624 var path = cy.collection(); 2625 var prev = i; 2626 var edge; 2627 path.merge(fromNode); 2628 2629 while (i !== j) { 2630 prev = i; 2631 i = next[i * N + j]; 2632 edge = edgeNext[prev * N + i]; 2633 path.merge(edge); 2634 path.merge(atIndex(i)); 2635 } 2636 2637 return path; 2638 } 2639 }; 2640 return res; 2641 } // floydWarshall 2642 2643 }; // elesfn 2644 2645 var bellmanFordDefaults = defaults({ 2646 weight: function weight(edge) { 2647 return 1; 2648 }, 2649 directed: false, 2650 root: null 2651 }); 2652 var elesfn$5 = { 2653 // Implemented from pseudocode from wikipedia 2654 bellmanFord: function bellmanFord(options) { 2655 var _this = this; 2656 2657 var _bellmanFordDefaults = bellmanFordDefaults(options), 2658 weight = _bellmanFordDefaults.weight, 2659 directed = _bellmanFordDefaults.directed, 2660 root = _bellmanFordDefaults.root; 2661 2662 var weightFn = weight; 2663 var eles = this; 2664 var cy = this.cy(); 2665 2666 var _this$byGroup = this.byGroup(), 2667 edges = _this$byGroup.edges, 2668 nodes = _this$byGroup.nodes; 2669 2670 var numNodes = nodes.length; 2671 var infoMap = new Map$1(); 2672 var hasNegativeWeightCycle = false; 2673 var negativeWeightCycles = []; 2674 root = cy.collection(root)[0]; // in case selector passed 2675 2676 edges.unmergeBy(function (edge) { 2677 return edge.isLoop(); 2678 }); 2679 var numEdges = edges.length; 2680 2681 var getInfo = function getInfo(node) { 2682 var obj = infoMap.get(node.id()); 2683 2684 if (!obj) { 2685 obj = {}; 2686 infoMap.set(node.id(), obj); 2687 } 2688 2689 return obj; 2690 }; 2691 2692 var getNodeFromTo = function getNodeFromTo(to) { 2693 return (string(to) ? cy.$(to) : to)[0]; 2694 }; 2695 2696 var distanceTo = function distanceTo(to) { 2697 return getInfo(getNodeFromTo(to)).dist; 2698 }; 2699 2700 var pathTo = function pathTo(to) { 2701 var thisStart = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : root; 2702 var end = getNodeFromTo(to); 2703 var path = []; 2704 var node = end; 2705 2706 for (;;) { 2707 if (node == null) { 2708 return _this.spawn(); 2709 } 2710 2711 var _getInfo = getInfo(node), 2712 edge = _getInfo.edge, 2713 pred = _getInfo.pred; 2714 2715 path.unshift(node[0]); 2716 2717 if (node.same(thisStart) && path.length > 0) { 2718 break; 2719 } 2720 2721 if (edge != null) { 2722 path.unshift(edge); 2723 } 2724 2725 node = pred; 2726 } 2727 2728 return eles.spawn(path); 2729 }; // Initializations { dist, pred, edge } 2730 2731 2732 for (var i = 0; i < numNodes; i++) { 2733 var node = nodes[i]; 2734 var info = getInfo(node); 2735 2736 if (node.same(root)) { 2737 info.dist = 0; 2738 } else { 2739 info.dist = Infinity; 2740 } 2741 2742 info.pred = null; 2743 info.edge = null; 2744 } // Edges relaxation 2745 2746 2747 var replacedEdge = false; 2748 2749 var checkForEdgeReplacement = function checkForEdgeReplacement(node1, node2, edge, info1, info2, weight) { 2750 var dist = info1.dist + weight; 2751 2752 if (dist < info2.dist && !edge.same(info1.edge)) { 2753 info2.dist = dist; 2754 info2.pred = node1; 2755 info2.edge = edge; 2756 replacedEdge = true; 2757 } 2758 }; 2759 2760 for (var _i = 1; _i < numNodes; _i++) { 2761 replacedEdge = false; 2762 2763 for (var e = 0; e < numEdges; e++) { 2764 var edge = edges[e]; 2765 var src = edge.source(); 2766 var tgt = edge.target(); 2767 2768 var _weight = weightFn(edge); 2769 2770 var srcInfo = getInfo(src); 2771 var tgtInfo = getInfo(tgt); 2772 checkForEdgeReplacement(src, tgt, edge, srcInfo, tgtInfo, _weight); // If undirected graph, we need to take into account the 'reverse' edge 2773 2774 if (!directed) { 2775 checkForEdgeReplacement(tgt, src, edge, tgtInfo, srcInfo, _weight); 2776 } 2777 } 2778 2779 if (!replacedEdge) { 2780 break; 2781 } 2782 } 2783 2784 if (replacedEdge) { 2785 // Check for negative weight cycles 2786 for (var _e = 0; _e < numEdges; _e++) { 2787 var _edge = edges[_e]; 2788 2789 var _src = _edge.source(); 2790 2791 var _tgt = _edge.target(); 2792 2793 var _weight2 = weightFn(_edge); 2794 2795 var srcDist = getInfo(_src).dist; 2796 var tgtDist = getInfo(_tgt).dist; 2797 2798 if (srcDist + _weight2 < tgtDist || !directed && tgtDist + _weight2 < srcDist) { 2799 warn('Graph contains a negative weight cycle for Bellman-Ford'); 2800 hasNegativeWeightCycle = true; 2801 break; 2802 } 2803 } 2804 } 2805 2806 return { 2807 distanceTo: distanceTo, 2808 pathTo: pathTo, 2809 hasNegativeWeightCycle: hasNegativeWeightCycle, 2810 negativeWeightCycles: negativeWeightCycles 2811 }; 2812 } // bellmanFord 2813 2814 }; // elesfn 2815 2816 var sqrt2 = Math.sqrt(2); // Function which colapses 2 (meta) nodes into one 2817 // Updates the remaining edge lists 2818 // Receives as a paramater the edge which causes the collapse 2819 2820 var collapse = function collapse(edgeIndex, nodeMap, remainingEdges) { 2821 if (remainingEdges.length === 0) { 2822 error("Karger-Stein must be run on a connected (sub)graph"); 2823 } 2824 2825 var edgeInfo = remainingEdges[edgeIndex]; 2826 var sourceIn = edgeInfo[1]; 2827 var targetIn = edgeInfo[2]; 2828 var partition1 = nodeMap[sourceIn]; 2829 var partition2 = nodeMap[targetIn]; 2830 var newEdges = remainingEdges; // re-use array 2831 // Delete all edges between partition1 and partition2 2832 2833 for (var i = newEdges.length - 1; i >= 0; i--) { 2834 var edge = newEdges[i]; 2835 var src = edge[1]; 2836 var tgt = edge[2]; 2837 2838 if (nodeMap[src] === partition1 && nodeMap[tgt] === partition2 || nodeMap[src] === partition2 && nodeMap[tgt] === partition1) { 2839 newEdges.splice(i, 1); 2840 } 2841 } // All edges pointing to partition2 should now point to partition1 2842 2843 2844 for (var _i = 0; _i < newEdges.length; _i++) { 2845 var _edge = newEdges[_i]; 2846 2847 if (_edge[1] === partition2) { 2848 // Check source 2849 newEdges[_i] = _edge.slice(); // copy 2850 2851 newEdges[_i][1] = partition1; 2852 } else if (_edge[2] === partition2) { 2853 // Check target 2854 newEdges[_i] = _edge.slice(); // copy 2855 2856 newEdges[_i][2] = partition1; 2857 } 2858 } // Move all nodes from partition2 to partition1 2859 2860 2861 for (var _i2 = 0; _i2 < nodeMap.length; _i2++) { 2862 if (nodeMap[_i2] === partition2) { 2863 nodeMap[_i2] = partition1; 2864 } 2865 } 2866 2867 return newEdges; 2868 }; // Contracts a graph until we reach a certain number of meta nodes 2869 2870 2871 var contractUntil = function contractUntil(metaNodeMap, remainingEdges, size, sizeLimit) { 2872 while (size > sizeLimit) { 2873 // Choose an edge randomly 2874 var edgeIndex = Math.floor(Math.random() * remainingEdges.length); // Collapse graph based on edge 2875 2876 remainingEdges = collapse(edgeIndex, metaNodeMap, remainingEdges); 2877 size--; 2878 } 2879 2880 return remainingEdges; 2881 }; 2882 2883 var elesfn$6 = { 2884 // Computes the minimum cut of an undirected graph 2885 // Returns the correct answer with high probability 2886 kargerStein: function kargerStein() { 2887 var _this = this; 2888 2889 var _this$byGroup = this.byGroup(), 2890 nodes = _this$byGroup.nodes, 2891 edges = _this$byGroup.edges; 2892 2893 edges.unmergeBy(function (edge) { 2894 return edge.isLoop(); 2895 }); 2896 var numNodes = nodes.length; 2897 var numEdges = edges.length; 2898 var numIter = Math.ceil(Math.pow(Math.log(numNodes) / Math.LN2, 2)); 2899 var stopSize = Math.floor(numNodes / sqrt2); 2900 2901 if (numNodes < 2) { 2902 error('At least 2 nodes are required for Karger-Stein algorithm'); 2903 return undefined; 2904 } // Now store edge destination as indexes 2905 // Format for each edge (edge index, source node index, target node index) 2906 2907 2908 var edgeIndexes = []; 2909 2910 for (var i = 0; i < numEdges; i++) { 2911 var e = edges[i]; 2912 edgeIndexes.push([i, nodes.indexOf(e.source()), nodes.indexOf(e.target())]); 2913 } // We will store the best cut found here 2914 2915 2916 var minCutSize = Infinity; 2917 var minCutEdgeIndexes = []; 2918 var minCutNodeMap = new Array(numNodes); // Initial meta node partition 2919 2920 var metaNodeMap = new Array(numNodes); 2921 var metaNodeMap2 = new Array(numNodes); 2922 2923 var copyNodesMap = function copyNodesMap(from, to) { 2924 for (var _i3 = 0; _i3 < numNodes; _i3++) { 2925 to[_i3] = from[_i3]; 2926 } 2927 }; // Main loop 2928 2929 2930 for (var iter = 0; iter <= numIter; iter++) { 2931 // Reset meta node partition 2932 for (var _i4 = 0; _i4 < numNodes; _i4++) { 2933 metaNodeMap[_i4] = _i4; 2934 } // Contract until stop point (stopSize nodes) 2935 2936 2937 var edgesState = contractUntil(metaNodeMap, edgeIndexes.slice(), numNodes, stopSize); 2938 var edgesState2 = edgesState.slice(); // copy 2939 // Create a copy of the colapsed nodes state 2940 2941 copyNodesMap(metaNodeMap, metaNodeMap2); // Run 2 iterations starting in the stop state 2942 2943 var res1 = contractUntil(metaNodeMap, edgesState, stopSize, 2); 2944 var res2 = contractUntil(metaNodeMap2, edgesState2, stopSize, 2); // Is any of the 2 results the best cut so far? 2945 2946 if (res1.length <= res2.length && res1.length < minCutSize) { 2947 minCutSize = res1.length; 2948 minCutEdgeIndexes = res1; 2949 copyNodesMap(metaNodeMap, minCutNodeMap); 2950 } else if (res2.length <= res1.length && res2.length < minCutSize) { 2951 minCutSize = res2.length; 2952 minCutEdgeIndexes = res2; 2953 copyNodesMap(metaNodeMap2, minCutNodeMap); 2954 } 2955 } // end of main loop 2956 // Construct result 2957 2958 2959 var cut = this.spawn(minCutEdgeIndexes.map(function (e) { 2960 return edges[e[0]]; 2961 })); 2962 var partition1 = this.spawn(); 2963 var partition2 = this.spawn(); // traverse metaNodeMap for best cut 2964 2965 var witnessNodePartition = minCutNodeMap[0]; 2966 2967 for (var _i5 = 0; _i5 < minCutNodeMap.length; _i5++) { 2968 var partitionId = minCutNodeMap[_i5]; 2969 var node = nodes[_i5]; 2970 2971 if (partitionId === witnessNodePartition) { 2972 partition1.merge(node); 2973 } else { 2974 partition2.merge(node); 2975 } 2976 } // construct components corresponding to each disjoint subset of nodes 2977 2978 2979 var constructComponent = function constructComponent(subset) { 2980 var component = _this.spawn(); 2981 2982 subset.forEach(function (node) { 2983 component.merge(node); 2984 node.connectedEdges().forEach(function (edge) { 2985 // ensure edge is within calling collection and edge is not in cut 2986 if (_this.contains(edge) && !cut.contains(edge)) { 2987 component.merge(edge); 2988 } 2989 }); 2990 }); 2991 return component; 2992 }; 2993 2994 var components = [constructComponent(partition1), constructComponent(partition2)]; 2995 var ret = { 2996 cut: cut, 2997 components: components, 2998 // n.b. partitions are included to be compatible with the old api spec 2999 // (could be removed in a future major version) 3000 partition1: partition1, 3001 partition2: partition2 3002 }; 3003 return ret; 3004 } 3005 }; // elesfn 3006 3007 var copyPosition = function copyPosition(p) { 3008 return { 3009 x: p.x, 3010 y: p.y 3011 }; 3012 }; 3013 var modelToRenderedPosition = function modelToRenderedPosition(p, zoom, pan) { 3014 return { 3015 x: p.x * zoom + pan.x, 3016 y: p.y * zoom + pan.y 3017 }; 3018 }; 3019 var renderedToModelPosition = function renderedToModelPosition(p, zoom, pan) { 3020 return { 3021 x: (p.x - pan.x) / zoom, 3022 y: (p.y - pan.y) / zoom 3023 }; 3024 }; 3025 var array2point = function array2point(arr) { 3026 return { 3027 x: arr[0], 3028 y: arr[1] 3029 }; 3030 }; 3031 var min = function min(arr) { 3032 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 3033 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 3034 var min = Infinity; 3035 3036 for (var i = begin; i < end; i++) { 3037 var val = arr[i]; 3038 3039 if (isFinite(val)) { 3040 min = Math.min(val, min); 3041 } 3042 } 3043 3044 return min; 3045 }; 3046 var max = function max(arr) { 3047 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 3048 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 3049 var max = -Infinity; 3050 3051 for (var i = begin; i < end; i++) { 3052 var val = arr[i]; 3053 3054 if (isFinite(val)) { 3055 max = Math.max(val, max); 3056 } 3057 } 3058 3059 return max; 3060 }; 3061 var mean = function mean(arr) { 3062 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 3063 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 3064 var total = 0; 3065 var n = 0; 3066 3067 for (var i = begin; i < end; i++) { 3068 var val = arr[i]; 3069 3070 if (isFinite(val)) { 3071 total += val; 3072 n++; 3073 } 3074 } 3075 3076 return total / n; 3077 }; 3078 var median = function median(arr) { 3079 var begin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 3080 var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length; 3081 var copy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 3082 var sort = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 3083 var includeHoles = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 3084 3085 if (copy) { 3086 arr = arr.slice(begin, end); 3087 } else { 3088 if (end < arr.length) { 3089 arr.splice(end, arr.length - end); 3090 } 3091 3092 if (begin > 0) { 3093 arr.splice(0, begin); 3094 } 3095 } // all non finite (e.g. Infinity, NaN) elements must be -Infinity so they go to the start 3096 3097 3098 var off = 0; // offset from non-finite values 3099 3100 for (var i = arr.length - 1; i >= 0; i--) { 3101 var v = arr[i]; 3102 3103 if (includeHoles) { 3104 if (!isFinite(v)) { 3105 arr[i] = -Infinity; 3106 off++; 3107 } 3108 } else { 3109 // just remove it if we don't want to consider holes 3110 arr.splice(i, 1); 3111 } 3112 } 3113 3114 if (sort) { 3115 arr.sort(function (a, b) { 3116 return a - b; 3117 }); // requires copy = true if you don't want to change the orig 3118 } 3119 3120 var len = arr.length; 3121 var mid = Math.floor(len / 2); 3122 3123 if (len % 2 !== 0) { 3124 return arr[mid + 1 + off]; 3125 } else { 3126 return (arr[mid - 1 + off] + arr[mid + off]) / 2; 3127 } 3128 }; 3129 var deg2rad = function deg2rad(deg) { 3130 return Math.PI * deg / 180; 3131 }; 3132 var getAngleFromDisp = function getAngleFromDisp(dispX, dispY) { 3133 return Math.atan2(dispY, dispX) - Math.PI / 2; 3134 }; 3135 var log2 = Math.log2 || function (n) { 3136 return Math.log(n) / Math.log(2); 3137 }; 3138 var signum = function signum(x) { 3139 if (x > 0) { 3140 return 1; 3141 } else if (x < 0) { 3142 return -1; 3143 } else { 3144 return 0; 3145 } 3146 }; 3147 var dist = function dist(p1, p2) { 3148 return Math.sqrt(sqdist(p1, p2)); 3149 }; 3150 var sqdist = function sqdist(p1, p2) { 3151 var dx = p2.x - p1.x; 3152 var dy = p2.y - p1.y; 3153 return dx * dx + dy * dy; 3154 }; 3155 var inPlaceSumNormalize = function inPlaceSumNormalize(v) { 3156 var length = v.length; // First, get sum of all elements 3157 3158 var total = 0; 3159 3160 for (var i = 0; i < length; i++) { 3161 total += v[i]; 3162 } // Now, divide each by the sum of all elements 3163 3164 3165 for (var _i = 0; _i < length; _i++) { 3166 v[_i] = v[_i] / total; 3167 } 3168 3169 return v; 3170 }; 3171 3172 var qbezierAt = function qbezierAt(p0, p1, p2, t) { 3173 return (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2; 3174 }; 3175 var qbezierPtAt = function qbezierPtAt(p0, p1, p2, t) { 3176 return { 3177 x: qbezierAt(p0.x, p1.x, p2.x, t), 3178 y: qbezierAt(p0.y, p1.y, p2.y, t) 3179 }; 3180 }; 3181 var lineAt = function lineAt(p0, p1, t, d) { 3182 var vec = { 3183 x: p1.x - p0.x, 3184 y: p1.y - p0.y 3185 }; 3186 var vecDist = dist(p0, p1); 3187 var normVec = { 3188 x: vec.x / vecDist, 3189 y: vec.y / vecDist 3190 }; 3191 t = t == null ? 0 : t; 3192 d = d != null ? d : t * vecDist; 3193 return { 3194 x: p0.x + normVec.x * d, 3195 y: p0.y + normVec.y * d 3196 }; 3197 }; 3198 var bound = function bound(min, val, max) { 3199 return Math.max(min, Math.min(max, val)); 3200 }; // makes a full bb (x1, y1, x2, y2, w, h) from implicit params 3201 3202 var makeBoundingBox = function makeBoundingBox(bb) { 3203 if (bb == null) { 3204 return { 3205 x1: Infinity, 3206 y1: Infinity, 3207 x2: -Infinity, 3208 y2: -Infinity, 3209 w: 0, 3210 h: 0 3211 }; 3212 } else if (bb.x1 != null && bb.y1 != null) { 3213 if (bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1) { 3214 return { 3215 x1: bb.x1, 3216 y1: bb.y1, 3217 x2: bb.x2, 3218 y2: bb.y2, 3219 w: bb.x2 - bb.x1, 3220 h: bb.y2 - bb.y1 3221 }; 3222 } else if (bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0) { 3223 return { 3224 x1: bb.x1, 3225 y1: bb.y1, 3226 x2: bb.x1 + bb.w, 3227 y2: bb.y1 + bb.h, 3228 w: bb.w, 3229 h: bb.h 3230 }; 3231 } 3232 } 3233 }; 3234 var copyBoundingBox = function copyBoundingBox(bb) { 3235 return { 3236 x1: bb.x1, 3237 x2: bb.x2, 3238 w: bb.w, 3239 y1: bb.y1, 3240 y2: bb.y2, 3241 h: bb.h 3242 }; 3243 }; 3244 var clearBoundingBox = function clearBoundingBox(bb) { 3245 bb.x1 = Infinity; 3246 bb.y1 = Infinity; 3247 bb.x2 = -Infinity; 3248 bb.y2 = -Infinity; 3249 bb.w = 0; 3250 bb.h = 0; 3251 }; 3252 var updateBoundingBox = function updateBoundingBox(bb1, bb2) { 3253 // update bb1 with bb2 bounds 3254 bb1.x1 = Math.min(bb1.x1, bb2.x1); 3255 bb1.x2 = Math.max(bb1.x2, bb2.x2); 3256 bb1.w = bb1.x2 - bb1.x1; 3257 bb1.y1 = Math.min(bb1.y1, bb2.y1); 3258 bb1.y2 = Math.max(bb1.y2, bb2.y2); 3259 bb1.h = bb1.y2 - bb1.y1; 3260 }; 3261 var expandBoundingBoxByPoint = function expandBoundingBoxByPoint(bb, x, y) { 3262 bb.x1 = Math.min(bb.x1, x); 3263 bb.x2 = Math.max(bb.x2, x); 3264 bb.w = bb.x2 - bb.x1; 3265 bb.y1 = Math.min(bb.y1, y); 3266 bb.y2 = Math.max(bb.y2, y); 3267 bb.h = bb.y2 - bb.y1; 3268 }; 3269 var expandBoundingBox = function expandBoundingBox(bb) { 3270 var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 3271 bb.x1 -= padding; 3272 bb.x2 += padding; 3273 bb.y1 -= padding; 3274 bb.y2 += padding; 3275 bb.w = bb.x2 - bb.x1; 3276 bb.h = bb.y2 - bb.y1; 3277 return bb; 3278 }; 3279 var expandBoundingBoxSides = function expandBoundingBoxSides(bb) { 3280 var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [0]; 3281 var top, right, bottom, left; 3282 3283 if (padding.length === 1) { 3284 top = right = bottom = left = padding[0]; 3285 } else if (padding.length === 2) { 3286 top = bottom = padding[0]; 3287 left = right = padding[1]; 3288 } else if (padding.length === 4) { 3289 var _padding = _slicedToArray(padding, 4); 3290 3291 top = _padding[0]; 3292 right = _padding[1]; 3293 bottom = _padding[2]; 3294 left = _padding[3]; 3295 } 3296 3297 bb.x1 -= left; 3298 bb.x2 += right; 3299 bb.y1 -= top; 3300 bb.y2 += bottom; 3301 bb.w = bb.x2 - bb.x1; 3302 bb.h = bb.y2 - bb.y1; 3303 return bb; 3304 }; 3305 3306 var assignBoundingBox = function assignBoundingBox(bb1, bb2) { 3307 bb1.x1 = bb2.x1; 3308 bb1.y1 = bb2.y1; 3309 bb1.x2 = bb2.x2; 3310 bb1.y2 = bb2.y2; 3311 bb1.w = bb1.x2 - bb1.x1; 3312 bb1.h = bb1.y2 - bb1.y1; 3313 }; 3314 var assignShiftToBoundingBox = function assignShiftToBoundingBox(bb, delta) { 3315 bb.x1 += delta.x; 3316 bb.x2 += delta.x; 3317 bb.y1 += delta.y; 3318 bb.y2 += delta.y; 3319 }; 3320 var boundingBoxesIntersect = function boundingBoxesIntersect(bb1, bb2) { 3321 // case: one bb to right of other 3322 if (bb1.x1 > bb2.x2) { 3323 return false; 3324 } 3325 3326 if (bb2.x1 > bb1.x2) { 3327 return false; 3328 } // case: one bb to left of other 3329 3330 3331 if (bb1.x2 < bb2.x1) { 3332 return false; 3333 } 3334 3335 if (bb2.x2 < bb1.x1) { 3336 return false; 3337 } // case: one bb above other 3338 3339 3340 if (bb1.y2 < bb2.y1) { 3341 return false; 3342 } 3343 3344 if (bb2.y2 < bb1.y1) { 3345 return false; 3346 } // case: one bb below other 3347 3348 3349 if (bb1.y1 > bb2.y2) { 3350 return false; 3351 } 3352 3353 if (bb2.y1 > bb1.y2) { 3354 return false; 3355 } // otherwise, must have some overlap 3356 3357 3358 return true; 3359 }; 3360 var inBoundingBox = function inBoundingBox(bb, x, y) { 3361 return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2; 3362 }; 3363 var pointInBoundingBox = function pointInBoundingBox(bb, pt) { 3364 return inBoundingBox(bb, pt.x, pt.y); 3365 }; 3366 var boundingBoxInBoundingBox = function boundingBoxInBoundingBox(bb1, bb2) { 3367 return inBoundingBox(bb1, bb2.x1, bb2.y1) && inBoundingBox(bb1, bb2.x2, bb2.y2); 3368 }; 3369 var roundRectangleIntersectLine = function roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding) { 3370 var cornerRadius = getRoundRectangleRadius(width, height); 3371 var halfWidth = width / 2; 3372 var halfHeight = height / 2; // Check intersections with straight line segments 3373 3374 var straightLineIntersections; // Top segment, left to right 3375 3376 { 3377 var topStartX = nodeX - halfWidth + cornerRadius - padding; 3378 var topStartY = nodeY - halfHeight - padding; 3379 var topEndX = nodeX + halfWidth - cornerRadius + padding; 3380 var topEndY = topStartY; 3381 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); 3382 3383 if (straightLineIntersections.length > 0) { 3384 return straightLineIntersections; 3385 } 3386 } // Right segment, top to bottom 3387 3388 { 3389 var rightStartX = nodeX + halfWidth + padding; 3390 var rightStartY = nodeY - halfHeight + cornerRadius - padding; 3391 var rightEndX = rightStartX; 3392 var rightEndY = nodeY + halfHeight - cornerRadius + padding; 3393 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false); 3394 3395 if (straightLineIntersections.length > 0) { 3396 return straightLineIntersections; 3397 } 3398 } // Bottom segment, left to right 3399 3400 { 3401 var bottomStartX = nodeX - halfWidth + cornerRadius - padding; 3402 var bottomStartY = nodeY + halfHeight + padding; 3403 var bottomEndX = nodeX + halfWidth - cornerRadius + padding; 3404 var bottomEndY = bottomStartY; 3405 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false); 3406 3407 if (straightLineIntersections.length > 0) { 3408 return straightLineIntersections; 3409 } 3410 } // Left segment, top to bottom 3411 3412 { 3413 var leftStartX = nodeX - halfWidth - padding; 3414 var leftStartY = nodeY - halfHeight + cornerRadius - padding; 3415 var leftEndX = leftStartX; 3416 var leftEndY = nodeY + halfHeight - cornerRadius + padding; 3417 straightLineIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false); 3418 3419 if (straightLineIntersections.length > 0) { 3420 return straightLineIntersections; 3421 } 3422 } // Check intersections with arc segments 3423 3424 var arcIntersections; // Top Left 3425 3426 { 3427 var topLeftCenterX = nodeX - halfWidth + cornerRadius; 3428 var topLeftCenterY = nodeY - halfHeight + cornerRadius; 3429 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, topLeftCenterX, topLeftCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 3430 3431 if (arcIntersections.length > 0 && arcIntersections[0] <= topLeftCenterX && arcIntersections[1] <= topLeftCenterY) { 3432 return [arcIntersections[0], arcIntersections[1]]; 3433 } 3434 } // Top Right 3435 3436 { 3437 var topRightCenterX = nodeX + halfWidth - cornerRadius; 3438 var topRightCenterY = nodeY - halfHeight + cornerRadius; 3439 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, topRightCenterX, topRightCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 3440 3441 if (arcIntersections.length > 0 && arcIntersections[0] >= topRightCenterX && arcIntersections[1] <= topRightCenterY) { 3442 return [arcIntersections[0], arcIntersections[1]]; 3443 } 3444 } // Bottom Right 3445 3446 { 3447 var bottomRightCenterX = nodeX + halfWidth - cornerRadius; 3448 var bottomRightCenterY = nodeY + halfHeight - cornerRadius; 3449 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, bottomRightCenterX, bottomRightCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 3450 3451 if (arcIntersections.length > 0 && arcIntersections[0] >= bottomRightCenterX && arcIntersections[1] >= bottomRightCenterY) { 3452 return [arcIntersections[0], arcIntersections[1]]; 3453 } 3454 } // Bottom Left 3455 3456 { 3457 var bottomLeftCenterX = nodeX - halfWidth + cornerRadius; 3458 var bottomLeftCenterY = nodeY + halfHeight - cornerRadius; 3459 arcIntersections = intersectLineCircle(x, y, nodeX, nodeY, bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding); // Ensure the intersection is on the desired quarter of the circle 3460 3461 if (arcIntersections.length > 0 && arcIntersections[0] <= bottomLeftCenterX && arcIntersections[1] >= bottomLeftCenterY) { 3462 return [arcIntersections[0], arcIntersections[1]]; 3463 } 3464 } 3465 return []; // if nothing 3466 }; 3467 var inLineVicinity = function inLineVicinity(x, y, lx1, ly1, lx2, ly2, tolerance) { 3468 var t = tolerance; 3469 var x1 = Math.min(lx1, lx2); 3470 var x2 = Math.max(lx1, lx2); 3471 var y1 = Math.min(ly1, ly2); 3472 var y2 = Math.max(ly1, ly2); 3473 return x1 - t <= x && x <= x2 + t && y1 - t <= y && y <= y2 + t; 3474 }; 3475 var inBezierVicinity = function inBezierVicinity(x, y, x1, y1, x2, y2, x3, y3, tolerance) { 3476 var bb = { 3477 x1: Math.min(x1, x3, x2) - tolerance, 3478 x2: Math.max(x1, x3, x2) + tolerance, 3479 y1: Math.min(y1, y3, y2) - tolerance, 3480 y2: Math.max(y1, y3, y2) + tolerance 3481 }; // if outside the rough bounding box for the bezier, then it can't be a hit 3482 3483 if (x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2) { 3484 // console.log('bezier out of rough bb') 3485 return false; 3486 } else { 3487 // console.log('do more expensive check'); 3488 return true; 3489 } 3490 }; 3491 var solveQuadratic = function solveQuadratic(a, b, c, val) { 3492 c -= val; 3493 var r = b * b - 4 * a * c; 3494 3495 if (r < 0) { 3496 return []; 3497 } 3498 3499 var sqrtR = Math.sqrt(r); 3500 var denom = 2 * a; 3501 var root1 = (-b + sqrtR) / denom; 3502 var root2 = (-b - sqrtR) / denom; 3503 return [root1, root2]; 3504 }; 3505 var solveCubic = function solveCubic(a, b, c, d, result) { 3506 // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where 3507 // r is the real component, i is the imaginary component 3508 // An implementation of the Cardano method from the year 1545 3509 // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots 3510 var epsilon = 0.00001; // avoid division by zero while keeping the overall expression close in value 3511 3512 if (a === 0) { 3513 a = epsilon; 3514 } 3515 3516 b /= a; 3517 c /= a; 3518 d /= a; 3519 var discriminant, q, r, dum1, s, t, term1, r13; 3520 q = (3.0 * c - b * b) / 9.0; 3521 r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b)); 3522 r /= 54.0; 3523 discriminant = q * q * q + r * r; 3524 result[1] = 0; 3525 term1 = b / 3.0; 3526 3527 if (discriminant > 0) { 3528 s = r + Math.sqrt(discriminant); 3529 s = s < 0 ? -Math.pow(-s, 1.0 / 3.0) : Math.pow(s, 1.0 / 3.0); 3530 t = r - Math.sqrt(discriminant); 3531 t = t < 0 ? -Math.pow(-t, 1.0 / 3.0) : Math.pow(t, 1.0 / 3.0); 3532 result[0] = -term1 + s + t; 3533 term1 += (s + t) / 2.0; 3534 result[4] = result[2] = -term1; 3535 term1 = Math.sqrt(3.0) * (-t + s) / 2; 3536 result[3] = term1; 3537 result[5] = -term1; 3538 return; 3539 } 3540 3541 result[5] = result[3] = 0; 3542 3543 if (discriminant === 0) { 3544 r13 = r < 0 ? -Math.pow(-r, 1.0 / 3.0) : Math.pow(r, 1.0 / 3.0); 3545 result[0] = -term1 + 2.0 * r13; 3546 result[4] = result[2] = -(r13 + term1); 3547 return; 3548 } 3549 3550 q = -q; 3551 dum1 = q * q * q; 3552 dum1 = Math.acos(r / Math.sqrt(dum1)); 3553 r13 = 2.0 * Math.sqrt(q); 3554 result[0] = -term1 + r13 * Math.cos(dum1 / 3.0); 3555 result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0); 3556 result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0); 3557 return; 3558 }; 3559 var sqdistToQuadraticBezier = function sqdistToQuadraticBezier(x, y, x1, y1, x2, y2, x3, y3) { 3560 // Find minimum distance by using the minimum of the distance 3561 // function between the given point and the curve 3562 // This gives the coefficients of the resulting cubic equation 3563 // whose roots tell us where a possible minimum is 3564 // (Coefficients are divided by 4) 3565 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; 3566 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; 3567 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; 3568 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); 3569 3570 var roots = []; // Use the cubic solving algorithm 3571 3572 solveCubic(a, b, c, d, roots); 3573 var zeroThreshold = 0.0000001; 3574 var params = []; 3575 3576 for (var index = 0; index < 6; index += 2) { 3577 if (Math.abs(roots[index + 1]) < zeroThreshold && roots[index] >= 0 && roots[index] <= 1.0) { 3578 params.push(roots[index]); 3579 } 3580 } 3581 3582 params.push(1.0); 3583 params.push(0.0); 3584 var minDistanceSquared = -1; 3585 var curX, curY, distSquared; 3586 3587 for (var i = 0; i < params.length; i++) { 3588 curX = Math.pow(1.0 - params[i], 2.0) * x1 + 2.0 * (1 - params[i]) * params[i] * x2 + params[i] * params[i] * x3; 3589 curY = Math.pow(1 - params[i], 2.0) * y1 + 2 * (1.0 - params[i]) * params[i] * y2 + params[i] * params[i] * y3; 3590 distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2); // debug('distance for param ' + params[i] + ": " + Math.sqrt(distSquared)); 3591 3592 if (minDistanceSquared >= 0) { 3593 if (distSquared < minDistanceSquared) { 3594 minDistanceSquared = distSquared; 3595 } 3596 } else { 3597 minDistanceSquared = distSquared; 3598 } 3599 } 3600 3601 return minDistanceSquared; 3602 }; 3603 var sqdistToFiniteLine = function sqdistToFiniteLine(x, y, x1, y1, x2, y2) { 3604 var offset = [x - x1, y - y1]; 3605 var line = [x2 - x1, y2 - y1]; 3606 var lineSq = line[0] * line[0] + line[1] * line[1]; 3607 var hypSq = offset[0] * offset[0] + offset[1] * offset[1]; 3608 var dotProduct = offset[0] * line[0] + offset[1] * line[1]; 3609 var adjSq = dotProduct * dotProduct / lineSq; 3610 3611 if (dotProduct < 0) { 3612 return hypSq; 3613 } 3614 3615 if (adjSq > lineSq) { 3616 return (x - x2) * (x - x2) + (y - y2) * (y - y2); 3617 } 3618 3619 return hypSq - adjSq; 3620 }; 3621 var pointInsidePolygonPoints = function pointInsidePolygonPoints(x, y, points) { 3622 var x1, y1, x2, y2; 3623 var y3; // Intersect with vertical line through (x, y) 3624 3625 var up = 0; // let down = 0; 3626 3627 for (var i = 0; i < points.length / 2; i++) { 3628 x1 = points[i * 2]; 3629 y1 = points[i * 2 + 1]; 3630 3631 if (i + 1 < points.length / 2) { 3632 x2 = points[(i + 1) * 2]; 3633 y2 = points[(i + 1) * 2 + 1]; 3634 } else { 3635 x2 = points[(i + 1 - points.length / 2) * 2]; 3636 y2 = points[(i + 1 - points.length / 2) * 2 + 1]; 3637 } 3638 3639 if (x1 == x && x2 == x) ; else if (x1 >= x && x >= x2 || x1 <= x && x <= x2) { 3640 y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1; 3641 3642 if (y3 > y) { 3643 up++; 3644 } // if( y3 < y ){ 3645 // down++; 3646 // } 3647 3648 } else { 3649 continue; 3650 } 3651 } 3652 3653 if (up % 2 === 0) { 3654 return false; 3655 } else { 3656 return true; 3657 } 3658 }; 3659 var pointInsidePolygon = function pointInsidePolygon(x, y, basePoints, centerX, centerY, width, height, direction, padding) { 3660 var transformedPoints = new Array(basePoints.length); // Gives negative angle 3661 3662 var angle; 3663 3664 if (direction[0] != null) { 3665 angle = Math.atan(direction[1] / direction[0]); 3666 3667 if (direction[0] < 0) { 3668 angle = angle + Math.PI / 2; 3669 } else { 3670 angle = -angle - Math.PI / 2; 3671 } 3672 } else { 3673 angle = direction; 3674 } 3675 3676 var cos = Math.cos(-angle); 3677 var sin = Math.sin(-angle); // console.log("base: " + basePoints); 3678 3679 for (var i = 0; i < transformedPoints.length / 2; i++) { 3680 transformedPoints[i * 2] = width / 2 * (basePoints[i * 2] * cos - basePoints[i * 2 + 1] * sin); 3681 transformedPoints[i * 2 + 1] = height / 2 * (basePoints[i * 2 + 1] * cos + basePoints[i * 2] * sin); 3682 transformedPoints[i * 2] += centerX; 3683 transformedPoints[i * 2 + 1] += centerY; 3684 } 3685 3686 var points; 3687 3688 if (padding > 0) { 3689 var expandedLineSet = expandPolygon(transformedPoints, -padding); 3690 points = joinLines(expandedLineSet); 3691 } else { 3692 points = transformedPoints; 3693 } 3694 3695 return pointInsidePolygonPoints(x, y, points); 3696 }; 3697 var pointInsideRoundPolygon = function pointInsideRoundPolygon(x, y, basePoints, centerX, centerY, width, height) { 3698 var cutPolygonPoints = new Array(basePoints.length); 3699 var halfW = width / 2; 3700 var halfH = height / 2; 3701 var cornerRadius = getRoundPolygonRadius(width, height); 3702 var squaredCornerRadius = cornerRadius * cornerRadius; 3703 3704 for (var i = 0; i < basePoints.length / 4; i++) { 3705 var sourceUv = void 0, 3706 destUv = void 0; 3707 3708 if (i === 0) { 3709 sourceUv = basePoints.length - 2; 3710 } else { 3711 sourceUv = i * 4 - 2; 3712 } 3713 3714 destUv = i * 4 + 2; 3715 var px = centerX + halfW * basePoints[i * 4]; 3716 var py = centerY + halfH * basePoints[i * 4 + 1]; 3717 var cosTheta = -basePoints[sourceUv] * basePoints[destUv] - basePoints[sourceUv + 1] * basePoints[destUv + 1]; 3718 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 3719 var cp0x = px - offset * basePoints[sourceUv]; 3720 var cp0y = py - offset * basePoints[sourceUv + 1]; 3721 var cp1x = px + offset * basePoints[destUv]; 3722 var cp1y = py + offset * basePoints[destUv + 1]; 3723 cutPolygonPoints[i * 4] = cp0x; 3724 cutPolygonPoints[i * 4 + 1] = cp0y; 3725 cutPolygonPoints[i * 4 + 2] = cp1x; 3726 cutPolygonPoints[i * 4 + 3] = cp1y; 3727 var orthx = basePoints[sourceUv + 1]; 3728 var orthy = -basePoints[sourceUv]; 3729 var cosAlpha = orthx * basePoints[destUv] + orthy * basePoints[destUv + 1]; 3730 3731 if (cosAlpha < 0) { 3732 orthx *= -1; 3733 orthy *= -1; 3734 } 3735 3736 var cx = cp0x + orthx * cornerRadius; 3737 var cy = cp0y + orthy * cornerRadius; 3738 var squaredDistance = Math.pow(cx - x, 2) + Math.pow(cy - y, 2); 3739 3740 if (squaredDistance <= squaredCornerRadius) { 3741 return true; 3742 } 3743 } 3744 3745 return pointInsidePolygonPoints(x, y, cutPolygonPoints); 3746 }; 3747 var joinLines = function joinLines(lineSet) { 3748 var vertices = new Array(lineSet.length / 2); 3749 var currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY; 3750 var nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY; 3751 3752 for (var i = 0; i < lineSet.length / 4; i++) { 3753 currentLineStartX = lineSet[i * 4]; 3754 currentLineStartY = lineSet[i * 4 + 1]; 3755 currentLineEndX = lineSet[i * 4 + 2]; 3756 currentLineEndY = lineSet[i * 4 + 3]; 3757 3758 if (i < lineSet.length / 4 - 1) { 3759 nextLineStartX = lineSet[(i + 1) * 4]; 3760 nextLineStartY = lineSet[(i + 1) * 4 + 1]; 3761 nextLineEndX = lineSet[(i + 1) * 4 + 2]; 3762 nextLineEndY = lineSet[(i + 1) * 4 + 3]; 3763 } else { 3764 nextLineStartX = lineSet[0]; 3765 nextLineStartY = lineSet[1]; 3766 nextLineEndX = lineSet[2]; 3767 nextLineEndY = lineSet[3]; 3768 } 3769 3770 var intersection = finiteLinesIntersect(currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY, nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY, true); 3771 vertices[i * 2] = intersection[0]; 3772 vertices[i * 2 + 1] = intersection[1]; 3773 } 3774 3775 return vertices; 3776 }; 3777 var expandPolygon = function expandPolygon(points, pad) { 3778 var expandedLineSet = new Array(points.length * 2); 3779 var currentPointX, currentPointY, nextPointX, nextPointY; 3780 3781 for (var i = 0; i < points.length / 2; i++) { 3782 currentPointX = points[i * 2]; 3783 currentPointY = points[i * 2 + 1]; 3784 3785 if (i < points.length / 2 - 1) { 3786 nextPointX = points[(i + 1) * 2]; 3787 nextPointY = points[(i + 1) * 2 + 1]; 3788 } else { 3789 nextPointX = points[0]; 3790 nextPointY = points[1]; 3791 } // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY] 3792 // Assume CCW polygon winding 3793 3794 3795 var offsetX = nextPointY - currentPointY; 3796 var offsetY = -(nextPointX - currentPointX); // Normalize 3797 3798 var offsetLength = Math.sqrt(offsetX * offsetX + offsetY * offsetY); 3799 var normalizedOffsetX = offsetX / offsetLength; 3800 var normalizedOffsetY = offsetY / offsetLength; 3801 expandedLineSet[i * 4] = currentPointX + normalizedOffsetX * pad; 3802 expandedLineSet[i * 4 + 1] = currentPointY + normalizedOffsetY * pad; 3803 expandedLineSet[i * 4 + 2] = nextPointX + normalizedOffsetX * pad; 3804 expandedLineSet[i * 4 + 3] = nextPointY + normalizedOffsetY * pad; 3805 } 3806 3807 return expandedLineSet; 3808 }; 3809 var intersectLineEllipse = function intersectLineEllipse(x, y, centerX, centerY, ellipseWradius, ellipseHradius) { 3810 var dispX = centerX - x; 3811 var dispY = centerY - y; 3812 dispX /= ellipseWradius; 3813 dispY /= ellipseHradius; 3814 var len = Math.sqrt(dispX * dispX + dispY * dispY); 3815 var newLength = len - 1; 3816 3817 if (newLength < 0) { 3818 return []; 3819 } 3820 3821 var lenProportion = newLength / len; 3822 return [(centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y]; 3823 }; 3824 var checkInEllipse = function checkInEllipse(x, y, width, height, centerX, centerY, padding) { 3825 x -= centerX; 3826 y -= centerY; 3827 x /= width / 2 + padding; 3828 y /= height / 2 + padding; 3829 return x * x + y * y <= 1; 3830 }; // Returns intersections of increasing distance from line's start point 3831 3832 var intersectLineCircle = function intersectLineCircle(x1, y1, x2, y2, centerX, centerY, radius) { 3833 // Calculate d, direction vector of line 3834 var d = [x2 - x1, y2 - y1]; // Direction vector of line 3835 3836 var f = [x1 - centerX, y1 - centerY]; 3837 var a = d[0] * d[0] + d[1] * d[1]; 3838 var b = 2 * (f[0] * d[0] + f[1] * d[1]); 3839 var c = f[0] * f[0] + f[1] * f[1] - radius * radius; 3840 var discriminant = b * b - 4 * a * c; 3841 3842 if (discriminant < 0) { 3843 return []; 3844 } 3845 3846 var t1 = (-b + Math.sqrt(discriminant)) / (2 * a); 3847 var t2 = (-b - Math.sqrt(discriminant)) / (2 * a); 3848 var tMin = Math.min(t1, t2); 3849 var tMax = Math.max(t1, t2); 3850 var inRangeParams = []; 3851 3852 if (tMin >= 0 && tMin <= 1) { 3853 inRangeParams.push(tMin); 3854 } 3855 3856 if (tMax >= 0 && tMax <= 1) { 3857 inRangeParams.push(tMax); 3858 } 3859 3860 if (inRangeParams.length === 0) { 3861 return []; 3862 } 3863 3864 var nearIntersectionX = inRangeParams[0] * d[0] + x1; 3865 var nearIntersectionY = inRangeParams[0] * d[1] + y1; 3866 3867 if (inRangeParams.length > 1) { 3868 if (inRangeParams[0] == inRangeParams[1]) { 3869 return [nearIntersectionX, nearIntersectionY]; 3870 } else { 3871 var farIntersectionX = inRangeParams[1] * d[0] + x1; 3872 var farIntersectionY = inRangeParams[1] * d[1] + y1; 3873 return [nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY]; 3874 } 3875 } else { 3876 return [nearIntersectionX, nearIntersectionY]; 3877 } 3878 }; 3879 var midOfThree = function midOfThree(a, b, c) { 3880 if (b <= a && a <= c || c <= a && a <= b) { 3881 return a; 3882 } else if (a <= b && b <= c || c <= b && b <= a) { 3883 return b; 3884 } else { 3885 return c; 3886 } 3887 }; // (x1,y1)=>(x2,y2) intersect with (x3,y3)=>(x4,y4) 3888 3889 var finiteLinesIntersect = function finiteLinesIntersect(x1, y1, x2, y2, x3, y3, x4, y4, infiniteLines) { 3890 var dx13 = x1 - x3; 3891 var dx21 = x2 - x1; 3892 var dx43 = x4 - x3; 3893 var dy13 = y1 - y3; 3894 var dy21 = y2 - y1; 3895 var dy43 = y4 - y3; 3896 var ua_t = dx43 * dy13 - dy43 * dx13; 3897 var ub_t = dx21 * dy13 - dy21 * dx13; 3898 var u_b = dy43 * dx21 - dx43 * dy21; 3899 3900 if (u_b !== 0) { 3901 var ua = ua_t / u_b; 3902 var ub = ub_t / u_b; 3903 var flptThreshold = 0.001; 3904 3905 var _min = 0 - flptThreshold; 3906 3907 var _max = 1 + flptThreshold; 3908 3909 if (_min <= ua && ua <= _max && _min <= ub && ub <= _max) { 3910 return [x1 + ua * dx21, y1 + ua * dy21]; 3911 } else { 3912 if (!infiniteLines) { 3913 return []; 3914 } else { 3915 return [x1 + ua * dx21, y1 + ua * dy21]; 3916 } 3917 } 3918 } else { 3919 if (ua_t === 0 || ub_t === 0) { 3920 // Parallel, coincident lines. Check if overlap 3921 // Check endpoint of second line 3922 if (midOfThree(x1, x2, x4) === x4) { 3923 return [x4, y4]; 3924 } // Check start point of second line 3925 3926 3927 if (midOfThree(x1, x2, x3) === x3) { 3928 return [x3, y3]; 3929 } // Endpoint of first line 3930 3931 3932 if (midOfThree(x3, x4, x2) === x2) { 3933 return [x2, y2]; 3934 } 3935 3936 return []; 3937 } else { 3938 // Parallel, non-coincident 3939 return []; 3940 } 3941 } 3942 }; // math.polygonIntersectLine( x, y, basePoints, centerX, centerY, width, height, padding ) 3943 // intersect a node polygon (pts transformed) 3944 // 3945 // math.polygonIntersectLine( x, y, basePoints, centerX, centerY ) 3946 // intersect the points (no transform) 3947 3948 var polygonIntersectLine = function polygonIntersectLine(x, y, basePoints, centerX, centerY, width, height, padding) { 3949 var intersections = []; 3950 var intersection; 3951 var transformedPoints = new Array(basePoints.length); 3952 var doTransform = true; 3953 3954 if (width == null) { 3955 doTransform = false; 3956 } 3957 3958 var points; 3959 3960 if (doTransform) { 3961 for (var i = 0; i < transformedPoints.length / 2; i++) { 3962 transformedPoints[i * 2] = basePoints[i * 2] * width + centerX; 3963 transformedPoints[i * 2 + 1] = basePoints[i * 2 + 1] * height + centerY; 3964 } 3965 3966 if (padding > 0) { 3967 var expandedLineSet = expandPolygon(transformedPoints, -padding); 3968 points = joinLines(expandedLineSet); 3969 } else { 3970 points = transformedPoints; 3971 } 3972 } else { 3973 points = basePoints; 3974 } 3975 3976 var currentX, currentY, nextX, nextY; 3977 3978 for (var _i2 = 0; _i2 < points.length / 2; _i2++) { 3979 currentX = points[_i2 * 2]; 3980 currentY = points[_i2 * 2 + 1]; 3981 3982 if (_i2 < points.length / 2 - 1) { 3983 nextX = points[(_i2 + 1) * 2]; 3984 nextY = points[(_i2 + 1) * 2 + 1]; 3985 } else { 3986 nextX = points[0]; 3987 nextY = points[1]; 3988 } 3989 3990 intersection = finiteLinesIntersect(x, y, centerX, centerY, currentX, currentY, nextX, nextY); 3991 3992 if (intersection.length !== 0) { 3993 intersections.push(intersection[0], intersection[1]); 3994 } 3995 } 3996 3997 return intersections; 3998 }; 3999 var roundPolygonIntersectLine = function roundPolygonIntersectLine(x, y, basePoints, centerX, centerY, width, height, padding) { 4000 var intersections = []; 4001 var intersection; 4002 var lines = new Array(basePoints.length); 4003 var halfW = width / 2; 4004 var halfH = height / 2; 4005 var cornerRadius = getRoundPolygonRadius(width, height); 4006 4007 for (var i = 0; i < basePoints.length / 4; i++) { 4008 var sourceUv = void 0, 4009 destUv = void 0; 4010 4011 if (i === 0) { 4012 sourceUv = basePoints.length - 2; 4013 } else { 4014 sourceUv = i * 4 - 2; 4015 } 4016 4017 destUv = i * 4 + 2; 4018 var px = centerX + halfW * basePoints[i * 4]; 4019 var py = centerY + halfH * basePoints[i * 4 + 1]; 4020 var cosTheta = -basePoints[sourceUv] * basePoints[destUv] - basePoints[sourceUv + 1] * basePoints[destUv + 1]; 4021 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 4022 var cp0x = px - offset * basePoints[sourceUv]; 4023 var cp0y = py - offset * basePoints[sourceUv + 1]; 4024 var cp1x = px + offset * basePoints[destUv]; 4025 var cp1y = py + offset * basePoints[destUv + 1]; 4026 4027 if (i === 0) { 4028 lines[basePoints.length - 2] = cp0x; 4029 lines[basePoints.length - 1] = cp0y; 4030 } else { 4031 lines[i * 4 - 2] = cp0x; 4032 lines[i * 4 - 1] = cp0y; 4033 } 4034 4035 lines[i * 4] = cp1x; 4036 lines[i * 4 + 1] = cp1y; 4037 var orthx = basePoints[sourceUv + 1]; 4038 var orthy = -basePoints[sourceUv]; 4039 var cosAlpha = orthx * basePoints[destUv] + orthy * basePoints[destUv + 1]; 4040 4041 if (cosAlpha < 0) { 4042 orthx *= -1; 4043 orthy *= -1; 4044 } 4045 4046 var cx = cp0x + orthx * cornerRadius; 4047 var cy = cp0y + orthy * cornerRadius; 4048 intersection = intersectLineCircle(x, y, centerX, centerY, cx, cy, cornerRadius); 4049 4050 if (intersection.length !== 0) { 4051 intersections.push(intersection[0], intersection[1]); 4052 } 4053 } 4054 4055 for (var _i3 = 0; _i3 < lines.length / 4; _i3++) { 4056 intersection = finiteLinesIntersect(x, y, centerX, centerY, lines[_i3 * 4], lines[_i3 * 4 + 1], lines[_i3 * 4 + 2], lines[_i3 * 4 + 3], false); 4057 4058 if (intersection.length !== 0) { 4059 intersections.push(intersection[0], intersection[1]); 4060 } 4061 } 4062 4063 if (intersections.length > 2) { 4064 var lowestIntersection = [intersections[0], intersections[1]]; 4065 var lowestSquaredDistance = Math.pow(lowestIntersection[0] - x, 2) + Math.pow(lowestIntersection[1] - y, 2); 4066 4067 for (var _i4 = 1; _i4 < intersections.length / 2; _i4++) { 4068 var squaredDistance = Math.pow(intersections[_i4 * 2] - x, 2) + Math.pow(intersections[_i4 * 2 + 1] - y, 2); 4069 4070 if (squaredDistance <= lowestSquaredDistance) { 4071 lowestIntersection[0] = intersections[_i4 * 2]; 4072 lowestIntersection[1] = intersections[_i4 * 2 + 1]; 4073 lowestSquaredDistance = squaredDistance; 4074 } 4075 } 4076 4077 return lowestIntersection; 4078 } 4079 4080 return intersections; 4081 }; 4082 var shortenIntersection = function shortenIntersection(intersection, offset, amount) { 4083 var disp = [intersection[0] - offset[0], intersection[1] - offset[1]]; 4084 var length = Math.sqrt(disp[0] * disp[0] + disp[1] * disp[1]); 4085 var lenRatio = (length - amount) / length; 4086 4087 if (lenRatio < 0) { 4088 lenRatio = 0.00001; 4089 } 4090 4091 return [offset[0] + lenRatio * disp[0], offset[1] + lenRatio * disp[1]]; 4092 }; 4093 var generateUnitNgonPointsFitToSquare = function generateUnitNgonPointsFitToSquare(sides, rotationRadians) { 4094 var points = generateUnitNgonPoints(sides, rotationRadians); 4095 points = fitPolygonToSquare(points); 4096 return points; 4097 }; 4098 var fitPolygonToSquare = function fitPolygonToSquare(points) { 4099 var x, y; 4100 var sides = points.length / 2; 4101 var minX = Infinity, 4102 minY = Infinity, 4103 maxX = -Infinity, 4104 maxY = -Infinity; 4105 4106 for (var i = 0; i < sides; i++) { 4107 x = points[2 * i]; 4108 y = points[2 * i + 1]; 4109 minX = Math.min(minX, x); 4110 maxX = Math.max(maxX, x); 4111 minY = Math.min(minY, y); 4112 maxY = Math.max(maxY, y); 4113 } // stretch factors 4114 4115 4116 var sx = 2 / (maxX - minX); 4117 var sy = 2 / (maxY - minY); 4118 4119 for (var _i5 = 0; _i5 < sides; _i5++) { 4120 x = points[2 * _i5] = points[2 * _i5] * sx; 4121 y = points[2 * _i5 + 1] = points[2 * _i5 + 1] * sy; 4122 minX = Math.min(minX, x); 4123 maxX = Math.max(maxX, x); 4124 minY = Math.min(minY, y); 4125 maxY = Math.max(maxY, y); 4126 } 4127 4128 if (minY < -1) { 4129 for (var _i6 = 0; _i6 < sides; _i6++) { 4130 y = points[2 * _i6 + 1] = points[2 * _i6 + 1] + (-1 - minY); 4131 } 4132 } 4133 4134 return points; 4135 }; 4136 var generateUnitNgonPoints = function generateUnitNgonPoints(sides, rotationRadians) { 4137 var increment = 1.0 / sides * 2 * Math.PI; 4138 var startAngle = sides % 2 === 0 ? Math.PI / 2.0 + increment / 2.0 : Math.PI / 2.0; 4139 startAngle += rotationRadians; 4140 var points = new Array(sides * 2); 4141 var currentAngle; 4142 4143 for (var i = 0; i < sides; i++) { 4144 currentAngle = i * increment + startAngle; 4145 points[2 * i] = Math.cos(currentAngle); // x 4146 4147 points[2 * i + 1] = Math.sin(-currentAngle); // y 4148 } 4149 4150 return points; 4151 }; // Set the default radius, unless half of width or height is smaller than default 4152 4153 var getRoundRectangleRadius = function getRoundRectangleRadius(width, height) { 4154 return Math.min(width / 4, height / 4, 8); 4155 }; // Set the default radius 4156 4157 var getRoundPolygonRadius = function getRoundPolygonRadius(width, height) { 4158 return Math.min(width / 10, height / 10, 8); 4159 }; 4160 var getCutRectangleCornerLength = function getCutRectangleCornerLength() { 4161 return 8; 4162 }; 4163 var bezierPtsToQuadCoeff = function bezierPtsToQuadCoeff(p0, p1, p2) { 4164 return [p0 - 2 * p1 + p2, 2 * (p1 - p0), p0]; 4165 }; // get curve width, height, and control point position offsets as a percentage of node height / width 4166 4167 var getBarrelCurveConstants = function getBarrelCurveConstants(width, height) { 4168 return { 4169 heightOffset: Math.min(15, 0.05 * height), 4170 widthOffset: Math.min(100, 0.25 * width), 4171 ctrlPtOffsetPct: 0.05 4172 }; 4173 }; 4174 4175 var pageRankDefaults = defaults({ 4176 dampingFactor: 0.8, 4177 precision: 0.000001, 4178 iterations: 200, 4179 weight: function weight(edge) { 4180 return 1; 4181 } 4182 }); 4183 var elesfn$7 = { 4184 pageRank: function pageRank(options) { 4185 var _pageRankDefaults = pageRankDefaults(options), 4186 dampingFactor = _pageRankDefaults.dampingFactor, 4187 precision = _pageRankDefaults.precision, 4188 iterations = _pageRankDefaults.iterations, 4189 weight = _pageRankDefaults.weight; 4190 4191 var cy = this._private.cy; 4192 4193 var _this$byGroup = this.byGroup(), 4194 nodes = _this$byGroup.nodes, 4195 edges = _this$byGroup.edges; 4196 4197 var numNodes = nodes.length; 4198 var numNodesSqd = numNodes * numNodes; 4199 var numEdges = edges.length; // Construct transposed adjacency matrix 4200 // First lets have a zeroed matrix of the right size 4201 // We'll also keep track of the sum of each column 4202 4203 var matrix = new Array(numNodesSqd); 4204 var columnSum = new Array(numNodes); 4205 var additionalProb = (1 - dampingFactor) / numNodes; // Create null matrix 4206 4207 for (var i = 0; i < numNodes; i++) { 4208 for (var j = 0; j < numNodes; j++) { 4209 var n = i * numNodes + j; 4210 matrix[n] = 0; 4211 } 4212 4213 columnSum[i] = 0; 4214 } // Now, process edges 4215 4216 4217 for (var _i = 0; _i < numEdges; _i++) { 4218 var edge = edges[_i]; 4219 var srcId = edge.data('source'); 4220 var tgtId = edge.data('target'); // Don't include loops in the matrix 4221 4222 if (srcId === tgtId) { 4223 continue; 4224 } 4225 4226 var s = nodes.indexOfId(srcId); 4227 var t = nodes.indexOfId(tgtId); 4228 var w = weight(edge); 4229 4230 var _n = t * numNodes + s; // Update matrix 4231 4232 4233 matrix[_n] += w; // Update column sum 4234 4235 columnSum[s] += w; 4236 } // Add additional probability based on damping factor 4237 // Also, take into account columns that have sum = 0 4238 4239 4240 var p = 1.0 / numNodes + additionalProb; // Shorthand 4241 // Traverse matrix, column by column 4242 4243 for (var _j = 0; _j < numNodes; _j++) { 4244 if (columnSum[_j] === 0) { 4245 // No 'links' out from node jth, assume equal probability for each possible node 4246 for (var _i2 = 0; _i2 < numNodes; _i2++) { 4247 var _n2 = _i2 * numNodes + _j; 4248 4249 matrix[_n2] = p; 4250 } 4251 } else { 4252 // Node jth has outgoing link, compute normalized probabilities 4253 for (var _i3 = 0; _i3 < numNodes; _i3++) { 4254 var _n3 = _i3 * numNodes + _j; 4255 4256 matrix[_n3] = matrix[_n3] / columnSum[_j] + additionalProb; 4257 } 4258 } 4259 } // Compute dominant eigenvector using power method 4260 4261 4262 var eigenvector = new Array(numNodes); 4263 var temp = new Array(numNodes); 4264 var previous; // Start with a vector of all 1's 4265 // Also, initialize a null vector which will be used as shorthand 4266 4267 for (var _i4 = 0; _i4 < numNodes; _i4++) { 4268 eigenvector[_i4] = 1; 4269 } 4270 4271 for (var iter = 0; iter < iterations; iter++) { 4272 // Temp array with all 0's 4273 for (var _i5 = 0; _i5 < numNodes; _i5++) { 4274 temp[_i5] = 0; 4275 } // Multiply matrix with previous result 4276 4277 4278 for (var _i6 = 0; _i6 < numNodes; _i6++) { 4279 for (var _j2 = 0; _j2 < numNodes; _j2++) { 4280 var _n4 = _i6 * numNodes + _j2; 4281 4282 temp[_i6] += matrix[_n4] * eigenvector[_j2]; 4283 } 4284 } 4285 4286 inPlaceSumNormalize(temp); 4287 previous = eigenvector; 4288 eigenvector = temp; 4289 temp = previous; 4290 var diff = 0; // Compute difference (squared module) of both vectors 4291 4292 for (var _i7 = 0; _i7 < numNodes; _i7++) { 4293 var delta = previous[_i7] - eigenvector[_i7]; 4294 diff += delta * delta; 4295 } // If difference is less than the desired threshold, stop iterating 4296 4297 4298 if (diff < precision) { 4299 break; 4300 } 4301 } // Construct result 4302 4303 4304 var res = { 4305 rank: function rank(node) { 4306 node = cy.collection(node)[0]; 4307 return eigenvector[nodes.indexOf(node)]; 4308 } 4309 }; 4310 return res; 4311 } // pageRank 4312 4313 }; // elesfn 4314 4315 var defaults$1 = defaults({ 4316 root: null, 4317 weight: function weight(edge) { 4318 return 1; 4319 }, 4320 directed: false, 4321 alpha: 0 4322 }); 4323 var elesfn$8 = { 4324 degreeCentralityNormalized: function degreeCentralityNormalized(options) { 4325 options = defaults$1(options); 4326 var cy = this.cy(); 4327 var nodes = this.nodes(); 4328 var numNodes = nodes.length; 4329 4330 if (!options.directed) { 4331 var degrees = {}; 4332 var maxDegree = 0; 4333 4334 for (var i = 0; i < numNodes; i++) { 4335 var node = nodes[i]; // add current node to the current options object and call degreeCentrality 4336 4337 options.root = node; 4338 var currDegree = this.degreeCentrality(options); 4339 4340 if (maxDegree < currDegree.degree) { 4341 maxDegree = currDegree.degree; 4342 } 4343 4344 degrees[node.id()] = currDegree.degree; 4345 } 4346 4347 return { 4348 degree: function degree(node) { 4349 if (maxDegree === 0) { 4350 return 0; 4351 } 4352 4353 if (string(node)) { 4354 // from is a selector string 4355 node = cy.filter(node); 4356 } 4357 4358 return degrees[node.id()] / maxDegree; 4359 } 4360 }; 4361 } else { 4362 var indegrees = {}; 4363 var outdegrees = {}; 4364 var maxIndegree = 0; 4365 var maxOutdegree = 0; 4366 4367 for (var _i = 0; _i < numNodes; _i++) { 4368 var _node = nodes[_i]; 4369 4370 var id = _node.id(); // add current node to the current options object and call degreeCentrality 4371 4372 4373 options.root = _node; 4374 4375 var _currDegree = this.degreeCentrality(options); 4376 4377 if (maxIndegree < _currDegree.indegree) maxIndegree = _currDegree.indegree; 4378 if (maxOutdegree < _currDegree.outdegree) maxOutdegree = _currDegree.outdegree; 4379 indegrees[id] = _currDegree.indegree; 4380 outdegrees[id] = _currDegree.outdegree; 4381 } 4382 4383 return { 4384 indegree: function indegree(node) { 4385 if (maxIndegree == 0) { 4386 return 0; 4387 } 4388 4389 if (string(node)) { 4390 // from is a selector string 4391 node = cy.filter(node); 4392 } 4393 4394 return indegrees[node.id()] / maxIndegree; 4395 }, 4396 outdegree: function outdegree(node) { 4397 if (maxOutdegree === 0) { 4398 return 0; 4399 } 4400 4401 if (string(node)) { 4402 // from is a selector string 4403 node = cy.filter(node); 4404 } 4405 4406 return outdegrees[node.id()] / maxOutdegree; 4407 } 4408 }; 4409 } 4410 }, 4411 // degreeCentralityNormalized 4412 // Implemented from the algorithm in Opsahl's paper 4413 // "Node centrality in weighted networks: Generalizing degree and shortest paths" 4414 // check the heading 2 "Degree" 4415 degreeCentrality: function degreeCentrality(options) { 4416 options = defaults$1(options); 4417 var cy = this.cy(); 4418 var callingEles = this; 4419 var _options = options, 4420 root = _options.root, 4421 weight = _options.weight, 4422 directed = _options.directed, 4423 alpha = _options.alpha; 4424 root = cy.collection(root)[0]; 4425 4426 if (!directed) { 4427 var connEdges = root.connectedEdges().intersection(callingEles); 4428 var k = connEdges.length; 4429 var s = 0; // Now, sum edge weights 4430 4431 for (var i = 0; i < connEdges.length; i++) { 4432 s += weight(connEdges[i]); 4433 } 4434 4435 return { 4436 degree: Math.pow(k, 1 - alpha) * Math.pow(s, alpha) 4437 }; 4438 } else { 4439 var edges = root.connectedEdges(); 4440 var incoming = edges.filter(function (edge) { 4441 return edge.target().same(root) && callingEles.has(edge); 4442 }); 4443 var outgoing = edges.filter(function (edge) { 4444 return edge.source().same(root) && callingEles.has(edge); 4445 }); 4446 var k_in = incoming.length; 4447 var k_out = outgoing.length; 4448 var s_in = 0; 4449 var s_out = 0; // Now, sum incoming edge weights 4450 4451 for (var _i2 = 0; _i2 < incoming.length; _i2++) { 4452 s_in += weight(incoming[_i2]); 4453 } // Now, sum outgoing edge weights 4454 4455 4456 for (var _i3 = 0; _i3 < outgoing.length; _i3++) { 4457 s_out += weight(outgoing[_i3]); 4458 } 4459 4460 return { 4461 indegree: Math.pow(k_in, 1 - alpha) * Math.pow(s_in, alpha), 4462 outdegree: Math.pow(k_out, 1 - alpha) * Math.pow(s_out, alpha) 4463 }; 4464 } 4465 } // degreeCentrality 4466 4467 }; // elesfn 4468 // nice, short mathemathical alias 4469 4470 elesfn$8.dc = elesfn$8.degreeCentrality; 4471 elesfn$8.dcn = elesfn$8.degreeCentralityNormalised = elesfn$8.degreeCentralityNormalized; 4472 4473 var defaults$2 = defaults({ 4474 harmonic: true, 4475 weight: function weight() { 4476 return 1; 4477 }, 4478 directed: false, 4479 root: null 4480 }); 4481 var elesfn$9 = { 4482 closenessCentralityNormalized: function closenessCentralityNormalized(options) { 4483 var _defaults = defaults$2(options), 4484 harmonic = _defaults.harmonic, 4485 weight = _defaults.weight, 4486 directed = _defaults.directed; 4487 4488 var cy = this.cy(); 4489 var closenesses = {}; 4490 var maxCloseness = 0; 4491 var nodes = this.nodes(); 4492 var fw = this.floydWarshall({ 4493 weight: weight, 4494 directed: directed 4495 }); // Compute closeness for every node and find the maximum closeness 4496 4497 for (var i = 0; i < nodes.length; i++) { 4498 var currCloseness = 0; 4499 var node_i = nodes[i]; 4500 4501 for (var j = 0; j < nodes.length; j++) { 4502 if (i !== j) { 4503 var d = fw.distance(node_i, nodes[j]); 4504 4505 if (harmonic) { 4506 currCloseness += 1 / d; 4507 } else { 4508 currCloseness += d; 4509 } 4510 } 4511 } 4512 4513 if (!harmonic) { 4514 currCloseness = 1 / currCloseness; 4515 } 4516 4517 if (maxCloseness < currCloseness) { 4518 maxCloseness = currCloseness; 4519 } 4520 4521 closenesses[node_i.id()] = currCloseness; 4522 } 4523 4524 return { 4525 closeness: function closeness(node) { 4526 if (maxCloseness == 0) { 4527 return 0; 4528 } 4529 4530 if (string(node)) { 4531 // from is a selector string 4532 node = cy.filter(node)[0].id(); 4533 } else { 4534 // from is a node 4535 node = node.id(); 4536 } 4537 4538 return closenesses[node] / maxCloseness; 4539 } 4540 }; 4541 }, 4542 // Implemented from pseudocode from wikipedia 4543 closenessCentrality: function closenessCentrality(options) { 4544 var _defaults2 = defaults$2(options), 4545 root = _defaults2.root, 4546 weight = _defaults2.weight, 4547 directed = _defaults2.directed, 4548 harmonic = _defaults2.harmonic; 4549 4550 root = this.filter(root)[0]; // we need distance from this node to every other node 4551 4552 var dijkstra = this.dijkstra({ 4553 root: root, 4554 weight: weight, 4555 directed: directed 4556 }); 4557 var totalDistance = 0; 4558 var nodes = this.nodes(); 4559 4560 for (var i = 0; i < nodes.length; i++) { 4561 var n = nodes[i]; 4562 4563 if (!n.same(root)) { 4564 var d = dijkstra.distanceTo(n); 4565 4566 if (harmonic) { 4567 totalDistance += 1 / d; 4568 } else { 4569 totalDistance += d; 4570 } 4571 } 4572 } 4573 4574 return harmonic ? totalDistance : 1 / totalDistance; 4575 } // closenessCentrality 4576 4577 }; // elesfn 4578 // nice, short mathemathical alias 4579 4580 elesfn$9.cc = elesfn$9.closenessCentrality; 4581 elesfn$9.ccn = elesfn$9.closenessCentralityNormalised = elesfn$9.closenessCentralityNormalized; 4582 4583 var defaults$3 = defaults({ 4584 weight: null, 4585 directed: false 4586 }); 4587 var elesfn$a = { 4588 // Implemented from the algorithm in the paper "On Variants of Shortest-Path Betweenness Centrality and their Generic Computation" by Ulrik Brandes 4589 betweennessCentrality: function betweennessCentrality(options) { 4590 var _defaults = defaults$3(options), 4591 directed = _defaults.directed, 4592 weight = _defaults.weight; 4593 4594 var weighted = weight != null; 4595 var cy = this.cy(); // starting 4596 4597 var V = this.nodes(); 4598 var A = {}; 4599 var _C = {}; 4600 var max = 0; 4601 var C = { 4602 set: function set(key, val) { 4603 _C[key] = val; 4604 4605 if (val > max) { 4606 max = val; 4607 } 4608 }, 4609 get: function get(key) { 4610 return _C[key]; 4611 } 4612 }; // A contains the neighborhoods of every node 4613 4614 for (var i = 0; i < V.length; i++) { 4615 var v = V[i]; 4616 var vid = v.id(); 4617 4618 if (directed) { 4619 A[vid] = v.outgoers().nodes(); // get outgoers of every node 4620 } else { 4621 A[vid] = v.openNeighborhood().nodes(); // get neighbors of every node 4622 } 4623 4624 C.set(vid, 0); 4625 } 4626 4627 var _loop = function _loop(s) { 4628 var sid = V[s].id(); 4629 var S = []; // stack 4630 4631 var P = {}; 4632 var g = {}; 4633 var d = {}; 4634 var Q = new heap$1(function (a, b) { 4635 return d[a] - d[b]; 4636 }); // queue 4637 // init dictionaries 4638 4639 for (var _i = 0; _i < V.length; _i++) { 4640 var _vid = V[_i].id(); 4641 4642 P[_vid] = []; 4643 g[_vid] = 0; 4644 d[_vid] = Infinity; 4645 } 4646 4647 g[sid] = 1; // sigma 4648 4649 d[sid] = 0; // distance to s 4650 4651 Q.push(sid); 4652 4653 while (!Q.empty()) { 4654 var _v = Q.pop(); 4655 4656 S.push(_v); 4657 4658 if (weighted) { 4659 for (var j = 0; j < A[_v].length; j++) { 4660 var w = A[_v][j]; 4661 var vEle = cy.getElementById(_v); 4662 var edge = void 0; 4663 4664 if (vEle.edgesTo(w).length > 0) { 4665 edge = vEle.edgesTo(w)[0]; 4666 } else { 4667 edge = w.edgesTo(vEle)[0]; 4668 } 4669 4670 var edgeWeight = weight(edge); 4671 w = w.id(); 4672 4673 if (d[w] > d[_v] + edgeWeight) { 4674 d[w] = d[_v] + edgeWeight; 4675 4676 if (Q.nodes.indexOf(w) < 0) { 4677 //if w is not in Q 4678 Q.push(w); 4679 } else { 4680 // update position if w is in Q 4681 Q.updateItem(w); 4682 } 4683 4684 g[w] = 0; 4685 P[w] = []; 4686 } 4687 4688 if (d[w] == d[_v] + edgeWeight) { 4689 g[w] = g[w] + g[_v]; 4690 P[w].push(_v); 4691 } 4692 } 4693 } else { 4694 for (var _j = 0; _j < A[_v].length; _j++) { 4695 var _w = A[_v][_j].id(); 4696 4697 if (d[_w] == Infinity) { 4698 Q.push(_w); 4699 d[_w] = d[_v] + 1; 4700 } 4701 4702 if (d[_w] == d[_v] + 1) { 4703 g[_w] = g[_w] + g[_v]; 4704 4705 P[_w].push(_v); 4706 } 4707 } 4708 } 4709 } 4710 4711 var e = {}; 4712 4713 for (var _i2 = 0; _i2 < V.length; _i2++) { 4714 e[V[_i2].id()] = 0; 4715 } 4716 4717 while (S.length > 0) { 4718 var _w2 = S.pop(); 4719 4720 for (var _j2 = 0; _j2 < P[_w2].length; _j2++) { 4721 var _v2 = P[_w2][_j2]; 4722 e[_v2] = e[_v2] + g[_v2] / g[_w2] * (1 + e[_w2]); 4723 4724 if (_w2 != V[s].id()) { 4725 C.set(_w2, C.get(_w2) + e[_w2]); 4726 } 4727 } 4728 } 4729 }; 4730 4731 for (var s = 0; s < V.length; s++) { 4732 _loop(s); 4733 } 4734 4735 var ret = { 4736 betweenness: function betweenness(node) { 4737 var id = cy.collection(node).id(); 4738 return C.get(id); 4739 }, 4740 betweennessNormalized: function betweennessNormalized(node) { 4741 if (max == 0) { 4742 return 0; 4743 } 4744 4745 var id = cy.collection(node).id(); 4746 return C.get(id) / max; 4747 } 4748 }; // alias 4749 4750 ret.betweennessNormalised = ret.betweennessNormalized; 4751 return ret; 4752 } // betweennessCentrality 4753 4754 }; // elesfn 4755 // nice, short mathemathical alias 4756 4757 elesfn$a.bc = elesfn$a.betweennessCentrality; 4758 4759 // Implemented by Zoe Xi @zoexi for GSOC 2016 4760 /* eslint-disable no-unused-vars */ 4761 4762 var defaults$4 = defaults({ 4763 expandFactor: 2, 4764 // affects time of computation and cluster granularity to some extent: M * M 4765 inflateFactor: 2, 4766 // affects cluster granularity (the greater the value, the more clusters): M(i,j) / E(j) 4767 multFactor: 1, 4768 // optional self loops for each node. Use a neutral value to improve cluster computations. 4769 maxIterations: 20, 4770 // maximum number of iterations of the MCL algorithm in a single run 4771 attributes: [// attributes/features used to group nodes, ie. similarity values between nodes 4772 function (edge) { 4773 return 1; 4774 }] 4775 }); 4776 /* eslint-enable */ 4777 4778 var setOptions = function setOptions(options) { 4779 return defaults$4(options); 4780 }; 4781 /* eslint-enable */ 4782 4783 4784 var getSimilarity = function getSimilarity(edge, attributes) { 4785 var total = 0; 4786 4787 for (var i = 0; i < attributes.length; i++) { 4788 total += attributes[i](edge); 4789 } 4790 4791 return total; 4792 }; 4793 4794 var addLoops = function addLoops(M, n, val) { 4795 for (var i = 0; i < n; i++) { 4796 M[i * n + i] = val; 4797 } 4798 }; 4799 4800 var normalize = function normalize(M, n) { 4801 var sum; 4802 4803 for (var col = 0; col < n; col++) { 4804 sum = 0; 4805 4806 for (var row = 0; row < n; row++) { 4807 sum += M[row * n + col]; 4808 } 4809 4810 for (var _row = 0; _row < n; _row++) { 4811 M[_row * n + col] = M[_row * n + col] / sum; 4812 } 4813 } 4814 }; // TODO: blocked matrix multiplication? 4815 4816 4817 var mmult = function mmult(A, B, n) { 4818 var C = new Array(n * n); 4819 4820 for (var i = 0; i < n; i++) { 4821 for (var j = 0; j < n; j++) { 4822 C[i * n + j] = 0; 4823 } 4824 4825 for (var k = 0; k < n; k++) { 4826 for (var _j = 0; _j < n; _j++) { 4827 C[i * n + _j] += A[i * n + k] * B[k * n + _j]; 4828 } 4829 } 4830 } 4831 4832 return C; 4833 }; 4834 4835 var expand = function expand(M, n, expandFactor 4836 /** power **/ 4837 ) { 4838 var _M = M.slice(0); 4839 4840 for (var p = 1; p < expandFactor; p++) { 4841 M = mmult(M, _M, n); 4842 } 4843 4844 return M; 4845 }; 4846 4847 var inflate = function inflate(M, n, inflateFactor 4848 /** r **/ 4849 ) { 4850 var _M = new Array(n * n); // M(i,j) ^ inflatePower 4851 4852 4853 for (var i = 0; i < n * n; i++) { 4854 _M[i] = Math.pow(M[i], inflateFactor); 4855 } 4856 4857 normalize(_M, n); 4858 return _M; 4859 }; 4860 4861 var hasConverged = function hasConverged(M, _M, n2, roundFactor) { 4862 // Check that both matrices have the same elements (i,j) 4863 for (var i = 0; i < n2; i++) { 4864 var v1 = Math.round(M[i] * Math.pow(10, roundFactor)) / Math.pow(10, roundFactor); // truncate to 'roundFactor' decimal places 4865 4866 var v2 = Math.round(_M[i] * Math.pow(10, roundFactor)) / Math.pow(10, roundFactor); 4867 4868 if (v1 !== v2) { 4869 return false; 4870 } 4871 } 4872 4873 return true; 4874 }; 4875 4876 var assign = function assign(M, n, nodes, cy) { 4877 var clusters = []; 4878 4879 for (var i = 0; i < n; i++) { 4880 var cluster = []; 4881 4882 for (var j = 0; j < n; j++) { 4883 // Row-wise attractors and elements that they attract belong in same cluster 4884 if (Math.round(M[i * n + j] * 1000) / 1000 > 0) { 4885 cluster.push(nodes[j]); 4886 } 4887 } 4888 4889 if (cluster.length !== 0) { 4890 clusters.push(cy.collection(cluster)); 4891 } 4892 } 4893 4894 return clusters; 4895 }; 4896 4897 var isDuplicate = function isDuplicate(c1, c2) { 4898 for (var i = 0; i < c1.length; i++) { 4899 if (!c2[i] || c1[i].id() !== c2[i].id()) { 4900 return false; 4901 } 4902 } 4903 4904 return true; 4905 }; 4906 4907 var removeDuplicates = function removeDuplicates(clusters) { 4908 for (var i = 0; i < clusters.length; i++) { 4909 for (var j = 0; j < clusters.length; j++) { 4910 if (i != j && isDuplicate(clusters[i], clusters[j])) { 4911 clusters.splice(j, 1); 4912 } 4913 } 4914 } 4915 4916 return clusters; 4917 }; 4918 4919 var markovClustering = function markovClustering(options) { 4920 var nodes = this.nodes(); 4921 var edges = this.edges(); 4922 var cy = this.cy(); // Set parameters of algorithm: 4923 4924 var opts = setOptions(options); // Map each node to its position in node array 4925 4926 var id2position = {}; 4927 4928 for (var i = 0; i < nodes.length; i++) { 4929 id2position[nodes[i].id()] = i; 4930 } // Generate stochastic matrix M from input graph G (should be symmetric/undirected) 4931 4932 4933 var n = nodes.length, 4934 n2 = n * n; 4935 4936 var M = new Array(n2), 4937 _M; 4938 4939 for (var _i = 0; _i < n2; _i++) { 4940 M[_i] = 0; 4941 } 4942 4943 for (var e = 0; e < edges.length; e++) { 4944 var edge = edges[e]; 4945 var _i2 = id2position[edge.source().id()]; 4946 var j = id2position[edge.target().id()]; 4947 var sim = getSimilarity(edge, opts.attributes); 4948 M[_i2 * n + j] += sim; // G should be symmetric and undirected 4949 4950 M[j * n + _i2] += sim; 4951 } // Begin Markov cluster algorithm 4952 // Step 1: Add self loops to each node, ie. add multFactor to matrix diagonal 4953 4954 4955 addLoops(M, n, opts.multFactor); // Step 2: M = normalize( M ); 4956 4957 normalize(M, n); 4958 var isStillMoving = true; 4959 var iterations = 0; 4960 4961 while (isStillMoving && iterations < opts.maxIterations) { 4962 isStillMoving = false; // Step 3: 4963 4964 _M = expand(M, n, opts.expandFactor); // Step 4: 4965 4966 M = inflate(_M, n, opts.inflateFactor); // Step 5: check to see if ~steady state has been reached 4967 4968 if (!hasConverged(M, _M, n2, 4)) { 4969 isStillMoving = true; 4970 } 4971 4972 iterations++; 4973 } // Build clusters from matrix 4974 4975 4976 var clusters = assign(M, n, nodes, cy); // Remove duplicate clusters due to symmetry of graph and M matrix 4977 4978 clusters = removeDuplicates(clusters); 4979 return clusters; 4980 }; 4981 4982 var markovClustering$1 = { 4983 markovClustering: markovClustering, 4984 mcl: markovClustering 4985 }; 4986 4987 // Common distance metrics for clustering algorithms 4988 4989 var identity = function identity(x) { 4990 return x; 4991 }; 4992 4993 var absDiff = function absDiff(p, q) { 4994 return Math.abs(q - p); 4995 }; 4996 4997 var addAbsDiff = function addAbsDiff(total, p, q) { 4998 return total + absDiff(p, q); 4999 }; 5000 5001 var addSquaredDiff = function addSquaredDiff(total, p, q) { 5002 return total + Math.pow(q - p, 2); 5003 }; 5004 5005 var sqrt = function sqrt(x) { 5006 return Math.sqrt(x); 5007 }; 5008 5009 var maxAbsDiff = function maxAbsDiff(currentMax, p, q) { 5010 return Math.max(currentMax, absDiff(p, q)); 5011 }; 5012 5013 var getDistance = function getDistance(length, getP, getQ, init, visit) { 5014 var post = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : identity; 5015 var ret = init; 5016 var p, q; 5017 5018 for (var dim = 0; dim < length; dim++) { 5019 p = getP(dim); 5020 q = getQ(dim); 5021 ret = visit(ret, p, q); 5022 } 5023 5024 return post(ret); 5025 }; 5026 5027 var distances = { 5028 euclidean: function euclidean(length, getP, getQ) { 5029 if (length >= 2) { 5030 return getDistance(length, getP, getQ, 0, addSquaredDiff, sqrt); 5031 } else { 5032 // for single attr case, more efficient to avoid sqrt 5033 return getDistance(length, getP, getQ, 0, addAbsDiff); 5034 } 5035 }, 5036 squaredEuclidean: function squaredEuclidean(length, getP, getQ) { 5037 return getDistance(length, getP, getQ, 0, addSquaredDiff); 5038 }, 5039 manhattan: function manhattan(length, getP, getQ) { 5040 return getDistance(length, getP, getQ, 0, addAbsDiff); 5041 }, 5042 max: function max(length, getP, getQ) { 5043 return getDistance(length, getP, getQ, -Infinity, maxAbsDiff); 5044 } 5045 }; // in case the user accidentally doesn't use camel case 5046 5047 distances['squared-euclidean'] = distances['squaredEuclidean']; 5048 distances['squaredeuclidean'] = distances['squaredEuclidean']; 5049 function clusteringDistance (method, length, getP, getQ, nodeP, nodeQ) { 5050 var impl; 5051 5052 if (fn(method)) { 5053 impl = method; 5054 } else { 5055 impl = distances[method] || distances.euclidean; 5056 } 5057 5058 if (length === 0 && fn(method)) { 5059 return impl(nodeP, nodeQ); 5060 } else { 5061 return impl(length, getP, getQ, nodeP, nodeQ); 5062 } 5063 } 5064 5065 var defaults$5 = defaults({ 5066 k: 2, 5067 m: 2, 5068 sensitivityThreshold: 0.0001, 5069 distance: 'euclidean', 5070 maxIterations: 10, 5071 attributes: [], 5072 testMode: false, 5073 testCentroids: null 5074 }); 5075 5076 var setOptions$1 = function setOptions(options) { 5077 return defaults$5(options); 5078 }; 5079 /* eslint-enable */ 5080 5081 5082 var getDist = function getDist(type, node, centroid, attributes, mode) { 5083 var noNodeP = mode !== 'kMedoids'; 5084 var getP = noNodeP ? function (i) { 5085 return centroid[i]; 5086 } : function (i) { 5087 return attributes[i](centroid); 5088 }; 5089 5090 var getQ = function getQ(i) { 5091 return attributes[i](node); 5092 }; 5093 5094 var nodeP = centroid; 5095 var nodeQ = node; 5096 return clusteringDistance(type, attributes.length, getP, getQ, nodeP, nodeQ); 5097 }; 5098 5099 var randomCentroids = function randomCentroids(nodes, k, attributes) { 5100 var ndim = attributes.length; 5101 var min = new Array(ndim); 5102 var max = new Array(ndim); 5103 var centroids = new Array(k); 5104 var centroid = null; // Find min, max values for each attribute dimension 5105 5106 for (var i = 0; i < ndim; i++) { 5107 min[i] = nodes.min(attributes[i]).value; 5108 max[i] = nodes.max(attributes[i]).value; 5109 } // Build k centroids, each represented as an n-dim feature vector 5110 5111 5112 for (var c = 0; c < k; c++) { 5113 centroid = []; 5114 5115 for (var _i = 0; _i < ndim; _i++) { 5116 centroid[_i] = Math.random() * (max[_i] - min[_i]) + min[_i]; // random initial value 5117 } 5118 5119 centroids[c] = centroid; 5120 } 5121 5122 return centroids; 5123 }; 5124 5125 var classify = function classify(node, centroids, distance, attributes, type) { 5126 var min = Infinity; 5127 var index = 0; 5128 5129 for (var i = 0; i < centroids.length; i++) { 5130 var dist = getDist(distance, node, centroids[i], attributes, type); 5131 5132 if (dist < min) { 5133 min = dist; 5134 index = i; 5135 } 5136 } 5137 5138 return index; 5139 }; 5140 5141 var buildCluster = function buildCluster(centroid, nodes, assignment) { 5142 var cluster = []; 5143 var node = null; 5144 5145 for (var n = 0; n < nodes.length; n++) { 5146 node = nodes[n]; 5147 5148 if (assignment[node.id()] === centroid) { 5149 //console.log("Node " + node.id() + " is associated with medoid #: " + m); 5150 cluster.push(node); 5151 } 5152 } 5153 5154 return cluster; 5155 }; 5156 5157 var haveValuesConverged = function haveValuesConverged(v1, v2, sensitivityThreshold) { 5158 return Math.abs(v2 - v1) <= sensitivityThreshold; 5159 }; 5160 5161 var haveMatricesConverged = function haveMatricesConverged(v1, v2, sensitivityThreshold) { 5162 for (var i = 0; i < v1.length; i++) { 5163 for (var j = 0; j < v1[i].length; j++) { 5164 var diff = Math.abs(v1[i][j] - v2[i][j]); 5165 5166 if (diff > sensitivityThreshold) { 5167 return false; 5168 } 5169 } 5170 } 5171 5172 return true; 5173 }; 5174 5175 var seenBefore = function seenBefore(node, medoids, n) { 5176 for (var i = 0; i < n; i++) { 5177 if (node === medoids[i]) return true; 5178 } 5179 5180 return false; 5181 }; 5182 5183 var randomMedoids = function randomMedoids(nodes, k) { 5184 var medoids = new Array(k); // For small data sets, the probability of medoid conflict is greater, 5185 // so we need to check to see if we've already seen or chose this node before. 5186 5187 if (nodes.length < 50) { 5188 // Randomly select k medoids from the n nodes 5189 for (var i = 0; i < k; i++) { 5190 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). 5191 // Instead choose a different random node. 5192 5193 while (seenBefore(node, medoids, i)) { 5194 node = nodes[Math.floor(Math.random() * nodes.length)]; 5195 } 5196 5197 medoids[i] = node; 5198 } 5199 } else { 5200 // Relatively large data set, so pretty safe to not check and just select random nodes 5201 for (var _i2 = 0; _i2 < k; _i2++) { 5202 medoids[_i2] = nodes[Math.floor(Math.random() * nodes.length)]; 5203 } 5204 } 5205 5206 return medoids; 5207 }; 5208 5209 var findCost = function findCost(potentialNewMedoid, cluster, attributes) { 5210 var cost = 0; 5211 5212 for (var n = 0; n < cluster.length; n++) { 5213 cost += getDist('manhattan', cluster[n], potentialNewMedoid, attributes, 'kMedoids'); 5214 } 5215 5216 return cost; 5217 }; 5218 5219 var kMeans = function kMeans(options) { 5220 var cy = this.cy(); 5221 var nodes = this.nodes(); 5222 var node = null; // Set parameters of algorithm: # of clusters, distance metric, etc. 5223 5224 var opts = setOptions$1(options); // Begin k-means algorithm 5225 5226 var clusters = new Array(opts.k); 5227 var assignment = {}; 5228 var centroids; // Step 1: Initialize centroid positions 5229 5230 if (opts.testMode) { 5231 if (typeof opts.testCentroids === 'number') { 5232 centroids = randomCentroids(nodes, opts.k, opts.attributes); 5233 } else if (_typeof(opts.testCentroids) === 'object') { 5234 centroids = opts.testCentroids; 5235 } else { 5236 centroids = randomCentroids(nodes, opts.k, opts.attributes); 5237 } 5238 } else { 5239 centroids = randomCentroids(nodes, opts.k, opts.attributes); 5240 } 5241 5242 var isStillMoving = true; 5243 var iterations = 0; 5244 5245 while (isStillMoving && iterations < opts.maxIterations) { 5246 // Step 2: Assign nodes to the nearest centroid 5247 for (var n = 0; n < nodes.length; n++) { 5248 node = nodes[n]; // Determine which cluster this node belongs to: node id => cluster # 5249 5250 assignment[node.id()] = classify(node, centroids, opts.distance, opts.attributes, 'kMeans'); 5251 } // Step 3: For each of the k clusters, update its centroid 5252 5253 5254 isStillMoving = false; 5255 5256 for (var c = 0; c < opts.k; c++) { 5257 // Get all nodes that belong to this cluster 5258 var cluster = buildCluster(c, nodes, assignment); 5259 5260 if (cluster.length === 0) { 5261 // If cluster is empty, break out early & move to next cluster 5262 continue; 5263 } // Update centroids by calculating avg of all nodes within the cluster. 5264 5265 5266 var ndim = opts.attributes.length; 5267 var centroid = centroids[c]; // [ dim_1, dim_2, dim_3, ... , dim_n ] 5268 5269 var newCentroid = new Array(ndim); 5270 var sum = new Array(ndim); 5271 5272 for (var d = 0; d < ndim; d++) { 5273 sum[d] = 0.0; 5274 5275 for (var i = 0; i < cluster.length; i++) { 5276 node = cluster[i]; 5277 sum[d] += opts.attributes[d](node); 5278 } 5279 5280 newCentroid[d] = sum[d] / cluster.length; // Check to see if algorithm has converged, i.e. when centroids no longer change 5281 5282 if (!haveValuesConverged(newCentroid[d], centroid[d], opts.sensitivityThreshold)) { 5283 isStillMoving = true; 5284 } 5285 } 5286 5287 centroids[c] = newCentroid; 5288 clusters[c] = cy.collection(cluster); 5289 } 5290 5291 iterations++; 5292 } 5293 5294 return clusters; 5295 }; 5296 5297 var kMedoids = function kMedoids(options) { 5298 var cy = this.cy(); 5299 var nodes = this.nodes(); 5300 var node = null; 5301 var opts = setOptions$1(options); // Begin k-medoids algorithm 5302 5303 var clusters = new Array(opts.k); 5304 var medoids; 5305 var assignment = {}; 5306 var curCost; 5307 var minCosts = new Array(opts.k); // minimum cost configuration for each cluster 5308 // Step 1: Initialize k medoids 5309 5310 if (opts.testMode) { 5311 if (typeof opts.testCentroids === 'number') ; else if (_typeof(opts.testCentroids) === 'object') { 5312 medoids = opts.testCentroids; 5313 } else { 5314 medoids = randomMedoids(nodes, opts.k); 5315 } 5316 } else { 5317 medoids = randomMedoids(nodes, opts.k); 5318 } 5319 5320 var isStillMoving = true; 5321 var iterations = 0; 5322 5323 while (isStillMoving && iterations < opts.maxIterations) { 5324 // Step 2: Assign nodes to the nearest medoid 5325 for (var n = 0; n < nodes.length; n++) { 5326 node = nodes[n]; // Determine which cluster this node belongs to: node id => cluster # 5327 5328 assignment[node.id()] = classify(node, medoids, opts.distance, opts.attributes, 'kMedoids'); 5329 } 5330 5331 isStillMoving = false; // Step 3: For each medoid m, and for each node assciated with mediod m, 5332 // select the node with the lowest configuration cost as new medoid. 5333 5334 for (var m = 0; m < medoids.length; m++) { 5335 // Get all nodes that belong to this medoid 5336 var cluster = buildCluster(m, nodes, assignment); 5337 5338 if (cluster.length === 0) { 5339 // If cluster is empty, break out early & move to next cluster 5340 continue; 5341 } 5342 5343 minCosts[m] = findCost(medoids[m], cluster, opts.attributes); // original cost 5344 // Select different medoid if its configuration has the lowest cost 5345 5346 for (var _n = 0; _n < cluster.length; _n++) { 5347 curCost = findCost(cluster[_n], cluster, opts.attributes); 5348 5349 if (curCost < minCosts[m]) { 5350 minCosts[m] = curCost; 5351 medoids[m] = cluster[_n]; 5352 isStillMoving = true; 5353 } 5354 } 5355 5356 clusters[m] = cy.collection(cluster); 5357 } 5358 5359 iterations++; 5360 } 5361 5362 return clusters; 5363 }; 5364 5365 var updateCentroids = function updateCentroids(centroids, nodes, U, weight, opts) { 5366 var numerator, denominator; 5367 5368 for (var n = 0; n < nodes.length; n++) { 5369 for (var c = 0; c < centroids.length; c++) { 5370 weight[n][c] = Math.pow(U[n][c], opts.m); 5371 } 5372 } 5373 5374 for (var _c = 0; _c < centroids.length; _c++) { 5375 for (var dim = 0; dim < opts.attributes.length; dim++) { 5376 numerator = 0; 5377 denominator = 0; 5378 5379 for (var _n2 = 0; _n2 < nodes.length; _n2++) { 5380 numerator += weight[_n2][_c] * opts.attributes[dim](nodes[_n2]); 5381 denominator += weight[_n2][_c]; 5382 } 5383 5384 centroids[_c][dim] = numerator / denominator; 5385 } 5386 } 5387 }; 5388 5389 var updateMembership = function updateMembership(U, _U, centroids, nodes, opts) { 5390 // Save previous step 5391 for (var i = 0; i < U.length; i++) { 5392 _U[i] = U[i].slice(); 5393 } 5394 5395 var sum, numerator, denominator; 5396 var pow = 2 / (opts.m - 1); 5397 5398 for (var c = 0; c < centroids.length; c++) { 5399 for (var n = 0; n < nodes.length; n++) { 5400 sum = 0; 5401 5402 for (var k = 0; k < centroids.length; k++) { 5403 // against all other centroids 5404 numerator = getDist(opts.distance, nodes[n], centroids[c], opts.attributes, 'cmeans'); 5405 denominator = getDist(opts.distance, nodes[n], centroids[k], opts.attributes, 'cmeans'); 5406 sum += Math.pow(numerator / denominator, pow); 5407 } 5408 5409 U[n][c] = 1 / sum; 5410 } 5411 } 5412 }; 5413 5414 var assign$1 = function assign(nodes, U, opts, cy) { 5415 var clusters = new Array(opts.k); 5416 5417 for (var c = 0; c < clusters.length; c++) { 5418 clusters[c] = []; 5419 } 5420 5421 var max; 5422 var index; 5423 5424 for (var n = 0; n < U.length; n++) { 5425 // for each node (U is N x C matrix) 5426 max = -Infinity; 5427 index = -1; // Determine which cluster the node is most likely to belong in 5428 5429 for (var _c2 = 0; _c2 < U[0].length; _c2++) { 5430 if (U[n][_c2] > max) { 5431 max = U[n][_c2]; 5432 index = _c2; 5433 } 5434 } 5435 5436 clusters[index].push(nodes[n]); 5437 } // Turn every array into a collection of nodes 5438 5439 5440 for (var _c3 = 0; _c3 < clusters.length; _c3++) { 5441 clusters[_c3] = cy.collection(clusters[_c3]); 5442 } 5443 5444 return clusters; 5445 }; 5446 5447 var fuzzyCMeans = function fuzzyCMeans(options) { 5448 var cy = this.cy(); 5449 var nodes = this.nodes(); 5450 var opts = setOptions$1(options); // Begin fuzzy c-means algorithm 5451 5452 var clusters; 5453 var centroids; 5454 var U; 5455 5456 var _U; 5457 5458 var weight; // Step 1: Initialize letiables. 5459 5460 _U = new Array(nodes.length); 5461 5462 for (var i = 0; i < nodes.length; i++) { 5463 // N x C matrix 5464 _U[i] = new Array(opts.k); 5465 } 5466 5467 U = new Array(nodes.length); 5468 5469 for (var _i3 = 0; _i3 < nodes.length; _i3++) { 5470 // N x C matrix 5471 U[_i3] = new Array(opts.k); 5472 } 5473 5474 for (var _i4 = 0; _i4 < nodes.length; _i4++) { 5475 var total = 0; 5476 5477 for (var j = 0; j < opts.k; j++) { 5478 U[_i4][j] = Math.random(); 5479 total += U[_i4][j]; 5480 } 5481 5482 for (var _j = 0; _j < opts.k; _j++) { 5483 U[_i4][_j] = U[_i4][_j] / total; 5484 } 5485 } 5486 5487 centroids = new Array(opts.k); 5488 5489 for (var _i5 = 0; _i5 < opts.k; _i5++) { 5490 centroids[_i5] = new Array(opts.attributes.length); 5491 } 5492 5493 weight = new Array(nodes.length); 5494 5495 for (var _i6 = 0; _i6 < nodes.length; _i6++) { 5496 // N x C matrix 5497 weight[_i6] = new Array(opts.k); 5498 } // end init FCM 5499 5500 5501 var isStillMoving = true; 5502 var iterations = 0; 5503 5504 while (isStillMoving && iterations < opts.maxIterations) { 5505 isStillMoving = false; // Step 2: Calculate the centroids for each step. 5506 5507 updateCentroids(centroids, nodes, U, weight, opts); // Step 3: Update the partition matrix U. 5508 5509 updateMembership(U, _U, centroids, nodes, opts); // Step 4: Check for convergence. 5510 5511 if (!haveMatricesConverged(U, _U, opts.sensitivityThreshold)) { 5512 isStillMoving = true; 5513 } 5514 5515 iterations++; 5516 } // Assign nodes to clusters with highest probability. 5517 5518 5519 clusters = assign$1(nodes, U, opts, cy); 5520 return { 5521 clusters: clusters, 5522 degreeOfMembership: U 5523 }; 5524 }; 5525 5526 var kClustering = { 5527 kMeans: kMeans, 5528 kMedoids: kMedoids, 5529 fuzzyCMeans: fuzzyCMeans, 5530 fcm: fuzzyCMeans 5531 }; 5532 5533 // Implemented by Zoe Xi @zoexi for GSOC 2016 5534 var defaults$6 = defaults({ 5535 distance: 'euclidean', 5536 // distance metric to compare nodes 5537 linkage: 'min', 5538 // linkage criterion : how to determine the distance between clusters of nodes 5539 mode: 'threshold', 5540 // mode:'threshold' => clusters must be threshold distance apart 5541 threshold: Infinity, 5542 // the distance threshold 5543 // mode:'dendrogram' => the nodes are organised as leaves in a tree (siblings are close), merging makes clusters 5544 addDendrogram: false, 5545 // whether to add the dendrogram to the graph for viz 5546 dendrogramDepth: 0, 5547 // depth at which dendrogram branches are merged into the returned clusters 5548 attributes: [] // array of attr functions 5549 5550 }); 5551 var linkageAliases = { 5552 'single': 'min', 5553 'complete': 'max' 5554 }; 5555 5556 var setOptions$2 = function setOptions(options) { 5557 var opts = defaults$6(options); 5558 var preferredAlias = linkageAliases[opts.linkage]; 5559 5560 if (preferredAlias != null) { 5561 opts.linkage = preferredAlias; 5562 } 5563 5564 return opts; 5565 }; 5566 5567 var mergeClosest = function mergeClosest(clusters, index, dists, mins, opts) { 5568 // Find two closest clusters from cached mins 5569 var minKey = 0; 5570 var min = Infinity; 5571 var dist; 5572 var attrs = opts.attributes; 5573 5574 var getDist = function getDist(n1, n2) { 5575 return clusteringDistance(opts.distance, attrs.length, function (i) { 5576 return attrs[i](n1); 5577 }, function (i) { 5578 return attrs[i](n2); 5579 }, n1, n2); 5580 }; 5581 5582 for (var i = 0; i < clusters.length; i++) { 5583 var key = clusters[i].key; 5584 var _dist = dists[key][mins[key]]; 5585 5586 if (_dist < min) { 5587 minKey = key; 5588 min = _dist; 5589 } 5590 } 5591 5592 if (opts.mode === 'threshold' && min >= opts.threshold || opts.mode === 'dendrogram' && clusters.length === 1) { 5593 return false; 5594 } 5595 5596 var c1 = index[minKey]; 5597 var c2 = index[mins[minKey]]; 5598 var merged; // Merge two closest clusters 5599 5600 if (opts.mode === 'dendrogram') { 5601 merged = { 5602 left: c1, 5603 right: c2, 5604 key: c1.key 5605 }; 5606 } else { 5607 merged = { 5608 value: c1.value.concat(c2.value), 5609 key: c1.key 5610 }; 5611 } 5612 5613 clusters[c1.index] = merged; 5614 clusters.splice(c2.index, 1); 5615 index[c1.key] = merged; // Update distances with new merged cluster 5616 5617 for (var _i = 0; _i < clusters.length; _i++) { 5618 var cur = clusters[_i]; 5619 5620 if (c1.key === cur.key) { 5621 dist = Infinity; 5622 } else if (opts.linkage === 'min') { 5623 dist = dists[c1.key][cur.key]; 5624 5625 if (dists[c1.key][cur.key] > dists[c2.key][cur.key]) { 5626 dist = dists[c2.key][cur.key]; 5627 } 5628 } else if (opts.linkage === 'max') { 5629 dist = dists[c1.key][cur.key]; 5630 5631 if (dists[c1.key][cur.key] < dists[c2.key][cur.key]) { 5632 dist = dists[c2.key][cur.key]; 5633 } 5634 } else if (opts.linkage === 'mean') { 5635 dist = (dists[c1.key][cur.key] * c1.size + dists[c2.key][cur.key] * c2.size) / (c1.size + c2.size); 5636 } else { 5637 if (opts.mode === 'dendrogram') dist = getDist(cur.value, c1.value);else dist = getDist(cur.value[0], c1.value[0]); 5638 } 5639 5640 dists[c1.key][cur.key] = dists[cur.key][c1.key] = dist; // distance matrix is symmetric 5641 } // Update cached mins 5642 5643 5644 for (var _i2 = 0; _i2 < clusters.length; _i2++) { 5645 var key1 = clusters[_i2].key; 5646 5647 if (mins[key1] === c1.key || mins[key1] === c2.key) { 5648 var _min = key1; 5649 5650 for (var j = 0; j < clusters.length; j++) { 5651 var key2 = clusters[j].key; 5652 5653 if (dists[key1][key2] < dists[key1][_min]) { 5654 _min = key2; 5655 } 5656 } 5657 5658 mins[key1] = _min; 5659 } 5660 5661 clusters[_i2].index = _i2; 5662 } // Clean up meta data used for clustering 5663 5664 5665 c1.key = c2.key = c1.index = c2.index = null; 5666 return true; 5667 }; 5668 5669 var getAllChildren = function getAllChildren(root, arr, cy) { 5670 if (!root) return; 5671 5672 if (root.value) { 5673 arr.push(root.value); 5674 } else { 5675 if (root.left) getAllChildren(root.left, arr); 5676 if (root.right) getAllChildren(root.right, arr); 5677 } 5678 }; 5679 5680 var buildDendrogram = function buildDendrogram(root, cy) { 5681 if (!root) return ''; 5682 5683 if (root.left && root.right) { 5684 var leftStr = buildDendrogram(root.left, cy); 5685 var rightStr = buildDendrogram(root.right, cy); 5686 var node = cy.add({ 5687 group: 'nodes', 5688 data: { 5689 id: leftStr + ',' + rightStr 5690 } 5691 }); 5692 cy.add({ 5693 group: 'edges', 5694 data: { 5695 source: leftStr, 5696 target: node.id() 5697 } 5698 }); 5699 cy.add({ 5700 group: 'edges', 5701 data: { 5702 source: rightStr, 5703 target: node.id() 5704 } 5705 }); 5706 return node.id(); 5707 } else if (root.value) { 5708 return root.value.id(); 5709 } 5710 }; 5711 5712 var buildClustersFromTree = function buildClustersFromTree(root, k, cy) { 5713 if (!root) return []; 5714 var left = [], 5715 right = [], 5716 leaves = []; 5717 5718 if (k === 0) { 5719 // don't cut tree, simply return all nodes as 1 single cluster 5720 if (root.left) getAllChildren(root.left, left); 5721 if (root.right) getAllChildren(root.right, right); 5722 leaves = left.concat(right); 5723 return [cy.collection(leaves)]; 5724 } else if (k === 1) { 5725 // cut at root 5726 if (root.value) { 5727 // leaf node 5728 return [cy.collection(root.value)]; 5729 } else { 5730 if (root.left) getAllChildren(root.left, left); 5731 if (root.right) getAllChildren(root.right, right); 5732 return [cy.collection(left), cy.collection(right)]; 5733 } 5734 } else { 5735 if (root.value) { 5736 return [cy.collection(root.value)]; 5737 } else { 5738 if (root.left) left = buildClustersFromTree(root.left, k - 1, cy); 5739 if (root.right) right = buildClustersFromTree(root.right, k - 1, cy); 5740 return left.concat(right); 5741 } 5742 } 5743 }; 5744 /* eslint-enable */ 5745 5746 5747 var hierarchicalClustering = function hierarchicalClustering(options) { 5748 var cy = this.cy(); 5749 var nodes = this.nodes(); // Set parameters of algorithm: linkage type, distance metric, etc. 5750 5751 var opts = setOptions$2(options); 5752 var attrs = opts.attributes; 5753 5754 var getDist = function getDist(n1, n2) { 5755 return clusteringDistance(opts.distance, attrs.length, function (i) { 5756 return attrs[i](n1); 5757 }, function (i) { 5758 return attrs[i](n2); 5759 }, n1, n2); 5760 }; // Begin hierarchical algorithm 5761 5762 5763 var clusters = []; 5764 var dists = []; // distances between each pair of clusters 5765 5766 var mins = []; // closest cluster for each cluster 5767 5768 var index = []; // hash of all clusters by key 5769 // In agglomerative (bottom-up) clustering, each node starts as its own cluster 5770 5771 for (var n = 0; n < nodes.length; n++) { 5772 var cluster = { 5773 value: opts.mode === 'dendrogram' ? nodes[n] : [nodes[n]], 5774 key: n, 5775 index: n 5776 }; 5777 clusters[n] = cluster; 5778 index[n] = cluster; 5779 dists[n] = []; 5780 mins[n] = 0; 5781 } // Calculate the distance between each pair of clusters 5782 5783 5784 for (var i = 0; i < clusters.length; i++) { 5785 for (var j = 0; j <= i; j++) { 5786 var dist = void 0; 5787 5788 if (opts.mode === 'dendrogram') { 5789 // modes store cluster values differently 5790 dist = i === j ? Infinity : getDist(clusters[i].value, clusters[j].value); 5791 } else { 5792 dist = i === j ? Infinity : getDist(clusters[i].value[0], clusters[j].value[0]); 5793 } 5794 5795 dists[i][j] = dist; 5796 dists[j][i] = dist; 5797 5798 if (dist < dists[i][mins[i]]) { 5799 mins[i] = j; // Cache mins: closest cluster to cluster i is cluster j 5800 } 5801 } 5802 } // Find the closest pair of clusters and merge them into a single cluster. 5803 // Update distances between new cluster and each of the old clusters, and loop until threshold reached. 5804 5805 5806 var merged = mergeClosest(clusters, index, dists, mins, opts); 5807 5808 while (merged) { 5809 merged = mergeClosest(clusters, index, dists, mins, opts); 5810 } 5811 5812 var retClusters; // Dendrogram mode builds the hierarchy and adds intermediary nodes + edges 5813 // in addition to returning the clusters. 5814 5815 if (opts.mode === 'dendrogram') { 5816 retClusters = buildClustersFromTree(clusters[0], opts.dendrogramDepth, cy); 5817 if (opts.addDendrogram) buildDendrogram(clusters[0], cy); 5818 } else { 5819 // Regular mode simply returns the clusters 5820 retClusters = new Array(clusters.length); 5821 clusters.forEach(function (cluster, i) { 5822 // Clean up meta data used for clustering 5823 cluster.key = cluster.index = null; 5824 retClusters[i] = cy.collection(cluster.value); 5825 }); 5826 } 5827 5828 return retClusters; 5829 }; 5830 5831 var hierarchicalClustering$1 = { 5832 hierarchicalClustering: hierarchicalClustering, 5833 hca: hierarchicalClustering 5834 }; 5835 5836 // Implemented by Zoe Xi @zoexi for GSOC 2016 5837 var defaults$7 = defaults({ 5838 distance: 'euclidean', 5839 // distance metric to compare attributes between two nodes 5840 preference: 'median', 5841 // suitability of a data point to serve as an exemplar 5842 damping: 0.8, 5843 // damping factor between [0.5, 1) 5844 maxIterations: 1000, 5845 // max number of iterations to run 5846 minIterations: 100, 5847 // min number of iterations to run in order for clustering to stop 5848 attributes: [// functions to quantify the similarity between any two points 5849 // e.g. node => node.data('weight') 5850 ] 5851 }); 5852 5853 var setOptions$3 = function setOptions(options) { 5854 var dmp = options.damping; 5855 var pref = options.preference; 5856 5857 if (!(0.5 <= dmp && dmp < 1)) { 5858 error("Damping must range on [0.5, 1). Got: ".concat(dmp)); 5859 } 5860 5861 var validPrefs = ['median', 'mean', 'min', 'max']; 5862 5863 if (!(validPrefs.some(function (v) { 5864 return v === pref; 5865 }) || number(pref))) { 5866 error("Preference must be one of [".concat(validPrefs.map(function (p) { 5867 return "'".concat(p, "'"); 5868 }).join(', '), "] or a number. Got: ").concat(pref)); 5869 } 5870 5871 return defaults$7(options); 5872 }; 5873 /* eslint-enable */ 5874 5875 5876 var getSimilarity$1 = function getSimilarity(type, n1, n2, attributes) { 5877 var attr = function attr(n, i) { 5878 return attributes[i](n); 5879 }; // nb negative because similarity should have an inverse relationship to distance 5880 5881 5882 return -clusteringDistance(type, attributes.length, function (i) { 5883 return attr(n1, i); 5884 }, function (i) { 5885 return attr(n2, i); 5886 }, n1, n2); 5887 }; 5888 5889 var getPreference = function getPreference(S, preference) { 5890 // larger preference = greater # of clusters 5891 var p = null; 5892 5893 if (preference === 'median') { 5894 p = median(S); 5895 } else if (preference === 'mean') { 5896 p = mean(S); 5897 } else if (preference === 'min') { 5898 p = min(S); 5899 } else if (preference === 'max') { 5900 p = max(S); 5901 } else { 5902 // Custom preference number, as set by user 5903 p = preference; 5904 } 5905 5906 return p; 5907 }; 5908 5909 var findExemplars = function findExemplars(n, R, A) { 5910 var indices = []; 5911 5912 for (var i = 0; i < n; i++) { 5913 if (R[i * n + i] + A[i * n + i] > 0) { 5914 indices.push(i); 5915 } 5916 } 5917 5918 return indices; 5919 }; 5920 5921 var assignClusters = function assignClusters(n, S, exemplars) { 5922 var clusters = []; 5923 5924 for (var i = 0; i < n; i++) { 5925 var index = -1; 5926 var max = -Infinity; 5927 5928 for (var ei = 0; ei < exemplars.length; ei++) { 5929 var e = exemplars[ei]; 5930 5931 if (S[i * n + e] > max) { 5932 index = e; 5933 max = S[i * n + e]; 5934 } 5935 } 5936 5937 if (index > 0) { 5938 clusters.push(index); 5939 } 5940 } 5941 5942 for (var _ei = 0; _ei < exemplars.length; _ei++) { 5943 clusters[exemplars[_ei]] = exemplars[_ei]; 5944 } 5945 5946 return clusters; 5947 }; 5948 5949 var assign$2 = function assign(n, S, exemplars) { 5950 var clusters = assignClusters(n, S, exemplars); 5951 5952 for (var ei = 0; ei < exemplars.length; ei++) { 5953 var ii = []; 5954 5955 for (var c = 0; c < clusters.length; c++) { 5956 if (clusters[c] === exemplars[ei]) { 5957 ii.push(c); 5958 } 5959 } 5960 5961 var maxI = -1; 5962 var maxSum = -Infinity; 5963 5964 for (var i = 0; i < ii.length; i++) { 5965 var sum = 0; 5966 5967 for (var j = 0; j < ii.length; j++) { 5968 sum += S[ii[j] * n + ii[i]]; 5969 } 5970 5971 if (sum > maxSum) { 5972 maxI = i; 5973 maxSum = sum; 5974 } 5975 } 5976 5977 exemplars[ei] = ii[maxI]; 5978 } 5979 5980 clusters = assignClusters(n, S, exemplars); 5981 return clusters; 5982 }; 5983 5984 var affinityPropagation = function affinityPropagation(options) { 5985 var cy = this.cy(); 5986 var nodes = this.nodes(); 5987 var opts = setOptions$3(options); // Map each node to its position in node array 5988 5989 var id2position = {}; 5990 5991 for (var i = 0; i < nodes.length; i++) { 5992 id2position[nodes[i].id()] = i; 5993 } // Begin affinity propagation algorithm 5994 5995 5996 var n; // number of data points 5997 5998 var n2; // size of matrices 5999 6000 var S; // similarity matrix (1D array) 6001 6002 var p; // preference/suitability of a data point to serve as an exemplar 6003 6004 var R; // responsibility matrix (1D array) 6005 6006 var A; // availability matrix (1D array) 6007 6008 n = nodes.length; 6009 n2 = n * n; // Initialize and build S similarity matrix 6010 6011 S = new Array(n2); 6012 6013 for (var _i = 0; _i < n2; _i++) { 6014 S[_i] = -Infinity; // for cases where two data points shouldn't be linked together 6015 } 6016 6017 for (var _i2 = 0; _i2 < n; _i2++) { 6018 for (var j = 0; j < n; j++) { 6019 if (_i2 !== j) { 6020 S[_i2 * n + j] = getSimilarity$1(opts.distance, nodes[_i2], nodes[j], opts.attributes); 6021 } 6022 } 6023 } // Place preferences on the diagonal of S 6024 6025 6026 p = getPreference(S, opts.preference); 6027 6028 for (var _i3 = 0; _i3 < n; _i3++) { 6029 S[_i3 * n + _i3] = p; 6030 } // Initialize R responsibility matrix 6031 6032 6033 R = new Array(n2); 6034 6035 for (var _i4 = 0; _i4 < n2; _i4++) { 6036 R[_i4] = 0.0; 6037 } // Initialize A availability matrix 6038 6039 6040 A = new Array(n2); 6041 6042 for (var _i5 = 0; _i5 < n2; _i5++) { 6043 A[_i5] = 0.0; 6044 } 6045 6046 var old = new Array(n); 6047 var Rp = new Array(n); 6048 var se = new Array(n); 6049 6050 for (var _i6 = 0; _i6 < n; _i6++) { 6051 old[_i6] = 0.0; 6052 Rp[_i6] = 0.0; 6053 se[_i6] = 0; 6054 } 6055 6056 var e = new Array(n * opts.minIterations); 6057 6058 for (var _i7 = 0; _i7 < e.length; _i7++) { 6059 e[_i7] = 0; 6060 } 6061 6062 var iter; 6063 6064 for (iter = 0; iter < opts.maxIterations; iter++) { 6065 // main algorithmic loop 6066 // Update R responsibility matrix 6067 for (var _i8 = 0; _i8 < n; _i8++) { 6068 var max = -Infinity, 6069 max2 = -Infinity, 6070 maxI = -1, 6071 AS = 0.0; 6072 6073 for (var _j = 0; _j < n; _j++) { 6074 old[_j] = R[_i8 * n + _j]; 6075 AS = A[_i8 * n + _j] + S[_i8 * n + _j]; 6076 6077 if (AS >= max) { 6078 max2 = max; 6079 max = AS; 6080 maxI = _j; 6081 } else if (AS > max2) { 6082 max2 = AS; 6083 } 6084 } 6085 6086 for (var _j2 = 0; _j2 < n; _j2++) { 6087 R[_i8 * n + _j2] = (1 - opts.damping) * (S[_i8 * n + _j2] - max) + opts.damping * old[_j2]; 6088 } 6089 6090 R[_i8 * n + maxI] = (1 - opts.damping) * (S[_i8 * n + maxI] - max2) + opts.damping * old[maxI]; 6091 } // Update A availability matrix 6092 6093 6094 for (var _i9 = 0; _i9 < n; _i9++) { 6095 var sum = 0; 6096 6097 for (var _j3 = 0; _j3 < n; _j3++) { 6098 old[_j3] = A[_j3 * n + _i9]; 6099 Rp[_j3] = Math.max(0, R[_j3 * n + _i9]); 6100 sum += Rp[_j3]; 6101 } 6102 6103 sum -= Rp[_i9]; 6104 Rp[_i9] = R[_i9 * n + _i9]; 6105 sum += Rp[_i9]; 6106 6107 for (var _j4 = 0; _j4 < n; _j4++) { 6108 A[_j4 * n + _i9] = (1 - opts.damping) * Math.min(0, sum - Rp[_j4]) + opts.damping * old[_j4]; 6109 } 6110 6111 A[_i9 * n + _i9] = (1 - opts.damping) * (sum - Rp[_i9]) + opts.damping * old[_i9]; 6112 } // Check for convergence 6113 6114 6115 var K = 0; 6116 6117 for (var _i10 = 0; _i10 < n; _i10++) { 6118 var E = A[_i10 * n + _i10] + R[_i10 * n + _i10] > 0 ? 1 : 0; 6119 e[iter % opts.minIterations * n + _i10] = E; 6120 K += E; 6121 } 6122 6123 if (K > 0 && (iter >= opts.minIterations - 1 || iter == opts.maxIterations - 1)) { 6124 var _sum = 0; 6125 6126 for (var _i11 = 0; _i11 < n; _i11++) { 6127 se[_i11] = 0; 6128 6129 for (var _j5 = 0; _j5 < opts.minIterations; _j5++) { 6130 se[_i11] += e[_j5 * n + _i11]; 6131 } 6132 6133 if (se[_i11] === 0 || se[_i11] === opts.minIterations) { 6134 _sum++; 6135 } 6136 } 6137 6138 if (_sum === n) { 6139 // then we have convergence 6140 break; 6141 } 6142 } 6143 } // Identify exemplars (cluster centers) 6144 6145 6146 var exemplarsIndices = findExemplars(n, R, A); // Assign nodes to clusters 6147 6148 var clusterIndices = assign$2(n, S, exemplarsIndices); 6149 var clusters = {}; 6150 6151 for (var c = 0; c < exemplarsIndices.length; c++) { 6152 clusters[exemplarsIndices[c]] = []; 6153 } 6154 6155 for (var _i12 = 0; _i12 < nodes.length; _i12++) { 6156 var pos = id2position[nodes[_i12].id()]; 6157 6158 var clusterIndex = clusterIndices[pos]; 6159 6160 if (clusterIndex != null) { 6161 // the node may have not been assigned a cluster if no valid attributes were specified 6162 clusters[clusterIndex].push(nodes[_i12]); 6163 } 6164 } 6165 6166 var retClusters = new Array(exemplarsIndices.length); 6167 6168 for (var _c = 0; _c < exemplarsIndices.length; _c++) { 6169 retClusters[_c] = cy.collection(clusters[exemplarsIndices[_c]]); 6170 } 6171 6172 return retClusters; 6173 }; 6174 6175 var affinityPropagation$1 = { 6176 affinityPropagation: affinityPropagation, 6177 ap: affinityPropagation 6178 }; 6179 6180 var hierholzerDefaults = defaults({ 6181 root: undefined, 6182 directed: false 6183 }); 6184 var elesfn$b = { 6185 hierholzer: function hierholzer(options) { 6186 if (!plainObject(options)) { 6187 var args = arguments; 6188 options = { 6189 root: args[0], 6190 directed: args[1] 6191 }; 6192 } 6193 6194 var _hierholzerDefaults = hierholzerDefaults(options), 6195 root = _hierholzerDefaults.root, 6196 directed = _hierholzerDefaults.directed; 6197 6198 var eles = this; 6199 var dflag = false; 6200 var oddIn; 6201 var oddOut; 6202 var startVertex; 6203 if (root) startVertex = string(root) ? this.filter(root)[0].id() : root[0].id(); 6204 var nodes = {}; 6205 var edges = {}; 6206 6207 if (directed) { 6208 eles.forEach(function (ele) { 6209 var id = ele.id(); 6210 6211 if (ele.isNode()) { 6212 var ind = ele.indegree(true); 6213 var outd = ele.outdegree(true); 6214 var d1 = ind - outd; 6215 var d2 = outd - ind; 6216 6217 if (d1 == 1) { 6218 if (oddIn) dflag = true;else oddIn = id; 6219 } else if (d2 == 1) { 6220 if (oddOut) dflag = true;else oddOut = id; 6221 } else if (d2 > 1 || d1 > 1) { 6222 dflag = true; 6223 } 6224 6225 nodes[id] = []; 6226 ele.outgoers().forEach(function (e) { 6227 if (e.isEdge()) nodes[id].push(e.id()); 6228 }); 6229 } else { 6230 edges[id] = [undefined, ele.target().id()]; 6231 } 6232 }); 6233 } else { 6234 eles.forEach(function (ele) { 6235 var id = ele.id(); 6236 6237 if (ele.isNode()) { 6238 var d = ele.degree(true); 6239 6240 if (d % 2) { 6241 if (!oddIn) oddIn = id;else if (!oddOut) oddOut = id;else dflag = true; 6242 } 6243 6244 nodes[id] = []; 6245 ele.connectedEdges().forEach(function (e) { 6246 return nodes[id].push(e.id()); 6247 }); 6248 } else { 6249 edges[id] = [ele.source().id(), ele.target().id()]; 6250 } 6251 }); 6252 } 6253 6254 var result = { 6255 found: false, 6256 trail: undefined 6257 }; 6258 if (dflag) return result;else if (oddOut && oddIn) { 6259 if (directed) { 6260 if (startVertex && oddOut != startVertex) { 6261 return result; 6262 } 6263 6264 startVertex = oddOut; 6265 } else { 6266 if (startVertex && oddOut != startVertex && oddIn != startVertex) { 6267 return result; 6268 } else if (!startVertex) { 6269 startVertex = oddOut; 6270 } 6271 } 6272 } else { 6273 if (!startVertex) startVertex = eles[0].id(); 6274 } 6275 6276 var walk = function walk(v) { 6277 var currentNode = v; 6278 var subtour = [v]; 6279 var adj, adjTail, adjHead; 6280 6281 while (nodes[currentNode].length) { 6282 adj = nodes[currentNode].shift(); 6283 adjTail = edges[adj][0]; 6284 adjHead = edges[adj][1]; 6285 6286 if (currentNode != adjHead) { 6287 nodes[adjHead] = nodes[adjHead].filter(function (e) { 6288 return e != adj; 6289 }); 6290 currentNode = adjHead; 6291 } else if (!directed && currentNode != adjTail) { 6292 nodes[adjTail] = nodes[adjTail].filter(function (e) { 6293 return e != adj; 6294 }); 6295 currentNode = adjTail; 6296 } 6297 6298 subtour.unshift(adj); 6299 subtour.unshift(currentNode); 6300 } 6301 6302 return subtour; 6303 }; 6304 6305 var trail = []; 6306 var subtour = []; 6307 subtour = walk(startVertex); 6308 6309 while (subtour.length != 1) { 6310 if (nodes[subtour[0]].length == 0) { 6311 trail.unshift(eles.getElementById(subtour.shift())); 6312 trail.unshift(eles.getElementById(subtour.shift())); 6313 } else { 6314 subtour = walk(subtour.shift()).concat(subtour); 6315 } 6316 } 6317 6318 trail.unshift(eles.getElementById(subtour.shift())); // final node 6319 6320 for (var d in nodes) { 6321 if (nodes[d].length) { 6322 return result; 6323 } 6324 } 6325 6326 result.found = true; 6327 result.trail = this.spawn(trail); 6328 return result; 6329 } 6330 }; 6331 6332 var hopcroftTarjanBiconnected = function hopcroftTarjanBiconnected() { 6333 var eles = this; 6334 var nodes = {}; 6335 var id = 0; 6336 var edgeCount = 0; 6337 var components = []; 6338 var stack = []; 6339 var visitedEdges = {}; 6340 6341 var buildComponent = function buildComponent(x, y) { 6342 var i = stack.length - 1; 6343 var cutset = []; 6344 var component = eles.spawn(); 6345 6346 while (stack[i].x != x || stack[i].y != y) { 6347 cutset.push(stack.pop().edge); 6348 i--; 6349 } 6350 6351 cutset.push(stack.pop().edge); 6352 cutset.forEach(function (edge) { 6353 var connectedNodes = edge.connectedNodes().intersection(eles); 6354 component.merge(edge); 6355 connectedNodes.forEach(function (node) { 6356 var nodeId = node.id(); 6357 var connectedEdges = node.connectedEdges().intersection(eles); 6358 component.merge(node); 6359 6360 if (!nodes[nodeId].cutVertex) { 6361 component.merge(connectedEdges); 6362 } else { 6363 component.merge(connectedEdges.filter(function (edge) { 6364 return edge.isLoop(); 6365 })); 6366 } 6367 }); 6368 }); 6369 components.push(component); 6370 }; 6371 6372 var biconnectedSearch = function biconnectedSearch(root, currentNode, parent) { 6373 if (root === parent) edgeCount += 1; 6374 nodes[currentNode] = { 6375 id: id, 6376 low: id++, 6377 cutVertex: false 6378 }; 6379 var edges = eles.getElementById(currentNode).connectedEdges().intersection(eles); 6380 6381 if (edges.size() === 0) { 6382 components.push(eles.spawn(eles.getElementById(currentNode))); 6383 } else { 6384 var sourceId, targetId, otherNodeId, edgeId; 6385 edges.forEach(function (edge) { 6386 sourceId = edge.source().id(); 6387 targetId = edge.target().id(); 6388 otherNodeId = sourceId === currentNode ? targetId : sourceId; 6389 6390 if (otherNodeId !== parent) { 6391 edgeId = edge.id(); 6392 6393 if (!visitedEdges[edgeId]) { 6394 visitedEdges[edgeId] = true; 6395 stack.push({ 6396 x: currentNode, 6397 y: otherNodeId, 6398 edge: edge 6399 }); 6400 } 6401 6402 if (!(otherNodeId in nodes)) { 6403 biconnectedSearch(root, otherNodeId, currentNode); 6404 nodes[currentNode].low = Math.min(nodes[currentNode].low, nodes[otherNodeId].low); 6405 6406 if (nodes[currentNode].id <= nodes[otherNodeId].low) { 6407 nodes[currentNode].cutVertex = true; 6408 buildComponent(currentNode, otherNodeId); 6409 } 6410 } else { 6411 nodes[currentNode].low = Math.min(nodes[currentNode].low, nodes[otherNodeId].id); 6412 } 6413 } 6414 }); 6415 } 6416 }; 6417 6418 eles.forEach(function (ele) { 6419 if (ele.isNode()) { 6420 var nodeId = ele.id(); 6421 6422 if (!(nodeId in nodes)) { 6423 edgeCount = 0; 6424 biconnectedSearch(nodeId, nodeId); 6425 nodes[nodeId].cutVertex = edgeCount > 1; 6426 } 6427 } 6428 }); 6429 var cutVertices = Object.keys(nodes).filter(function (id) { 6430 return nodes[id].cutVertex; 6431 }).map(function (id) { 6432 return eles.getElementById(id); 6433 }); 6434 return { 6435 cut: eles.spawn(cutVertices), 6436 components: components 6437 }; 6438 }; 6439 6440 var hopcroftTarjanBiconnected$1 = { 6441 hopcroftTarjanBiconnected: hopcroftTarjanBiconnected, 6442 htbc: hopcroftTarjanBiconnected, 6443 htb: hopcroftTarjanBiconnected, 6444 hopcroftTarjanBiconnectedComponents: hopcroftTarjanBiconnected 6445 }; 6446 6447 var elesfn$c = {}; 6448 [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) { 6449 extend(elesfn$c, props); 6450 }); 6451 6452 /*! 6453 Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable 6454 Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) 6455 Licensed under The MIT License (http://opensource.org/licenses/MIT) 6456 */ 6457 6458 /* promise states [Promises/A+ 2.1] */ 6459 var STATE_PENDING = 0; 6460 /* [Promises/A+ 2.1.1] */ 6461 6462 var STATE_FULFILLED = 1; 6463 /* [Promises/A+ 2.1.2] */ 6464 6465 var STATE_REJECTED = 2; 6466 /* [Promises/A+ 2.1.3] */ 6467 6468 /* promise object constructor */ 6469 6470 var api = function api(executor) { 6471 /* optionally support non-constructor/plain-function call */ 6472 if (!(this instanceof api)) return new api(executor); 6473 /* initialize object */ 6474 6475 this.id = 'Thenable/1.0.7'; 6476 this.state = STATE_PENDING; 6477 /* initial state */ 6478 6479 this.fulfillValue = undefined; 6480 /* initial value */ 6481 6482 /* [Promises/A+ 1.3, 2.1.2.2] */ 6483 6484 this.rejectReason = undefined; 6485 /* initial reason */ 6486 6487 /* [Promises/A+ 1.5, 2.1.3.2] */ 6488 6489 this.onFulfilled = []; 6490 /* initial handlers */ 6491 6492 this.onRejected = []; 6493 /* initial handlers */ 6494 6495 /* provide optional information-hiding proxy */ 6496 6497 this.proxy = { 6498 then: this.then.bind(this) 6499 }; 6500 /* support optional executor function */ 6501 6502 if (typeof executor === 'function') executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); 6503 }; 6504 /* promise API methods */ 6505 6506 6507 api.prototype = { 6508 /* promise resolving methods */ 6509 fulfill: function fulfill(value) { 6510 return deliver(this, STATE_FULFILLED, 'fulfillValue', value); 6511 }, 6512 reject: function reject(value) { 6513 return deliver(this, STATE_REJECTED, 'rejectReason', value); 6514 }, 6515 6516 /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ 6517 then: function then(onFulfilled, onRejected) { 6518 var curr = this; 6519 var next = new api(); 6520 /* [Promises/A+ 2.2.7] */ 6521 6522 curr.onFulfilled.push(resolver(onFulfilled, next, 'fulfill')); 6523 /* [Promises/A+ 2.2.2/2.2.6] */ 6524 6525 curr.onRejected.push(resolver(onRejected, next, 'reject')); 6526 /* [Promises/A+ 2.2.3/2.2.6] */ 6527 6528 execute(curr); 6529 return next.proxy; 6530 /* [Promises/A+ 2.2.7, 3.3] */ 6531 } 6532 }; 6533 /* deliver an action */ 6534 6535 var deliver = function deliver(curr, state, name, value) { 6536 if (curr.state === STATE_PENDING) { 6537 curr.state = state; 6538 /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ 6539 6540 curr[name] = value; 6541 /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ 6542 6543 execute(curr); 6544 } 6545 6546 return curr; 6547 }; 6548 /* execute all handlers */ 6549 6550 6551 var execute = function execute(curr) { 6552 if (curr.state === STATE_FULFILLED) execute_handlers(curr, 'onFulfilled', curr.fulfillValue);else if (curr.state === STATE_REJECTED) execute_handlers(curr, 'onRejected', curr.rejectReason); 6553 }; 6554 /* execute particular set of handlers */ 6555 6556 6557 var execute_handlers = function execute_handlers(curr, name, value) { 6558 /* global setImmediate: true */ 6559 6560 /* global setTimeout: true */ 6561 6562 /* short-circuit processing */ 6563 if (curr[name].length === 0) return; 6564 /* iterate over all handlers, exactly once */ 6565 6566 var handlers = curr[name]; 6567 curr[name] = []; 6568 /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ 6569 6570 var func = function func() { 6571 for (var i = 0; i < handlers.length; i++) { 6572 handlers[i](value); 6573 } 6574 /* [Promises/A+ 2.2.5] */ 6575 6576 }; 6577 /* execute procedure asynchronously */ 6578 6579 /* [Promises/A+ 2.2.4, 3.1] */ 6580 6581 6582 if (typeof setImmediate === 'function') setImmediate(func);else setTimeout(func, 0); 6583 }; 6584 /* generate a resolver function */ 6585 6586 6587 var resolver = function resolver(cb, next, method) { 6588 return function (value) { 6589 if (typeof cb !== 'function') 6590 /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ 6591 next[method].call(next, value); 6592 /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ 6593 else { 6594 var result; 6595 6596 try { 6597 result = cb(value); 6598 } 6599 /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ 6600 catch (e) { 6601 next.reject(e); 6602 /* [Promises/A+ 2.2.7.2] */ 6603 6604 return; 6605 } 6606 6607 resolve(next, result); 6608 /* [Promises/A+ 2.2.7.1] */ 6609 } 6610 }; 6611 }; 6612 /* "Promise Resolution Procedure" */ 6613 6614 /* [Promises/A+ 2.3] */ 6615 6616 6617 var resolve = function resolve(promise, x) { 6618 /* sanity check arguments */ 6619 6620 /* [Promises/A+ 2.3.1] */ 6621 if (promise === x || promise.proxy === x) { 6622 promise.reject(new TypeError('cannot resolve promise with itself')); 6623 return; 6624 } 6625 /* surgically check for a "then" method 6626 (mainly to just call the "getter" of "then" only once) */ 6627 6628 6629 var then; 6630 6631 if (_typeof(x) === 'object' && x !== null || typeof x === 'function') { 6632 try { 6633 then = x.then; 6634 } 6635 /* [Promises/A+ 2.3.3.1, 3.5] */ 6636 catch (e) { 6637 promise.reject(e); 6638 /* [Promises/A+ 2.3.3.2] */ 6639 6640 return; 6641 } 6642 } 6643 /* handle own Thenables [Promises/A+ 2.3.2] 6644 and similar "thenables" [Promises/A+ 2.3.3] */ 6645 6646 6647 if (typeof then === 'function') { 6648 var resolved = false; 6649 6650 try { 6651 /* call retrieved "then" method */ 6652 6653 /* [Promises/A+ 2.3.3.3] */ 6654 then.call(x, 6655 /* resolvePromise */ 6656 6657 /* [Promises/A+ 2.3.3.3.1] */ 6658 function (y) { 6659 if (resolved) return; 6660 resolved = true; 6661 /* [Promises/A+ 2.3.3.3.3] */ 6662 6663 if (y === x) 6664 /* [Promises/A+ 3.6] */ 6665 promise.reject(new TypeError('circular thenable chain'));else resolve(promise, y); 6666 }, 6667 /* rejectPromise */ 6668 6669 /* [Promises/A+ 2.3.3.3.2] */ 6670 function (r) { 6671 if (resolved) return; 6672 resolved = true; 6673 /* [Promises/A+ 2.3.3.3.3] */ 6674 6675 promise.reject(r); 6676 }); 6677 } catch (e) { 6678 if (!resolved) 6679 /* [Promises/A+ 2.3.3.3.3] */ 6680 promise.reject(e); 6681 /* [Promises/A+ 2.3.3.3.4] */ 6682 } 6683 6684 return; 6685 } 6686 /* handle other values */ 6687 6688 6689 promise.fulfill(x); 6690 /* [Promises/A+ 2.3.4, 2.3.3.4] */ 6691 }; // so we always have Promise.all() 6692 6693 6694 api.all = function (ps) { 6695 return new api(function (resolveAll, rejectAll) { 6696 var vals = new Array(ps.length); 6697 var doneCount = 0; 6698 6699 var fulfill = function fulfill(i, val) { 6700 vals[i] = val; 6701 doneCount++; 6702 6703 if (doneCount === ps.length) { 6704 resolveAll(vals); 6705 } 6706 }; 6707 6708 for (var i = 0; i < ps.length; i++) { 6709 (function (i) { 6710 var p = ps[i]; 6711 var isPromise = p != null && p.then != null; 6712 6713 if (isPromise) { 6714 p.then(function (val) { 6715 fulfill(i, val); 6716 }, function (err) { 6717 rejectAll(err); 6718 }); 6719 } else { 6720 var val = p; 6721 fulfill(i, val); 6722 } 6723 })(i); 6724 } 6725 }); 6726 }; 6727 6728 api.resolve = function (val) { 6729 return new api(function (resolve, reject) { 6730 resolve(val); 6731 }); 6732 }; 6733 6734 api.reject = function (val) { 6735 return new api(function (resolve, reject) { 6736 reject(val); 6737 }); 6738 }; 6739 6740 var Promise$1 = typeof Promise !== 'undefined' ? Promise : api; // eslint-disable-line no-undef 6741 6742 var Animation = function Animation(target, opts, opts2) { 6743 var isCore = core(target); 6744 var isEle = !isCore; 6745 6746 var _p = this._private = extend({ 6747 duration: 1000 6748 }, opts, opts2); 6749 6750 _p.target = target; 6751 _p.style = _p.style || _p.css; 6752 _p.started = false; 6753 _p.playing = false; 6754 _p.hooked = false; 6755 _p.applying = false; 6756 _p.progress = 0; 6757 _p.completes = []; 6758 _p.frames = []; 6759 6760 if (_p.complete && fn(_p.complete)) { 6761 _p.completes.push(_p.complete); 6762 } 6763 6764 if (isEle) { 6765 var pos = target.position(); 6766 _p.startPosition = _p.startPosition || { 6767 x: pos.x, 6768 y: pos.y 6769 }; 6770 _p.startStyle = _p.startStyle || target.cy().style().getAnimationStartStyle(target, _p.style); 6771 } 6772 6773 if (isCore) { 6774 var pan = target.pan(); 6775 _p.startPan = { 6776 x: pan.x, 6777 y: pan.y 6778 }; 6779 _p.startZoom = target.zoom(); 6780 } // for future timeline/animations impl 6781 6782 6783 this.length = 1; 6784 this[0] = this; 6785 }; 6786 6787 var anifn = Animation.prototype; 6788 extend(anifn, { 6789 instanceString: function instanceString() { 6790 return 'animation'; 6791 }, 6792 hook: function hook() { 6793 var _p = this._private; 6794 6795 if (!_p.hooked) { 6796 // add to target's animation queue 6797 var q; 6798 var tAni = _p.target._private.animation; 6799 6800 if (_p.queue) { 6801 q = tAni.queue; 6802 } else { 6803 q = tAni.current; 6804 } 6805 6806 q.push(this); // add to the animation loop pool 6807 6808 if (elementOrCollection(_p.target)) { 6809 _p.target.cy().addToAnimationPool(_p.target); 6810 } 6811 6812 _p.hooked = true; 6813 } 6814 6815 return this; 6816 }, 6817 play: function play() { 6818 var _p = this._private; // autorewind 6819 6820 if (_p.progress === 1) { 6821 _p.progress = 0; 6822 } 6823 6824 _p.playing = true; 6825 _p.started = false; // needs to be started by animation loop 6826 6827 _p.stopped = false; 6828 this.hook(); // the animation loop will start the animation... 6829 6830 return this; 6831 }, 6832 playing: function playing() { 6833 return this._private.playing; 6834 }, 6835 apply: function apply() { 6836 var _p = this._private; 6837 _p.applying = true; 6838 _p.started = false; // needs to be started by animation loop 6839 6840 _p.stopped = false; 6841 this.hook(); // the animation loop will apply the animation at this progress 6842 6843 return this; 6844 }, 6845 applying: function applying() { 6846 return this._private.applying; 6847 }, 6848 pause: function pause() { 6849 var _p = this._private; 6850 _p.playing = false; 6851 _p.started = false; 6852 return this; 6853 }, 6854 stop: function stop() { 6855 var _p = this._private; 6856 _p.playing = false; 6857 _p.started = false; 6858 _p.stopped = true; // to be removed from animation queues 6859 6860 return this; 6861 }, 6862 rewind: function rewind() { 6863 return this.progress(0); 6864 }, 6865 fastforward: function fastforward() { 6866 return this.progress(1); 6867 }, 6868 time: function time(t) { 6869 var _p = this._private; 6870 6871 if (t === undefined) { 6872 return _p.progress * _p.duration; 6873 } else { 6874 return this.progress(t / _p.duration); 6875 } 6876 }, 6877 progress: function progress(p) { 6878 var _p = this._private; 6879 var wasPlaying = _p.playing; 6880 6881 if (p === undefined) { 6882 return _p.progress; 6883 } else { 6884 if (wasPlaying) { 6885 this.pause(); 6886 } 6887 6888 _p.progress = p; 6889 _p.started = false; 6890 6891 if (wasPlaying) { 6892 this.play(); 6893 } 6894 } 6895 6896 return this; 6897 }, 6898 completed: function completed() { 6899 return this._private.progress === 1; 6900 }, 6901 reverse: function reverse() { 6902 var _p = this._private; 6903 var wasPlaying = _p.playing; 6904 6905 if (wasPlaying) { 6906 this.pause(); 6907 } 6908 6909 _p.progress = 1 - _p.progress; 6910 _p.started = false; 6911 6912 var swap = function swap(a, b) { 6913 var _pa = _p[a]; 6914 6915 if (_pa == null) { 6916 return; 6917 } 6918 6919 _p[a] = _p[b]; 6920 _p[b] = _pa; 6921 }; 6922 6923 swap('zoom', 'startZoom'); 6924 swap('pan', 'startPan'); 6925 swap('position', 'startPosition'); // swap styles 6926 6927 if (_p.style) { 6928 for (var i = 0; i < _p.style.length; i++) { 6929 var prop = _p.style[i]; 6930 var name = prop.name; 6931 var startStyleProp = _p.startStyle[name]; 6932 _p.startStyle[name] = prop; 6933 _p.style[i] = startStyleProp; 6934 } 6935 } 6936 6937 if (wasPlaying) { 6938 this.play(); 6939 } 6940 6941 return this; 6942 }, 6943 promise: function promise(type) { 6944 var _p = this._private; 6945 var arr; 6946 6947 switch (type) { 6948 case 'frame': 6949 arr = _p.frames; 6950 break; 6951 6952 default: 6953 case 'complete': 6954 case 'completed': 6955 arr = _p.completes; 6956 } 6957 6958 return new Promise$1(function (resolve, reject) { 6959 arr.push(function () { 6960 resolve(); 6961 }); 6962 }); 6963 } 6964 }); 6965 anifn.complete = anifn.completed; 6966 anifn.run = anifn.play; 6967 anifn.running = anifn.playing; 6968 6969 var define = { 6970 animated: function animated() { 6971 return function animatedImpl() { 6972 var self = this; 6973 var selfIsArrayLike = self.length !== undefined; 6974 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6975 6976 var cy = this._private.cy || this; 6977 6978 if (!cy.styleEnabled()) { 6979 return false; 6980 } 6981 6982 var ele = all[0]; 6983 6984 if (ele) { 6985 return ele._private.animation.current.length > 0; 6986 } 6987 }; 6988 }, 6989 // animated 6990 clearQueue: function clearQueue() { 6991 return function clearQueueImpl() { 6992 var self = this; 6993 var selfIsArrayLike = self.length !== undefined; 6994 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 6995 6996 var cy = this._private.cy || this; 6997 6998 if (!cy.styleEnabled()) { 6999 return this; 7000 } 7001 7002 for (var i = 0; i < all.length; i++) { 7003 var ele = all[i]; 7004 ele._private.animation.queue = []; 7005 } 7006 7007 return this; 7008 }; 7009 }, 7010 // clearQueue 7011 delay: function delay() { 7012 return function delayImpl(time, complete) { 7013 var cy = this._private.cy || this; 7014 7015 if (!cy.styleEnabled()) { 7016 return this; 7017 } 7018 7019 return this.animate({ 7020 delay: time, 7021 duration: time, 7022 complete: complete 7023 }); 7024 }; 7025 }, 7026 // delay 7027 delayAnimation: function delayAnimation() { 7028 return function delayAnimationImpl(time, complete) { 7029 var cy = this._private.cy || this; 7030 7031 if (!cy.styleEnabled()) { 7032 return this; 7033 } 7034 7035 return this.animation({ 7036 delay: time, 7037 duration: time, 7038 complete: complete 7039 }); 7040 }; 7041 }, 7042 // delay 7043 animation: function animation() { 7044 return function animationImpl(properties, params) { 7045 var self = this; 7046 var selfIsArrayLike = self.length !== undefined; 7047 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 7048 7049 var cy = this._private.cy || this; 7050 var isCore = !selfIsArrayLike; 7051 var isEles = !isCore; 7052 7053 if (!cy.styleEnabled()) { 7054 return this; 7055 } 7056 7057 var style = cy.style(); 7058 properties = extend({}, properties, params); 7059 var propertiesEmpty = Object.keys(properties).length === 0; 7060 7061 if (propertiesEmpty) { 7062 return new Animation(all[0], properties); // nothing to animate 7063 } 7064 7065 if (properties.duration === undefined) { 7066 properties.duration = 400; 7067 } 7068 7069 switch (properties.duration) { 7070 case 'slow': 7071 properties.duration = 600; 7072 break; 7073 7074 case 'fast': 7075 properties.duration = 200; 7076 break; 7077 } 7078 7079 if (isEles) { 7080 properties.style = style.getPropsList(properties.style || properties.css); 7081 properties.css = undefined; 7082 } 7083 7084 if (isEles && properties.renderedPosition != null) { 7085 var rpos = properties.renderedPosition; 7086 var pan = cy.pan(); 7087 var zoom = cy.zoom(); 7088 properties.position = renderedToModelPosition(rpos, zoom, pan); 7089 } // override pan w/ panBy if set 7090 7091 7092 if (isCore && properties.panBy != null) { 7093 var panBy = properties.panBy; 7094 var cyPan = cy.pan(); 7095 properties.pan = { 7096 x: cyPan.x + panBy.x, 7097 y: cyPan.y + panBy.y 7098 }; 7099 } // override pan w/ center if set 7100 7101 7102 var center = properties.center || properties.centre; 7103 7104 if (isCore && center != null) { 7105 var centerPan = cy.getCenterPan(center.eles, properties.zoom); 7106 7107 if (centerPan != null) { 7108 properties.pan = centerPan; 7109 } 7110 } // override pan & zoom w/ fit if set 7111 7112 7113 if (isCore && properties.fit != null) { 7114 var fit = properties.fit; 7115 var fitVp = cy.getFitViewport(fit.eles || fit.boundingBox, fit.padding); 7116 7117 if (fitVp != null) { 7118 properties.pan = fitVp.pan; 7119 properties.zoom = fitVp.zoom; 7120 } 7121 } // override zoom (& potentially pan) w/ zoom obj if set 7122 7123 7124 if (isCore && plainObject(properties.zoom)) { 7125 var vp = cy.getZoomedViewport(properties.zoom); 7126 7127 if (vp != null) { 7128 if (vp.zoomed) { 7129 properties.zoom = vp.zoom; 7130 } 7131 7132 if (vp.panned) { 7133 properties.pan = vp.pan; 7134 } 7135 } else { 7136 properties.zoom = null; // an inavalid zoom (e.g. no delta) gets automatically destroyed 7137 } 7138 } 7139 7140 return new Animation(all[0], properties); 7141 }; 7142 }, 7143 // animate 7144 animate: function animate() { 7145 return function animateImpl(properties, params) { 7146 var self = this; 7147 var selfIsArrayLike = self.length !== undefined; 7148 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 7149 7150 var cy = this._private.cy || this; 7151 7152 if (!cy.styleEnabled()) { 7153 return this; 7154 } 7155 7156 if (params) { 7157 properties = extend({}, properties, params); 7158 } // manually hook and run the animation 7159 7160 7161 for (var i = 0; i < all.length; i++) { 7162 var ele = all[i]; 7163 var queue = ele.animated() && (properties.queue === undefined || properties.queue); 7164 var ani = ele.animation(properties, queue ? { 7165 queue: true 7166 } : undefined); 7167 ani.play(); 7168 } 7169 7170 return this; // chaining 7171 }; 7172 }, 7173 // animate 7174 stop: function stop() { 7175 return function stopImpl(clearQueue, jumpToEnd) { 7176 var self = this; 7177 var selfIsArrayLike = self.length !== undefined; 7178 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 7179 7180 var cy = this._private.cy || this; 7181 7182 if (!cy.styleEnabled()) { 7183 return this; 7184 } 7185 7186 for (var i = 0; i < all.length; i++) { 7187 var ele = all[i]; 7188 var _p = ele._private; 7189 var anis = _p.animation.current; 7190 7191 for (var j = 0; j < anis.length; j++) { 7192 var ani = anis[j]; 7193 var ani_p = ani._private; 7194 7195 if (jumpToEnd) { 7196 // next iteration of the animation loop, the animation 7197 // will go straight to the end and be removed 7198 ani_p.duration = 0; 7199 } 7200 } // clear the queue of future animations 7201 7202 7203 if (clearQueue) { 7204 _p.animation.queue = []; 7205 } 7206 7207 if (!jumpToEnd) { 7208 _p.animation.current = []; 7209 } 7210 } // we have to notify (the animation loop doesn't do it for us on `stop`) 7211 7212 7213 cy.notify('draw'); 7214 return this; 7215 }; 7216 } // stop 7217 7218 }; // define 7219 7220 var define$1 = { 7221 // access data field 7222 data: function data(params) { 7223 var defaults = { 7224 field: 'data', 7225 bindingEvent: 'data', 7226 allowBinding: false, 7227 allowSetting: false, 7228 allowGetting: false, 7229 settingEvent: 'data', 7230 settingTriggersEvent: false, 7231 triggerFnName: 'trigger', 7232 immutableKeys: {}, 7233 // key => true if immutable 7234 updateStyle: false, 7235 beforeGet: function beforeGet(self) {}, 7236 beforeSet: function beforeSet(self, obj) {}, 7237 onSet: function onSet(self) {}, 7238 canSet: function canSet(self) { 7239 return true; 7240 } 7241 }; 7242 params = extend({}, defaults, params); 7243 return function dataImpl(name, value) { 7244 var p = params; 7245 var self = this; 7246 var selfIsArrayLike = self.length !== undefined; 7247 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 7248 7249 var single = selfIsArrayLike ? self[0] : self; // .data('foo', ...) 7250 7251 if (string(name)) { 7252 // set or get property 7253 // .data('foo') 7254 if (p.allowGetting && value === undefined) { 7255 // get 7256 var ret; 7257 7258 if (single) { 7259 p.beforeGet(single); 7260 ret = single._private[p.field][name]; 7261 } 7262 7263 return ret; // .data('foo', 'bar') 7264 } else if (p.allowSetting && value !== undefined) { 7265 // set 7266 var valid = !p.immutableKeys[name]; 7267 7268 if (valid) { 7269 var change = _defineProperty({}, name, value); 7270 7271 p.beforeSet(self, change); 7272 7273 for (var i = 0, l = all.length; i < l; i++) { 7274 var ele = all[i]; 7275 7276 if (p.canSet(ele)) { 7277 ele._private[p.field][name] = value; 7278 } 7279 } // update mappers if asked 7280 7281 7282 if (p.updateStyle) { 7283 self.updateStyle(); 7284 } // call onSet callback 7285 7286 7287 p.onSet(self); 7288 7289 if (p.settingTriggersEvent) { 7290 self[p.triggerFnName](p.settingEvent); 7291 } 7292 } 7293 } // .data({ 'foo': 'bar' }) 7294 7295 } else if (p.allowSetting && plainObject(name)) { 7296 // extend 7297 var obj = name; 7298 var k, v; 7299 var keys = Object.keys(obj); 7300 p.beforeSet(self, obj); 7301 7302 for (var _i = 0; _i < keys.length; _i++) { 7303 k = keys[_i]; 7304 v = obj[k]; 7305 7306 var _valid = !p.immutableKeys[k]; 7307 7308 if (_valid) { 7309 for (var j = 0; j < all.length; j++) { 7310 var _ele = all[j]; 7311 7312 if (p.canSet(_ele)) { 7313 _ele._private[p.field][k] = v; 7314 } 7315 } 7316 } 7317 } // update mappers if asked 7318 7319 7320 if (p.updateStyle) { 7321 self.updateStyle(); 7322 } // call onSet callback 7323 7324 7325 p.onSet(self); 7326 7327 if (p.settingTriggersEvent) { 7328 self[p.triggerFnName](p.settingEvent); 7329 } // .data(function(){ ... }) 7330 7331 } else if (p.allowBinding && fn(name)) { 7332 // bind to event 7333 var fn$1 = name; 7334 self.on(p.bindingEvent, fn$1); // .data() 7335 } else if (p.allowGetting && name === undefined) { 7336 // get whole object 7337 var _ret; 7338 7339 if (single) { 7340 p.beforeGet(single); 7341 _ret = single._private[p.field]; 7342 } 7343 7344 return _ret; 7345 } 7346 7347 return self; // maintain chainability 7348 }; // function 7349 }, 7350 // data 7351 // remove data field 7352 removeData: function removeData(params) { 7353 var defaults = { 7354 field: 'data', 7355 event: 'data', 7356 triggerFnName: 'trigger', 7357 triggerEvent: false, 7358 immutableKeys: {} // key => true if immutable 7359 7360 }; 7361 params = extend({}, defaults, params); 7362 return function removeDataImpl(names) { 7363 var p = params; 7364 var self = this; 7365 var selfIsArrayLike = self.length !== undefined; 7366 var all = selfIsArrayLike ? self : [self]; // put in array if not array-like 7367 // .removeData('foo bar') 7368 7369 if (string(names)) { 7370 // then get the list of keys, and delete them 7371 var keys = names.split(/\s+/); 7372 var l = keys.length; 7373 7374 for (var i = 0; i < l; i++) { 7375 // delete each non-empty key 7376 var key = keys[i]; 7377 7378 if (emptyString(key)) { 7379 continue; 7380 } 7381 7382 var valid = !p.immutableKeys[key]; // not valid if immutable 7383 7384 if (valid) { 7385 for (var i_a = 0, l_a = all.length; i_a < l_a; i_a++) { 7386 all[i_a]._private[p.field][key] = undefined; 7387 } 7388 } 7389 } 7390 7391 if (p.triggerEvent) { 7392 self[p.triggerFnName](p.event); 7393 } // .removeData() 7394 7395 } else if (names === undefined) { 7396 // then delete all keys 7397 for (var _i_a = 0, _l_a = all.length; _i_a < _l_a; _i_a++) { 7398 var _privateFields = all[_i_a]._private[p.field]; 7399 7400 var _keys = Object.keys(_privateFields); 7401 7402 for (var _i2 = 0; _i2 < _keys.length; _i2++) { 7403 var _key = _keys[_i2]; 7404 var validKeyToDelete = !p.immutableKeys[_key]; 7405 7406 if (validKeyToDelete) { 7407 _privateFields[_key] = undefined; 7408 } 7409 } 7410 } 7411 7412 if (p.triggerEvent) { 7413 self[p.triggerFnName](p.event); 7414 } 7415 } 7416 7417 return self; // maintain chaining 7418 }; // function 7419 } // removeData 7420 7421 }; // define 7422 7423 var define$2 = { 7424 eventAliasesOn: function eventAliasesOn(proto) { 7425 var p = proto; 7426 p.addListener = p.listen = p.bind = p.on; 7427 p.unlisten = p.unbind = p.off = p.removeListener; 7428 p.trigger = p.emit; // this is just a wrapper alias of .on() 7429 7430 p.pon = p.promiseOn = function (events, selector) { 7431 var self = this; 7432 var args = Array.prototype.slice.call(arguments, 0); 7433 return new Promise$1(function (resolve, reject) { 7434 var callback = function callback(e) { 7435 self.off.apply(self, offArgs); 7436 resolve(e); 7437 }; 7438 7439 var onArgs = args.concat([callback]); 7440 var offArgs = onArgs.concat([]); 7441 self.on.apply(self, onArgs); 7442 }); 7443 }; 7444 } 7445 }; // define 7446 7447 // use this module to cherry pick functions into your prototype 7448 var define$3 = {}; 7449 [define, define$1, define$2].forEach(function (m) { 7450 extend(define$3, m); 7451 }); 7452 7453 var elesfn$d = { 7454 animate: define$3.animate(), 7455 animation: define$3.animation(), 7456 animated: define$3.animated(), 7457 clearQueue: define$3.clearQueue(), 7458 delay: define$3.delay(), 7459 delayAnimation: define$3.delayAnimation(), 7460 stop: define$3.stop() 7461 }; 7462 7463 var elesfn$e = { 7464 classes: function classes(_classes) { 7465 var self = this; 7466 7467 if (_classes === undefined) { 7468 var ret = []; 7469 7470 self[0]._private.classes.forEach(function (cls) { 7471 return ret.push(cls); 7472 }); 7473 7474 return ret; 7475 } else if (!array(_classes)) { 7476 // extract classes from string 7477 _classes = (_classes || '').match(/\S+/g) || []; 7478 } 7479 7480 var changed = []; 7481 var classesSet = new Set$1(_classes); // check and update each ele 7482 7483 for (var j = 0; j < self.length; j++) { 7484 var ele = self[j]; 7485 var _p = ele._private; 7486 var eleClasses = _p.classes; 7487 var changedEle = false; // check if ele has all of the passed classes 7488 7489 for (var i = 0; i < _classes.length; i++) { 7490 var cls = _classes[i]; 7491 var eleHasClass = eleClasses.has(cls); 7492 7493 if (!eleHasClass) { 7494 changedEle = true; 7495 break; 7496 } 7497 } // check if ele has classes outside of those passed 7498 7499 7500 if (!changedEle) { 7501 changedEle = eleClasses.size !== _classes.length; 7502 } 7503 7504 if (changedEle) { 7505 _p.classes = classesSet; 7506 changed.push(ele); 7507 } 7508 } // trigger update style on those eles that had class changes 7509 7510 7511 if (changed.length > 0) { 7512 this.spawn(changed).updateStyle().emit('class'); 7513 } 7514 7515 return self; 7516 }, 7517 addClass: function addClass(classes) { 7518 return this.toggleClass(classes, true); 7519 }, 7520 hasClass: function hasClass(className) { 7521 var ele = this[0]; 7522 return ele != null && ele._private.classes.has(className); 7523 }, 7524 toggleClass: function toggleClass(classes, toggle) { 7525 if (!array(classes)) { 7526 // extract classes from string 7527 classes = classes.match(/\S+/g) || []; 7528 } 7529 7530 var self = this; 7531 var toggleUndefd = toggle === undefined; 7532 var changed = []; // eles who had classes changed 7533 7534 for (var i = 0, il = self.length; i < il; i++) { 7535 var ele = self[i]; 7536 var eleClasses = ele._private.classes; 7537 var changedEle = false; 7538 7539 for (var j = 0; j < classes.length; j++) { 7540 var cls = classes[j]; 7541 var hasClass = eleClasses.has(cls); 7542 var changedNow = false; 7543 7544 if (toggle || toggleUndefd && !hasClass) { 7545 eleClasses.add(cls); 7546 changedNow = true; 7547 } else if (!toggle || toggleUndefd && hasClass) { 7548 eleClasses["delete"](cls); 7549 changedNow = true; 7550 } 7551 7552 if (!changedEle && changedNow) { 7553 changed.push(ele); 7554 changedEle = true; 7555 } 7556 } // for j classes 7557 7558 } // for i eles 7559 // trigger update style on those eles that had class changes 7560 7561 7562 if (changed.length > 0) { 7563 this.spawn(changed).updateStyle().emit('class'); 7564 } 7565 7566 return self; 7567 }, 7568 removeClass: function removeClass(classes) { 7569 return this.toggleClass(classes, false); 7570 }, 7571 flashClass: function flashClass(classes, duration) { 7572 var self = this; 7573 7574 if (duration == null) { 7575 duration = 250; 7576 } else if (duration === 0) { 7577 return self; // nothing to do really 7578 } 7579 7580 self.addClass(classes); 7581 setTimeout(function () { 7582 self.removeClass(classes); 7583 }, duration); 7584 return self; 7585 } 7586 }; 7587 elesfn$e.className = elesfn$e.classNames = elesfn$e.classes; 7588 7589 var tokens = { 7590 metaChar: '[\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]', 7591 // chars we need to escape in let names, etc 7592 comparatorOp: '=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=', 7593 // binary comparison op (used in data selectors) 7594 boolOp: '\\?|\\!|\\^', 7595 // boolean (unary) operators (used in data selectors) 7596 string: '"(?:\\\\"|[^"])*"' + '|' + "'(?:\\\\'|[^'])*'", 7597 // string literals (used in data selectors) -- doublequotes | singlequotes 7598 number: number$1, 7599 // number literal (used in data selectors) --- e.g. 0.1234, 1234, 12e123 7600 meta: 'degree|indegree|outdegree', 7601 // allowed metadata fields (i.e. allowed functions to use from Collection) 7602 separator: '\\s*,\\s*', 7603 // queries are separated by commas, e.g. edge[foo = 'bar'], node.someClass 7604 descendant: '\\s+', 7605 child: '\\s+>\\s+', 7606 subject: '\\$', 7607 group: 'node|edge|\\*', 7608 directedEdge: '\\s+->\\s+', 7609 undirectedEdge: '\\s+<->\\s+' 7610 }; 7611 tokens.variable = '(?:[\\w-]|(?:\\\\' + tokens.metaChar + '))+'; // a variable name 7612 7613 tokens.value = tokens.string + '|' + tokens.number; // a value literal, either a string or number 7614 7615 tokens.className = tokens.variable; // a class name (follows variable conventions) 7616 7617 tokens.id = tokens.variable; // an element id (follows variable conventions) 7618 7619 (function () { 7620 var ops, op, i; // add @ variants to comparatorOp 7621 7622 ops = tokens.comparatorOp.split('|'); 7623 7624 for (i = 0; i < ops.length; i++) { 7625 op = ops[i]; 7626 tokens.comparatorOp += '|@' + op; 7627 } // add ! variants to comparatorOp 7628 7629 7630 ops = tokens.comparatorOp.split('|'); 7631 7632 for (i = 0; i < ops.length; i++) { 7633 op = ops[i]; 7634 7635 if (op.indexOf('!') >= 0) { 7636 continue; 7637 } // skip ops that explicitly contain ! 7638 7639 7640 if (op === '=') { 7641 continue; 7642 } // skip = b/c != is explicitly defined 7643 7644 7645 tokens.comparatorOp += '|\\!' + op; 7646 } 7647 })(); 7648 7649 /** 7650 * Make a new query object 7651 * 7652 * @prop type {Type} The type enum (int) of the query 7653 * @prop checks List of checks to make against an ele to test for a match 7654 */ 7655 var newQuery = function newQuery() { 7656 return { 7657 checks: [] 7658 }; 7659 }; 7660 7661 /** 7662 * A check type enum-like object. Uses integer values for fast match() lookup. 7663 * The ordering does not matter as long as the ints are unique. 7664 */ 7665 var Type = { 7666 /** E.g. node */ 7667 GROUP: 0, 7668 7669 /** A collection of elements */ 7670 COLLECTION: 1, 7671 7672 /** A filter(ele) function */ 7673 FILTER: 2, 7674 7675 /** E.g. [foo > 1] */ 7676 DATA_COMPARE: 3, 7677 7678 /** E.g. [foo] */ 7679 DATA_EXIST: 4, 7680 7681 /** E.g. [?foo] */ 7682 DATA_BOOL: 5, 7683 7684 /** E.g. [[degree > 2]] */ 7685 META_COMPARE: 6, 7686 7687 /** E.g. :selected */ 7688 STATE: 7, 7689 7690 /** E.g. #foo */ 7691 ID: 8, 7692 7693 /** E.g. .foo */ 7694 CLASS: 9, 7695 7696 /** E.g. #foo <-> #bar */ 7697 UNDIRECTED_EDGE: 10, 7698 7699 /** E.g. #foo -> #bar */ 7700 DIRECTED_EDGE: 11, 7701 7702 /** E.g. $#foo -> #bar */ 7703 NODE_SOURCE: 12, 7704 7705 /** E.g. #foo -> $#bar */ 7706 NODE_TARGET: 13, 7707 7708 /** E.g. $#foo <-> #bar */ 7709 NODE_NEIGHBOR: 14, 7710 7711 /** E.g. #foo > #bar */ 7712 CHILD: 15, 7713 7714 /** E.g. #foo #bar */ 7715 DESCENDANT: 16, 7716 7717 /** E.g. $#foo > #bar */ 7718 PARENT: 17, 7719 7720 /** E.g. $#foo #bar */ 7721 ANCESTOR: 18, 7722 7723 /** E.g. #foo > $bar > #baz */ 7724 COMPOUND_SPLIT: 19, 7725 7726 /** Always matches, useful placeholder for subject in `COMPOUND_SPLIT` */ 7727 TRUE: 20 7728 }; 7729 7730 var stateSelectors = [{ 7731 selector: ':selected', 7732 matches: function matches(ele) { 7733 return ele.selected(); 7734 } 7735 }, { 7736 selector: ':unselected', 7737 matches: function matches(ele) { 7738 return !ele.selected(); 7739 } 7740 }, { 7741 selector: ':selectable', 7742 matches: function matches(ele) { 7743 return ele.selectable(); 7744 } 7745 }, { 7746 selector: ':unselectable', 7747 matches: function matches(ele) { 7748 return !ele.selectable(); 7749 } 7750 }, { 7751 selector: ':locked', 7752 matches: function matches(ele) { 7753 return ele.locked(); 7754 } 7755 }, { 7756 selector: ':unlocked', 7757 matches: function matches(ele) { 7758 return !ele.locked(); 7759 } 7760 }, { 7761 selector: ':visible', 7762 matches: function matches(ele) { 7763 return ele.visible(); 7764 } 7765 }, { 7766 selector: ':hidden', 7767 matches: function matches(ele) { 7768 return !ele.visible(); 7769 } 7770 }, { 7771 selector: ':transparent', 7772 matches: function matches(ele) { 7773 return ele.transparent(); 7774 } 7775 }, { 7776 selector: ':grabbed', 7777 matches: function matches(ele) { 7778 return ele.grabbed(); 7779 } 7780 }, { 7781 selector: ':free', 7782 matches: function matches(ele) { 7783 return !ele.grabbed(); 7784 } 7785 }, { 7786 selector: ':removed', 7787 matches: function matches(ele) { 7788 return ele.removed(); 7789 } 7790 }, { 7791 selector: ':inside', 7792 matches: function matches(ele) { 7793 return !ele.removed(); 7794 } 7795 }, { 7796 selector: ':grabbable', 7797 matches: function matches(ele) { 7798 return ele.grabbable(); 7799 } 7800 }, { 7801 selector: ':ungrabbable', 7802 matches: function matches(ele) { 7803 return !ele.grabbable(); 7804 } 7805 }, { 7806 selector: ':animated', 7807 matches: function matches(ele) { 7808 return ele.animated(); 7809 } 7810 }, { 7811 selector: ':unanimated', 7812 matches: function matches(ele) { 7813 return !ele.animated(); 7814 } 7815 }, { 7816 selector: ':parent', 7817 matches: function matches(ele) { 7818 return ele.isParent(); 7819 } 7820 }, { 7821 selector: ':childless', 7822 matches: function matches(ele) { 7823 return ele.isChildless(); 7824 } 7825 }, { 7826 selector: ':child', 7827 matches: function matches(ele) { 7828 return ele.isChild(); 7829 } 7830 }, { 7831 selector: ':orphan', 7832 matches: function matches(ele) { 7833 return ele.isOrphan(); 7834 } 7835 }, { 7836 selector: ':nonorphan', 7837 matches: function matches(ele) { 7838 return ele.isChild(); 7839 } 7840 }, { 7841 selector: ':compound', 7842 matches: function matches(ele) { 7843 if (ele.isNode()) { 7844 return ele.isParent(); 7845 } else { 7846 return ele.source().isParent() || ele.target().isParent(); 7847 } 7848 } 7849 }, { 7850 selector: ':loop', 7851 matches: function matches(ele) { 7852 return ele.isLoop(); 7853 } 7854 }, { 7855 selector: ':simple', 7856 matches: function matches(ele) { 7857 return ele.isSimple(); 7858 } 7859 }, { 7860 selector: ':active', 7861 matches: function matches(ele) { 7862 return ele.active(); 7863 } 7864 }, { 7865 selector: ':inactive', 7866 matches: function matches(ele) { 7867 return !ele.active(); 7868 } 7869 }, { 7870 selector: ':backgrounding', 7871 matches: function matches(ele) { 7872 return ele.backgrounding(); 7873 } 7874 }, { 7875 selector: ':nonbackgrounding', 7876 matches: function matches(ele) { 7877 return !ele.backgrounding(); 7878 } 7879 }].sort(function (a, b) { 7880 // n.b. selectors that are starting substrings of others must have the longer ones first 7881 return descending(a.selector, b.selector); 7882 }); 7883 7884 var lookup = function () { 7885 var selToFn = {}; 7886 var s; 7887 7888 for (var i = 0; i < stateSelectors.length; i++) { 7889 s = stateSelectors[i]; 7890 selToFn[s.selector] = s.matches; 7891 } 7892 7893 return selToFn; 7894 }(); 7895 7896 var stateSelectorMatches = function stateSelectorMatches(sel, ele) { 7897 return lookup[sel](ele); 7898 }; 7899 var stateSelectorRegex = '(' + stateSelectors.map(function (s) { 7900 return s.selector; 7901 }).join('|') + ')'; 7902 7903 // so that values get compared properly in Selector.filter() 7904 7905 var cleanMetaChars = function cleanMetaChars(str) { 7906 return str.replace(new RegExp('\\\\(' + tokens.metaChar + ')', 'g'), function (match, $1) { 7907 return $1; 7908 }); 7909 }; 7910 7911 var replaceLastQuery = function replaceLastQuery(selector, examiningQuery, replacementQuery) { 7912 selector[selector.length - 1] = replacementQuery; 7913 }; // NOTE: add new expression syntax here to have it recognised by the parser; 7914 // - a query contains all adjacent (i.e. no separator in between) expressions; 7915 // - the current query is stored in selector[i] 7916 // - you need to check the query objects in match() for it actually filter properly, but that's pretty straight forward 7917 7918 7919 var exprs = [{ 7920 name: 'group', 7921 // just used for identifying when debugging 7922 query: true, 7923 regex: '(' + tokens.group + ')', 7924 populate: function populate(selector, query, _ref) { 7925 var _ref2 = _slicedToArray(_ref, 1), 7926 group = _ref2[0]; 7927 7928 query.checks.push({ 7929 type: Type.GROUP, 7930 value: group === '*' ? group : group + 's' 7931 }); 7932 } 7933 }, { 7934 name: 'state', 7935 query: true, 7936 regex: stateSelectorRegex, 7937 populate: function populate(selector, query, _ref3) { 7938 var _ref4 = _slicedToArray(_ref3, 1), 7939 state = _ref4[0]; 7940 7941 query.checks.push({ 7942 type: Type.STATE, 7943 value: state 7944 }); 7945 } 7946 }, { 7947 name: 'id', 7948 query: true, 7949 regex: '\\#(' + tokens.id + ')', 7950 populate: function populate(selector, query, _ref5) { 7951 var _ref6 = _slicedToArray(_ref5, 1), 7952 id = _ref6[0]; 7953 7954 query.checks.push({ 7955 type: Type.ID, 7956 value: cleanMetaChars(id) 7957 }); 7958 } 7959 }, { 7960 name: 'className', 7961 query: true, 7962 regex: '\\.(' + tokens.className + ')', 7963 populate: function populate(selector, query, _ref7) { 7964 var _ref8 = _slicedToArray(_ref7, 1), 7965 className = _ref8[0]; 7966 7967 query.checks.push({ 7968 type: Type.CLASS, 7969 value: cleanMetaChars(className) 7970 }); 7971 } 7972 }, { 7973 name: 'dataExists', 7974 query: true, 7975 regex: '\\[\\s*(' + tokens.variable + ')\\s*\\]', 7976 populate: function populate(selector, query, _ref9) { 7977 var _ref10 = _slicedToArray(_ref9, 1), 7978 variable = _ref10[0]; 7979 7980 query.checks.push({ 7981 type: Type.DATA_EXIST, 7982 field: cleanMetaChars(variable) 7983 }); 7984 } 7985 }, { 7986 name: 'dataCompare', 7987 query: true, 7988 regex: '\\[\\s*(' + tokens.variable + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.value + ')\\s*\\]', 7989 populate: function populate(selector, query, _ref11) { 7990 var _ref12 = _slicedToArray(_ref11, 3), 7991 variable = _ref12[0], 7992 comparatorOp = _ref12[1], 7993 value = _ref12[2]; 7994 7995 var valueIsString = new RegExp('^' + tokens.string + '$').exec(value) != null; 7996 7997 if (valueIsString) { 7998 value = value.substring(1, value.length - 1); 7999 } else { 8000 value = parseFloat(value); 8001 } 8002 8003 query.checks.push({ 8004 type: Type.DATA_COMPARE, 8005 field: cleanMetaChars(variable), 8006 operator: comparatorOp, 8007 value: value 8008 }); 8009 } 8010 }, { 8011 name: 'dataBool', 8012 query: true, 8013 regex: '\\[\\s*(' + tokens.boolOp + ')\\s*(' + tokens.variable + ')\\s*\\]', 8014 populate: function populate(selector, query, _ref13) { 8015 var _ref14 = _slicedToArray(_ref13, 2), 8016 boolOp = _ref14[0], 8017 variable = _ref14[1]; 8018 8019 query.checks.push({ 8020 type: Type.DATA_BOOL, 8021 field: cleanMetaChars(variable), 8022 operator: boolOp 8023 }); 8024 } 8025 }, { 8026 name: 'metaCompare', 8027 query: true, 8028 regex: '\\[\\[\\s*(' + tokens.meta + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.number + ')\\s*\\]\\]', 8029 populate: function populate(selector, query, _ref15) { 8030 var _ref16 = _slicedToArray(_ref15, 3), 8031 meta = _ref16[0], 8032 comparatorOp = _ref16[1], 8033 number = _ref16[2]; 8034 8035 query.checks.push({ 8036 type: Type.META_COMPARE, 8037 field: cleanMetaChars(meta), 8038 operator: comparatorOp, 8039 value: parseFloat(number) 8040 }); 8041 } 8042 }, { 8043 name: 'nextQuery', 8044 separator: true, 8045 regex: tokens.separator, 8046 populate: function populate(selector, query) { 8047 var currentSubject = selector.currentSubject; 8048 var edgeCount = selector.edgeCount; 8049 var compoundCount = selector.compoundCount; 8050 var lastQ = selector[selector.length - 1]; 8051 8052 if (currentSubject != null) { 8053 lastQ.subject = currentSubject; 8054 selector.currentSubject = null; 8055 } 8056 8057 lastQ.edgeCount = edgeCount; 8058 lastQ.compoundCount = compoundCount; 8059 selector.edgeCount = 0; 8060 selector.compoundCount = 0; // go on to next query 8061 8062 var nextQuery = selector[selector.length++] = newQuery(); 8063 return nextQuery; // this is the new query to be filled by the following exprs 8064 } 8065 }, { 8066 name: 'directedEdge', 8067 separator: true, 8068 regex: tokens.directedEdge, 8069 populate: function populate(selector, query) { 8070 if (selector.currentSubject == null) { 8071 // undirected edge 8072 var edgeQuery = newQuery(); 8073 var source = query; 8074 var target = newQuery(); 8075 edgeQuery.checks.push({ 8076 type: Type.DIRECTED_EDGE, 8077 source: source, 8078 target: target 8079 }); // the query in the selector should be the edge rather than the source 8080 8081 replaceLastQuery(selector, query, edgeQuery); 8082 selector.edgeCount++; // we're now populating the target query with expressions that follow 8083 8084 return target; 8085 } else { 8086 // source/target 8087 var srcTgtQ = newQuery(); 8088 var _source = query; 8089 8090 var _target = newQuery(); 8091 8092 srcTgtQ.checks.push({ 8093 type: Type.NODE_SOURCE, 8094 source: _source, 8095 target: _target 8096 }); // the query in the selector should be the neighbourhood rather than the node 8097 8098 replaceLastQuery(selector, query, srcTgtQ); 8099 selector.edgeCount++; 8100 return _target; // now populating the target with the following expressions 8101 } 8102 } 8103 }, { 8104 name: 'undirectedEdge', 8105 separator: true, 8106 regex: tokens.undirectedEdge, 8107 populate: function populate(selector, query) { 8108 if (selector.currentSubject == null) { 8109 // undirected edge 8110 var edgeQuery = newQuery(); 8111 var source = query; 8112 var target = newQuery(); 8113 edgeQuery.checks.push({ 8114 type: Type.UNDIRECTED_EDGE, 8115 nodes: [source, target] 8116 }); // the query in the selector should be the edge rather than the source 8117 8118 replaceLastQuery(selector, query, edgeQuery); 8119 selector.edgeCount++; // we're now populating the target query with expressions that follow 8120 8121 return target; 8122 } else { 8123 // neighbourhood 8124 var nhoodQ = newQuery(); 8125 var node = query; 8126 var neighbor = newQuery(); 8127 nhoodQ.checks.push({ 8128 type: Type.NODE_NEIGHBOR, 8129 node: node, 8130 neighbor: neighbor 8131 }); // the query in the selector should be the neighbourhood rather than the node 8132 8133 replaceLastQuery(selector, query, nhoodQ); 8134 return neighbor; // now populating the neighbor with following expressions 8135 } 8136 } 8137 }, { 8138 name: 'child', 8139 separator: true, 8140 regex: tokens.child, 8141 populate: function populate(selector, query) { 8142 if (selector.currentSubject == null) { 8143 // default: child query 8144 var parentChildQuery = newQuery(); 8145 var child = newQuery(); 8146 var parent = selector[selector.length - 1]; 8147 parentChildQuery.checks.push({ 8148 type: Type.CHILD, 8149 parent: parent, 8150 child: child 8151 }); // the query in the selector should be the '>' itself 8152 8153 replaceLastQuery(selector, query, parentChildQuery); 8154 selector.compoundCount++; // we're now populating the child query with expressions that follow 8155 8156 return child; 8157 } else if (selector.currentSubject === query) { 8158 // compound split query 8159 var compound = newQuery(); 8160 var left = selector[selector.length - 1]; 8161 var right = newQuery(); 8162 var subject = newQuery(); 8163 8164 var _child = newQuery(); 8165 8166 var _parent = newQuery(); // set up the root compound q 8167 8168 8169 compound.checks.push({ 8170 type: Type.COMPOUND_SPLIT, 8171 left: left, 8172 right: right, 8173 subject: subject 8174 }); // populate the subject and replace the q at the old spot (within left) with TRUE 8175 8176 subject.checks = query.checks; // take the checks from the left 8177 8178 query.checks = [{ 8179 type: Type.TRUE 8180 }]; // checks under left refs the subject implicitly 8181 // set up the right q 8182 8183 _parent.checks.push({ 8184 type: Type.TRUE 8185 }); // parent implicitly refs the subject 8186 8187 8188 right.checks.push({ 8189 type: Type.PARENT, 8190 // type is swapped on right side queries 8191 parent: _parent, 8192 child: _child // empty for now 8193 8194 }); 8195 replaceLastQuery(selector, left, compound); // update the ref since we moved things around for `query` 8196 8197 selector.currentSubject = subject; 8198 selector.compoundCount++; 8199 return _child; // now populating the right side's child 8200 } else { 8201 // parent query 8202 // info for parent query 8203 var _parent2 = newQuery(); 8204 8205 var _child2 = newQuery(); 8206 8207 var pcQChecks = [{ 8208 type: Type.PARENT, 8209 parent: _parent2, 8210 child: _child2 8211 }]; // the parent-child query takes the place of the query previously being populated 8212 8213 _parent2.checks = query.checks; // the previous query contains the checks for the parent 8214 8215 query.checks = pcQChecks; // pc query takes over 8216 8217 selector.compoundCount++; 8218 return _child2; // we're now populating the child 8219 } 8220 } 8221 }, { 8222 name: 'descendant', 8223 separator: true, 8224 regex: tokens.descendant, 8225 populate: function populate(selector, query) { 8226 if (selector.currentSubject == null) { 8227 // default: descendant query 8228 var ancChQuery = newQuery(); 8229 var descendant = newQuery(); 8230 var ancestor = selector[selector.length - 1]; 8231 ancChQuery.checks.push({ 8232 type: Type.DESCENDANT, 8233 ancestor: ancestor, 8234 descendant: descendant 8235 }); // the query in the selector should be the '>' itself 8236 8237 replaceLastQuery(selector, query, ancChQuery); 8238 selector.compoundCount++; // we're now populating the descendant query with expressions that follow 8239 8240 return descendant; 8241 } else if (selector.currentSubject === query) { 8242 // compound split query 8243 var compound = newQuery(); 8244 var left = selector[selector.length - 1]; 8245 var right = newQuery(); 8246 var subject = newQuery(); 8247 8248 var _descendant = newQuery(); 8249 8250 var _ancestor = newQuery(); // set up the root compound q 8251 8252 8253 compound.checks.push({ 8254 type: Type.COMPOUND_SPLIT, 8255 left: left, 8256 right: right, 8257 subject: subject 8258 }); // populate the subject and replace the q at the old spot (within left) with TRUE 8259 8260 subject.checks = query.checks; // take the checks from the left 8261 8262 query.checks = [{ 8263 type: Type.TRUE 8264 }]; // checks under left refs the subject implicitly 8265 // set up the right q 8266 8267 _ancestor.checks.push({ 8268 type: Type.TRUE 8269 }); // ancestor implicitly refs the subject 8270 8271 8272 right.checks.push({ 8273 type: Type.ANCESTOR, 8274 // type is swapped on right side queries 8275 ancestor: _ancestor, 8276 descendant: _descendant // empty for now 8277 8278 }); 8279 replaceLastQuery(selector, left, compound); // update the ref since we moved things around for `query` 8280 8281 selector.currentSubject = subject; 8282 selector.compoundCount++; 8283 return _descendant; // now populating the right side's descendant 8284 } else { 8285 // ancestor query 8286 // info for parent query 8287 var _ancestor2 = newQuery(); 8288 8289 var _descendant2 = newQuery(); 8290 8291 var adQChecks = [{ 8292 type: Type.ANCESTOR, 8293 ancestor: _ancestor2, 8294 descendant: _descendant2 8295 }]; // the parent-child query takes the place of the query previously being populated 8296 8297 _ancestor2.checks = query.checks; // the previous query contains the checks for the parent 8298 8299 query.checks = adQChecks; // pc query takes over 8300 8301 selector.compoundCount++; 8302 return _descendant2; // we're now populating the child 8303 } 8304 } 8305 }, { 8306 name: 'subject', 8307 modifier: true, 8308 regex: tokens.subject, 8309 populate: function populate(selector, query) { 8310 if (selector.currentSubject != null && selector.currentSubject !== query) { 8311 warn('Redefinition of subject in selector `' + selector.toString() + '`'); 8312 return false; 8313 } 8314 8315 selector.currentSubject = query; 8316 var topQ = selector[selector.length - 1]; 8317 var topChk = topQ.checks[0]; 8318 var topType = topChk == null ? null : topChk.type; 8319 8320 if (topType === Type.DIRECTED_EDGE) { 8321 // directed edge with subject on the target 8322 // change to target node check 8323 topChk.type = Type.NODE_TARGET; 8324 } else if (topType === Type.UNDIRECTED_EDGE) { 8325 // undirected edge with subject on the second node 8326 // change to neighbor check 8327 topChk.type = Type.NODE_NEIGHBOR; 8328 topChk.node = topChk.nodes[1]; // second node is subject 8329 8330 topChk.neighbor = topChk.nodes[0]; // clean up unused fields for new type 8331 8332 topChk.nodes = null; 8333 } 8334 } 8335 }]; 8336 exprs.forEach(function (e) { 8337 return e.regexObj = new RegExp('^' + e.regex); 8338 }); 8339 8340 /** 8341 * Of all the expressions, find the first match in the remaining text. 8342 * @param {string} remaining The remaining text to parse 8343 * @returns The matched expression and the newly remaining text `{ expr, match, name, remaining }` 8344 */ 8345 8346 var consumeExpr = function consumeExpr(remaining) { 8347 var expr; 8348 var match; 8349 var name; 8350 8351 for (var j = 0; j < exprs.length; j++) { 8352 var e = exprs[j]; 8353 var n = e.name; 8354 var m = remaining.match(e.regexObj); 8355 8356 if (m != null) { 8357 match = m; 8358 expr = e; 8359 name = n; 8360 var consumed = m[0]; 8361 remaining = remaining.substring(consumed.length); 8362 break; // we've consumed one expr, so we can return now 8363 } 8364 } 8365 8366 return { 8367 expr: expr, 8368 match: match, 8369 name: name, 8370 remaining: remaining 8371 }; 8372 }; 8373 /** 8374 * Consume all the leading whitespace 8375 * @param {string} remaining The text to consume 8376 * @returns The text with the leading whitespace removed 8377 */ 8378 8379 8380 var consumeWhitespace = function consumeWhitespace(remaining) { 8381 var match = remaining.match(/^\s+/); 8382 8383 if (match) { 8384 var consumed = match[0]; 8385 remaining = remaining.substring(consumed.length); 8386 } 8387 8388 return remaining; 8389 }; 8390 /** 8391 * Parse the string and store the parsed representation in the Selector. 8392 * @param {string} selector The selector string 8393 * @returns `true` if the selector was successfully parsed, `false` otherwise 8394 */ 8395 8396 8397 var parse = function parse(selector) { 8398 var self = this; 8399 var remaining = self.inputText = selector; 8400 var currentQuery = self[0] = newQuery(); 8401 self.length = 1; 8402 remaining = consumeWhitespace(remaining); // get rid of leading whitespace 8403 8404 for (;;) { 8405 var exprInfo = consumeExpr(remaining); 8406 8407 if (exprInfo.expr == null) { 8408 warn('The selector `' + selector + '`is invalid'); 8409 return false; 8410 } else { 8411 var args = exprInfo.match.slice(1); // let the token populate the selector object in currentQuery 8412 8413 var ret = exprInfo.expr.populate(self, currentQuery, args); 8414 8415 if (ret === false) { 8416 return false; // exit if population failed 8417 } else if (ret != null) { 8418 currentQuery = ret; // change the current query to be filled if the expr specifies 8419 } 8420 } 8421 8422 remaining = exprInfo.remaining; // we're done when there's nothing left to parse 8423 8424 if (remaining.match(/^\s*$/)) { 8425 break; 8426 } 8427 } 8428 8429 var lastQ = self[self.length - 1]; 8430 8431 if (self.currentSubject != null) { 8432 lastQ.subject = self.currentSubject; 8433 } 8434 8435 lastQ.edgeCount = self.edgeCount; 8436 lastQ.compoundCount = self.compoundCount; 8437 8438 for (var i = 0; i < self.length; i++) { 8439 var q = self[i]; // in future, this could potentially be allowed if there were operator precedence and detection of invalid combinations 8440 8441 if (q.compoundCount > 0 && q.edgeCount > 0) { 8442 warn('The selector `' + selector + '` is invalid because it uses both a compound selector and an edge selector'); 8443 return false; 8444 } 8445 8446 if (q.edgeCount > 1) { 8447 warn('The selector `' + selector + '` is invalid because it uses multiple edge selectors'); 8448 return false; 8449 } else if (q.edgeCount === 1) { 8450 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.'); 8451 } 8452 } 8453 8454 return true; // success 8455 }; 8456 /** 8457 * Get the selector represented as a string. This value uses default formatting, 8458 * so things like spacing may differ from the input text passed to the constructor. 8459 * @returns {string} The selector string 8460 */ 8461 8462 8463 var toString = function toString() { 8464 if (this.toStringCache != null) { 8465 return this.toStringCache; 8466 } 8467 8468 var clean = function clean(obj) { 8469 if (obj == null) { 8470 return ''; 8471 } else { 8472 return obj; 8473 } 8474 }; 8475 8476 var cleanVal = function cleanVal(val) { 8477 if (string(val)) { 8478 return '"' + val + '"'; 8479 } else { 8480 return clean(val); 8481 } 8482 }; 8483 8484 var space = function space(val) { 8485 return ' ' + val + ' '; 8486 }; 8487 8488 var checkToString = function checkToString(check, subject) { 8489 var type = check.type, 8490 value = check.value; 8491 8492 switch (type) { 8493 case Type.GROUP: 8494 { 8495 var group = clean(value); 8496 return group.substring(0, group.length - 1); 8497 } 8498 8499 case Type.DATA_COMPARE: 8500 { 8501 var field = check.field, 8502 operator = check.operator; 8503 return '[' + field + space(clean(operator)) + cleanVal(value) + ']'; 8504 } 8505 8506 case Type.DATA_BOOL: 8507 { 8508 var _operator = check.operator, 8509 _field = check.field; 8510 return '[' + clean(_operator) + _field + ']'; 8511 } 8512 8513 case Type.DATA_EXIST: 8514 { 8515 var _field2 = check.field; 8516 return '[' + _field2 + ']'; 8517 } 8518 8519 case Type.META_COMPARE: 8520 { 8521 var _operator2 = check.operator, 8522 _field3 = check.field; 8523 return '[[' + _field3 + space(clean(_operator2)) + cleanVal(value) + ']]'; 8524 } 8525 8526 case Type.STATE: 8527 { 8528 return value; 8529 } 8530 8531 case Type.ID: 8532 { 8533 return '#' + value; 8534 } 8535 8536 case Type.CLASS: 8537 { 8538 return '.' + value; 8539 } 8540 8541 case Type.PARENT: 8542 case Type.CHILD: 8543 { 8544 return queryToString(check.parent, subject) + space('>') + queryToString(check.child, subject); 8545 } 8546 8547 case Type.ANCESTOR: 8548 case Type.DESCENDANT: 8549 { 8550 return queryToString(check.ancestor, subject) + ' ' + queryToString(check.descendant, subject); 8551 } 8552 8553 case Type.COMPOUND_SPLIT: 8554 { 8555 var lhs = queryToString(check.left, subject); 8556 var sub = queryToString(check.subject, subject); 8557 var rhs = queryToString(check.right, subject); 8558 return lhs + (lhs.length > 0 ? ' ' : '') + sub + rhs; 8559 } 8560 8561 case Type.TRUE: 8562 { 8563 return ''; 8564 } 8565 } 8566 }; 8567 8568 var queryToString = function queryToString(query, subject) { 8569 return query.checks.reduce(function (str, chk, i) { 8570 return str + (subject === query && i === 0 ? '$' : '') + checkToString(chk, subject); 8571 }, ''); 8572 }; 8573 8574 var str = ''; 8575 8576 for (var i = 0; i < this.length; i++) { 8577 var query = this[i]; 8578 str += queryToString(query, query.subject); 8579 8580 if (this.length > 1 && i < this.length - 1) { 8581 str += ', '; 8582 } 8583 } 8584 8585 this.toStringCache = str; 8586 return str; 8587 }; 8588 var parse$1 = { 8589 parse: parse, 8590 toString: toString 8591 }; 8592 8593 var valCmp = function valCmp(fieldVal, operator, value) { 8594 var matches; 8595 var isFieldStr = string(fieldVal); 8596 var isFieldNum = number(fieldVal); 8597 var isValStr = string(value); 8598 var fieldStr, valStr; 8599 var caseInsensitive = false; 8600 var notExpr = false; 8601 var isIneqCmp = false; 8602 8603 if (operator.indexOf('!') >= 0) { 8604 operator = operator.replace('!', ''); 8605 notExpr = true; 8606 } 8607 8608 if (operator.indexOf('@') >= 0) { 8609 operator = operator.replace('@', ''); 8610 caseInsensitive = true; 8611 } 8612 8613 if (isFieldStr || isValStr || caseInsensitive) { 8614 fieldStr = !isFieldStr && !isFieldNum ? '' : '' + fieldVal; 8615 valStr = '' + value; 8616 } // if we're doing a case insensitive comparison, then we're using a STRING comparison 8617 // even if we're comparing numbers 8618 8619 8620 if (caseInsensitive) { 8621 fieldVal = fieldStr = fieldStr.toLowerCase(); 8622 value = valStr = valStr.toLowerCase(); 8623 } 8624 8625 switch (operator) { 8626 case '*=': 8627 matches = fieldStr.indexOf(valStr) >= 0; 8628 break; 8629 8630 case '$=': 8631 matches = fieldStr.indexOf(valStr, fieldStr.length - valStr.length) >= 0; 8632 break; 8633 8634 case '^=': 8635 matches = fieldStr.indexOf(valStr) === 0; 8636 break; 8637 8638 case '=': 8639 matches = fieldVal === value; 8640 break; 8641 8642 case '>': 8643 isIneqCmp = true; 8644 matches = fieldVal > value; 8645 break; 8646 8647 case '>=': 8648 isIneqCmp = true; 8649 matches = fieldVal >= value; 8650 break; 8651 8652 case '<': 8653 isIneqCmp = true; 8654 matches = fieldVal < value; 8655 break; 8656 8657 case '<=': 8658 isIneqCmp = true; 8659 matches = fieldVal <= value; 8660 break; 8661 8662 default: 8663 matches = false; 8664 break; 8665 } // apply the not op, but null vals for inequalities should always stay non-matching 8666 8667 8668 if (notExpr && (fieldVal != null || !isIneqCmp)) { 8669 matches = !matches; 8670 } 8671 8672 return matches; 8673 }; 8674 var boolCmp = function boolCmp(fieldVal, operator) { 8675 switch (operator) { 8676 case '?': 8677 return fieldVal ? true : false; 8678 8679 case '!': 8680 return fieldVal ? false : true; 8681 8682 case '^': 8683 return fieldVal === undefined; 8684 } 8685 }; 8686 var existCmp = function existCmp(fieldVal) { 8687 return fieldVal !== undefined; 8688 }; 8689 var data = function data(ele, field) { 8690 return ele.data(field); 8691 }; 8692 var meta = function meta(ele, field) { 8693 return ele[field](); 8694 }; 8695 8696 /** A lookup of `match(check, ele)` functions by `Type` int */ 8697 8698 var match = []; 8699 /** 8700 * Returns whether the query matches for the element 8701 * @param query The `{ type, value, ... }` query object 8702 * @param ele The element to compare against 8703 */ 8704 8705 var matches = function matches(query, ele) { 8706 return query.checks.every(function (chk) { 8707 return match[chk.type](chk, ele); 8708 }); 8709 }; 8710 8711 match[Type.GROUP] = function (check, ele) { 8712 var group = check.value; 8713 return group === '*' || group === ele.group(); 8714 }; 8715 8716 match[Type.STATE] = function (check, ele) { 8717 var stateSelector = check.value; 8718 return stateSelectorMatches(stateSelector, ele); 8719 }; 8720 8721 match[Type.ID] = function (check, ele) { 8722 var id = check.value; 8723 return ele.id() === id; 8724 }; 8725 8726 match[Type.CLASS] = function (check, ele) { 8727 var cls = check.value; 8728 return ele.hasClass(cls); 8729 }; 8730 8731 match[Type.META_COMPARE] = function (check, ele) { 8732 var field = check.field, 8733 operator = check.operator, 8734 value = check.value; 8735 return valCmp(meta(ele, field), operator, value); 8736 }; 8737 8738 match[Type.DATA_COMPARE] = function (check, ele) { 8739 var field = check.field, 8740 operator = check.operator, 8741 value = check.value; 8742 return valCmp(data(ele, field), operator, value); 8743 }; 8744 8745 match[Type.DATA_BOOL] = function (check, ele) { 8746 var field = check.field, 8747 operator = check.operator; 8748 return boolCmp(data(ele, field), operator); 8749 }; 8750 8751 match[Type.DATA_EXIST] = function (check, ele) { 8752 var field = check.field, 8753 operator = check.operator; 8754 return existCmp(data(ele, field)); 8755 }; 8756 8757 match[Type.UNDIRECTED_EDGE] = function (check, ele) { 8758 var qA = check.nodes[0]; 8759 var qB = check.nodes[1]; 8760 var src = ele.source(); 8761 var tgt = ele.target(); 8762 return matches(qA, src) && matches(qB, tgt) || matches(qB, src) && matches(qA, tgt); 8763 }; 8764 8765 match[Type.NODE_NEIGHBOR] = function (check, ele) { 8766 return matches(check.node, ele) && ele.neighborhood().some(function (n) { 8767 return n.isNode() && matches(check.neighbor, n); 8768 }); 8769 }; 8770 8771 match[Type.DIRECTED_EDGE] = function (check, ele) { 8772 return matches(check.source, ele.source()) && matches(check.target, ele.target()); 8773 }; 8774 8775 match[Type.NODE_SOURCE] = function (check, ele) { 8776 return matches(check.source, ele) && ele.outgoers().some(function (n) { 8777 return n.isNode() && matches(check.target, n); 8778 }); 8779 }; 8780 8781 match[Type.NODE_TARGET] = function (check, ele) { 8782 return matches(check.target, ele) && ele.incomers().some(function (n) { 8783 return n.isNode() && matches(check.source, n); 8784 }); 8785 }; 8786 8787 match[Type.CHILD] = function (check, ele) { 8788 return matches(check.child, ele) && matches(check.parent, ele.parent()); 8789 }; 8790 8791 match[Type.PARENT] = function (check, ele) { 8792 return matches(check.parent, ele) && ele.children().some(function (c) { 8793 return matches(check.child, c); 8794 }); 8795 }; 8796 8797 match[Type.DESCENDANT] = function (check, ele) { 8798 return matches(check.descendant, ele) && ele.ancestors().some(function (a) { 8799 return matches(check.ancestor, a); 8800 }); 8801 }; 8802 8803 match[Type.ANCESTOR] = function (check, ele) { 8804 return matches(check.ancestor, ele) && ele.descendants().some(function (d) { 8805 return matches(check.descendant, d); 8806 }); 8807 }; 8808 8809 match[Type.COMPOUND_SPLIT] = function (check, ele) { 8810 return matches(check.subject, ele) && matches(check.left, ele) && matches(check.right, ele); 8811 }; 8812 8813 match[Type.TRUE] = function () { 8814 return true; 8815 }; 8816 8817 match[Type.COLLECTION] = function (check, ele) { 8818 var collection = check.value; 8819 return collection.has(ele); 8820 }; 8821 8822 match[Type.FILTER] = function (check, ele) { 8823 var filter = check.value; 8824 return filter(ele); 8825 }; 8826 8827 var filter = function filter(collection) { 8828 var self = this; // for 1 id #foo queries, just get the element 8829 8830 if (self.length === 1 && self[0].checks.length === 1 && self[0].checks[0].type === Type.ID) { 8831 return collection.getElementById(self[0].checks[0].value).collection(); 8832 } 8833 8834 var selectorFunction = function selectorFunction(element) { 8835 for (var j = 0; j < self.length; j++) { 8836 var query = self[j]; 8837 8838 if (matches(query, element)) { 8839 return true; 8840 } 8841 } 8842 8843 return false; 8844 }; 8845 8846 if (self.text() == null) { 8847 selectorFunction = function selectorFunction() { 8848 return true; 8849 }; 8850 } 8851 8852 return collection.filter(selectorFunction); 8853 }; // filter 8854 // does selector match a single element? 8855 8856 8857 var matches$1 = function matches$1(ele) { 8858 var self = this; 8859 8860 for (var j = 0; j < self.length; j++) { 8861 var query = self[j]; 8862 8863 if (matches(query, ele)) { 8864 return true; 8865 } 8866 } 8867 8868 return false; 8869 }; // matches 8870 8871 8872 var matching = { 8873 matches: matches$1, 8874 filter: filter 8875 }; 8876 8877 var Selector = function Selector(selector) { 8878 this.inputText = selector; 8879 this.currentSubject = null; 8880 this.compoundCount = 0; 8881 this.edgeCount = 0; 8882 this.length = 0; 8883 8884 if (selector == null || string(selector) && selector.match(/^\s*$/)) ; else if (elementOrCollection(selector)) { 8885 this.addQuery({ 8886 checks: [{ 8887 type: Type.COLLECTION, 8888 value: selector.collection() 8889 }] 8890 }); 8891 } else if (fn(selector)) { 8892 this.addQuery({ 8893 checks: [{ 8894 type: Type.FILTER, 8895 value: selector 8896 }] 8897 }); 8898 } else if (string(selector)) { 8899 if (!this.parse(selector)) { 8900 this.invalid = true; 8901 } 8902 } else { 8903 error('A selector must be created from a string; found '); 8904 } 8905 }; 8906 8907 var selfn = Selector.prototype; 8908 [parse$1, matching].forEach(function (p) { 8909 return extend(selfn, p); 8910 }); 8911 8912 selfn.text = function () { 8913 return this.inputText; 8914 }; 8915 8916 selfn.size = function () { 8917 return this.length; 8918 }; 8919 8920 selfn.eq = function (i) { 8921 return this[i]; 8922 }; 8923 8924 selfn.sameText = function (otherSel) { 8925 return !this.invalid && !otherSel.invalid && this.text() === otherSel.text(); 8926 }; 8927 8928 selfn.addQuery = function (q) { 8929 this[this.length++] = q; 8930 }; 8931 8932 selfn.selector = selfn.toString; 8933 8934 var elesfn$f = { 8935 allAre: function allAre(selector) { 8936 var selObj = new Selector(selector); 8937 return this.every(function (ele) { 8938 return selObj.matches(ele); 8939 }); 8940 }, 8941 is: function is(selector) { 8942 var selObj = new Selector(selector); 8943 return this.some(function (ele) { 8944 return selObj.matches(ele); 8945 }); 8946 }, 8947 some: function some(fn, thisArg) { 8948 for (var i = 0; i < this.length; i++) { 8949 var ret = !thisArg ? fn(this[i], i, this) : fn.apply(thisArg, [this[i], i, this]); 8950 8951 if (ret) { 8952 return true; 8953 } 8954 } 8955 8956 return false; 8957 }, 8958 every: function every(fn, thisArg) { 8959 for (var i = 0; i < this.length; i++) { 8960 var ret = !thisArg ? fn(this[i], i, this) : fn.apply(thisArg, [this[i], i, this]); 8961 8962 if (!ret) { 8963 return false; 8964 } 8965 } 8966 8967 return true; 8968 }, 8969 same: function same(collection) { 8970 // cheap collection ref check 8971 if (this === collection) { 8972 return true; 8973 } 8974 8975 collection = this.cy().collection(collection); 8976 var thisLength = this.length; 8977 var collectionLength = collection.length; // cheap length check 8978 8979 if (thisLength !== collectionLength) { 8980 return false; 8981 } // cheap element ref check 8982 8983 8984 if (thisLength === 1) { 8985 return this[0] === collection[0]; 8986 } 8987 8988 return this.every(function (ele) { 8989 return collection.hasElementWithId(ele.id()); 8990 }); 8991 }, 8992 anySame: function anySame(collection) { 8993 collection = this.cy().collection(collection); 8994 return this.some(function (ele) { 8995 return collection.hasElementWithId(ele.id()); 8996 }); 8997 }, 8998 allAreNeighbors: function allAreNeighbors(collection) { 8999 collection = this.cy().collection(collection); 9000 var nhood = this.neighborhood(); 9001 return collection.every(function (ele) { 9002 return nhood.hasElementWithId(ele.id()); 9003 }); 9004 }, 9005 contains: function contains(collection) { 9006 collection = this.cy().collection(collection); 9007 var self = this; 9008 return collection.every(function (ele) { 9009 return self.hasElementWithId(ele.id()); 9010 }); 9011 } 9012 }; 9013 elesfn$f.allAreNeighbours = elesfn$f.allAreNeighbors; 9014 elesfn$f.has = elesfn$f.contains; 9015 elesfn$f.equal = elesfn$f.equals = elesfn$f.same; 9016 9017 var cache = function cache(fn, name) { 9018 return function traversalCache(arg1, arg2, arg3, arg4) { 9019 var selectorOrEles = arg1; 9020 var eles = this; 9021 var key; 9022 9023 if (selectorOrEles == null) { 9024 key = ''; 9025 } else if (elementOrCollection(selectorOrEles) && selectorOrEles.length === 1) { 9026 key = selectorOrEles.id(); 9027 } 9028 9029 if (eles.length === 1 && key) { 9030 var _p = eles[0]._private; 9031 var tch = _p.traversalCache = _p.traversalCache || {}; 9032 var ch = tch[name] = tch[name] || []; 9033 var hash = hashString(key); 9034 var cacheHit = ch[hash]; 9035 9036 if (cacheHit) { 9037 return cacheHit; 9038 } else { 9039 return ch[hash] = fn.call(eles, arg1, arg2, arg3, arg4); 9040 } 9041 } else { 9042 return fn.call(eles, arg1, arg2, arg3, arg4); 9043 } 9044 }; 9045 }; 9046 9047 var elesfn$g = { 9048 parent: function parent(selector) { 9049 var parents = []; // optimisation for single ele call 9050 9051 if (this.length === 1) { 9052 var parent = this[0]._private.parent; 9053 9054 if (parent) { 9055 return parent; 9056 } 9057 } 9058 9059 for (var i = 0; i < this.length; i++) { 9060 var ele = this[i]; 9061 var _parent = ele._private.parent; 9062 9063 if (_parent) { 9064 parents.push(_parent); 9065 } 9066 } 9067 9068 return this.spawn(parents, { 9069 unique: true 9070 }).filter(selector); 9071 }, 9072 parents: function parents(selector) { 9073 var parents = []; 9074 var eles = this.parent(); 9075 9076 while (eles.nonempty()) { 9077 for (var i = 0; i < eles.length; i++) { 9078 var ele = eles[i]; 9079 parents.push(ele); 9080 } 9081 9082 eles = eles.parent(); 9083 } 9084 9085 return this.spawn(parents, { 9086 unique: true 9087 }).filter(selector); 9088 }, 9089 commonAncestors: function commonAncestors(selector) { 9090 var ancestors; 9091 9092 for (var i = 0; i < this.length; i++) { 9093 var ele = this[i]; 9094 var parents = ele.parents(); 9095 ancestors = ancestors || parents; 9096 ancestors = ancestors.intersect(parents); // current list must be common with current ele parents set 9097 } 9098 9099 return ancestors.filter(selector); 9100 }, 9101 orphans: function orphans(selector) { 9102 return this.stdFilter(function (ele) { 9103 return ele.isOrphan(); 9104 }).filter(selector); 9105 }, 9106 nonorphans: function nonorphans(selector) { 9107 return this.stdFilter(function (ele) { 9108 return ele.isChild(); 9109 }).filter(selector); 9110 }, 9111 children: cache(function (selector) { 9112 var children = []; 9113 9114 for (var i = 0; i < this.length; i++) { 9115 var ele = this[i]; 9116 var eleChildren = ele._private.children; 9117 9118 for (var j = 0; j < eleChildren.length; j++) { 9119 children.push(eleChildren[j]); 9120 } 9121 } 9122 9123 return this.spawn(children, { 9124 unique: true 9125 }).filter(selector); 9126 }, 'children'), 9127 siblings: function siblings(selector) { 9128 return this.parent().children().not(this).filter(selector); 9129 }, 9130 isParent: function isParent() { 9131 var ele = this[0]; 9132 9133 if (ele) { 9134 return ele.isNode() && ele._private.children.length !== 0; 9135 } 9136 }, 9137 isChildless: function isChildless() { 9138 var ele = this[0]; 9139 9140 if (ele) { 9141 return ele.isNode() && ele._private.children.length === 0; 9142 } 9143 }, 9144 isChild: function isChild() { 9145 var ele = this[0]; 9146 9147 if (ele) { 9148 return ele.isNode() && ele._private.parent != null; 9149 } 9150 }, 9151 isOrphan: function isOrphan() { 9152 var ele = this[0]; 9153 9154 if (ele) { 9155 return ele.isNode() && ele._private.parent == null; 9156 } 9157 }, 9158 descendants: function descendants(selector) { 9159 var elements = []; 9160 9161 function add(eles) { 9162 for (var i = 0; i < eles.length; i++) { 9163 var ele = eles[i]; 9164 elements.push(ele); 9165 9166 if (ele.children().nonempty()) { 9167 add(ele.children()); 9168 } 9169 } 9170 } 9171 9172 add(this.children()); 9173 return this.spawn(elements, { 9174 unique: true 9175 }).filter(selector); 9176 } 9177 }; 9178 9179 function forEachCompound(eles, fn, includeSelf, recursiveStep) { 9180 var q = []; 9181 var did = new Set$1(); 9182 var cy = eles.cy(); 9183 var hasCompounds = cy.hasCompoundNodes(); 9184 9185 for (var i = 0; i < eles.length; i++) { 9186 var ele = eles[i]; 9187 9188 if (includeSelf) { 9189 q.push(ele); 9190 } else if (hasCompounds) { 9191 recursiveStep(q, did, ele); 9192 } 9193 } 9194 9195 while (q.length > 0) { 9196 var _ele = q.shift(); 9197 9198 fn(_ele); 9199 did.add(_ele.id()); 9200 9201 if (hasCompounds) { 9202 recursiveStep(q, did, _ele); 9203 } 9204 } 9205 9206 return eles; 9207 } 9208 9209 function addChildren(q, did, ele) { 9210 if (ele.isParent()) { 9211 var children = ele._private.children; 9212 9213 for (var i = 0; i < children.length; i++) { 9214 var child = children[i]; 9215 9216 if (!did.has(child.id())) { 9217 q.push(child); 9218 } 9219 } 9220 } 9221 } // very efficient version of eles.add( eles.descendants() ).forEach() 9222 // for internal use 9223 9224 9225 elesfn$g.forEachDown = function (fn) { 9226 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 9227 return forEachCompound(this, fn, includeSelf, addChildren); 9228 }; 9229 9230 function addParent(q, did, ele) { 9231 if (ele.isChild()) { 9232 var parent = ele._private.parent; 9233 9234 if (!did.has(parent.id())) { 9235 q.push(parent); 9236 } 9237 } 9238 } 9239 9240 elesfn$g.forEachUp = function (fn) { 9241 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 9242 return forEachCompound(this, fn, includeSelf, addParent); 9243 }; 9244 9245 function addParentAndChildren(q, did, ele) { 9246 addParent(q, did, ele); 9247 addChildren(q, did, ele); 9248 } 9249 9250 elesfn$g.forEachUpAndDown = function (fn) { 9251 var includeSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 9252 return forEachCompound(this, fn, includeSelf, addParentAndChildren); 9253 }; // aliases 9254 9255 9256 elesfn$g.ancestors = elesfn$g.parents; 9257 9258 var fn$1, elesfn$h; 9259 fn$1 = elesfn$h = { 9260 data: define$3.data({ 9261 field: 'data', 9262 bindingEvent: 'data', 9263 allowBinding: true, 9264 allowSetting: true, 9265 settingEvent: 'data', 9266 settingTriggersEvent: true, 9267 triggerFnName: 'trigger', 9268 allowGetting: true, 9269 immutableKeys: { 9270 'id': true, 9271 'source': true, 9272 'target': true, 9273 'parent': true 9274 }, 9275 updateStyle: true 9276 }), 9277 removeData: define$3.removeData({ 9278 field: 'data', 9279 event: 'data', 9280 triggerFnName: 'trigger', 9281 triggerEvent: true, 9282 immutableKeys: { 9283 'id': true, 9284 'source': true, 9285 'target': true, 9286 'parent': true 9287 }, 9288 updateStyle: true 9289 }), 9290 scratch: define$3.data({ 9291 field: 'scratch', 9292 bindingEvent: 'scratch', 9293 allowBinding: true, 9294 allowSetting: true, 9295 settingEvent: 'scratch', 9296 settingTriggersEvent: true, 9297 triggerFnName: 'trigger', 9298 allowGetting: true, 9299 updateStyle: true 9300 }), 9301 removeScratch: define$3.removeData({ 9302 field: 'scratch', 9303 event: 'scratch', 9304 triggerFnName: 'trigger', 9305 triggerEvent: true, 9306 updateStyle: true 9307 }), 9308 rscratch: define$3.data({ 9309 field: 'rscratch', 9310 allowBinding: false, 9311 allowSetting: true, 9312 settingTriggersEvent: false, 9313 allowGetting: true 9314 }), 9315 removeRscratch: define$3.removeData({ 9316 field: 'rscratch', 9317 triggerEvent: false 9318 }), 9319 id: function id() { 9320 var ele = this[0]; 9321 9322 if (ele) { 9323 return ele._private.data.id; 9324 } 9325 } 9326 }; // aliases 9327 9328 fn$1.attr = fn$1.data; 9329 fn$1.removeAttr = fn$1.removeData; 9330 var data$1 = elesfn$h; 9331 9332 var elesfn$i = {}; 9333 9334 function defineDegreeFunction(callback) { 9335 return function (includeLoops) { 9336 var self = this; 9337 9338 if (includeLoops === undefined) { 9339 includeLoops = true; 9340 } 9341 9342 if (self.length === 0) { 9343 return; 9344 } 9345 9346 if (self.isNode() && !self.removed()) { 9347 var degree = 0; 9348 var node = self[0]; 9349 var connectedEdges = node._private.edges; 9350 9351 for (var i = 0; i < connectedEdges.length; i++) { 9352 var edge = connectedEdges[i]; 9353 9354 if (!includeLoops && edge.isLoop()) { 9355 continue; 9356 } 9357 9358 degree += callback(node, edge); 9359 } 9360 9361 return degree; 9362 } else { 9363 return; 9364 } 9365 }; 9366 } 9367 9368 extend(elesfn$i, { 9369 degree: defineDegreeFunction(function (node, edge) { 9370 if (edge.source().same(edge.target())) { 9371 return 2; 9372 } else { 9373 return 1; 9374 } 9375 }), 9376 indegree: defineDegreeFunction(function (node, edge) { 9377 if (edge.target().same(node)) { 9378 return 1; 9379 } else { 9380 return 0; 9381 } 9382 }), 9383 outdegree: defineDegreeFunction(function (node, edge) { 9384 if (edge.source().same(node)) { 9385 return 1; 9386 } else { 9387 return 0; 9388 } 9389 }) 9390 }); 9391 9392 function defineDegreeBoundsFunction(degreeFn, callback) { 9393 return function (includeLoops) { 9394 var ret; 9395 var nodes = this.nodes(); 9396 9397 for (var i = 0; i < nodes.length; i++) { 9398 var ele = nodes[i]; 9399 var degree = ele[degreeFn](includeLoops); 9400 9401 if (degree !== undefined && (ret === undefined || callback(degree, ret))) { 9402 ret = degree; 9403 } 9404 } 9405 9406 return ret; 9407 }; 9408 } 9409 9410 extend(elesfn$i, { 9411 minDegree: defineDegreeBoundsFunction('degree', function (degree, min) { 9412 return degree < min; 9413 }), 9414 maxDegree: defineDegreeBoundsFunction('degree', function (degree, max) { 9415 return degree > max; 9416 }), 9417 minIndegree: defineDegreeBoundsFunction('indegree', function (degree, min) { 9418 return degree < min; 9419 }), 9420 maxIndegree: defineDegreeBoundsFunction('indegree', function (degree, max) { 9421 return degree > max; 9422 }), 9423 minOutdegree: defineDegreeBoundsFunction('outdegree', function (degree, min) { 9424 return degree < min; 9425 }), 9426 maxOutdegree: defineDegreeBoundsFunction('outdegree', function (degree, max) { 9427 return degree > max; 9428 }) 9429 }); 9430 extend(elesfn$i, { 9431 totalDegree: function totalDegree(includeLoops) { 9432 var total = 0; 9433 var nodes = this.nodes(); 9434 9435 for (var i = 0; i < nodes.length; i++) { 9436 total += nodes[i].degree(includeLoops); 9437 } 9438 9439 return total; 9440 } 9441 }); 9442 9443 var fn$2, elesfn$j; 9444 9445 var beforePositionSet = function beforePositionSet(eles, newPos, silent) { 9446 for (var i = 0; i < eles.length; i++) { 9447 var ele = eles[i]; 9448 9449 if (!ele.locked()) { 9450 var oldPos = ele._private.position; 9451 var delta = { 9452 x: newPos.x != null ? newPos.x - oldPos.x : 0, 9453 y: newPos.y != null ? newPos.y - oldPos.y : 0 9454 }; 9455 9456 if (ele.isParent() && !(delta.x === 0 && delta.y === 0)) { 9457 ele.children().shift(delta, silent); 9458 } 9459 9460 ele.shiftCachedBoundingBox(delta); 9461 } 9462 } 9463 }; 9464 9465 var positionDef = { 9466 field: 'position', 9467 bindingEvent: 'position', 9468 allowBinding: true, 9469 allowSetting: true, 9470 settingEvent: 'position', 9471 settingTriggersEvent: true, 9472 triggerFnName: 'emitAndNotify', 9473 allowGetting: true, 9474 validKeys: ['x', 'y'], 9475 beforeGet: function beforeGet(ele) { 9476 ele.updateCompoundBounds(); 9477 }, 9478 beforeSet: function beforeSet(eles, newPos) { 9479 beforePositionSet(eles, newPos, false); 9480 }, 9481 onSet: function onSet(eles) { 9482 eles.dirtyCompoundBoundsCache(); 9483 }, 9484 canSet: function canSet(ele) { 9485 return !ele.locked(); 9486 } 9487 }; 9488 fn$2 = elesfn$j = { 9489 position: define$3.data(positionDef), 9490 // position but no notification to renderer 9491 silentPosition: define$3.data(extend({}, positionDef, { 9492 allowBinding: false, 9493 allowSetting: true, 9494 settingTriggersEvent: false, 9495 allowGetting: false, 9496 beforeSet: function beforeSet(eles, newPos) { 9497 beforePositionSet(eles, newPos, true); 9498 } 9499 })), 9500 positions: function positions(pos, silent) { 9501 if (plainObject(pos)) { 9502 if (silent) { 9503 this.silentPosition(pos); 9504 } else { 9505 this.position(pos); 9506 } 9507 } else if (fn(pos)) { 9508 var _fn = pos; 9509 var cy = this.cy(); 9510 cy.startBatch(); 9511 9512 for (var i = 0; i < this.length; i++) { 9513 var ele = this[i]; 9514 9515 var _pos = void 0; 9516 9517 if (_pos = _fn(ele, i)) { 9518 if (silent) { 9519 ele.silentPosition(_pos); 9520 } else { 9521 ele.position(_pos); 9522 } 9523 } 9524 } 9525 9526 cy.endBatch(); 9527 } 9528 9529 return this; // chaining 9530 }, 9531 silentPositions: function silentPositions(pos) { 9532 return this.positions(pos, true); 9533 }, 9534 shift: function shift(dim, val, silent) { 9535 var delta; 9536 9537 if (plainObject(dim)) { 9538 delta = { 9539 x: number(dim.x) ? dim.x : 0, 9540 y: number(dim.y) ? dim.y : 0 9541 }; 9542 silent = val; 9543 } else if (string(dim) && number(val)) { 9544 delta = { 9545 x: 0, 9546 y: 0 9547 }; 9548 delta[dim] = val; 9549 } 9550 9551 if (delta != null) { 9552 var cy = this.cy(); 9553 cy.startBatch(); 9554 9555 for (var i = 0; i < this.length; i++) { 9556 var ele = this[i]; 9557 var pos = ele.position(); 9558 var newPos = { 9559 x: pos.x + delta.x, 9560 y: pos.y + delta.y 9561 }; 9562 9563 if (silent) { 9564 ele.silentPosition(newPos); 9565 } else { 9566 ele.position(newPos); 9567 } 9568 } 9569 9570 cy.endBatch(); 9571 } 9572 9573 return this; 9574 }, 9575 silentShift: function silentShift(dim, val) { 9576 if (plainObject(dim)) { 9577 this.shift(dim, true); 9578 } else if (string(dim) && number(val)) { 9579 this.shift(dim, val, true); 9580 } 9581 9582 return this; 9583 }, 9584 // get/set the rendered (i.e. on screen) positon of the element 9585 renderedPosition: function renderedPosition(dim, val) { 9586 var ele = this[0]; 9587 var cy = this.cy(); 9588 var zoom = cy.zoom(); 9589 var pan = cy.pan(); 9590 var rpos = plainObject(dim) ? dim : undefined; 9591 var setting = rpos !== undefined || val !== undefined && string(dim); 9592 9593 if (ele && ele.isNode()) { 9594 // must have an element and must be a node to return position 9595 if (setting) { 9596 for (var i = 0; i < this.length; i++) { 9597 var _ele = this[i]; 9598 9599 if (val !== undefined) { 9600 // set one dimension 9601 _ele.position(dim, (val - pan[dim]) / zoom); 9602 } else if (rpos !== undefined) { 9603 // set whole position 9604 _ele.position(renderedToModelPosition(rpos, zoom, pan)); 9605 } 9606 } 9607 } else { 9608 // getting 9609 var pos = ele.position(); 9610 rpos = modelToRenderedPosition(pos, zoom, pan); 9611 9612 if (dim === undefined) { 9613 // then return the whole rendered position 9614 return rpos; 9615 } else { 9616 // then return the specified dimension 9617 return rpos[dim]; 9618 } 9619 } 9620 } else if (!setting) { 9621 return undefined; // for empty collection case 9622 } 9623 9624 return this; // chaining 9625 }, 9626 // get/set the position relative to the parent 9627 relativePosition: function relativePosition(dim, val) { 9628 var ele = this[0]; 9629 var cy = this.cy(); 9630 var ppos = plainObject(dim) ? dim : undefined; 9631 var setting = ppos !== undefined || val !== undefined && string(dim); 9632 var hasCompoundNodes = cy.hasCompoundNodes(); 9633 9634 if (ele && ele.isNode()) { 9635 // must have an element and must be a node to return position 9636 if (setting) { 9637 for (var i = 0; i < this.length; i++) { 9638 var _ele2 = this[i]; 9639 var parent = hasCompoundNodes ? _ele2.parent() : null; 9640 var hasParent = parent && parent.length > 0; 9641 var relativeToParent = hasParent; 9642 9643 if (hasParent) { 9644 parent = parent[0]; 9645 } 9646 9647 var origin = relativeToParent ? parent.position() : { 9648 x: 0, 9649 y: 0 9650 }; 9651 9652 if (val !== undefined) { 9653 // set one dimension 9654 _ele2.position(dim, val + origin[dim]); 9655 } else if (ppos !== undefined) { 9656 // set whole position 9657 _ele2.position({ 9658 x: ppos.x + origin.x, 9659 y: ppos.y + origin.y 9660 }); 9661 } 9662 } 9663 } else { 9664 // getting 9665 var pos = ele.position(); 9666 9667 var _parent = hasCompoundNodes ? ele.parent() : null; 9668 9669 var _hasParent = _parent && _parent.length > 0; 9670 9671 var _relativeToParent = _hasParent; 9672 9673 if (_hasParent) { 9674 _parent = _parent[0]; 9675 } 9676 9677 var _origin = _relativeToParent ? _parent.position() : { 9678 x: 0, 9679 y: 0 9680 }; 9681 9682 ppos = { 9683 x: pos.x - _origin.x, 9684 y: pos.y - _origin.y 9685 }; 9686 9687 if (dim === undefined) { 9688 // then return the whole rendered position 9689 return ppos; 9690 } else { 9691 // then return the specified dimension 9692 return ppos[dim]; 9693 } 9694 } 9695 } else if (!setting) { 9696 return undefined; // for empty collection case 9697 } 9698 9699 return this; // chaining 9700 } 9701 }; // aliases 9702 9703 fn$2.modelPosition = fn$2.point = fn$2.position; 9704 fn$2.modelPositions = fn$2.points = fn$2.positions; 9705 fn$2.renderedPoint = fn$2.renderedPosition; 9706 fn$2.relativePoint = fn$2.relativePosition; 9707 var position = elesfn$j; 9708 9709 var fn$3, elesfn$k; 9710 fn$3 = elesfn$k = {}; 9711 9712 elesfn$k.renderedBoundingBox = function (options) { 9713 var bb = this.boundingBox(options); 9714 var cy = this.cy(); 9715 var zoom = cy.zoom(); 9716 var pan = cy.pan(); 9717 var x1 = bb.x1 * zoom + pan.x; 9718 var x2 = bb.x2 * zoom + pan.x; 9719 var y1 = bb.y1 * zoom + pan.y; 9720 var y2 = bb.y2 * zoom + pan.y; 9721 return { 9722 x1: x1, 9723 x2: x2, 9724 y1: y1, 9725 y2: y2, 9726 w: x2 - x1, 9727 h: y2 - y1 9728 }; 9729 }; 9730 9731 elesfn$k.dirtyCompoundBoundsCache = function () { 9732 var cy = this.cy(); 9733 9734 if (!cy.styleEnabled() || !cy.hasCompoundNodes()) { 9735 return this; 9736 } 9737 9738 this.forEachUp(function (ele) { 9739 if (ele.isParent()) { 9740 var _p = ele._private; 9741 _p.compoundBoundsClean = false; 9742 _p.bbCache = null; 9743 ele.emitAndNotify('bounds'); 9744 } 9745 }); 9746 return this; 9747 }; 9748 9749 elesfn$k.updateCompoundBounds = function () { 9750 var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 9751 var cy = this.cy(); // not possible to do on non-compound graphs or with the style disabled 9752 9753 if (!cy.styleEnabled() || !cy.hasCompoundNodes()) { 9754 return this; 9755 } // save cycles when batching -- but bounds will be stale (or not exist yet) 9756 9757 9758 if (!force && cy.batching()) { 9759 return this; 9760 } 9761 9762 function update(parent) { 9763 if (!parent.isParent()) { 9764 return; 9765 } 9766 9767 var _p = parent._private; 9768 var children = parent.children(); 9769 var includeLabels = parent.pstyle('compound-sizing-wrt-labels').value === 'include'; 9770 var min = { 9771 width: { 9772 val: parent.pstyle('min-width').pfValue, 9773 left: parent.pstyle('min-width-bias-left'), 9774 right: parent.pstyle('min-width-bias-right') 9775 }, 9776 height: { 9777 val: parent.pstyle('min-height').pfValue, 9778 top: parent.pstyle('min-height-bias-top'), 9779 bottom: parent.pstyle('min-height-bias-bottom') 9780 } 9781 }; 9782 var bb = children.boundingBox({ 9783 includeLabels: includeLabels, 9784 includeOverlays: false, 9785 // updating the compound bounds happens outside of the regular 9786 // cache cycle (i.e. before fired events) 9787 useCache: false 9788 }); 9789 var pos = _p.position; // if children take up zero area then keep position and fall back on stylesheet w/h 9790 9791 if (bb.w === 0 || bb.h === 0) { 9792 bb = { 9793 w: parent.pstyle('width').pfValue, 9794 h: parent.pstyle('height').pfValue 9795 }; 9796 bb.x1 = pos.x - bb.w / 2; 9797 bb.x2 = pos.x + bb.w / 2; 9798 bb.y1 = pos.y - bb.h / 2; 9799 bb.y2 = pos.y + bb.h / 2; 9800 } 9801 9802 function computeBiasValues(propDiff, propBias, propBiasComplement) { 9803 var biasDiff = 0; 9804 var biasComplementDiff = 0; 9805 var biasTotal = propBias + propBiasComplement; 9806 9807 if (propDiff > 0 && biasTotal > 0) { 9808 biasDiff = propBias / biasTotal * propDiff; 9809 biasComplementDiff = propBiasComplement / biasTotal * propDiff; 9810 } 9811 9812 return { 9813 biasDiff: biasDiff, 9814 biasComplementDiff: biasComplementDiff 9815 }; 9816 } 9817 9818 function computePaddingValues(width, height, paddingObject, relativeTo) { 9819 // Assuming percentage is number from 0 to 1 9820 if (paddingObject.units === '%') { 9821 switch (relativeTo) { 9822 case 'width': 9823 return width > 0 ? paddingObject.pfValue * width : 0; 9824 9825 case 'height': 9826 return height > 0 ? paddingObject.pfValue * height : 0; 9827 9828 case 'average': 9829 return width > 0 && height > 0 ? paddingObject.pfValue * (width + height) / 2 : 0; 9830 9831 case 'min': 9832 return width > 0 && height > 0 ? width > height ? paddingObject.pfValue * height : paddingObject.pfValue * width : 0; 9833 9834 case 'max': 9835 return width > 0 && height > 0 ? width > height ? paddingObject.pfValue * width : paddingObject.pfValue * height : 0; 9836 9837 default: 9838 return 0; 9839 } 9840 } else if (paddingObject.units === 'px') { 9841 return paddingObject.pfValue; 9842 } else { 9843 return 0; 9844 } 9845 } 9846 9847 var leftVal = min.width.left.value; 9848 9849 if (min.width.left.units === 'px' && min.width.val > 0) { 9850 leftVal = leftVal * 100 / min.width.val; 9851 } 9852 9853 var rightVal = min.width.right.value; 9854 9855 if (min.width.right.units === 'px' && min.width.val > 0) { 9856 rightVal = rightVal * 100 / min.width.val; 9857 } 9858 9859 var topVal = min.height.top.value; 9860 9861 if (min.height.top.units === 'px' && min.height.val > 0) { 9862 topVal = topVal * 100 / min.height.val; 9863 } 9864 9865 var bottomVal = min.height.bottom.value; 9866 9867 if (min.height.bottom.units === 'px' && min.height.val > 0) { 9868 bottomVal = bottomVal * 100 / min.height.val; 9869 } 9870 9871 var widthBiasDiffs = computeBiasValues(min.width.val - bb.w, leftVal, rightVal); 9872 var diffLeft = widthBiasDiffs.biasDiff; 9873 var diffRight = widthBiasDiffs.biasComplementDiff; 9874 var heightBiasDiffs = computeBiasValues(min.height.val - bb.h, topVal, bottomVal); 9875 var diffTop = heightBiasDiffs.biasDiff; 9876 var diffBottom = heightBiasDiffs.biasComplementDiff; 9877 _p.autoPadding = computePaddingValues(bb.w, bb.h, parent.pstyle('padding'), parent.pstyle('padding-relative-to').value); 9878 _p.autoWidth = Math.max(bb.w, min.width.val); 9879 pos.x = (-diffLeft + bb.x1 + bb.x2 + diffRight) / 2; 9880 _p.autoHeight = Math.max(bb.h, min.height.val); 9881 pos.y = (-diffTop + bb.y1 + bb.y2 + diffBottom) / 2; 9882 } 9883 9884 for (var i = 0; i < this.length; i++) { 9885 var ele = this[i]; 9886 var _p = ele._private; 9887 9888 if (!_p.compoundBoundsClean) { 9889 update(ele); 9890 9891 if (!cy.batching()) { 9892 _p.compoundBoundsClean = true; 9893 } 9894 } 9895 } 9896 9897 return this; 9898 }; 9899 9900 var noninf = function noninf(x) { 9901 if (x === Infinity || x === -Infinity) { 9902 return 0; 9903 } 9904 9905 return x; 9906 }; 9907 9908 var updateBounds = function updateBounds(b, x1, y1, x2, y2) { 9909 // don't update with zero area boxes 9910 if (x2 - x1 === 0 || y2 - y1 === 0) { 9911 return; 9912 } // don't update with null dim 9913 9914 9915 if (x1 == null || y1 == null || x2 == null || y2 == null) { 9916 return; 9917 } 9918 9919 b.x1 = x1 < b.x1 ? x1 : b.x1; 9920 b.x2 = x2 > b.x2 ? x2 : b.x2; 9921 b.y1 = y1 < b.y1 ? y1 : b.y1; 9922 b.y2 = y2 > b.y2 ? y2 : b.y2; 9923 b.w = b.x2 - b.x1; 9924 b.h = b.y2 - b.y1; 9925 }; 9926 9927 var updateBoundsFromBox = function updateBoundsFromBox(b, b2) { 9928 if (b2 == null) { 9929 return b; 9930 } 9931 9932 return updateBounds(b, b2.x1, b2.y1, b2.x2, b2.y2); 9933 }; 9934 9935 var prefixedProperty = function prefixedProperty(obj, field, prefix) { 9936 return getPrefixedProperty(obj, field, prefix); 9937 }; 9938 9939 var updateBoundsFromArrow = function updateBoundsFromArrow(bounds, ele, prefix) { 9940 if (ele.cy().headless()) { 9941 return; 9942 } 9943 9944 var _p = ele._private; 9945 var rstyle = _p.rstyle; 9946 var halfArW = rstyle.arrowWidth / 2; 9947 var arrowType = ele.pstyle(prefix + '-arrow-shape').value; 9948 var x; 9949 var y; 9950 9951 if (arrowType !== 'none') { 9952 if (prefix === 'source') { 9953 x = rstyle.srcX; 9954 y = rstyle.srcY; 9955 } else if (prefix === 'target') { 9956 x = rstyle.tgtX; 9957 y = rstyle.tgtY; 9958 } else { 9959 x = rstyle.midX; 9960 y = rstyle.midY; 9961 } // always store the individual arrow bounds 9962 9963 9964 var bbs = _p.arrowBounds = _p.arrowBounds || {}; 9965 var bb = bbs[prefix] = bbs[prefix] || {}; 9966 bb.x1 = x - halfArW; 9967 bb.y1 = y - halfArW; 9968 bb.x2 = x + halfArW; 9969 bb.y2 = y + halfArW; 9970 bb.w = bb.x2 - bb.x1; 9971 bb.h = bb.y2 - bb.y1; 9972 expandBoundingBox(bb, 1); 9973 updateBounds(bounds, bb.x1, bb.y1, bb.x2, bb.y2); 9974 } 9975 }; 9976 9977 var updateBoundsFromLabel = function updateBoundsFromLabel(bounds, ele, prefix) { 9978 if (ele.cy().headless()) { 9979 return; 9980 } 9981 9982 var prefixDash; 9983 9984 if (prefix) { 9985 prefixDash = prefix + '-'; 9986 } else { 9987 prefixDash = ''; 9988 } 9989 9990 var _p = ele._private; 9991 var rstyle = _p.rstyle; 9992 var label = ele.pstyle(prefixDash + 'label').strValue; 9993 9994 if (label) { 9995 var halign = ele.pstyle('text-halign'); 9996 var valign = ele.pstyle('text-valign'); 9997 var labelWidth = prefixedProperty(rstyle, 'labelWidth', prefix); 9998 var labelHeight = prefixedProperty(rstyle, 'labelHeight', prefix); 9999 var labelX = prefixedProperty(rstyle, 'labelX', prefix); 10000 var labelY = prefixedProperty(rstyle, 'labelY', prefix); 10001 var marginX = ele.pstyle(prefixDash + 'text-margin-x').pfValue; 10002 var marginY = ele.pstyle(prefixDash + 'text-margin-y').pfValue; 10003 var isEdge = ele.isEdge(); 10004 var rotation = ele.pstyle(prefixDash + 'text-rotation'); 10005 var outlineWidth = ele.pstyle('text-outline-width').pfValue; 10006 var borderWidth = ele.pstyle('text-border-width').pfValue; 10007 var halfBorderWidth = borderWidth / 2; 10008 var padding = ele.pstyle('text-background-padding').pfValue; 10009 var lh = labelHeight; 10010 var lw = labelWidth; 10011 var lw_2 = lw / 2; 10012 var lh_2 = lh / 2; 10013 var lx1, lx2, ly1, ly2; 10014 10015 if (isEdge) { 10016 lx1 = labelX - lw_2; 10017 lx2 = labelX + lw_2; 10018 ly1 = labelY - lh_2; 10019 ly2 = labelY + lh_2; 10020 } else { 10021 switch (halign.value) { 10022 case 'left': 10023 lx1 = labelX - lw; 10024 lx2 = labelX; 10025 break; 10026 10027 case 'center': 10028 lx1 = labelX - lw_2; 10029 lx2 = labelX + lw_2; 10030 break; 10031 10032 case 'right': 10033 lx1 = labelX; 10034 lx2 = labelX + lw; 10035 break; 10036 } 10037 10038 switch (valign.value) { 10039 case 'top': 10040 ly1 = labelY - lh; 10041 ly2 = labelY; 10042 break; 10043 10044 case 'center': 10045 ly1 = labelY - lh_2; 10046 ly2 = labelY + lh_2; 10047 break; 10048 10049 case 'bottom': 10050 ly1 = labelY; 10051 ly2 = labelY + lh; 10052 break; 10053 } 10054 } // shift by margin and expand by outline and border 10055 10056 10057 lx1 += marginX - Math.max(outlineWidth, halfBorderWidth) - padding; 10058 lx2 += marginX + Math.max(outlineWidth, halfBorderWidth) + padding; 10059 ly1 += marginY - Math.max(outlineWidth, halfBorderWidth) - padding; 10060 ly2 += marginY + Math.max(outlineWidth, halfBorderWidth) + padding; // always store the unrotated label bounds separately 10061 10062 var bbPrefix = prefix || 'main'; 10063 var bbs = _p.labelBounds; 10064 var bb = bbs[bbPrefix] = bbs[bbPrefix] || {}; 10065 bb.x1 = lx1; 10066 bb.y1 = ly1; 10067 bb.x2 = lx2; 10068 bb.y2 = ly2; 10069 bb.w = lx2 - lx1; 10070 bb.h = ly2 - ly1; 10071 expandBoundingBox(bb, 1); // expand to work around browser dimension inaccuracies 10072 10073 var isAutorotate = isEdge && rotation.strValue === 'autorotate'; 10074 var isPfValue = rotation.pfValue != null && rotation.pfValue !== 0; 10075 10076 if (isAutorotate || isPfValue) { 10077 var theta = isAutorotate ? prefixedProperty(_p.rstyle, 'labelAngle', prefix) : rotation.pfValue; 10078 var cos = Math.cos(theta); 10079 var sin = Math.sin(theta); // rotation point (default value for center-center) 10080 10081 var xo = (lx1 + lx2) / 2; 10082 var yo = (ly1 + ly2) / 2; 10083 10084 if (!isEdge) { 10085 switch (halign.value) { 10086 case 'left': 10087 xo = lx2; 10088 break; 10089 10090 case 'right': 10091 xo = lx1; 10092 break; 10093 } 10094 10095 switch (valign.value) { 10096 case 'top': 10097 yo = ly2; 10098 break; 10099 10100 case 'bottom': 10101 yo = ly1; 10102 break; 10103 } 10104 } 10105 10106 var rotate = function rotate(x, y) { 10107 x = x - xo; 10108 y = y - yo; 10109 return { 10110 x: x * cos - y * sin + xo, 10111 y: x * sin + y * cos + yo 10112 }; 10113 }; 10114 10115 var px1y1 = rotate(lx1, ly1); 10116 var px1y2 = rotate(lx1, ly2); 10117 var px2y1 = rotate(lx2, ly1); 10118 var px2y2 = rotate(lx2, ly2); 10119 lx1 = Math.min(px1y1.x, px1y2.x, px2y1.x, px2y2.x); 10120 lx2 = Math.max(px1y1.x, px1y2.x, px2y1.x, px2y2.x); 10121 ly1 = Math.min(px1y1.y, px1y2.y, px2y1.y, px2y2.y); 10122 ly2 = Math.max(px1y1.y, px1y2.y, px2y1.y, px2y2.y); 10123 } 10124 10125 var bbPrefixRot = bbPrefix + 'Rot'; 10126 var bbRot = bbs[bbPrefixRot] = bbs[bbPrefixRot] || {}; 10127 bbRot.x1 = lx1; 10128 bbRot.y1 = ly1; 10129 bbRot.x2 = lx2; 10130 bbRot.y2 = ly2; 10131 bbRot.w = lx2 - lx1; 10132 bbRot.h = ly2 - ly1; 10133 updateBounds(bounds, lx1, ly1, lx2, ly2); 10134 updateBounds(_p.labelBounds.all, lx1, ly1, lx2, ly2); 10135 } 10136 10137 return bounds; 10138 }; // get the bounding box of the elements (in raw model position) 10139 10140 10141 var boundingBoxImpl = function boundingBoxImpl(ele, options) { 10142 var cy = ele._private.cy; 10143 var styleEnabled = cy.styleEnabled(); 10144 var headless = cy.headless(); 10145 var bounds = makeBoundingBox(); 10146 var _p = ele._private; 10147 var isNode = ele.isNode(); 10148 var isEdge = ele.isEdge(); 10149 var ex1, ex2, ey1, ey2; // extrema of body / lines 10150 10151 var x, y; // node pos 10152 10153 var rstyle = _p.rstyle; 10154 var manualExpansion = isNode && styleEnabled ? ele.pstyle('bounds-expansion').pfValue : [0]; // must use `display` prop only, as reading `compound.width()` causes recursion 10155 // (other factors like width values will be considered later in this function anyway) 10156 10157 var isDisplayed = function isDisplayed(ele) { 10158 return ele.pstyle('display').value !== 'none'; 10159 }; 10160 10161 var displayed = !styleEnabled || isDisplayed(ele) // must take into account connected nodes b/c of implicit edge hiding on display:none node 10162 && (!isEdge || isDisplayed(ele.source()) && isDisplayed(ele.target())); 10163 10164 if (displayed) { 10165 // displayed suffices, since we will find zero area eles anyway 10166 var overlayOpacity = 0; 10167 var overlayPadding = 0; 10168 10169 if (styleEnabled && options.includeOverlays) { 10170 overlayOpacity = ele.pstyle('overlay-opacity').value; 10171 10172 if (overlayOpacity !== 0) { 10173 overlayPadding = ele.pstyle('overlay-padding').value; 10174 } 10175 } 10176 10177 var w = 0; 10178 var wHalf = 0; 10179 10180 if (styleEnabled) { 10181 w = ele.pstyle('width').pfValue; 10182 wHalf = w / 2; 10183 } 10184 10185 if (isNode && options.includeNodes) { 10186 var pos = ele.position(); 10187 x = pos.x; 10188 y = pos.y; 10189 10190 var _w = ele.outerWidth(); 10191 10192 var halfW = _w / 2; 10193 var h = ele.outerHeight(); 10194 var halfH = h / 2; // handle node dimensions 10195 ///////////////////////// 10196 10197 ex1 = x - halfW; 10198 ex2 = x + halfW; 10199 ey1 = y - halfH; 10200 ey2 = y + halfH; 10201 updateBounds(bounds, ex1, ey1, ex2, ey2); 10202 } else if (isEdge && options.includeEdges) { 10203 if (styleEnabled && !headless) { 10204 var curveStyle = ele.pstyle('curve-style').strValue; // handle edge dimensions (rough box estimate) 10205 ////////////////////////////////////////////// 10206 10207 ex1 = Math.min(rstyle.srcX, rstyle.midX, rstyle.tgtX); 10208 ex2 = Math.max(rstyle.srcX, rstyle.midX, rstyle.tgtX); 10209 ey1 = Math.min(rstyle.srcY, rstyle.midY, rstyle.tgtY); 10210 ey2 = Math.max(rstyle.srcY, rstyle.midY, rstyle.tgtY); // take into account edge width 10211 10212 ex1 -= wHalf; 10213 ex2 += wHalf; 10214 ey1 -= wHalf; 10215 ey2 += wHalf; 10216 updateBounds(bounds, ex1, ey1, ex2, ey2); // precise edges 10217 //////////////// 10218 10219 if (curveStyle === 'haystack') { 10220 var hpts = rstyle.haystackPts; 10221 10222 if (hpts && hpts.length === 2) { 10223 ex1 = hpts[0].x; 10224 ey1 = hpts[0].y; 10225 ex2 = hpts[1].x; 10226 ey2 = hpts[1].y; 10227 10228 if (ex1 > ex2) { 10229 var temp = ex1; 10230 ex1 = ex2; 10231 ex2 = temp; 10232 } 10233 10234 if (ey1 > ey2) { 10235 var _temp = ey1; 10236 ey1 = ey2; 10237 ey2 = _temp; 10238 } 10239 10240 updateBounds(bounds, ex1 - wHalf, ey1 - wHalf, ex2 + wHalf, ey2 + wHalf); 10241 } 10242 } else if (curveStyle === 'bezier' || curveStyle === 'unbundled-bezier' || curveStyle === 'segments' || curveStyle === 'taxi') { 10243 var pts; 10244 10245 switch (curveStyle) { 10246 case 'bezier': 10247 case 'unbundled-bezier': 10248 pts = rstyle.bezierPts; 10249 break; 10250 10251 case 'segments': 10252 case 'taxi': 10253 pts = rstyle.linePts; 10254 break; 10255 } 10256 10257 if (pts != null) { 10258 for (var j = 0; j < pts.length; j++) { 10259 var pt = pts[j]; 10260 ex1 = pt.x - wHalf; 10261 ex2 = pt.x + wHalf; 10262 ey1 = pt.y - wHalf; 10263 ey2 = pt.y + wHalf; 10264 updateBounds(bounds, ex1, ey1, ex2, ey2); 10265 } 10266 } 10267 } // bezier-like or segment-like edge 10268 10269 } else { 10270 // headless or style disabled 10271 // fallback on source and target positions 10272 ////////////////////////////////////////// 10273 var n1 = ele.source(); 10274 var n1pos = n1.position(); 10275 var n2 = ele.target(); 10276 var n2pos = n2.position(); 10277 ex1 = n1pos.x; 10278 ex2 = n2pos.x; 10279 ey1 = n1pos.y; 10280 ey2 = n2pos.y; 10281 10282 if (ex1 > ex2) { 10283 var _temp2 = ex1; 10284 ex1 = ex2; 10285 ex2 = _temp2; 10286 } 10287 10288 if (ey1 > ey2) { 10289 var _temp3 = ey1; 10290 ey1 = ey2; 10291 ey2 = _temp3; 10292 } // take into account edge width 10293 10294 10295 ex1 -= wHalf; 10296 ex2 += wHalf; 10297 ey1 -= wHalf; 10298 ey2 += wHalf; 10299 updateBounds(bounds, ex1, ey1, ex2, ey2); 10300 } // headless or style disabled 10301 10302 } // edges 10303 // handle edge arrow size 10304 ///////////////////////// 10305 10306 10307 if (styleEnabled && options.includeEdges && isEdge) { 10308 updateBoundsFromArrow(bounds, ele, 'mid-source'); 10309 updateBoundsFromArrow(bounds, ele, 'mid-target'); 10310 updateBoundsFromArrow(bounds, ele, 'source'); 10311 updateBoundsFromArrow(bounds, ele, 'target'); 10312 } // ghost 10313 //////// 10314 10315 10316 if (styleEnabled) { 10317 var ghost = ele.pstyle('ghost').value === 'yes'; 10318 10319 if (ghost) { 10320 var gx = ele.pstyle('ghost-offset-x').pfValue; 10321 var gy = ele.pstyle('ghost-offset-y').pfValue; 10322 updateBounds(bounds, bounds.x1 + gx, bounds.y1 + gy, bounds.x2 + gx, bounds.y2 + gy); 10323 } 10324 } // always store the body bounds separately from the labels 10325 10326 10327 var bbBody = _p.bodyBounds = _p.bodyBounds || {}; 10328 assignBoundingBox(bbBody, bounds); 10329 expandBoundingBoxSides(bbBody, manualExpansion); 10330 expandBoundingBox(bbBody, 1); // expand to work around browser dimension inaccuracies 10331 // overlay 10332 ////////// 10333 10334 if (styleEnabled) { 10335 ex1 = bounds.x1; 10336 ex2 = bounds.x2; 10337 ey1 = bounds.y1; 10338 ey2 = bounds.y2; 10339 updateBounds(bounds, ex1 - overlayPadding, ey1 - overlayPadding, ex2 + overlayPadding, ey2 + overlayPadding); 10340 } // always store the body bounds separately from the labels 10341 10342 10343 var bbOverlay = _p.overlayBounds = _p.overlayBounds || {}; 10344 assignBoundingBox(bbOverlay, bounds); 10345 expandBoundingBoxSides(bbOverlay, manualExpansion); 10346 expandBoundingBox(bbOverlay, 1); // expand to work around browser dimension inaccuracies 10347 // handle label dimensions 10348 ////////////////////////// 10349 10350 var bbLabels = _p.labelBounds = _p.labelBounds || {}; 10351 10352 if (bbLabels.all != null) { 10353 clearBoundingBox(bbLabels.all); 10354 } else { 10355 bbLabels.all = makeBoundingBox(); 10356 } 10357 10358 if (styleEnabled && options.includeLabels) { 10359 if (options.includeMainLabels) { 10360 updateBoundsFromLabel(bounds, ele, null); 10361 } 10362 10363 if (isEdge) { 10364 if (options.includeSourceLabels) { 10365 updateBoundsFromLabel(bounds, ele, 'source'); 10366 } 10367 10368 if (options.includeTargetLabels) { 10369 updateBoundsFromLabel(bounds, ele, 'target'); 10370 } 10371 } 10372 } // style enabled for labels 10373 10374 } // if displayed 10375 10376 10377 bounds.x1 = noninf(bounds.x1); 10378 bounds.y1 = noninf(bounds.y1); 10379 bounds.x2 = noninf(bounds.x2); 10380 bounds.y2 = noninf(bounds.y2); 10381 bounds.w = noninf(bounds.x2 - bounds.x1); 10382 bounds.h = noninf(bounds.y2 - bounds.y1); 10383 10384 if (bounds.w > 0 && bounds.h > 0 && displayed) { 10385 expandBoundingBoxSides(bounds, manualExpansion); // expand bounds by 1 because antialiasing can increase the visual/effective size by 1 on all sides 10386 10387 expandBoundingBox(bounds, 1); 10388 } 10389 10390 return bounds; 10391 }; 10392 10393 var getKey = function getKey(opts) { 10394 var i = 0; 10395 10396 var tf = function tf(val) { 10397 return (val ? 1 : 0) << i++; 10398 }; 10399 10400 var key = 0; 10401 key += tf(opts.incudeNodes); 10402 key += tf(opts.includeEdges); 10403 key += tf(opts.includeLabels); 10404 key += tf(opts.includeMainLabels); 10405 key += tf(opts.includeSourceLabels); 10406 key += tf(opts.includeTargetLabels); 10407 key += tf(opts.includeOverlays); 10408 return key; 10409 }; 10410 10411 var getBoundingBoxPosKey = function getBoundingBoxPosKey(ele) { 10412 if (ele.isEdge()) { 10413 var p1 = ele.source().position(); 10414 var p2 = ele.target().position(); 10415 10416 var r = function r(x) { 10417 return Math.round(x); 10418 }; 10419 10420 return hashIntsArray([r(p1.x), r(p1.y), r(p2.x), r(p2.y)]); 10421 } else { 10422 return 0; 10423 } 10424 }; 10425 10426 var cachedBoundingBoxImpl = function cachedBoundingBoxImpl(ele, opts) { 10427 var _p = ele._private; 10428 var bb; 10429 var isEdge = ele.isEdge(); 10430 var key = opts == null ? defBbOptsKey : getKey(opts); 10431 var usingDefOpts = key === defBbOptsKey; 10432 var currPosKey = getBoundingBoxPosKey(ele); 10433 var isPosKeySame = _p.bbCachePosKey === currPosKey; 10434 var useCache = opts.useCache && isPosKeySame; 10435 10436 var isDirty = function isDirty(ele) { 10437 return ele._private.bbCache == null; 10438 }; 10439 10440 var needRecalc = !useCache || isDirty(ele) || isEdge && isDirty(ele.source()) || isDirty(ele.target()); 10441 10442 if (needRecalc) { 10443 if (!isPosKeySame) { 10444 ele.recalculateRenderedStyle(); 10445 } 10446 10447 bb = boundingBoxImpl(ele, defBbOpts); 10448 _p.bbCache = bb; 10449 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; 10450 _p.bbCachePosKey = currPosKey; 10451 } else { 10452 bb = _p.bbCache; 10453 } 10454 10455 if (!needRecalc && (_p.bbCacheShift.x !== 0 || _p.bbCacheShift.y !== 0)) { 10456 var shift = assignShiftToBoundingBox; 10457 var delta = _p.bbCacheShift; 10458 10459 var safeShift = function safeShift(bb, delta) { 10460 if (bb != null) { 10461 shift(bb, delta); 10462 } 10463 }; 10464 10465 shift(bb, delta); 10466 var bodyBounds = _p.bodyBounds, 10467 overlayBounds = _p.overlayBounds, 10468 labelBounds = _p.labelBounds, 10469 arrowBounds = _p.arrowBounds; 10470 safeShift(bodyBounds, delta); 10471 safeShift(overlayBounds, delta); 10472 10473 if (arrowBounds != null) { 10474 safeShift(arrowBounds.source, delta); 10475 safeShift(arrowBounds.target, delta); 10476 safeShift(arrowBounds['mid-source'], delta); 10477 safeShift(arrowBounds['mid-target'], delta); 10478 } 10479 10480 if (labelBounds != null) { 10481 safeShift(labelBounds.main, delta); 10482 safeShift(labelBounds.all, delta); 10483 safeShift(labelBounds.source, delta); 10484 safeShift(labelBounds.target, delta); 10485 } 10486 } // always reset the shift, because we either applied the shift or cleared it by doing a fresh recalc 10487 10488 10489 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; // not using def opts => need to build up bb from combination of sub bbs 10490 10491 if (!usingDefOpts) { 10492 var isNode = ele.isNode(); 10493 bb = makeBoundingBox(); 10494 10495 if (opts.includeNodes && isNode || opts.includeEdges && !isNode) { 10496 if (opts.includeOverlays) { 10497 updateBoundsFromBox(bb, _p.overlayBounds); 10498 } else { 10499 updateBoundsFromBox(bb, _p.bodyBounds); 10500 } 10501 } 10502 10503 if (opts.includeLabels) { 10504 if (opts.includeMainLabels && (!isEdge || opts.includeSourceLabels && opts.includeTargetLabels)) { 10505 updateBoundsFromBox(bb, _p.labelBounds.all); 10506 } else { 10507 if (opts.includeMainLabels) { 10508 updateBoundsFromBox(bb, _p.labelBounds.mainRot); 10509 } 10510 10511 if (opts.includeSourceLabels) { 10512 updateBoundsFromBox(bb, _p.labelBounds.sourceRot); 10513 } 10514 10515 if (opts.includeTargetLabels) { 10516 updateBoundsFromBox(bb, _p.labelBounds.targetRot); 10517 } 10518 } 10519 } 10520 10521 bb.w = bb.x2 - bb.x1; 10522 bb.h = bb.y2 - bb.y1; 10523 } 10524 10525 return bb; 10526 }; 10527 10528 var defBbOpts = { 10529 includeNodes: true, 10530 includeEdges: true, 10531 includeLabels: true, 10532 includeMainLabels: true, 10533 includeSourceLabels: true, 10534 includeTargetLabels: true, 10535 includeOverlays: true, 10536 useCache: true 10537 }; 10538 var defBbOptsKey = getKey(defBbOpts); 10539 var filledBbOpts = defaults(defBbOpts); 10540 10541 elesfn$k.boundingBox = function (options) { 10542 var bounds; // the main usecase is ele.boundingBox() for a single element with no/def options 10543 // specified s.t. the cache is used, so check for this case to make it faster by 10544 // avoiding the overhead of the rest of the function 10545 10546 if (this.length === 1 && this[0]._private.bbCache != null && (options === undefined || options.useCache === undefined || options.useCache === true)) { 10547 if (options === undefined) { 10548 options = defBbOpts; 10549 } else { 10550 options = filledBbOpts(options); 10551 } 10552 10553 bounds = cachedBoundingBoxImpl(this[0], options); 10554 } else { 10555 bounds = makeBoundingBox(); 10556 options = options || defBbOpts; 10557 var opts = filledBbOpts(options); 10558 var eles = this; 10559 var cy = eles.cy(); 10560 var styleEnabled = cy.styleEnabled(); 10561 10562 if (styleEnabled) { 10563 for (var i = 0; i < eles.length; i++) { 10564 var ele = eles[i]; 10565 var _p = ele._private; 10566 var currPosKey = getBoundingBoxPosKey(ele); 10567 var isPosKeySame = _p.bbCachePosKey === currPosKey; 10568 var useCache = opts.useCache && isPosKeySame; 10569 ele.recalculateRenderedStyle(useCache); 10570 } 10571 } 10572 10573 this.updateCompoundBounds(); 10574 10575 for (var _i = 0; _i < eles.length; _i++) { 10576 var _ele = eles[_i]; 10577 updateBoundsFromBox(bounds, cachedBoundingBoxImpl(_ele, opts)); 10578 } 10579 } 10580 10581 bounds.x1 = noninf(bounds.x1); 10582 bounds.y1 = noninf(bounds.y1); 10583 bounds.x2 = noninf(bounds.x2); 10584 bounds.y2 = noninf(bounds.y2); 10585 bounds.w = noninf(bounds.x2 - bounds.x1); 10586 bounds.h = noninf(bounds.y2 - bounds.y1); 10587 return bounds; 10588 }; 10589 10590 elesfn$k.dirtyBoundingBoxCache = function () { 10591 for (var i = 0; i < this.length; i++) { 10592 var _p = this[i]._private; 10593 _p.bbCache = null; 10594 _p.bbCacheShift.x = _p.bbCacheShift.y = 0; 10595 _p.bbCachePosKey = null; 10596 _p.bodyBounds = null; 10597 _p.overlayBounds = null; 10598 _p.labelBounds.all = null; 10599 _p.labelBounds.source = null; 10600 _p.labelBounds.target = null; 10601 _p.labelBounds.main = null; 10602 _p.labelBounds.sourceRot = null; 10603 _p.labelBounds.targetRot = null; 10604 _p.labelBounds.mainRot = null; 10605 _p.arrowBounds.source = null; 10606 _p.arrowBounds.target = null; 10607 _p.arrowBounds['mid-source'] = null; 10608 _p.arrowBounds['mid-target'] = null; 10609 } 10610 10611 this.emitAndNotify('bounds'); 10612 return this; 10613 }; 10614 10615 elesfn$k.shiftCachedBoundingBox = function (delta) { 10616 for (var i = 0; i < this.length; i++) { 10617 var ele = this[i]; 10618 var _p = ele._private; 10619 var bb = _p.bbCache; 10620 10621 if (bb != null) { 10622 _p.bbCacheShift.x += delta.x; 10623 _p.bbCacheShift.y += delta.y; 10624 } 10625 } 10626 10627 this.emitAndNotify('bounds'); 10628 return this; 10629 }; // private helper to get bounding box for custom node positions 10630 // - good for perf in certain cases but currently requires dirtying the rendered style 10631 // - would be better to not modify the nodes but the nodes are read directly everywhere in the renderer... 10632 // - try to use for only things like discrete layouts where the node position would change anyway 10633 10634 10635 elesfn$k.boundingBoxAt = function (fn) { 10636 var nodes = this.nodes(); 10637 var cy = this.cy(); 10638 var hasCompoundNodes = cy.hasCompoundNodes(); 10639 10640 if (hasCompoundNodes) { 10641 nodes = nodes.filter(function (node) { 10642 return !node.isParent(); 10643 }); 10644 } 10645 10646 if (plainObject(fn)) { 10647 var obj = fn; 10648 10649 fn = function fn() { 10650 return obj; 10651 }; 10652 } 10653 10654 var storeOldPos = function storeOldPos(node, i) { 10655 return node._private.bbAtOldPos = fn(node, i); 10656 }; 10657 10658 var getOldPos = function getOldPos(node) { 10659 return node._private.bbAtOldPos; 10660 }; 10661 10662 cy.startBatch(); 10663 nodes.forEach(storeOldPos).silentPositions(fn); 10664 10665 if (hasCompoundNodes) { 10666 this.updateCompoundBounds(true); // force update b/c we're inside a batch cycle 10667 } 10668 10669 var bb = copyBoundingBox(this.boundingBox({ 10670 useCache: false 10671 })); 10672 nodes.silentPositions(getOldPos); 10673 cy.endBatch(); 10674 return bb; 10675 }; 10676 10677 fn$3.boundingbox = fn$3.bb = fn$3.boundingBox; 10678 fn$3.renderedBoundingbox = fn$3.renderedBoundingBox; 10679 var bounds = elesfn$k; 10680 10681 var fn$4, elesfn$l; 10682 fn$4 = elesfn$l = {}; 10683 10684 var defineDimFns = function defineDimFns(opts) { 10685 opts.uppercaseName = capitalize(opts.name); 10686 opts.autoName = 'auto' + opts.uppercaseName; 10687 opts.labelName = 'label' + opts.uppercaseName; 10688 opts.outerName = 'outer' + opts.uppercaseName; 10689 opts.uppercaseOuterName = capitalize(opts.outerName); 10690 10691 fn$4[opts.name] = function dimImpl() { 10692 var ele = this[0]; 10693 var _p = ele._private; 10694 var cy = _p.cy; 10695 var styleEnabled = cy._private.styleEnabled; 10696 10697 if (ele) { 10698 if (styleEnabled) { 10699 if (ele.isParent()) { 10700 ele.updateCompoundBounds(); 10701 return _p[opts.autoName] || 0; 10702 } 10703 10704 var d = ele.pstyle(opts.name); 10705 10706 switch (d.strValue) { 10707 case 'label': 10708 ele.recalculateRenderedStyle(); 10709 return _p.rstyle[opts.labelName] || 0; 10710 10711 default: 10712 return d.pfValue; 10713 } 10714 } else { 10715 return 1; 10716 } 10717 } 10718 }; 10719 10720 fn$4['outer' + opts.uppercaseName] = function outerDimImpl() { 10721 var ele = this[0]; 10722 var _p = ele._private; 10723 var cy = _p.cy; 10724 var styleEnabled = cy._private.styleEnabled; 10725 10726 if (ele) { 10727 if (styleEnabled) { 10728 var dim = ele[opts.name](); 10729 var border = ele.pstyle('border-width').pfValue; // n.b. 1/2 each side 10730 10731 var padding = 2 * ele.padding(); 10732 return dim + border + padding; 10733 } else { 10734 return 1; 10735 } 10736 } 10737 }; 10738 10739 fn$4['rendered' + opts.uppercaseName] = function renderedDimImpl() { 10740 var ele = this[0]; 10741 10742 if (ele) { 10743 var d = ele[opts.name](); 10744 return d * this.cy().zoom(); 10745 } 10746 }; 10747 10748 fn$4['rendered' + opts.uppercaseOuterName] = function renderedOuterDimImpl() { 10749 var ele = this[0]; 10750 10751 if (ele) { 10752 var od = ele[opts.outerName](); 10753 return od * this.cy().zoom(); 10754 } 10755 }; 10756 }; 10757 10758 defineDimFns({ 10759 name: 'width' 10760 }); 10761 defineDimFns({ 10762 name: 'height' 10763 }); 10764 10765 elesfn$l.padding = function () { 10766 var ele = this[0]; 10767 var _p = ele._private; 10768 10769 if (ele.isParent()) { 10770 ele.updateCompoundBounds(); 10771 10772 if (_p.autoPadding !== undefined) { 10773 return _p.autoPadding; 10774 } else { 10775 return ele.pstyle('padding').pfValue; 10776 } 10777 } else { 10778 return ele.pstyle('padding').pfValue; 10779 } 10780 }; 10781 10782 elesfn$l.paddedHeight = function () { 10783 var ele = this[0]; 10784 return ele.height() + 2 * ele.padding(); 10785 }; 10786 10787 elesfn$l.paddedWidth = function () { 10788 var ele = this[0]; 10789 return ele.width() + 2 * ele.padding(); 10790 }; 10791 10792 var widthHeight = elesfn$l; 10793 10794 var ifEdge = function ifEdge(ele, getValue) { 10795 if (ele.isEdge()) { 10796 return getValue(ele); 10797 } 10798 }; 10799 10800 var ifEdgeRenderedPosition = function ifEdgeRenderedPosition(ele, getPoint) { 10801 if (ele.isEdge()) { 10802 var cy = ele.cy(); 10803 return modelToRenderedPosition(getPoint(ele), cy.zoom(), cy.pan()); 10804 } 10805 }; 10806 10807 var ifEdgeRenderedPositions = function ifEdgeRenderedPositions(ele, getPoints) { 10808 if (ele.isEdge()) { 10809 var cy = ele.cy(); 10810 var pan = cy.pan(); 10811 var zoom = cy.zoom(); 10812 return getPoints(ele).map(function (p) { 10813 return modelToRenderedPosition(p, zoom, pan); 10814 }); 10815 } 10816 }; 10817 10818 var controlPoints = function controlPoints(ele) { 10819 return ele.renderer().getControlPoints(ele); 10820 }; 10821 10822 var segmentPoints = function segmentPoints(ele) { 10823 return ele.renderer().getSegmentPoints(ele); 10824 }; 10825 10826 var sourceEndpoint = function sourceEndpoint(ele) { 10827 return ele.renderer().getSourceEndpoint(ele); 10828 }; 10829 10830 var targetEndpoint = function targetEndpoint(ele) { 10831 return ele.renderer().getTargetEndpoint(ele); 10832 }; 10833 10834 var midpoint = function midpoint(ele) { 10835 return ele.renderer().getEdgeMidpoint(ele); 10836 }; 10837 10838 var pts = { 10839 controlPoints: { 10840 get: controlPoints, 10841 mult: true 10842 }, 10843 segmentPoints: { 10844 get: segmentPoints, 10845 mult: true 10846 }, 10847 sourceEndpoint: { 10848 get: sourceEndpoint 10849 }, 10850 targetEndpoint: { 10851 get: targetEndpoint 10852 }, 10853 midpoint: { 10854 get: midpoint 10855 } 10856 }; 10857 10858 var renderedName = function renderedName(name) { 10859 return 'rendered' + name[0].toUpperCase() + name.substr(1); 10860 }; 10861 10862 var edgePoints = Object.keys(pts).reduce(function (obj, name) { 10863 var spec = pts[name]; 10864 var rName = renderedName(name); 10865 10866 obj[name] = function () { 10867 return ifEdge(this, spec.get); 10868 }; 10869 10870 if (spec.mult) { 10871 obj[rName] = function () { 10872 return ifEdgeRenderedPositions(this, spec.get); 10873 }; 10874 } else { 10875 obj[rName] = function () { 10876 return ifEdgeRenderedPosition(this, spec.get); 10877 }; 10878 } 10879 10880 return obj; 10881 }, {}); 10882 10883 var dimensions = extend({}, position, bounds, widthHeight, edgePoints); 10884 10885 /*! 10886 Event object based on jQuery events, MIT license 10887 10888 https://jquery.org/license/ 10889 https://tldrlegal.com/license/mit-license 10890 https://github.com/jquery/jquery/blob/master/src/event.js 10891 */ 10892 var Event = function Event(src, props) { 10893 this.recycle(src, props); 10894 }; 10895 10896 function returnFalse() { 10897 return false; 10898 } 10899 10900 function returnTrue() { 10901 return true; 10902 } // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 10903 10904 10905 Event.prototype = { 10906 instanceString: function instanceString() { 10907 return 'event'; 10908 }, 10909 recycle: function recycle(src, props) { 10910 this.isImmediatePropagationStopped = this.isPropagationStopped = this.isDefaultPrevented = returnFalse; 10911 10912 if (src != null && src.preventDefault) { 10913 // Browser Event object 10914 this.type = src.type; // Events bubbling up the document may have been marked as prevented 10915 // by a handler lower down the tree; reflect the correct value. 10916 10917 this.isDefaultPrevented = src.defaultPrevented ? returnTrue : returnFalse; 10918 } else if (src != null && src.type) { 10919 // Plain object containing all event details 10920 props = src; 10921 } else { 10922 // Event string 10923 this.type = src; 10924 } // Put explicitly provided properties onto the event object 10925 10926 10927 if (props != null) { 10928 // more efficient to manually copy fields we use 10929 this.originalEvent = props.originalEvent; 10930 this.type = props.type != null ? props.type : this.type; 10931 this.cy = props.cy; 10932 this.target = props.target; 10933 this.position = props.position; 10934 this.renderedPosition = props.renderedPosition; 10935 this.namespace = props.namespace; 10936 this.layout = props.layout; 10937 } 10938 10939 if (this.cy != null && this.position != null && this.renderedPosition == null) { 10940 // create a rendered position based on the passed position 10941 var pos = this.position; 10942 var zoom = this.cy.zoom(); 10943 var pan = this.cy.pan(); 10944 this.renderedPosition = { 10945 x: pos.x * zoom + pan.x, 10946 y: pos.y * zoom + pan.y 10947 }; 10948 } // Create a timestamp if incoming event doesn't have one 10949 10950 10951 this.timeStamp = src && src.timeStamp || Date.now(); 10952 }, 10953 preventDefault: function preventDefault() { 10954 this.isDefaultPrevented = returnTrue; 10955 var e = this.originalEvent; 10956 10957 if (!e) { 10958 return; 10959 } // if preventDefault exists run it on the original event 10960 10961 10962 if (e.preventDefault) { 10963 e.preventDefault(); 10964 } 10965 }, 10966 stopPropagation: function stopPropagation() { 10967 this.isPropagationStopped = returnTrue; 10968 var e = this.originalEvent; 10969 10970 if (!e) { 10971 return; 10972 } // if stopPropagation exists run it on the original event 10973 10974 10975 if (e.stopPropagation) { 10976 e.stopPropagation(); 10977 } 10978 }, 10979 stopImmediatePropagation: function stopImmediatePropagation() { 10980 this.isImmediatePropagationStopped = returnTrue; 10981 this.stopPropagation(); 10982 }, 10983 isDefaultPrevented: returnFalse, 10984 isPropagationStopped: returnFalse, 10985 isImmediatePropagationStopped: returnFalse 10986 }; 10987 10988 var eventRegex = /^([^.]+)(\.(?:[^.]+))?$/; // regex for matching event strings (e.g. "click.namespace") 10989 10990 var universalNamespace = '.*'; // matches as if no namespace specified and prevents users from unbinding accidentally 10991 10992 var defaults$8 = { 10993 qualifierCompare: function qualifierCompare(q1, q2) { 10994 return q1 === q2; 10995 }, 10996 eventMatches: function eventMatches() 10997 /*context, listener, eventObj*/ 10998 { 10999 return true; 11000 }, 11001 addEventFields: function addEventFields() 11002 /*context, evt*/ 11003 {}, 11004 callbackContext: function callbackContext(context 11005 /*, listener, eventObj*/ 11006 ) { 11007 return context; 11008 }, 11009 beforeEmit: function beforeEmit() 11010 /* context, listener, eventObj */ 11011 {}, 11012 afterEmit: function afterEmit() 11013 /* context, listener, eventObj */ 11014 {}, 11015 bubble: function bubble() 11016 /*context*/ 11017 { 11018 return false; 11019 }, 11020 parent: function parent() 11021 /*context*/ 11022 { 11023 return null; 11024 }, 11025 context: null 11026 }; 11027 var defaultsKeys = Object.keys(defaults$8); 11028 var emptyOpts = {}; 11029 11030 function Emitter() { 11031 var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : emptyOpts; 11032 var context = arguments.length > 1 ? arguments[1] : undefined; 11033 11034 // micro-optimisation vs Object.assign() -- reduces Element instantiation time 11035 for (var i = 0; i < defaultsKeys.length; i++) { 11036 var key = defaultsKeys[i]; 11037 this[key] = opts[key] || defaults$8[key]; 11038 } 11039 11040 this.context = context || this.context; 11041 this.listeners = []; 11042 this.emitting = 0; 11043 } 11044 11045 var p = Emitter.prototype; 11046 11047 var forEachEvent = function forEachEvent(self, handler, events, qualifier, callback, conf, confOverrides) { 11048 if (fn(qualifier)) { 11049 callback = qualifier; 11050 qualifier = null; 11051 } 11052 11053 if (confOverrides) { 11054 if (conf == null) { 11055 conf = confOverrides; 11056 } else { 11057 conf = extend({}, conf, confOverrides); 11058 } 11059 } 11060 11061 var eventList = array(events) ? events : events.split(/\s+/); 11062 11063 for (var i = 0; i < eventList.length; i++) { 11064 var evt = eventList[i]; 11065 11066 if (emptyString(evt)) { 11067 continue; 11068 } 11069 11070 var match = evt.match(eventRegex); // type[.namespace] 11071 11072 if (match) { 11073 var type = match[1]; 11074 var namespace = match[2] ? match[2] : null; 11075 var ret = handler(self, evt, type, namespace, qualifier, callback, conf); 11076 11077 if (ret === false) { 11078 break; 11079 } // allow exiting early 11080 11081 } 11082 } 11083 }; 11084 11085 var makeEventObj = function makeEventObj(self, obj) { 11086 self.addEventFields(self.context, obj); 11087 return new Event(obj.type, obj); 11088 }; 11089 11090 var forEachEventObj = function forEachEventObj(self, handler, events) { 11091 if (event(events)) { 11092 handler(self, events); 11093 return; 11094 } else if (plainObject(events)) { 11095 handler(self, makeEventObj(self, events)); 11096 return; 11097 } 11098 11099 var eventList = array(events) ? events : events.split(/\s+/); 11100 11101 for (var i = 0; i < eventList.length; i++) { 11102 var evt = eventList[i]; 11103 11104 if (emptyString(evt)) { 11105 continue; 11106 } 11107 11108 var match = evt.match(eventRegex); // type[.namespace] 11109 11110 if (match) { 11111 var type = match[1]; 11112 var namespace = match[2] ? match[2] : null; 11113 var eventObj = makeEventObj(self, { 11114 type: type, 11115 namespace: namespace, 11116 target: self.context 11117 }); 11118 handler(self, eventObj); 11119 } 11120 } 11121 }; 11122 11123 p.on = p.addListener = function (events, qualifier, callback, conf, confOverrides) { 11124 forEachEvent(this, function (self, event, type, namespace, qualifier, callback, conf) { 11125 if (fn(callback)) { 11126 self.listeners.push({ 11127 event: event, 11128 // full event string 11129 callback: callback, 11130 // callback to run 11131 type: type, 11132 // the event type (e.g. 'click') 11133 namespace: namespace, 11134 // the event namespace (e.g. ".foo") 11135 qualifier: qualifier, 11136 // a restriction on whether to match this emitter 11137 conf: conf // additional configuration 11138 11139 }); 11140 } 11141 }, events, qualifier, callback, conf, confOverrides); 11142 return this; 11143 }; 11144 11145 p.one = function (events, qualifier, callback, conf) { 11146 return this.on(events, qualifier, callback, conf, { 11147 one: true 11148 }); 11149 }; 11150 11151 p.removeListener = p.off = function (events, qualifier, callback, conf) { 11152 var _this = this; 11153 11154 if (this.emitting !== 0) { 11155 this.listeners = copyArray(this.listeners); 11156 } 11157 11158 var listeners = this.listeners; 11159 11160 var _loop = function _loop(i) { 11161 var listener = listeners[i]; 11162 forEachEvent(_this, function (self, event, type, namespace, qualifier, callback 11163 /*, conf*/ 11164 ) { 11165 if ((listener.type === type || events === '*') && (!namespace && listener.namespace !== '.*' || listener.namespace === namespace) && (!qualifier || self.qualifierCompare(listener.qualifier, qualifier)) && (!callback || listener.callback === callback)) { 11166 listeners.splice(i, 1); 11167 return false; 11168 } 11169 }, events, qualifier, callback, conf); 11170 }; 11171 11172 for (var i = listeners.length - 1; i >= 0; i--) { 11173 _loop(i); 11174 } 11175 11176 return this; 11177 }; 11178 11179 p.removeAllListeners = function () { 11180 return this.removeListener('*'); 11181 }; 11182 11183 p.emit = p.trigger = function (events, extraParams, manualCallback) { 11184 var listeners = this.listeners; 11185 var numListenersBeforeEmit = listeners.length; 11186 this.emitting++; 11187 11188 if (!array(extraParams)) { 11189 extraParams = [extraParams]; 11190 } 11191 11192 forEachEventObj(this, function (self, eventObj) { 11193 if (manualCallback != null) { 11194 listeners = [{ 11195 event: eventObj.event, 11196 type: eventObj.type, 11197 namespace: eventObj.namespace, 11198 callback: manualCallback 11199 }]; 11200 numListenersBeforeEmit = listeners.length; 11201 } 11202 11203 var _loop2 = function _loop2(i) { 11204 var listener = listeners[i]; 11205 11206 if (listener.type === eventObj.type && (!listener.namespace || listener.namespace === eventObj.namespace || listener.namespace === universalNamespace) && self.eventMatches(self.context, listener, eventObj)) { 11207 var args = [eventObj]; 11208 11209 if (extraParams != null) { 11210 push(args, extraParams); 11211 } 11212 11213 self.beforeEmit(self.context, listener, eventObj); 11214 11215 if (listener.conf && listener.conf.one) { 11216 self.listeners = self.listeners.filter(function (l) { 11217 return l !== listener; 11218 }); 11219 } 11220 11221 var context = self.callbackContext(self.context, listener, eventObj); 11222 var ret = listener.callback.apply(context, args); 11223 self.afterEmit(self.context, listener, eventObj); 11224 11225 if (ret === false) { 11226 eventObj.stopPropagation(); 11227 eventObj.preventDefault(); 11228 } 11229 } // if listener matches 11230 11231 }; 11232 11233 for (var i = 0; i < numListenersBeforeEmit; i++) { 11234 _loop2(i); 11235 } // for listener 11236 11237 11238 if (self.bubble(self.context) && !eventObj.isPropagationStopped()) { 11239 self.parent(self.context).emit(eventObj, extraParams); 11240 } 11241 }, events); 11242 this.emitting--; 11243 return this; 11244 }; 11245 11246 var emitterOptions = { 11247 qualifierCompare: function qualifierCompare(selector1, selector2) { 11248 if (selector1 == null || selector2 == null) { 11249 return selector1 == null && selector2 == null; 11250 } else { 11251 return selector1.sameText(selector2); 11252 } 11253 }, 11254 eventMatches: function eventMatches(ele, listener, eventObj) { 11255 var selector = listener.qualifier; 11256 11257 if (selector != null) { 11258 return ele !== eventObj.target && element(eventObj.target) && selector.matches(eventObj.target); 11259 } 11260 11261 return true; 11262 }, 11263 addEventFields: function addEventFields(ele, evt) { 11264 evt.cy = ele.cy(); 11265 evt.target = ele; 11266 }, 11267 callbackContext: function callbackContext(ele, listener, eventObj) { 11268 return listener.qualifier != null ? eventObj.target : ele; 11269 }, 11270 beforeEmit: function beforeEmit(context, listener 11271 /*, eventObj*/ 11272 ) { 11273 if (listener.conf && listener.conf.once) { 11274 listener.conf.onceCollection.removeListener(listener.event, listener.qualifier, listener.callback); 11275 } 11276 }, 11277 bubble: function bubble() { 11278 return true; 11279 }, 11280 parent: function parent(ele) { 11281 return ele.isChild() ? ele.parent() : ele.cy(); 11282 } 11283 }; 11284 11285 var argSelector = function argSelector(arg) { 11286 if (string(arg)) { 11287 return new Selector(arg); 11288 } else { 11289 return arg; 11290 } 11291 }; 11292 11293 var elesfn$m = { 11294 createEmitter: function createEmitter() { 11295 for (var i = 0; i < this.length; i++) { 11296 var ele = this[i]; 11297 var _p = ele._private; 11298 11299 if (!_p.emitter) { 11300 _p.emitter = new Emitter(emitterOptions, ele); 11301 } 11302 } 11303 11304 return this; 11305 }, 11306 emitter: function emitter() { 11307 return this._private.emitter; 11308 }, 11309 on: function on(events, selector, callback) { 11310 var argSel = argSelector(selector); 11311 11312 for (var i = 0; i < this.length; i++) { 11313 var ele = this[i]; 11314 ele.emitter().on(events, argSel, callback); 11315 } 11316 11317 return this; 11318 }, 11319 removeListener: function removeListener(events, selector, callback) { 11320 var argSel = argSelector(selector); 11321 11322 for (var i = 0; i < this.length; i++) { 11323 var ele = this[i]; 11324 ele.emitter().removeListener(events, argSel, callback); 11325 } 11326 11327 return this; 11328 }, 11329 removeAllListeners: function removeAllListeners() { 11330 for (var i = 0; i < this.length; i++) { 11331 var ele = this[i]; 11332 ele.emitter().removeAllListeners(); 11333 } 11334 11335 return this; 11336 }, 11337 one: function one(events, selector, callback) { 11338 var argSel = argSelector(selector); 11339 11340 for (var i = 0; i < this.length; i++) { 11341 var ele = this[i]; 11342 ele.emitter().one(events, argSel, callback); 11343 } 11344 11345 return this; 11346 }, 11347 once: function once(events, selector, callback) { 11348 var argSel = argSelector(selector); 11349 11350 for (var i = 0; i < this.length; i++) { 11351 var ele = this[i]; 11352 ele.emitter().on(events, argSel, callback, { 11353 once: true, 11354 onceCollection: this 11355 }); 11356 } 11357 }, 11358 emit: function emit(events, extraParams) { 11359 for (var i = 0; i < this.length; i++) { 11360 var ele = this[i]; 11361 ele.emitter().emit(events, extraParams); 11362 } 11363 11364 return this; 11365 }, 11366 emitAndNotify: function emitAndNotify(event, extraParams) { 11367 // for internal use only 11368 if (this.length === 0) { 11369 return; 11370 } // empty collections don't need to notify anything 11371 // notify renderer 11372 11373 11374 this.cy().notify(event, this); 11375 this.emit(event, extraParams); 11376 return this; 11377 } 11378 }; 11379 define$3.eventAliasesOn(elesfn$m); 11380 11381 var elesfn$n = { 11382 nodes: function nodes(selector) { 11383 return this.filter(function (ele) { 11384 return ele.isNode(); 11385 }).filter(selector); 11386 }, 11387 edges: function edges(selector) { 11388 return this.filter(function (ele) { 11389 return ele.isEdge(); 11390 }).filter(selector); 11391 }, 11392 // internal helper to get nodes and edges as separate collections with single iteration over elements 11393 byGroup: function byGroup() { 11394 var nodes = this.spawn(); 11395 var edges = this.spawn(); 11396 11397 for (var i = 0; i < this.length; i++) { 11398 var ele = this[i]; 11399 11400 if (ele.isNode()) { 11401 nodes.merge(ele); 11402 } else { 11403 edges.merge(ele); 11404 } 11405 } 11406 11407 return { 11408 nodes: nodes, 11409 edges: edges 11410 }; 11411 }, 11412 filter: function filter(_filter, thisArg) { 11413 if (_filter === undefined) { 11414 // check this first b/c it's the most common/performant case 11415 return this; 11416 } else if (string(_filter) || elementOrCollection(_filter)) { 11417 return new Selector(_filter).filter(this); 11418 } else if (fn(_filter)) { 11419 var filterEles = this.spawn(); 11420 var eles = this; 11421 11422 for (var i = 0; i < eles.length; i++) { 11423 var ele = eles[i]; 11424 var include = thisArg ? _filter.apply(thisArg, [ele, i, eles]) : _filter(ele, i, eles); 11425 11426 if (include) { 11427 filterEles.merge(ele); 11428 } 11429 } 11430 11431 return filterEles; 11432 } 11433 11434 return this.spawn(); // if not handled by above, give 'em an empty collection 11435 }, 11436 not: function not(toRemove) { 11437 if (!toRemove) { 11438 return this; 11439 } else { 11440 if (string(toRemove)) { 11441 toRemove = this.filter(toRemove); 11442 } 11443 11444 var elements = []; 11445 var rMap = toRemove._private.map; 11446 11447 for (var i = 0; i < this.length; i++) { 11448 var element = this[i]; 11449 var remove = rMap.has(element.id()); 11450 11451 if (!remove) { 11452 elements.push(element); 11453 } 11454 } 11455 11456 return this.spawn(elements); 11457 } 11458 }, 11459 absoluteComplement: function absoluteComplement() { 11460 var cy = this.cy(); 11461 return cy.mutableElements().not(this); 11462 }, 11463 intersect: function intersect(other) { 11464 // if a selector is specified, then filter by it instead 11465 if (string(other)) { 11466 var selector = other; 11467 return this.filter(selector); 11468 } 11469 11470 var elements = []; 11471 var col1 = this; 11472 var col2 = other; 11473 var col1Smaller = this.length < other.length; 11474 var map2 = col1Smaller ? col2._private.map : col1._private.map; 11475 var col = col1Smaller ? col1 : col2; 11476 11477 for (var i = 0; i < col.length; i++) { 11478 var id = col[i]._private.data.id; 11479 var entry = map2.get(id); 11480 11481 if (entry) { 11482 elements.push(entry.ele); 11483 } 11484 } 11485 11486 return this.spawn(elements); 11487 }, 11488 xor: function xor(other) { 11489 var cy = this._private.cy; 11490 11491 if (string(other)) { 11492 other = cy.$(other); 11493 } 11494 11495 var elements = []; 11496 var col1 = this; 11497 var col2 = other; 11498 11499 var add = function add(col, other) { 11500 for (var i = 0; i < col.length; i++) { 11501 var ele = col[i]; 11502 var id = ele._private.data.id; 11503 var inOther = other.hasElementWithId(id); 11504 11505 if (!inOther) { 11506 elements.push(ele); 11507 } 11508 } 11509 }; 11510 11511 add(col1, col2); 11512 add(col2, col1); 11513 return this.spawn(elements); 11514 }, 11515 diff: function diff(other) { 11516 var cy = this._private.cy; 11517 11518 if (string(other)) { 11519 other = cy.$(other); 11520 } 11521 11522 var left = []; 11523 var right = []; 11524 var both = []; 11525 var col1 = this; 11526 var col2 = other; 11527 11528 var add = function add(col, other, retEles) { 11529 for (var i = 0; i < col.length; i++) { 11530 var ele = col[i]; 11531 var id = ele._private.data.id; 11532 var inOther = other.hasElementWithId(id); 11533 11534 if (inOther) { 11535 both.push(ele); 11536 } else { 11537 retEles.push(ele); 11538 } 11539 } 11540 }; 11541 11542 add(col1, col2, left); 11543 add(col2, col1, right); 11544 return { 11545 left: this.spawn(left, { 11546 unique: true 11547 }), 11548 right: this.spawn(right, { 11549 unique: true 11550 }), 11551 both: this.spawn(both, { 11552 unique: true 11553 }) 11554 }; 11555 }, 11556 add: function add(toAdd) { 11557 var cy = this._private.cy; 11558 11559 if (!toAdd) { 11560 return this; 11561 } 11562 11563 if (string(toAdd)) { 11564 var selector = toAdd; 11565 toAdd = cy.mutableElements().filter(selector); 11566 } 11567 11568 var elements = []; 11569 11570 for (var i = 0; i < this.length; i++) { 11571 elements.push(this[i]); 11572 } 11573 11574 var map = this._private.map; 11575 11576 for (var _i = 0; _i < toAdd.length; _i++) { 11577 var add = !map.has(toAdd[_i].id()); 11578 11579 if (add) { 11580 elements.push(toAdd[_i]); 11581 } 11582 } 11583 11584 return this.spawn(elements); 11585 }, 11586 // in place merge on calling collection 11587 merge: function merge(toAdd) { 11588 var _p = this._private; 11589 var cy = _p.cy; 11590 11591 if (!toAdd) { 11592 return this; 11593 } 11594 11595 if (toAdd && string(toAdd)) { 11596 var selector = toAdd; 11597 toAdd = cy.mutableElements().filter(selector); 11598 } 11599 11600 var map = _p.map; 11601 11602 for (var i = 0; i < toAdd.length; i++) { 11603 var toAddEle = toAdd[i]; 11604 var id = toAddEle._private.data.id; 11605 var add = !map.has(id); 11606 11607 if (add) { 11608 var index = this.length++; 11609 this[index] = toAddEle; 11610 map.set(id, { 11611 ele: toAddEle, 11612 index: index 11613 }); 11614 } else { 11615 // replace 11616 var _index = map.get(id).index; 11617 this[_index] = toAddEle; 11618 map.set(id, { 11619 ele: toAddEle, 11620 index: _index 11621 }); 11622 } 11623 } 11624 11625 return this; // chaining 11626 }, 11627 unmergeAt: function unmergeAt(i) { 11628 var ele = this[i]; 11629 var id = ele.id(); 11630 var _p = this._private; 11631 var map = _p.map; // remove ele 11632 11633 this[i] = undefined; 11634 map["delete"](id); 11635 var unmergedLastEle = i === this.length - 1; // replace empty spot with last ele in collection 11636 11637 if (this.length > 1 && !unmergedLastEle) { 11638 var lastEleI = this.length - 1; 11639 var lastEle = this[lastEleI]; 11640 var lastEleId = lastEle._private.data.id; 11641 this[lastEleI] = undefined; 11642 this[i] = lastEle; 11643 map.set(lastEleId, { 11644 ele: lastEle, 11645 index: i 11646 }); 11647 } // the collection is now 1 ele smaller 11648 11649 11650 this.length--; 11651 return this; 11652 }, 11653 // remove single ele in place in calling collection 11654 unmergeOne: function unmergeOne(ele) { 11655 ele = ele[0]; 11656 var _p = this._private; 11657 var id = ele._private.data.id; 11658 var map = _p.map; 11659 var entry = map.get(id); 11660 11661 if (!entry) { 11662 return this; // no need to remove 11663 } 11664 11665 var i = entry.index; 11666 this.unmergeAt(i); 11667 return this; 11668 }, 11669 // remove eles in place on calling collection 11670 unmerge: function unmerge(toRemove) { 11671 var cy = this._private.cy; 11672 11673 if (!toRemove) { 11674 return this; 11675 } 11676 11677 if (toRemove && string(toRemove)) { 11678 var selector = toRemove; 11679 toRemove = cy.mutableElements().filter(selector); 11680 } 11681 11682 for (var i = 0; i < toRemove.length; i++) { 11683 this.unmergeOne(toRemove[i]); 11684 } 11685 11686 return this; // chaining 11687 }, 11688 unmergeBy: function unmergeBy(toRmFn) { 11689 for (var i = this.length - 1; i >= 0; i--) { 11690 var ele = this[i]; 11691 11692 if (toRmFn(ele)) { 11693 this.unmergeAt(i); 11694 } 11695 } 11696 11697 return this; 11698 }, 11699 map: function map(mapFn, thisArg) { 11700 var arr = []; 11701 var eles = this; 11702 11703 for (var i = 0; i < eles.length; i++) { 11704 var ele = eles[i]; 11705 var ret = thisArg ? mapFn.apply(thisArg, [ele, i, eles]) : mapFn(ele, i, eles); 11706 arr.push(ret); 11707 } 11708 11709 return arr; 11710 }, 11711 reduce: function reduce(fn, initialValue) { 11712 var val = initialValue; 11713 var eles = this; 11714 11715 for (var i = 0; i < eles.length; i++) { 11716 val = fn(val, eles[i], i, eles); 11717 } 11718 11719 return val; 11720 }, 11721 max: function max(valFn, thisArg) { 11722 var max = -Infinity; 11723 var maxEle; 11724 var eles = this; 11725 11726 for (var i = 0; i < eles.length; i++) { 11727 var ele = eles[i]; 11728 var val = thisArg ? valFn.apply(thisArg, [ele, i, eles]) : valFn(ele, i, eles); 11729 11730 if (val > max) { 11731 max = val; 11732 maxEle = ele; 11733 } 11734 } 11735 11736 return { 11737 value: max, 11738 ele: maxEle 11739 }; 11740 }, 11741 min: function min(valFn, thisArg) { 11742 var min = Infinity; 11743 var minEle; 11744 var eles = this; 11745 11746 for (var i = 0; i < eles.length; i++) { 11747 var ele = eles[i]; 11748 var val = thisArg ? valFn.apply(thisArg, [ele, i, eles]) : valFn(ele, i, eles); 11749 11750 if (val < min) { 11751 min = val; 11752 minEle = ele; 11753 } 11754 } 11755 11756 return { 11757 value: min, 11758 ele: minEle 11759 }; 11760 } 11761 }; // aliases 11762 11763 var fn$5 = elesfn$n; 11764 fn$5['u'] = fn$5['|'] = fn$5['+'] = fn$5.union = fn$5.or = fn$5.add; 11765 fn$5['\\'] = fn$5['!'] = fn$5['-'] = fn$5.difference = fn$5.relativeComplement = fn$5.subtract = fn$5.not; 11766 fn$5['n'] = fn$5['&'] = fn$5['.'] = fn$5.and = fn$5.intersection = fn$5.intersect; 11767 fn$5['^'] = fn$5['(+)'] = fn$5['(-)'] = fn$5.symmetricDifference = fn$5.symdiff = fn$5.xor; 11768 fn$5.fnFilter = fn$5.filterFn = fn$5.stdFilter = fn$5.filter; 11769 fn$5.complement = fn$5.abscomp = fn$5.absoluteComplement; 11770 11771 var elesfn$o = { 11772 isNode: function isNode() { 11773 return this.group() === 'nodes'; 11774 }, 11775 isEdge: function isEdge() { 11776 return this.group() === 'edges'; 11777 }, 11778 isLoop: function isLoop() { 11779 return this.isEdge() && this.source()[0] === this.target()[0]; 11780 }, 11781 isSimple: function isSimple() { 11782 return this.isEdge() && this.source()[0] !== this.target()[0]; 11783 }, 11784 group: function group() { 11785 var ele = this[0]; 11786 11787 if (ele) { 11788 return ele._private.group; 11789 } 11790 } 11791 }; 11792 11793 /** 11794 * Elements are drawn in a specific order based on compound depth (low to high), the element type (nodes above edges), 11795 * and z-index (low to high). These styles affect how this applies: 11796 * 11797 * z-compound-depth: May be `bottom | orphan | auto | top`. The first drawn is `bottom`, then `orphan` which is the 11798 * same depth as the root of the compound graph, followed by the default value `auto` which draws in order from 11799 * root to leaves of the compound graph. The last drawn is `top`. 11800 * z-index-compare: May be `auto | manual`. The default value is `auto` which always draws edges under nodes. 11801 * `manual` ignores this convention and draws based on the `z-index` value setting. 11802 * z-index: An integer value that affects the relative draw order of elements. In general, an element with a higher 11803 * `z-index` will be drawn on top of an element with a lower `z-index`. 11804 */ 11805 11806 var zIndexSort = function zIndexSort(a, b) { 11807 var cy = a.cy(); 11808 var hasCompoundNodes = cy.hasCompoundNodes(); 11809 11810 function getDepth(ele) { 11811 var style = ele.pstyle('z-compound-depth'); 11812 11813 if (style.value === 'auto') { 11814 return hasCompoundNodes ? ele.zDepth() : 0; 11815 } else if (style.value === 'bottom') { 11816 return -1; 11817 } else if (style.value === 'top') { 11818 return MAX_INT; 11819 } // 'orphan' 11820 11821 11822 return 0; 11823 } 11824 11825 var depthDiff = getDepth(a) - getDepth(b); 11826 11827 if (depthDiff !== 0) { 11828 return depthDiff; 11829 } 11830 11831 function getEleDepth(ele) { 11832 var style = ele.pstyle('z-index-compare'); 11833 11834 if (style.value === 'auto') { 11835 return ele.isNode() ? 1 : 0; 11836 } // 'manual' 11837 11838 11839 return 0; 11840 } 11841 11842 var eleDiff = getEleDepth(a) - getEleDepth(b); 11843 11844 if (eleDiff !== 0) { 11845 return eleDiff; 11846 } 11847 11848 var zDiff = a.pstyle('z-index').value - b.pstyle('z-index').value; 11849 11850 if (zDiff !== 0) { 11851 return zDiff; 11852 } // compare indices in the core (order added to graph w/ last on top) 11853 11854 11855 return a.poolIndex() - b.poolIndex(); 11856 }; 11857 11858 var elesfn$p = { 11859 forEach: function forEach(fn$1, thisArg) { 11860 if (fn(fn$1)) { 11861 var N = this.length; 11862 11863 for (var i = 0; i < N; i++) { 11864 var ele = this[i]; 11865 var ret = thisArg ? fn$1.apply(thisArg, [ele, i, this]) : fn$1(ele, i, this); 11866 11867 if (ret === false) { 11868 break; 11869 } // exit each early on return false 11870 11871 } 11872 } 11873 11874 return this; 11875 }, 11876 toArray: function toArray() { 11877 var array = []; 11878 11879 for (var i = 0; i < this.length; i++) { 11880 array.push(this[i]); 11881 } 11882 11883 return array; 11884 }, 11885 slice: function slice(start, end) { 11886 var array = []; 11887 var thisSize = this.length; 11888 11889 if (end == null) { 11890 end = thisSize; 11891 } 11892 11893 if (start == null) { 11894 start = 0; 11895 } 11896 11897 if (start < 0) { 11898 start = thisSize + start; 11899 } 11900 11901 if (end < 0) { 11902 end = thisSize + end; 11903 } 11904 11905 for (var i = start; i >= 0 && i < end && i < thisSize; i++) { 11906 array.push(this[i]); 11907 } 11908 11909 return this.spawn(array); 11910 }, 11911 size: function size() { 11912 return this.length; 11913 }, 11914 eq: function eq(i) { 11915 return this[i] || this.spawn(); 11916 }, 11917 first: function first() { 11918 return this[0] || this.spawn(); 11919 }, 11920 last: function last() { 11921 return this[this.length - 1] || this.spawn(); 11922 }, 11923 empty: function empty() { 11924 return this.length === 0; 11925 }, 11926 nonempty: function nonempty() { 11927 return !this.empty(); 11928 }, 11929 sort: function sort(sortFn) { 11930 if (!fn(sortFn)) { 11931 return this; 11932 } 11933 11934 var sorted = this.toArray().sort(sortFn); 11935 return this.spawn(sorted); 11936 }, 11937 sortByZIndex: function sortByZIndex() { 11938 return this.sort(zIndexSort); 11939 }, 11940 zDepth: function zDepth() { 11941 var ele = this[0]; 11942 11943 if (!ele) { 11944 return undefined; 11945 } // let cy = ele.cy(); 11946 11947 11948 var _p = ele._private; 11949 var group = _p.group; 11950 11951 if (group === 'nodes') { 11952 var depth = _p.data.parent ? ele.parents().size() : 0; 11953 11954 if (!ele.isParent()) { 11955 return MAX_INT - 1; // childless nodes always on top 11956 } 11957 11958 return depth; 11959 } else { 11960 var src = _p.source; 11961 var tgt = _p.target; 11962 var srcDepth = src.zDepth(); 11963 var tgtDepth = tgt.zDepth(); 11964 return Math.max(srcDepth, tgtDepth, 0); // depth of deepest parent 11965 } 11966 } 11967 }; 11968 elesfn$p.each = elesfn$p.forEach; 11969 11970 var defineSymbolIterator = function defineSymbolIterator() { 11971 var typeofUndef = "undefined" ; 11972 var isIteratorSupported = (typeof Symbol === "undefined" ? "undefined" : _typeof(Symbol)) != typeofUndef && _typeof(Symbol.iterator) != typeofUndef; // eslint-disable-line no-undef 11973 11974 if (isIteratorSupported) { 11975 elesfn$p[Symbol.iterator] = function () { 11976 var _this = this; 11977 11978 // eslint-disable-line no-undef 11979 var entry = { 11980 value: undefined, 11981 done: false 11982 }; 11983 var i = 0; 11984 var length = this.length; 11985 return _defineProperty({ 11986 next: function next() { 11987 if (i < length) { 11988 entry.value = _this[i++]; 11989 } else { 11990 entry.value = undefined; 11991 entry.done = true; 11992 } 11993 11994 return entry; 11995 } 11996 }, Symbol.iterator, function () { 11997 // eslint-disable-line no-undef 11998 return this; 11999 }); 12000 }; 12001 } 12002 }; 12003 12004 defineSymbolIterator(); 12005 12006 var getLayoutDimensionOptions = defaults({ 12007 nodeDimensionsIncludeLabels: false 12008 }); 12009 var elesfn$q = { 12010 // Calculates and returns node dimensions { x, y } based on options given 12011 layoutDimensions: function layoutDimensions(options) { 12012 options = getLayoutDimensionOptions(options); 12013 var dims; 12014 12015 if (!this.takesUpSpace()) { 12016 dims = { 12017 w: 0, 12018 h: 0 12019 }; 12020 } else if (options.nodeDimensionsIncludeLabels) { 12021 var bbDim = this.boundingBox(); 12022 dims = { 12023 w: bbDim.w, 12024 h: bbDim.h 12025 }; 12026 } else { 12027 dims = { 12028 w: this.outerWidth(), 12029 h: this.outerHeight() 12030 }; 12031 } // sanitise the dimensions for external layouts (avoid division by zero) 12032 12033 12034 if (dims.w === 0 || dims.h === 0) { 12035 dims.w = dims.h = 1; 12036 } 12037 12038 return dims; 12039 }, 12040 // using standard layout options, apply position function (w/ or w/o animation) 12041 layoutPositions: function layoutPositions(layout, options, fn) { 12042 var nodes = this.nodes(); 12043 var cy = this.cy(); 12044 var layoutEles = options.eles; // nodes & edges 12045 12046 var getMemoizeKey = function getMemoizeKey(node) { 12047 return node.id(); 12048 }; 12049 12050 var fnMem = memoize(fn, getMemoizeKey); // memoized version of position function 12051 12052 layout.emit({ 12053 type: 'layoutstart', 12054 layout: layout 12055 }); 12056 layout.animations = []; 12057 12058 var calculateSpacing = function calculateSpacing(spacing, nodesBb, pos) { 12059 var center = { 12060 x: nodesBb.x1 + nodesBb.w / 2, 12061 y: nodesBb.y1 + nodesBb.h / 2 12062 }; 12063 var spacingVector = { 12064 // scale from center of bounding box (not necessarily 0,0) 12065 x: (pos.x - center.x) * spacing, 12066 y: (pos.y - center.y) * spacing 12067 }; 12068 return { 12069 x: center.x + spacingVector.x, 12070 y: center.y + spacingVector.y 12071 }; 12072 }; 12073 12074 var useSpacingFactor = options.spacingFactor && options.spacingFactor !== 1; 12075 12076 var spacingBb = function spacingBb() { 12077 if (!useSpacingFactor) { 12078 return null; 12079 } 12080 12081 var bb = makeBoundingBox(); 12082 12083 for (var i = 0; i < nodes.length; i++) { 12084 var node = nodes[i]; 12085 var pos = fnMem(node, i); 12086 expandBoundingBoxByPoint(bb, pos.x, pos.y); 12087 } 12088 12089 return bb; 12090 }; 12091 12092 var bb = spacingBb(); 12093 var getFinalPos = memoize(function (node, i) { 12094 var newPos = fnMem(node, i); 12095 12096 if (useSpacingFactor) { 12097 var spacing = Math.abs(options.spacingFactor); 12098 newPos = calculateSpacing(spacing, bb, newPos); 12099 } 12100 12101 if (options.transform != null) { 12102 newPos = options.transform(node, newPos); 12103 } 12104 12105 return newPos; 12106 }, getMemoizeKey); 12107 12108 if (options.animate) { 12109 for (var i = 0; i < nodes.length; i++) { 12110 var node = nodes[i]; 12111 var newPos = getFinalPos(node, i); 12112 var animateNode = options.animateFilter == null || options.animateFilter(node, i); 12113 12114 if (animateNode) { 12115 var ani = node.animation({ 12116 position: newPos, 12117 duration: options.animationDuration, 12118 easing: options.animationEasing 12119 }); 12120 layout.animations.push(ani); 12121 } else { 12122 node.position(newPos); 12123 } 12124 } 12125 12126 if (options.fit) { 12127 var fitAni = cy.animation({ 12128 fit: { 12129 boundingBox: layoutEles.boundingBoxAt(getFinalPos), 12130 padding: options.padding 12131 }, 12132 duration: options.animationDuration, 12133 easing: options.animationEasing 12134 }); 12135 layout.animations.push(fitAni); 12136 } else if (options.zoom !== undefined && options.pan !== undefined) { 12137 var zoomPanAni = cy.animation({ 12138 zoom: options.zoom, 12139 pan: options.pan, 12140 duration: options.animationDuration, 12141 easing: options.animationEasing 12142 }); 12143 layout.animations.push(zoomPanAni); 12144 } 12145 12146 layout.animations.forEach(function (ani) { 12147 return ani.play(); 12148 }); 12149 layout.one('layoutready', options.ready); 12150 layout.emit({ 12151 type: 'layoutready', 12152 layout: layout 12153 }); 12154 Promise$1.all(layout.animations.map(function (ani) { 12155 return ani.promise(); 12156 })).then(function () { 12157 layout.one('layoutstop', options.stop); 12158 layout.emit({ 12159 type: 'layoutstop', 12160 layout: layout 12161 }); 12162 }); 12163 } else { 12164 nodes.positions(getFinalPos); 12165 12166 if (options.fit) { 12167 cy.fit(options.eles, options.padding); 12168 } 12169 12170 if (options.zoom != null) { 12171 cy.zoom(options.zoom); 12172 } 12173 12174 if (options.pan) { 12175 cy.pan(options.pan); 12176 } 12177 12178 layout.one('layoutready', options.ready); 12179 layout.emit({ 12180 type: 'layoutready', 12181 layout: layout 12182 }); 12183 layout.one('layoutstop', options.stop); 12184 layout.emit({ 12185 type: 'layoutstop', 12186 layout: layout 12187 }); 12188 } 12189 12190 return this; // chaining 12191 }, 12192 layout: function layout(options) { 12193 var cy = this.cy(); 12194 return cy.makeLayout(extend({}, options, { 12195 eles: this 12196 })); 12197 } 12198 }; // aliases: 12199 12200 elesfn$q.createLayout = elesfn$q.makeLayout = elesfn$q.layout; 12201 12202 function styleCache(key, fn, ele) { 12203 var _p = ele._private; 12204 var cache = _p.styleCache = _p.styleCache || []; 12205 var val; 12206 12207 if ((val = cache[key]) != null) { 12208 return val; 12209 } else { 12210 val = cache[key] = fn(ele); 12211 return val; 12212 } 12213 } 12214 12215 function cacheStyleFunction(key, fn) { 12216 key = hashString(key); 12217 return function cachedStyleFunction(ele) { 12218 return styleCache(key, fn, ele); 12219 }; 12220 } 12221 12222 function cachePrototypeStyleFunction(key, fn) { 12223 key = hashString(key); 12224 12225 var selfFn = function selfFn(ele) { 12226 return fn.call(ele); 12227 }; 12228 12229 return function cachedPrototypeStyleFunction() { 12230 var ele = this[0]; 12231 12232 if (ele) { 12233 return styleCache(key, selfFn, ele); 12234 } 12235 }; 12236 } 12237 12238 var elesfn$r = { 12239 recalculateRenderedStyle: function recalculateRenderedStyle(useCache) { 12240 var cy = this.cy(); 12241 var renderer = cy.renderer(); 12242 var styleEnabled = cy.styleEnabled(); 12243 12244 if (renderer && styleEnabled) { 12245 renderer.recalculateRenderedStyle(this, useCache); 12246 } 12247 12248 return this; 12249 }, 12250 dirtyStyleCache: function dirtyStyleCache() { 12251 var cy = this.cy(); 12252 12253 var dirty = function dirty(ele) { 12254 return ele._private.styleCache = null; 12255 }; 12256 12257 if (cy.hasCompoundNodes()) { 12258 var eles; 12259 eles = this.spawnSelf().merge(this.descendants()).merge(this.parents()); 12260 eles.merge(eles.connectedEdges()); 12261 eles.forEach(dirty); 12262 } else { 12263 this.forEach(function (ele) { 12264 dirty(ele); 12265 ele.connectedEdges().forEach(dirty); 12266 }); 12267 } 12268 12269 return this; 12270 }, 12271 // fully updates (recalculates) the style for the elements 12272 updateStyle: function updateStyle(notifyRenderer) { 12273 var cy = this._private.cy; 12274 12275 if (!cy.styleEnabled()) { 12276 return this; 12277 } 12278 12279 if (cy.batching()) { 12280 var bEles = cy._private.batchStyleEles; 12281 bEles.merge(this); 12282 return this; // chaining and exit early when batching 12283 } 12284 12285 var hasCompounds = cy.hasCompoundNodes(); 12286 var style = cy.style(); 12287 var updatedEles = this; 12288 notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; 12289 12290 if (hasCompounds) { 12291 // then add everything up and down for compound selector checks 12292 updatedEles = this.spawnSelf().merge(this.descendants()).merge(this.parents()); 12293 } 12294 12295 var changedEles = style.apply(updatedEles); 12296 12297 if (notifyRenderer) { 12298 changedEles.emitAndNotify('style'); // let renderer know we changed style 12299 } else { 12300 changedEles.emit('style'); // just fire the event 12301 } 12302 12303 return this; // chaining 12304 }, 12305 // get the internal parsed style object for the specified property 12306 parsedStyle: function parsedStyle(property) { 12307 var includeNonDefault = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 12308 var ele = this[0]; 12309 var cy = ele.cy(); 12310 12311 if (!cy.styleEnabled()) { 12312 return; 12313 } 12314 12315 if (ele) { 12316 var overriddenStyle = ele._private.style[property]; 12317 12318 if (overriddenStyle != null) { 12319 return overriddenStyle; 12320 } else if (includeNonDefault) { 12321 return cy.style().getDefaultProperty(property); 12322 } else { 12323 return null; 12324 } 12325 } 12326 }, 12327 numericStyle: function numericStyle(property) { 12328 var ele = this[0]; 12329 12330 if (!ele.cy().styleEnabled()) { 12331 return; 12332 } 12333 12334 if (ele) { 12335 var pstyle = ele.pstyle(property); 12336 return pstyle.pfValue !== undefined ? pstyle.pfValue : pstyle.value; 12337 } 12338 }, 12339 numericStyleUnits: function numericStyleUnits(property) { 12340 var ele = this[0]; 12341 12342 if (!ele.cy().styleEnabled()) { 12343 return; 12344 } 12345 12346 if (ele) { 12347 return ele.pstyle(property).units; 12348 } 12349 }, 12350 // get the specified css property as a rendered value (i.e. on-screen value) 12351 // or get the whole rendered style if no property specified (NB doesn't allow setting) 12352 renderedStyle: function renderedStyle(property) { 12353 var cy = this.cy(); 12354 12355 if (!cy.styleEnabled()) { 12356 return this; 12357 } 12358 12359 var ele = this[0]; 12360 12361 if (ele) { 12362 return cy.style().getRenderedStyle(ele, property); 12363 } 12364 }, 12365 // read the calculated css style of the element or override the style (via a bypass) 12366 style: function style(name, value) { 12367 var cy = this.cy(); 12368 12369 if (!cy.styleEnabled()) { 12370 return this; 12371 } 12372 12373 var updateTransitions = false; 12374 var style = cy.style(); 12375 12376 if (plainObject(name)) { 12377 // then extend the bypass 12378 var props = name; 12379 style.applyBypass(this, props, updateTransitions); 12380 this.emitAndNotify('style'); // let the renderer know we've updated style 12381 } else if (string(name)) { 12382 if (value === undefined) { 12383 // then get the property from the style 12384 var ele = this[0]; 12385 12386 if (ele) { 12387 return style.getStylePropertyValue(ele, name); 12388 } else { 12389 // empty collection => can't get any value 12390 return; 12391 } 12392 } else { 12393 // then set the bypass with the property value 12394 style.applyBypass(this, name, value, updateTransitions); 12395 this.emitAndNotify('style'); // let the renderer know we've updated style 12396 } 12397 } else if (name === undefined) { 12398 var _ele = this[0]; 12399 12400 if (_ele) { 12401 return style.getRawStyle(_ele); 12402 } else { 12403 // empty collection => can't get any value 12404 return; 12405 } 12406 } 12407 12408 return this; // chaining 12409 }, 12410 removeStyle: function removeStyle(names) { 12411 var cy = this.cy(); 12412 12413 if (!cy.styleEnabled()) { 12414 return this; 12415 } 12416 12417 var updateTransitions = false; 12418 var style = cy.style(); 12419 var eles = this; 12420 12421 if (names === undefined) { 12422 for (var i = 0; i < eles.length; i++) { 12423 var ele = eles[i]; 12424 style.removeAllBypasses(ele, updateTransitions); 12425 } 12426 } else { 12427 names = names.split(/\s+/); 12428 12429 for (var _i = 0; _i < eles.length; _i++) { 12430 var _ele2 = eles[_i]; 12431 style.removeBypasses(_ele2, names, updateTransitions); 12432 } 12433 } 12434 12435 this.emitAndNotify('style'); // let the renderer know we've updated style 12436 12437 return this; // chaining 12438 }, 12439 show: function show() { 12440 this.css('display', 'element'); 12441 return this; // chaining 12442 }, 12443 hide: function hide() { 12444 this.css('display', 'none'); 12445 return this; // chaining 12446 }, 12447 effectiveOpacity: function effectiveOpacity() { 12448 var cy = this.cy(); 12449 12450 if (!cy.styleEnabled()) { 12451 return 1; 12452 } 12453 12454 var hasCompoundNodes = cy.hasCompoundNodes(); 12455 var ele = this[0]; 12456 12457 if (ele) { 12458 var _p = ele._private; 12459 var parentOpacity = ele.pstyle('opacity').value; 12460 12461 if (!hasCompoundNodes) { 12462 return parentOpacity; 12463 } 12464 12465 var parents = !_p.data.parent ? null : ele.parents(); 12466 12467 if (parents) { 12468 for (var i = 0; i < parents.length; i++) { 12469 var parent = parents[i]; 12470 var opacity = parent.pstyle('opacity').value; 12471 parentOpacity = opacity * parentOpacity; 12472 } 12473 } 12474 12475 return parentOpacity; 12476 } 12477 }, 12478 transparent: function transparent() { 12479 var cy = this.cy(); 12480 12481 if (!cy.styleEnabled()) { 12482 return false; 12483 } 12484 12485 var ele = this[0]; 12486 var hasCompoundNodes = ele.cy().hasCompoundNodes(); 12487 12488 if (ele) { 12489 if (!hasCompoundNodes) { 12490 return ele.pstyle('opacity').value === 0; 12491 } else { 12492 return ele.effectiveOpacity() === 0; 12493 } 12494 } 12495 }, 12496 backgrounding: function backgrounding() { 12497 var cy = this.cy(); 12498 12499 if (!cy.styleEnabled()) { 12500 return false; 12501 } 12502 12503 var ele = this[0]; 12504 return ele._private.backgrounding ? true : false; 12505 } 12506 }; 12507 12508 function checkCompound(ele, parentOk) { 12509 var _p = ele._private; 12510 var parents = _p.data.parent ? ele.parents() : null; 12511 12512 if (parents) { 12513 for (var i = 0; i < parents.length; i++) { 12514 var parent = parents[i]; 12515 12516 if (!parentOk(parent)) { 12517 return false; 12518 } 12519 } 12520 } 12521 12522 return true; 12523 } 12524 12525 function defineDerivedStateFunction(specs) { 12526 var ok = specs.ok; 12527 var edgeOkViaNode = specs.edgeOkViaNode || specs.ok; 12528 var parentOk = specs.parentOk || specs.ok; 12529 return function () { 12530 var cy = this.cy(); 12531 12532 if (!cy.styleEnabled()) { 12533 return true; 12534 } 12535 12536 var ele = this[0]; 12537 var hasCompoundNodes = cy.hasCompoundNodes(); 12538 12539 if (ele) { 12540 var _p = ele._private; 12541 12542 if (!ok(ele)) { 12543 return false; 12544 } 12545 12546 if (ele.isNode()) { 12547 return !hasCompoundNodes || checkCompound(ele, parentOk); 12548 } else { 12549 var src = _p.source; 12550 var tgt = _p.target; 12551 return edgeOkViaNode(src) && (!hasCompoundNodes || checkCompound(src, edgeOkViaNode)) && (src === tgt || edgeOkViaNode(tgt) && (!hasCompoundNodes || checkCompound(tgt, edgeOkViaNode))); 12552 } 12553 } 12554 }; 12555 } 12556 12557 var eleTakesUpSpace = cacheStyleFunction('eleTakesUpSpace', function (ele) { 12558 return ele.pstyle('display').value === 'element' && ele.width() !== 0 && (ele.isNode() ? ele.height() !== 0 : true); 12559 }); 12560 elesfn$r.takesUpSpace = cachePrototypeStyleFunction('takesUpSpace', defineDerivedStateFunction({ 12561 ok: eleTakesUpSpace 12562 })); 12563 var eleInteractive = cacheStyleFunction('eleInteractive', function (ele) { 12564 return ele.pstyle('events').value === 'yes' && ele.pstyle('visibility').value === 'visible' && eleTakesUpSpace(ele); 12565 }); 12566 var parentInteractive = cacheStyleFunction('parentInteractive', function (parent) { 12567 return parent.pstyle('visibility').value === 'visible' && eleTakesUpSpace(parent); 12568 }); 12569 elesfn$r.interactive = cachePrototypeStyleFunction('interactive', defineDerivedStateFunction({ 12570 ok: eleInteractive, 12571 parentOk: parentInteractive, 12572 edgeOkViaNode: eleTakesUpSpace 12573 })); 12574 12575 elesfn$r.noninteractive = function () { 12576 var ele = this[0]; 12577 12578 if (ele) { 12579 return !ele.interactive(); 12580 } 12581 }; 12582 12583 var eleVisible = cacheStyleFunction('eleVisible', function (ele) { 12584 return ele.pstyle('visibility').value === 'visible' && ele.pstyle('opacity').pfValue !== 0 && eleTakesUpSpace(ele); 12585 }); 12586 var edgeVisibleViaNode = eleTakesUpSpace; 12587 elesfn$r.visible = cachePrototypeStyleFunction('visible', defineDerivedStateFunction({ 12588 ok: eleVisible, 12589 edgeOkViaNode: edgeVisibleViaNode 12590 })); 12591 12592 elesfn$r.hidden = function () { 12593 var ele = this[0]; 12594 12595 if (ele) { 12596 return !ele.visible(); 12597 } 12598 }; 12599 12600 elesfn$r.isBundledBezier = cachePrototypeStyleFunction('isBundledBezier', function () { 12601 if (!this.cy().styleEnabled()) { 12602 return false; 12603 } 12604 12605 return !this.removed() && this.pstyle('curve-style').value === 'bezier' && this.takesUpSpace(); 12606 }); 12607 elesfn$r.bypass = elesfn$r.css = elesfn$r.style; 12608 elesfn$r.renderedCss = elesfn$r.renderedStyle; 12609 elesfn$r.removeBypass = elesfn$r.removeCss = elesfn$r.removeStyle; 12610 elesfn$r.pstyle = elesfn$r.parsedStyle; 12611 12612 var elesfn$s = {}; 12613 12614 function defineSwitchFunction(params) { 12615 return function () { 12616 var args = arguments; 12617 var changedEles = []; // e.g. cy.nodes().select( data, handler ) 12618 12619 if (args.length === 2) { 12620 var data = args[0]; 12621 var handler = args[1]; 12622 this.on(params.event, data, handler); 12623 } // e.g. cy.nodes().select( handler ) 12624 else if (args.length === 1 && fn(args[0])) { 12625 var _handler = args[0]; 12626 this.on(params.event, _handler); 12627 } // e.g. cy.nodes().select() 12628 // e.g. (private) cy.nodes().select(['tapselect']) 12629 else if (args.length === 0 || args.length === 1 && array(args[0])) { 12630 var addlEvents = args.length === 1 ? args[0] : null; 12631 12632 for (var i = 0; i < this.length; i++) { 12633 var ele = this[i]; 12634 var able = !params.ableField || ele._private[params.ableField]; 12635 var changed = ele._private[params.field] != params.value; 12636 12637 if (params.overrideAble) { 12638 var overrideAble = params.overrideAble(ele); 12639 12640 if (overrideAble !== undefined) { 12641 able = overrideAble; 12642 12643 if (!overrideAble) { 12644 return this; 12645 } // to save cycles assume not able for all on override 12646 12647 } 12648 } 12649 12650 if (able) { 12651 ele._private[params.field] = params.value; 12652 12653 if (changed) { 12654 changedEles.push(ele); 12655 } 12656 } 12657 } 12658 12659 var changedColl = this.spawn(changedEles); 12660 changedColl.updateStyle(); // change of state => possible change of style 12661 12662 changedColl.emit(params.event); 12663 12664 if (addlEvents) { 12665 changedColl.emit(addlEvents); 12666 } 12667 } 12668 12669 return this; 12670 }; 12671 } 12672 12673 function defineSwitchSet(params) { 12674 elesfn$s[params.field] = function () { 12675 var ele = this[0]; 12676 12677 if (ele) { 12678 if (params.overrideField) { 12679 var val = params.overrideField(ele); 12680 12681 if (val !== undefined) { 12682 return val; 12683 } 12684 } 12685 12686 return ele._private[params.field]; 12687 } 12688 }; 12689 12690 elesfn$s[params.on] = defineSwitchFunction({ 12691 event: params.on, 12692 field: params.field, 12693 ableField: params.ableField, 12694 overrideAble: params.overrideAble, 12695 value: true 12696 }); 12697 elesfn$s[params.off] = defineSwitchFunction({ 12698 event: params.off, 12699 field: params.field, 12700 ableField: params.ableField, 12701 overrideAble: params.overrideAble, 12702 value: false 12703 }); 12704 } 12705 12706 defineSwitchSet({ 12707 field: 'locked', 12708 overrideField: function overrideField(ele) { 12709 return ele.cy().autolock() ? true : undefined; 12710 }, 12711 on: 'lock', 12712 off: 'unlock' 12713 }); 12714 defineSwitchSet({ 12715 field: 'grabbable', 12716 overrideField: function overrideField(ele) { 12717 return ele.cy().autoungrabify() || ele.pannable() ? false : undefined; 12718 }, 12719 on: 'grabify', 12720 off: 'ungrabify' 12721 }); 12722 defineSwitchSet({ 12723 field: 'selected', 12724 ableField: 'selectable', 12725 overrideAble: function overrideAble(ele) { 12726 return ele.cy().autounselectify() ? false : undefined; 12727 }, 12728 on: 'select', 12729 off: 'unselect' 12730 }); 12731 defineSwitchSet({ 12732 field: 'selectable', 12733 overrideField: function overrideField(ele) { 12734 return ele.cy().autounselectify() ? false : undefined; 12735 }, 12736 on: 'selectify', 12737 off: 'unselectify' 12738 }); 12739 elesfn$s.deselect = elesfn$s.unselect; 12740 12741 elesfn$s.grabbed = function () { 12742 var ele = this[0]; 12743 12744 if (ele) { 12745 return ele._private.grabbed; 12746 } 12747 }; 12748 12749 defineSwitchSet({ 12750 field: 'active', 12751 on: 'activate', 12752 off: 'unactivate' 12753 }); 12754 defineSwitchSet({ 12755 field: 'pannable', 12756 on: 'panify', 12757 off: 'unpanify' 12758 }); 12759 12760 elesfn$s.inactive = function () { 12761 var ele = this[0]; 12762 12763 if (ele) { 12764 return !ele._private.active; 12765 } 12766 }; 12767 12768 var elesfn$t = {}; // DAG functions 12769 //////////////// 12770 12771 var defineDagExtremity = function defineDagExtremity(params) { 12772 return function dagExtremityImpl(selector) { 12773 var eles = this; 12774 var ret = []; 12775 12776 for (var i = 0; i < eles.length; i++) { 12777 var ele = eles[i]; 12778 12779 if (!ele.isNode()) { 12780 continue; 12781 } 12782 12783 var disqualified = false; 12784 var edges = ele.connectedEdges(); 12785 12786 for (var j = 0; j < edges.length; j++) { 12787 var edge = edges[j]; 12788 var src = edge.source(); 12789 var tgt = edge.target(); 12790 12791 if (params.noIncomingEdges && tgt === ele && src !== ele || params.noOutgoingEdges && src === ele && tgt !== ele) { 12792 disqualified = true; 12793 break; 12794 } 12795 } 12796 12797 if (!disqualified) { 12798 ret.push(ele); 12799 } 12800 } 12801 12802 return this.spawn(ret, { 12803 unique: true 12804 }).filter(selector); 12805 }; 12806 }; 12807 12808 var defineDagOneHop = function defineDagOneHop(params) { 12809 return function (selector) { 12810 var eles = this; 12811 var oEles = []; 12812 12813 for (var i = 0; i < eles.length; i++) { 12814 var ele = eles[i]; 12815 12816 if (!ele.isNode()) { 12817 continue; 12818 } 12819 12820 var edges = ele.connectedEdges(); 12821 12822 for (var j = 0; j < edges.length; j++) { 12823 var edge = edges[j]; 12824 var src = edge.source(); 12825 var tgt = edge.target(); 12826 12827 if (params.outgoing && src === ele) { 12828 oEles.push(edge); 12829 oEles.push(tgt); 12830 } else if (params.incoming && tgt === ele) { 12831 oEles.push(edge); 12832 oEles.push(src); 12833 } 12834 } 12835 } 12836 12837 return this.spawn(oEles, { 12838 unique: true 12839 }).filter(selector); 12840 }; 12841 }; 12842 12843 var defineDagAllHops = function defineDagAllHops(params) { 12844 return function (selector) { 12845 var eles = this; 12846 var sEles = []; 12847 var sElesIds = {}; 12848 12849 for (;;) { 12850 var next = params.outgoing ? eles.outgoers() : eles.incomers(); 12851 12852 if (next.length === 0) { 12853 break; 12854 } // done if none left 12855 12856 12857 var newNext = false; 12858 12859 for (var i = 0; i < next.length; i++) { 12860 var n = next[i]; 12861 var nid = n.id(); 12862 12863 if (!sElesIds[nid]) { 12864 sElesIds[nid] = true; 12865 sEles.push(n); 12866 newNext = true; 12867 } 12868 } 12869 12870 if (!newNext) { 12871 break; 12872 } // done if touched all outgoers already 12873 12874 12875 eles = next; 12876 } 12877 12878 return this.spawn(sEles, { 12879 unique: true 12880 }).filter(selector); 12881 }; 12882 }; 12883 12884 elesfn$t.clearTraversalCache = function () { 12885 for (var i = 0; i < this.length; i++) { 12886 this[i]._private.traversalCache = null; 12887 } 12888 }; 12889 12890 extend(elesfn$t, { 12891 // get the root nodes in the DAG 12892 roots: defineDagExtremity({ 12893 noIncomingEdges: true 12894 }), 12895 // get the leaf nodes in the DAG 12896 leaves: defineDagExtremity({ 12897 noOutgoingEdges: true 12898 }), 12899 // normally called children in graph theory 12900 // these nodes =edges=> outgoing nodes 12901 outgoers: cache(defineDagOneHop({ 12902 outgoing: true 12903 }), 'outgoers'), 12904 // aka DAG descendants 12905 successors: defineDagAllHops({ 12906 outgoing: true 12907 }), 12908 // normally called parents in graph theory 12909 // these nodes <=edges= incoming nodes 12910 incomers: cache(defineDagOneHop({ 12911 incoming: true 12912 }), 'incomers'), 12913 // aka DAG ancestors 12914 predecessors: defineDagAllHops({ 12915 incoming: true 12916 }) 12917 }); // Neighbourhood functions 12918 ////////////////////////// 12919 12920 extend(elesfn$t, { 12921 neighborhood: cache(function (selector) { 12922 var elements = []; 12923 var nodes = this.nodes(); 12924 12925 for (var i = 0; i < nodes.length; i++) { 12926 // for all nodes 12927 var node = nodes[i]; 12928 var connectedEdges = node.connectedEdges(); // for each connected edge, add the edge and the other node 12929 12930 for (var j = 0; j < connectedEdges.length; j++) { 12931 var edge = connectedEdges[j]; 12932 var src = edge.source(); 12933 var tgt = edge.target(); 12934 var otherNode = node === src ? tgt : src; // need check in case of loop 12935 12936 if (otherNode.length > 0) { 12937 elements.push(otherNode[0]); // add node 1 hop away 12938 } // add connected edge 12939 12940 12941 elements.push(edge[0]); 12942 } 12943 } 12944 12945 return this.spawn(elements, { 12946 unique: true 12947 }).filter(selector); 12948 }, 'neighborhood'), 12949 closedNeighborhood: function closedNeighborhood(selector) { 12950 return this.neighborhood().add(this).filter(selector); 12951 }, 12952 openNeighborhood: function openNeighborhood(selector) { 12953 return this.neighborhood(selector); 12954 } 12955 }); // aliases 12956 12957 elesfn$t.neighbourhood = elesfn$t.neighborhood; 12958 elesfn$t.closedNeighbourhood = elesfn$t.closedNeighborhood; 12959 elesfn$t.openNeighbourhood = elesfn$t.openNeighborhood; // Edge functions 12960 ///////////////// 12961 12962 extend(elesfn$t, { 12963 source: cache(function sourceImpl(selector) { 12964 var ele = this[0]; 12965 var src; 12966 12967 if (ele) { 12968 src = ele._private.source || ele.cy().collection(); 12969 } 12970 12971 return src && selector ? src.filter(selector) : src; 12972 }, 'source'), 12973 target: cache(function targetImpl(selector) { 12974 var ele = this[0]; 12975 var tgt; 12976 12977 if (ele) { 12978 tgt = ele._private.target || ele.cy().collection(); 12979 } 12980 12981 return tgt && selector ? tgt.filter(selector) : tgt; 12982 }, 'target'), 12983 sources: defineSourceFunction({ 12984 attr: 'source' 12985 }), 12986 targets: defineSourceFunction({ 12987 attr: 'target' 12988 }) 12989 }); 12990 12991 function defineSourceFunction(params) { 12992 return function sourceImpl(selector) { 12993 var sources = []; 12994 12995 for (var i = 0; i < this.length; i++) { 12996 var ele = this[i]; 12997 var src = ele._private[params.attr]; 12998 12999 if (src) { 13000 sources.push(src); 13001 } 13002 } 13003 13004 return this.spawn(sources, { 13005 unique: true 13006 }).filter(selector); 13007 }; 13008 } 13009 13010 extend(elesfn$t, { 13011 edgesWith: cache(defineEdgesWithFunction(), 'edgesWith'), 13012 edgesTo: cache(defineEdgesWithFunction({ 13013 thisIsSrc: true 13014 }), 'edgesTo') 13015 }); 13016 13017 function defineEdgesWithFunction(params) { 13018 return function edgesWithImpl(otherNodes) { 13019 var elements = []; 13020 var cy = this._private.cy; 13021 var p = params || {}; // get elements if a selector is specified 13022 13023 if (string(otherNodes)) { 13024 otherNodes = cy.$(otherNodes); 13025 } 13026 13027 for (var h = 0; h < otherNodes.length; h++) { 13028 var edges = otherNodes[h]._private.edges; 13029 13030 for (var i = 0; i < edges.length; i++) { 13031 var edge = edges[i]; 13032 var edgeData = edge._private.data; 13033 var thisToOther = this.hasElementWithId(edgeData.source) && otherNodes.hasElementWithId(edgeData.target); 13034 var otherToThis = otherNodes.hasElementWithId(edgeData.source) && this.hasElementWithId(edgeData.target); 13035 var edgeConnectsThisAndOther = thisToOther || otherToThis; 13036 13037 if (!edgeConnectsThisAndOther) { 13038 continue; 13039 } 13040 13041 if (p.thisIsSrc || p.thisIsTgt) { 13042 if (p.thisIsSrc && !thisToOther) { 13043 continue; 13044 } 13045 13046 if (p.thisIsTgt && !otherToThis) { 13047 continue; 13048 } 13049 } 13050 13051 elements.push(edge); 13052 } 13053 } 13054 13055 return this.spawn(elements, { 13056 unique: true 13057 }); 13058 }; 13059 } 13060 13061 extend(elesfn$t, { 13062 connectedEdges: cache(function (selector) { 13063 var retEles = []; 13064 var eles = this; 13065 13066 for (var i = 0; i < eles.length; i++) { 13067 var node = eles[i]; 13068 13069 if (!node.isNode()) { 13070 continue; 13071 } 13072 13073 var edges = node._private.edges; 13074 13075 for (var j = 0; j < edges.length; j++) { 13076 var edge = edges[j]; 13077 retEles.push(edge); 13078 } 13079 } 13080 13081 return this.spawn(retEles, { 13082 unique: true 13083 }).filter(selector); 13084 }, 'connectedEdges'), 13085 connectedNodes: cache(function (selector) { 13086 var retEles = []; 13087 var eles = this; 13088 13089 for (var i = 0; i < eles.length; i++) { 13090 var edge = eles[i]; 13091 13092 if (!edge.isEdge()) { 13093 continue; 13094 } 13095 13096 retEles.push(edge.source()[0]); 13097 retEles.push(edge.target()[0]); 13098 } 13099 13100 return this.spawn(retEles, { 13101 unique: true 13102 }).filter(selector); 13103 }, 'connectedNodes'), 13104 parallelEdges: cache(defineParallelEdgesFunction(), 'parallelEdges'), 13105 codirectedEdges: cache(defineParallelEdgesFunction({ 13106 codirected: true 13107 }), 'codirectedEdges') 13108 }); 13109 13110 function defineParallelEdgesFunction(params) { 13111 var defaults = { 13112 codirected: false 13113 }; 13114 params = extend({}, defaults, params); 13115 return function parallelEdgesImpl(selector) { 13116 // micro-optimised for renderer 13117 var elements = []; 13118 var edges = this.edges(); 13119 var p = params; // look at all the edges in the collection 13120 13121 for (var i = 0; i < edges.length; i++) { 13122 var edge1 = edges[i]; 13123 var edge1_p = edge1._private; 13124 var src1 = edge1_p.source; 13125 var srcid1 = src1._private.data.id; 13126 var tgtid1 = edge1_p.data.target; 13127 var srcEdges1 = src1._private.edges; // look at edges connected to the src node of this edge 13128 13129 for (var j = 0; j < srcEdges1.length; j++) { 13130 var edge2 = srcEdges1[j]; 13131 var edge2data = edge2._private.data; 13132 var tgtid2 = edge2data.target; 13133 var srcid2 = edge2data.source; 13134 var codirected = tgtid2 === tgtid1 && srcid2 === srcid1; 13135 var oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2; 13136 13137 if (p.codirected && codirected || !p.codirected && (codirected || oppdirected)) { 13138 elements.push(edge2); 13139 } 13140 } 13141 } 13142 13143 return this.spawn(elements, { 13144 unique: true 13145 }).filter(selector); 13146 }; 13147 } // Misc functions 13148 ///////////////// 13149 13150 13151 extend(elesfn$t, { 13152 components: function components(root) { 13153 var self = this; 13154 var cy = self.cy(); 13155 var visited = cy.collection(); 13156 var unvisited = root == null ? self.nodes() : root.nodes(); 13157 var components = []; 13158 13159 if (root != null && unvisited.empty()) { 13160 // root may contain only edges 13161 unvisited = root.sources(); // doesn't matter which node to use (undirected), so just use the source sides 13162 } 13163 13164 var visitInComponent = function visitInComponent(node, component) { 13165 visited.merge(node); 13166 unvisited.unmerge(node); 13167 component.merge(node); 13168 }; 13169 13170 if (unvisited.empty()) { 13171 return self.spawn(); 13172 } 13173 13174 var _loop = function _loop() { 13175 // each iteration yields a component 13176 var cmpt = cy.collection(); 13177 components.push(cmpt); 13178 var root = unvisited[0]; 13179 visitInComponent(root, cmpt); 13180 self.bfs({ 13181 directed: false, 13182 roots: root, 13183 visit: function visit(v) { 13184 return visitInComponent(v, cmpt); 13185 } 13186 }); 13187 cmpt.forEach(function (node) { 13188 node.connectedEdges().forEach(function (e) { 13189 // connectedEdges() usually cached 13190 if (self.has(e) && cmpt.has(e.source()) && cmpt.has(e.target())) { 13191 // has() is cheap 13192 cmpt.merge(e); // forEach() only considers nodes -- sets N at call time 13193 } 13194 }); 13195 }); 13196 }; 13197 13198 do { 13199 _loop(); 13200 } while (unvisited.length > 0); 13201 13202 return components; 13203 }, 13204 component: function component() { 13205 var ele = this[0]; 13206 return ele.cy().mutableElements().components(ele)[0]; 13207 } 13208 }); 13209 elesfn$t.componentsOf = elesfn$t.components; 13210 13211 var idFactory = { 13212 generate: function generate(cy, element, tryThisId) { 13213 var id = tryThisId != null ? tryThisId : uuid(); 13214 13215 while (cy.hasElementWithId(id)) { 13216 id = uuid(); 13217 } 13218 13219 return id; 13220 } 13221 }; // represents a set of nodes, edges, or both together 13222 13223 var Collection = function Collection(cy, elements, options) { 13224 if (cy === undefined || !core(cy)) { 13225 error('A collection must have a reference to the core'); 13226 return; 13227 } 13228 13229 var map = new Map$1(); 13230 var createdElements = false; 13231 13232 if (!elements) { 13233 elements = []; 13234 } else if (elements.length > 0 && plainObject(elements[0]) && !element(elements[0])) { 13235 createdElements = true; // make elements from json and restore all at once later 13236 13237 var eles = []; 13238 var elesIds = new Set$1(); 13239 13240 for (var i = 0, l = elements.length; i < l; i++) { 13241 var json = elements[i]; 13242 13243 if (json.data == null) { 13244 json.data = {}; 13245 } 13246 13247 var _data = json.data; // make sure newly created elements have valid ids 13248 13249 if (_data.id == null) { 13250 _data.id = idFactory.generate(cy, json); 13251 } else if (cy.hasElementWithId(_data.id) || elesIds.has(_data.id)) { 13252 continue; // can't create element if prior id already exists 13253 } 13254 13255 var ele = new Element(cy, json, false); 13256 eles.push(ele); 13257 elesIds.add(_data.id); 13258 } 13259 13260 elements = eles; 13261 } 13262 13263 this.length = 0; 13264 13265 for (var _i = 0, _l = elements.length; _i < _l; _i++) { 13266 var element$1 = elements[_i][0]; // [0] in case elements is an array of collections, rather than array of elements 13267 13268 if (element$1 == null) { 13269 continue; 13270 } 13271 13272 var id = element$1._private.data.id; 13273 13274 if (options == null || options.unique && !map.has(id)) { 13275 map.set(id, { 13276 index: this.length, 13277 ele: element$1 13278 }); 13279 this[this.length] = element$1; 13280 this.length++; 13281 } 13282 } 13283 13284 this._private = { 13285 cy: cy, 13286 map: map 13287 }; // restore the elements if we created them from json 13288 13289 if (createdElements) { 13290 this.restore(); 13291 } 13292 }; // Functions 13293 //////////////////////////////////////////////////////////////////////////////////////////////////// 13294 // keep the prototypes in sync (an element has the same functions as a collection) 13295 // and use elefn and elesfn as shorthands to the prototypes 13296 13297 13298 var elesfn$u = Element.prototype = Collection.prototype; 13299 13300 elesfn$u.instanceString = function () { 13301 return 'collection'; 13302 }; 13303 13304 elesfn$u.spawn = function (cy, eles, opts) { 13305 if (!core(cy)) { 13306 // cy is optional 13307 opts = eles; 13308 eles = cy; 13309 cy = this.cy(); 13310 } 13311 13312 return new Collection(cy, eles, opts); 13313 }; 13314 13315 elesfn$u.spawnSelf = function () { 13316 return this.spawn(this); 13317 }; 13318 13319 elesfn$u.cy = function () { 13320 return this._private.cy; 13321 }; 13322 13323 elesfn$u.renderer = function () { 13324 return this._private.cy.renderer(); 13325 }; 13326 13327 elesfn$u.element = function () { 13328 return this[0]; 13329 }; 13330 13331 elesfn$u.collection = function () { 13332 if (collection(this)) { 13333 return this; 13334 } else { 13335 // an element 13336 return new Collection(this._private.cy, [this]); 13337 } 13338 }; 13339 13340 elesfn$u.unique = function () { 13341 return new Collection(this._private.cy, this, { 13342 unique: true 13343 }); 13344 }; 13345 13346 elesfn$u.hasElementWithId = function (id) { 13347 id = '' + id; // id must be string 13348 13349 return this._private.map.has(id); 13350 }; 13351 13352 elesfn$u.getElementById = function (id) { 13353 id = '' + id; // id must be string 13354 13355 var cy = this._private.cy; 13356 13357 var entry = this._private.map.get(id); 13358 13359 return entry ? entry.ele : new Collection(cy); // get ele or empty collection 13360 }; 13361 13362 elesfn$u.$id = elesfn$u.getElementById; 13363 13364 elesfn$u.poolIndex = function () { 13365 var cy = this._private.cy; 13366 var eles = cy._private.elements; 13367 var id = this[0]._private.data.id; 13368 return eles._private.map.get(id).index; 13369 }; 13370 13371 elesfn$u.indexOf = function (ele) { 13372 var id = ele[0]._private.data.id; 13373 return this._private.map.get(id).index; 13374 }; 13375 13376 elesfn$u.indexOfId = function (id) { 13377 id = '' + id; // id must be string 13378 13379 return this._private.map.get(id).index; 13380 }; 13381 13382 elesfn$u.json = function (obj) { 13383 var ele = this.element(); 13384 var cy = this.cy(); 13385 13386 if (ele == null && obj) { 13387 return this; 13388 } // can't set to no eles 13389 13390 13391 if (ele == null) { 13392 return undefined; 13393 } // can't get from no eles 13394 13395 13396 var p = ele._private; 13397 13398 if (plainObject(obj)) { 13399 // set 13400 cy.startBatch(); 13401 13402 if (obj.data) { 13403 ele.data(obj.data); 13404 var _data2 = p.data; 13405 13406 if (ele.isEdge()) { 13407 // source and target are immutable via data() 13408 var move = false; 13409 var spec = {}; 13410 var src = obj.data.source; 13411 var tgt = obj.data.target; 13412 13413 if (src != null && src != _data2.source) { 13414 spec.source = '' + src; // id must be string 13415 13416 move = true; 13417 } 13418 13419 if (tgt != null && tgt != _data2.target) { 13420 spec.target = '' + tgt; // id must be string 13421 13422 move = true; 13423 } 13424 13425 if (move) { 13426 ele = ele.move(spec); 13427 } 13428 } else { 13429 // parent is immutable via data() 13430 var newParentValSpecd = 'parent' in obj.data; 13431 var parent = obj.data.parent; 13432 13433 if (newParentValSpecd && (parent != null || _data2.parent != null) && parent != _data2.parent) { 13434 if (parent === undefined) { 13435 // can't set undefined imperatively, so use null 13436 parent = null; 13437 } 13438 13439 if (parent != null) { 13440 parent = '' + parent; // id must be string 13441 } 13442 13443 ele = ele.move({ 13444 parent: parent 13445 }); 13446 } 13447 } 13448 } 13449 13450 if (obj.position) { 13451 ele.position(obj.position); 13452 } // ignore group -- immutable 13453 13454 13455 var checkSwitch = function checkSwitch(k, trueFnName, falseFnName) { 13456 var obj_k = obj[k]; 13457 13458 if (obj_k != null && obj_k !== p[k]) { 13459 if (obj_k) { 13460 ele[trueFnName](); 13461 } else { 13462 ele[falseFnName](); 13463 } 13464 } 13465 }; 13466 13467 checkSwitch('removed', 'remove', 'restore'); 13468 checkSwitch('selected', 'select', 'unselect'); 13469 checkSwitch('selectable', 'selectify', 'unselectify'); 13470 checkSwitch('locked', 'lock', 'unlock'); 13471 checkSwitch('grabbable', 'grabify', 'ungrabify'); 13472 checkSwitch('pannable', 'panify', 'unpanify'); 13473 13474 if (obj.classes != null) { 13475 ele.classes(obj.classes); 13476 } 13477 13478 cy.endBatch(); 13479 return this; 13480 } else if (obj === undefined) { 13481 // get 13482 var json = { 13483 data: copy(p.data), 13484 position: copy(p.position), 13485 group: p.group, 13486 removed: p.removed, 13487 selected: p.selected, 13488 selectable: p.selectable, 13489 locked: p.locked, 13490 grabbable: p.grabbable, 13491 pannable: p.pannable, 13492 classes: null 13493 }; 13494 json.classes = ''; 13495 var i = 0; 13496 p.classes.forEach(function (cls) { 13497 return json.classes += i++ === 0 ? cls : ' ' + cls; 13498 }); 13499 return json; 13500 } 13501 }; 13502 13503 elesfn$u.jsons = function () { 13504 var jsons = []; 13505 13506 for (var i = 0; i < this.length; i++) { 13507 var ele = this[i]; 13508 var json = ele.json(); 13509 jsons.push(json); 13510 } 13511 13512 return jsons; 13513 }; 13514 13515 elesfn$u.clone = function () { 13516 var cy = this.cy(); 13517 var elesArr = []; 13518 13519 for (var i = 0; i < this.length; i++) { 13520 var ele = this[i]; 13521 var json = ele.json(); 13522 var clone = new Element(cy, json, false); // NB no restore 13523 13524 elesArr.push(clone); 13525 } 13526 13527 return new Collection(cy, elesArr); 13528 }; 13529 13530 elesfn$u.copy = elesfn$u.clone; 13531 13532 elesfn$u.restore = function () { 13533 var notifyRenderer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 13534 var addToPool = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 13535 var self = this; 13536 var cy = self.cy(); 13537 var cy_p = cy._private; // create arrays of nodes and edges, since we need to 13538 // restore the nodes first 13539 13540 var nodes = []; 13541 var edges = []; 13542 var elements; 13543 13544 for (var _i2 = 0, l = self.length; _i2 < l; _i2++) { 13545 var ele = self[_i2]; 13546 13547 if (addToPool && !ele.removed()) { 13548 // don't need to handle this ele 13549 continue; 13550 } // keep nodes first in the array and edges after 13551 13552 13553 if (ele.isNode()) { 13554 // put to front of array if node 13555 nodes.push(ele); 13556 } else { 13557 // put to end of array if edge 13558 edges.push(ele); 13559 } 13560 } 13561 13562 elements = nodes.concat(edges); 13563 var i; 13564 13565 var removeFromElements = function removeFromElements() { 13566 elements.splice(i, 1); 13567 i--; 13568 }; // now, restore each element 13569 13570 13571 for (i = 0; i < elements.length; i++) { 13572 var _ele = elements[i]; 13573 var _private = _ele._private; 13574 var _data3 = _private.data; // the traversal cache should start fresh when ele is added 13575 13576 _ele.clearTraversalCache(); // set id and validate 13577 13578 13579 if (!addToPool && !_private.removed) ; else if (_data3.id === undefined) { 13580 _data3.id = idFactory.generate(cy, _ele); 13581 } else if (number(_data3.id)) { 13582 _data3.id = '' + _data3.id; // now it's a string 13583 } else if (emptyString(_data3.id) || !string(_data3.id)) { 13584 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 13585 13586 removeFromElements(); 13587 continue; 13588 } else if (cy.hasElementWithId(_data3.id)) { 13589 error('Can not create second element with ID `' + _data3.id + '`'); // can't create element if one already has that id 13590 13591 removeFromElements(); 13592 continue; 13593 } 13594 13595 var id = _data3.id; // id is finalised, now let's keep a ref 13596 13597 if (_ele.isNode()) { 13598 // extra checks for nodes 13599 var pos = _private.position; // make sure the nodes have a defined position 13600 13601 if (pos.x == null) { 13602 pos.x = 0; 13603 } 13604 13605 if (pos.y == null) { 13606 pos.y = 0; 13607 } 13608 } 13609 13610 if (_ele.isEdge()) { 13611 // extra checks for edges 13612 var edge = _ele; 13613 var fields = ['source', 'target']; 13614 var fieldsLength = fields.length; 13615 var badSourceOrTarget = false; 13616 13617 for (var j = 0; j < fieldsLength; j++) { 13618 var field = fields[j]; 13619 var val = _data3[field]; 13620 13621 if (number(val)) { 13622 val = _data3[field] = '' + _data3[field]; // now string 13623 } 13624 13625 if (val == null || val === '') { 13626 // can't create if source or target is not defined properly 13627 error('Can not create edge `' + id + '` with unspecified ' + field); 13628 badSourceOrTarget = true; 13629 } else if (!cy.hasElementWithId(val)) { 13630 // can't create edge if one of its nodes doesn't exist 13631 error('Can not create edge `' + id + '` with nonexistant ' + field + ' `' + val + '`'); 13632 badSourceOrTarget = true; 13633 } 13634 } 13635 13636 if (badSourceOrTarget) { 13637 removeFromElements(); 13638 continue; 13639 } // can't create this 13640 13641 13642 var src = cy.getElementById(_data3.source); 13643 var tgt = cy.getElementById(_data3.target); // only one edge in node if loop 13644 13645 if (src.same(tgt)) { 13646 src._private.edges.push(edge); 13647 } else { 13648 src._private.edges.push(edge); 13649 13650 tgt._private.edges.push(edge); 13651 } 13652 13653 edge._private.source = src; 13654 edge._private.target = tgt; 13655 } // if is edge 13656 // create mock ids / indexes maps for element so it can be used like collections 13657 13658 13659 _private.map = new Map$1(); 13660 13661 _private.map.set(id, { 13662 ele: _ele, 13663 index: 0 13664 }); 13665 13666 _private.removed = false; 13667 13668 if (addToPool) { 13669 cy.addToPool(_ele); 13670 } 13671 } // for each element 13672 // do compound node sanity checks 13673 13674 13675 for (var _i3 = 0; _i3 < nodes.length; _i3++) { 13676 // each node 13677 var node = nodes[_i3]; 13678 var _data4 = node._private.data; 13679 13680 if (number(_data4.parent)) { 13681 // then automake string 13682 _data4.parent = '' + _data4.parent; 13683 } 13684 13685 var parentId = _data4.parent; 13686 var specifiedParent = parentId != null; 13687 13688 if (specifiedParent) { 13689 var parent = cy.getElementById(parentId); 13690 13691 if (parent.empty()) { 13692 // non-existant parent; just remove it 13693 _data4.parent = undefined; 13694 } else { 13695 var selfAsParent = false; 13696 var ancestor = parent; 13697 13698 while (!ancestor.empty()) { 13699 if (node.same(ancestor)) { 13700 // mark self as parent and remove from data 13701 selfAsParent = true; 13702 _data4.parent = undefined; // remove parent reference 13703 // exit or we loop forever 13704 13705 break; 13706 } 13707 13708 ancestor = ancestor.parent(); 13709 } 13710 13711 if (!selfAsParent) { 13712 // connect with children 13713 parent[0]._private.children.push(node); 13714 13715 node._private.parent = parent[0]; // let the core know we have a compound graph 13716 13717 cy_p.hasCompoundNodes = true; 13718 } 13719 } // else 13720 13721 } // if specified parent 13722 13723 } // for each node 13724 13725 13726 if (elements.length > 0) { 13727 var restored = new Collection(cy, elements); 13728 13729 for (var _i4 = 0; _i4 < restored.length; _i4++) { 13730 var _ele2 = restored[_i4]; 13731 13732 if (_ele2.isNode()) { 13733 continue; 13734 } // adding an edge invalidates the traversal caches for the parallel edges 13735 13736 13737 _ele2.parallelEdges().clearTraversalCache(); // adding an edge invalidates the traversal cache for the connected nodes 13738 13739 13740 _ele2.source().clearTraversalCache(); 13741 13742 _ele2.target().clearTraversalCache(); 13743 } 13744 13745 var toUpdateStyle; 13746 13747 if (cy_p.hasCompoundNodes) { 13748 toUpdateStyle = cy.collection().merge(restored).merge(restored.connectedNodes()).merge(restored.parent()); 13749 } else { 13750 toUpdateStyle = restored; 13751 } 13752 13753 toUpdateStyle.dirtyCompoundBoundsCache().dirtyBoundingBoxCache().updateStyle(notifyRenderer); 13754 13755 if (notifyRenderer) { 13756 restored.emitAndNotify('add'); 13757 } else if (addToPool) { 13758 restored.emit('add'); 13759 } 13760 } 13761 13762 return self; // chainability 13763 }; 13764 13765 elesfn$u.removed = function () { 13766 var ele = this[0]; 13767 return ele && ele._private.removed; 13768 }; 13769 13770 elesfn$u.inside = function () { 13771 var ele = this[0]; 13772 return ele && !ele._private.removed; 13773 }; 13774 13775 elesfn$u.remove = function () { 13776 var notifyRenderer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 13777 var removeFromPool = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 13778 var self = this; 13779 var elesToRemove = []; 13780 var elesToRemoveIds = {}; 13781 var cy = self._private.cy; // add connected edges 13782 13783 function addConnectedEdges(node) { 13784 var edges = node._private.edges; 13785 13786 for (var i = 0; i < edges.length; i++) { 13787 add(edges[i]); 13788 } 13789 } // add descendant nodes 13790 13791 13792 function addChildren(node) { 13793 var children = node._private.children; 13794 13795 for (var i = 0; i < children.length; i++) { 13796 add(children[i]); 13797 } 13798 } 13799 13800 function add(ele) { 13801 var alreadyAdded = elesToRemoveIds[ele.id()]; 13802 13803 if (removeFromPool && ele.removed() || alreadyAdded) { 13804 return; 13805 } else { 13806 elesToRemoveIds[ele.id()] = true; 13807 } 13808 13809 if (ele.isNode()) { 13810 elesToRemove.push(ele); // nodes are removed last 13811 13812 addConnectedEdges(ele); 13813 addChildren(ele); 13814 } else { 13815 elesToRemove.unshift(ele); // edges are removed first 13816 } 13817 } // make the list of elements to remove 13818 // (may be removing more than specified due to connected edges etc) 13819 13820 13821 for (var i = 0, l = self.length; i < l; i++) { 13822 var ele = self[i]; 13823 add(ele); 13824 } 13825 13826 function removeEdgeRef(node, edge) { 13827 var connectedEdges = node._private.edges; 13828 removeFromArray(connectedEdges, edge); // removing an edges invalidates the traversal cache for its nodes 13829 13830 node.clearTraversalCache(); 13831 } 13832 13833 function removeParallelRef(pllEdge) { 13834 // removing an edge invalidates the traversal caches for the parallel edges 13835 pllEdge.clearTraversalCache(); 13836 } 13837 13838 var alteredParents = []; 13839 alteredParents.ids = {}; 13840 13841 function removeChildRef(parent, ele) { 13842 ele = ele[0]; 13843 parent = parent[0]; 13844 var children = parent._private.children; 13845 var pid = parent.id(); 13846 removeFromArray(children, ele); // remove parent => child ref 13847 13848 ele._private.parent = null; // remove child => parent ref 13849 13850 if (!alteredParents.ids[pid]) { 13851 alteredParents.ids[pid] = true; 13852 alteredParents.push(parent); 13853 } 13854 } 13855 13856 self.dirtyCompoundBoundsCache(); 13857 13858 if (removeFromPool) { 13859 cy.removeFromPool(elesToRemove); // remove from core pool 13860 } 13861 13862 for (var _i5 = 0; _i5 < elesToRemove.length; _i5++) { 13863 var _ele3 = elesToRemove[_i5]; 13864 13865 if (_ele3.isEdge()) { 13866 // remove references to this edge in its connected nodes 13867 var src = _ele3.source()[0]; 13868 13869 var tgt = _ele3.target()[0]; 13870 13871 removeEdgeRef(src, _ele3); 13872 removeEdgeRef(tgt, _ele3); 13873 13874 var pllEdges = _ele3.parallelEdges(); 13875 13876 for (var j = 0; j < pllEdges.length; j++) { 13877 var pllEdge = pllEdges[j]; 13878 removeParallelRef(pllEdge); 13879 13880 if (pllEdge.isBundledBezier()) { 13881 pllEdge.dirtyBoundingBoxCache(); 13882 } 13883 } 13884 } else { 13885 // remove reference to parent 13886 var parent = _ele3.parent(); 13887 13888 if (parent.length !== 0) { 13889 removeChildRef(parent, _ele3); 13890 } 13891 } 13892 13893 if (removeFromPool) { 13894 // mark as removed 13895 _ele3._private.removed = true; 13896 } 13897 } // check to see if we have a compound graph or not 13898 13899 13900 var elesStillInside = cy._private.elements; 13901 cy._private.hasCompoundNodes = false; 13902 13903 for (var _i6 = 0; _i6 < elesStillInside.length; _i6++) { 13904 var _ele4 = elesStillInside[_i6]; 13905 13906 if (_ele4.isParent()) { 13907 cy._private.hasCompoundNodes = true; 13908 break; 13909 } 13910 } 13911 13912 var removedElements = new Collection(this.cy(), elesToRemove); 13913 13914 if (removedElements.size() > 0) { 13915 // must manually notify since trigger won't do this automatically once removed 13916 if (notifyRenderer) { 13917 removedElements.emitAndNotify('remove'); 13918 } else if (removeFromPool) { 13919 removedElements.emit('remove'); 13920 } 13921 } // the parents who were modified by the removal need their style updated 13922 13923 13924 for (var _i7 = 0; _i7 < alteredParents.length; _i7++) { 13925 var _ele5 = alteredParents[_i7]; 13926 13927 if (!removeFromPool || !_ele5.removed()) { 13928 _ele5.updateStyle(); 13929 } 13930 } 13931 13932 return removedElements; 13933 }; 13934 13935 elesfn$u.move = function (struct) { 13936 var cy = this._private.cy; 13937 var eles = this; // just clean up refs, caches, etc. in the same way as when removing and then restoring 13938 // (our calls to remove/restore do not remove from the graph or make events) 13939 13940 var notifyRenderer = false; 13941 var modifyPool = false; 13942 13943 var toString = function toString(id) { 13944 return id == null ? id : '' + id; 13945 }; // id must be string 13946 13947 13948 if (struct.source !== undefined || struct.target !== undefined) { 13949 var srcId = toString(struct.source); 13950 var tgtId = toString(struct.target); 13951 var srcExists = srcId != null && cy.hasElementWithId(srcId); 13952 var tgtExists = tgtId != null && cy.hasElementWithId(tgtId); 13953 13954 if (srcExists || tgtExists) { 13955 cy.batch(function () { 13956 // avoid duplicate style updates 13957 eles.remove(notifyRenderer, modifyPool); // clean up refs etc. 13958 13959 eles.emitAndNotify('moveout'); 13960 13961 for (var i = 0; i < eles.length; i++) { 13962 var ele = eles[i]; 13963 var _data5 = ele._private.data; 13964 13965 if (ele.isEdge()) { 13966 if (srcExists) { 13967 _data5.source = srcId; 13968 } 13969 13970 if (tgtExists) { 13971 _data5.target = tgtId; 13972 } 13973 } 13974 } 13975 13976 eles.restore(notifyRenderer, modifyPool); // make new refs, style, etc. 13977 }); 13978 eles.emitAndNotify('move'); 13979 } 13980 } else if (struct.parent !== undefined) { 13981 // move node to new parent 13982 var parentId = toString(struct.parent); 13983 var parentExists = parentId === null || cy.hasElementWithId(parentId); 13984 13985 if (parentExists) { 13986 var pidToAssign = parentId === null ? undefined : parentId; 13987 cy.batch(function () { 13988 // avoid duplicate style updates 13989 var updated = eles.remove(notifyRenderer, modifyPool); // clean up refs etc. 13990 13991 updated.emitAndNotify('moveout'); 13992 13993 for (var i = 0; i < eles.length; i++) { 13994 var ele = eles[i]; 13995 var _data6 = ele._private.data; 13996 13997 if (ele.isNode()) { 13998 _data6.parent = pidToAssign; 13999 } 14000 } 14001 14002 updated.restore(notifyRenderer, modifyPool); // make new refs, style, etc. 14003 }); 14004 eles.emitAndNotify('move'); 14005 } 14006 } 14007 14008 return this; 14009 }; 14010 14011 [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) { 14012 extend(elesfn$u, props); 14013 }); 14014 14015 var corefn = { 14016 add: function add(opts) { 14017 var elements; 14018 var cy = this; // add the elements 14019 14020 if (elementOrCollection(opts)) { 14021 var eles = opts; 14022 14023 if (eles._private.cy === cy) { 14024 // same instance => just restore 14025 elements = eles.restore(); 14026 } else { 14027 // otherwise, copy from json 14028 var jsons = []; 14029 14030 for (var i = 0; i < eles.length; i++) { 14031 var ele = eles[i]; 14032 jsons.push(ele.json()); 14033 } 14034 14035 elements = new Collection(cy, jsons); 14036 } 14037 } // specify an array of options 14038 else if (array(opts)) { 14039 var _jsons = opts; 14040 elements = new Collection(cy, _jsons); 14041 } // specify via opts.nodes and opts.edges 14042 else if (plainObject(opts) && (array(opts.nodes) || array(opts.edges))) { 14043 var elesByGroup = opts; 14044 var _jsons2 = []; 14045 var grs = ['nodes', 'edges']; 14046 14047 for (var _i = 0, il = grs.length; _i < il; _i++) { 14048 var group = grs[_i]; 14049 var elesArray = elesByGroup[group]; 14050 14051 if (array(elesArray)) { 14052 for (var j = 0, jl = elesArray.length; j < jl; j++) { 14053 var json = extend({ 14054 group: group 14055 }, elesArray[j]); 14056 14057 _jsons2.push(json); 14058 } 14059 } 14060 } 14061 14062 elements = new Collection(cy, _jsons2); 14063 } // specify options for one element 14064 else { 14065 var _json = opts; 14066 elements = new Element(cy, _json).collection(); 14067 } 14068 14069 return elements; 14070 }, 14071 remove: function remove(collection) { 14072 if (elementOrCollection(collection)) ; else if (string(collection)) { 14073 var selector = collection; 14074 collection = this.$(selector); 14075 } 14076 14077 return collection.remove(); 14078 } 14079 }; 14080 14081 /* global Float32Array */ 14082 14083 /*! Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 14084 function generateCubicBezier(mX1, mY1, mX2, mY2) { 14085 var NEWTON_ITERATIONS = 4, 14086 NEWTON_MIN_SLOPE = 0.001, 14087 SUBDIVISION_PRECISION = 0.0000001, 14088 SUBDIVISION_MAX_ITERATIONS = 10, 14089 kSplineTableSize = 11, 14090 kSampleStepSize = 1.0 / (kSplineTableSize - 1.0), 14091 float32ArraySupported = typeof Float32Array !== 'undefined'; 14092 /* Must contain four arguments. */ 14093 14094 if (arguments.length !== 4) { 14095 return false; 14096 } 14097 /* Arguments must be numbers. */ 14098 14099 14100 for (var i = 0; i < 4; ++i) { 14101 if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) { 14102 return false; 14103 } 14104 } 14105 /* X values must be in the [0, 1] range. */ 14106 14107 14108 mX1 = Math.min(mX1, 1); 14109 mX2 = Math.min(mX2, 1); 14110 mX1 = Math.max(mX1, 0); 14111 mX2 = Math.max(mX2, 0); 14112 var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); 14113 14114 function A(aA1, aA2) { 14115 return 1.0 - 3.0 * aA2 + 3.0 * aA1; 14116 } 14117 14118 function B(aA1, aA2) { 14119 return 3.0 * aA2 - 6.0 * aA1; 14120 } 14121 14122 function C(aA1) { 14123 return 3.0 * aA1; 14124 } 14125 14126 function calcBezier(aT, aA1, aA2) { 14127 return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; 14128 } 14129 14130 function getSlope(aT, aA1, aA2) { 14131 return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); 14132 } 14133 14134 function newtonRaphsonIterate(aX, aGuessT) { 14135 for (var _i = 0; _i < NEWTON_ITERATIONS; ++_i) { 14136 var currentSlope = getSlope(aGuessT, mX1, mX2); 14137 14138 if (currentSlope === 0.0) { 14139 return aGuessT; 14140 } 14141 14142 var currentX = calcBezier(aGuessT, mX1, mX2) - aX; 14143 aGuessT -= currentX / currentSlope; 14144 } 14145 14146 return aGuessT; 14147 } 14148 14149 function calcSampleValues() { 14150 for (var _i2 = 0; _i2 < kSplineTableSize; ++_i2) { 14151 mSampleValues[_i2] = calcBezier(_i2 * kSampleStepSize, mX1, mX2); 14152 } 14153 } 14154 14155 function binarySubdivide(aX, aA, aB) { 14156 var currentX, 14157 currentT, 14158 i = 0; 14159 14160 do { 14161 currentT = aA + (aB - aA) / 2.0; 14162 currentX = calcBezier(currentT, mX1, mX2) - aX; 14163 14164 if (currentX > 0.0) { 14165 aB = currentT; 14166 } else { 14167 aA = currentT; 14168 } 14169 } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); 14170 14171 return currentT; 14172 } 14173 14174 function getTForX(aX) { 14175 var intervalStart = 0.0, 14176 currentSample = 1, 14177 lastSample = kSplineTableSize - 1; 14178 14179 for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { 14180 intervalStart += kSampleStepSize; 14181 } 14182 14183 --currentSample; 14184 var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]), 14185 guessForT = intervalStart + dist * kSampleStepSize, 14186 initialSlope = getSlope(guessForT, mX1, mX2); 14187 14188 if (initialSlope >= NEWTON_MIN_SLOPE) { 14189 return newtonRaphsonIterate(aX, guessForT); 14190 } else if (initialSlope === 0.0) { 14191 return guessForT; 14192 } else { 14193 return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize); 14194 } 14195 } 14196 14197 var _precomputed = false; 14198 14199 function precompute() { 14200 _precomputed = true; 14201 14202 if (mX1 !== mY1 || mX2 !== mY2) { 14203 calcSampleValues(); 14204 } 14205 } 14206 14207 var f = function f(aX) { 14208 if (!_precomputed) { 14209 precompute(); 14210 } 14211 14212 if (mX1 === mY1 && mX2 === mY2) { 14213 return aX; 14214 } 14215 14216 if (aX === 0) { 14217 return 0; 14218 } 14219 14220 if (aX === 1) { 14221 return 1; 14222 } 14223 14224 return calcBezier(getTForX(aX), mY1, mY2); 14225 }; 14226 14227 f.getControlPoints = function () { 14228 return [{ 14229 x: mX1, 14230 y: mY1 14231 }, { 14232 x: mX2, 14233 y: mY2 14234 }]; 14235 }; 14236 14237 var str = "generateBezier(" + [mX1, mY1, mX2, mY2] + ")"; 14238 14239 f.toString = function () { 14240 return str; 14241 }; 14242 14243 return f; 14244 } 14245 14246 /*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 14247 14248 /* 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 14249 then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */ 14250 var generateSpringRK4 = function () { 14251 function springAccelerationForState(state) { 14252 return -state.tension * state.x - state.friction * state.v; 14253 } 14254 14255 function springEvaluateStateWithDerivative(initialState, dt, derivative) { 14256 var state = { 14257 x: initialState.x + derivative.dx * dt, 14258 v: initialState.v + derivative.dv * dt, 14259 tension: initialState.tension, 14260 friction: initialState.friction 14261 }; 14262 return { 14263 dx: state.v, 14264 dv: springAccelerationForState(state) 14265 }; 14266 } 14267 14268 function springIntegrateState(state, dt) { 14269 var a = { 14270 dx: state.v, 14271 dv: springAccelerationForState(state) 14272 }, 14273 b = springEvaluateStateWithDerivative(state, dt * 0.5, a), 14274 c = springEvaluateStateWithDerivative(state, dt * 0.5, b), 14275 d = springEvaluateStateWithDerivative(state, dt, c), 14276 dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx), 14277 dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv); 14278 state.x = state.x + dxdt * dt; 14279 state.v = state.v + dvdt * dt; 14280 return state; 14281 } 14282 14283 return function springRK4Factory(tension, friction, duration) { 14284 var initState = { 14285 x: -1, 14286 v: 0, 14287 tension: null, 14288 friction: null 14289 }, 14290 path = [0], 14291 time_lapsed = 0, 14292 tolerance = 1 / 10000, 14293 DT = 16 / 1000, 14294 have_duration, 14295 dt, 14296 last_state; 14297 tension = parseFloat(tension) || 500; 14298 friction = parseFloat(friction) || 20; 14299 duration = duration || null; 14300 initState.tension = tension; 14301 initState.friction = friction; 14302 have_duration = duration !== null; 14303 /* Calculate the actual time it takes for this animation to complete with the provided conditions. */ 14304 14305 if (have_duration) { 14306 /* Run the simulation without a duration. */ 14307 time_lapsed = springRK4Factory(tension, friction); 14308 /* Compute the adjusted time delta. */ 14309 14310 dt = time_lapsed / duration * DT; 14311 } else { 14312 dt = DT; 14313 } 14314 14315 for (;;) { 14316 /* Next/step function .*/ 14317 last_state = springIntegrateState(last_state || initState, dt); 14318 /* Store the position. */ 14319 14320 path.push(1 + last_state.x); 14321 time_lapsed += 16; 14322 /* If the change threshold is reached, break. */ 14323 14324 if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) { 14325 break; 14326 } 14327 } 14328 /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the 14329 computed path and returns a snapshot of the position according to a given percentComplete. */ 14330 14331 14332 return !have_duration ? time_lapsed : function (percentComplete) { 14333 return path[percentComplete * (path.length - 1) | 0]; 14334 }; 14335 }; 14336 }(); 14337 14338 var cubicBezier = function cubicBezier(t1, p1, t2, p2) { 14339 var bezier = generateCubicBezier(t1, p1, t2, p2); 14340 return function (start, end, percent) { 14341 return start + (end - start) * bezier(percent); 14342 }; 14343 }; 14344 14345 var easings = { 14346 'linear': function linear(start, end, percent) { 14347 return start + (end - start) * percent; 14348 }, 14349 // default easings 14350 'ease': cubicBezier(0.25, 0.1, 0.25, 1), 14351 'ease-in': cubicBezier(0.42, 0, 1, 1), 14352 'ease-out': cubicBezier(0, 0, 0.58, 1), 14353 'ease-in-out': cubicBezier(0.42, 0, 0.58, 1), 14354 // sine 14355 'ease-in-sine': cubicBezier(0.47, 0, 0.745, 0.715), 14356 'ease-out-sine': cubicBezier(0.39, 0.575, 0.565, 1), 14357 'ease-in-out-sine': cubicBezier(0.445, 0.05, 0.55, 0.95), 14358 // quad 14359 'ease-in-quad': cubicBezier(0.55, 0.085, 0.68, 0.53), 14360 'ease-out-quad': cubicBezier(0.25, 0.46, 0.45, 0.94), 14361 'ease-in-out-quad': cubicBezier(0.455, 0.03, 0.515, 0.955), 14362 // cubic 14363 'ease-in-cubic': cubicBezier(0.55, 0.055, 0.675, 0.19), 14364 'ease-out-cubic': cubicBezier(0.215, 0.61, 0.355, 1), 14365 'ease-in-out-cubic': cubicBezier(0.645, 0.045, 0.355, 1), 14366 // quart 14367 'ease-in-quart': cubicBezier(0.895, 0.03, 0.685, 0.22), 14368 'ease-out-quart': cubicBezier(0.165, 0.84, 0.44, 1), 14369 'ease-in-out-quart': cubicBezier(0.77, 0, 0.175, 1), 14370 // quint 14371 'ease-in-quint': cubicBezier(0.755, 0.05, 0.855, 0.06), 14372 'ease-out-quint': cubicBezier(0.23, 1, 0.32, 1), 14373 'ease-in-out-quint': cubicBezier(0.86, 0, 0.07, 1), 14374 // expo 14375 'ease-in-expo': cubicBezier(0.95, 0.05, 0.795, 0.035), 14376 'ease-out-expo': cubicBezier(0.19, 1, 0.22, 1), 14377 'ease-in-out-expo': cubicBezier(1, 0, 0, 1), 14378 // circ 14379 'ease-in-circ': cubicBezier(0.6, 0.04, 0.98, 0.335), 14380 'ease-out-circ': cubicBezier(0.075, 0.82, 0.165, 1), 14381 'ease-in-out-circ': cubicBezier(0.785, 0.135, 0.15, 0.86), 14382 // user param easings... 14383 'spring': function spring(tension, friction, duration) { 14384 if (duration === 0) { 14385 // can't get a spring w/ duration 0 14386 return easings.linear; // duration 0 => jump to end so impl doesn't matter 14387 } 14388 14389 var spring = generateSpringRK4(tension, friction, duration); 14390 return function (start, end, percent) { 14391 return start + (end - start) * spring(percent); 14392 }; 14393 }, 14394 'cubic-bezier': cubicBezier 14395 }; 14396 14397 function getEasedValue(type, start, end, percent, easingFn) { 14398 if (percent === 1) { 14399 return end; 14400 } 14401 14402 if (start === end) { 14403 return end; 14404 } 14405 14406 var val = easingFn(start, end, percent); 14407 14408 if (type == null) { 14409 return val; 14410 } 14411 14412 if (type.roundValue || type.color) { 14413 val = Math.round(val); 14414 } 14415 14416 if (type.min !== undefined) { 14417 val = Math.max(val, type.min); 14418 } 14419 14420 if (type.max !== undefined) { 14421 val = Math.min(val, type.max); 14422 } 14423 14424 return val; 14425 } 14426 14427 function getValue(prop, spec) { 14428 if (prop.pfValue != null || prop.value != null) { 14429 if (prop.pfValue != null && (spec == null || spec.type.units !== '%')) { 14430 return prop.pfValue; 14431 } else { 14432 return prop.value; 14433 } 14434 } else { 14435 return prop; 14436 } 14437 } 14438 14439 function ease(startProp, endProp, percent, easingFn, propSpec) { 14440 var type = propSpec != null ? propSpec.type : null; 14441 14442 if (percent < 0) { 14443 percent = 0; 14444 } else if (percent > 1) { 14445 percent = 1; 14446 } 14447 14448 var start = getValue(startProp, propSpec); 14449 var end = getValue(endProp, propSpec); 14450 14451 if (number(start) && number(end)) { 14452 return getEasedValue(type, start, end, percent, easingFn); 14453 } else if (array(start) && array(end)) { 14454 var easedArr = []; 14455 14456 for (var i = 0; i < end.length; i++) { 14457 var si = start[i]; 14458 var ei = end[i]; 14459 14460 if (si != null && ei != null) { 14461 var val = getEasedValue(type, si, ei, percent, easingFn); 14462 easedArr.push(val); 14463 } else { 14464 easedArr.push(ei); 14465 } 14466 } 14467 14468 return easedArr; 14469 } 14470 14471 return undefined; 14472 } 14473 14474 function step(self, ani, now, isCore) { 14475 var isEles = !isCore; 14476 var _p = self._private; 14477 var ani_p = ani._private; 14478 var pEasing = ani_p.easing; 14479 var startTime = ani_p.startTime; 14480 var cy = isCore ? self : self.cy(); 14481 var style = cy.style(); 14482 14483 if (!ani_p.easingImpl) { 14484 if (pEasing == null) { 14485 // use default 14486 ani_p.easingImpl = easings['linear']; 14487 } else { 14488 // then define w/ name 14489 var easingVals; 14490 14491 if (string(pEasing)) { 14492 var easingProp = style.parse('transition-timing-function', pEasing); 14493 easingVals = easingProp.value; 14494 } else { 14495 // then assume preparsed array 14496 easingVals = pEasing; 14497 } 14498 14499 var name, args; 14500 14501 if (string(easingVals)) { 14502 name = easingVals; 14503 args = []; 14504 } else { 14505 name = easingVals[1]; 14506 args = easingVals.slice(2).map(function (n) { 14507 return +n; 14508 }); 14509 } 14510 14511 if (args.length > 0) { 14512 // create with args 14513 if (name === 'spring') { 14514 args.push(ani_p.duration); // need duration to generate spring 14515 } 14516 14517 ani_p.easingImpl = easings[name].apply(null, args); 14518 } else { 14519 // static impl by name 14520 ani_p.easingImpl = easings[name]; 14521 } 14522 } 14523 } 14524 14525 var easing = ani_p.easingImpl; 14526 var percent; 14527 14528 if (ani_p.duration === 0) { 14529 percent = 1; 14530 } else { 14531 percent = (now - startTime) / ani_p.duration; 14532 } 14533 14534 if (ani_p.applying) { 14535 percent = ani_p.progress; 14536 } 14537 14538 if (percent < 0) { 14539 percent = 0; 14540 } else if (percent > 1) { 14541 percent = 1; 14542 } 14543 14544 if (ani_p.delay == null) { 14545 // then update 14546 var startPos = ani_p.startPosition; 14547 var endPos = ani_p.position; 14548 14549 if (endPos && isEles && !self.locked()) { 14550 var newPos = {}; 14551 14552 if (valid(startPos.x, endPos.x)) { 14553 newPos.x = ease(startPos.x, endPos.x, percent, easing); 14554 } 14555 14556 if (valid(startPos.y, endPos.y)) { 14557 newPos.y = ease(startPos.y, endPos.y, percent, easing); 14558 } 14559 14560 self.position(newPos); 14561 } 14562 14563 var startPan = ani_p.startPan; 14564 var endPan = ani_p.pan; 14565 var pan = _p.pan; 14566 var animatingPan = endPan != null && isCore; 14567 14568 if (animatingPan) { 14569 if (valid(startPan.x, endPan.x)) { 14570 pan.x = ease(startPan.x, endPan.x, percent, easing); 14571 } 14572 14573 if (valid(startPan.y, endPan.y)) { 14574 pan.y = ease(startPan.y, endPan.y, percent, easing); 14575 } 14576 14577 self.emit('pan'); 14578 } 14579 14580 var startZoom = ani_p.startZoom; 14581 var endZoom = ani_p.zoom; 14582 var animatingZoom = endZoom != null && isCore; 14583 14584 if (animatingZoom) { 14585 if (valid(startZoom, endZoom)) { 14586 _p.zoom = bound(_p.minZoom, ease(startZoom, endZoom, percent, easing), _p.maxZoom); 14587 } 14588 14589 self.emit('zoom'); 14590 } 14591 14592 if (animatingPan || animatingZoom) { 14593 self.emit('viewport'); 14594 } 14595 14596 var props = ani_p.style; 14597 14598 if (props && props.length > 0 && isEles) { 14599 for (var i = 0; i < props.length; i++) { 14600 var prop = props[i]; 14601 var _name = prop.name; 14602 var end = prop; 14603 var start = ani_p.startStyle[_name]; 14604 var propSpec = style.properties[start.name]; 14605 var easedVal = ease(start, end, percent, easing, propSpec); 14606 style.overrideBypass(self, _name, easedVal); 14607 } // for props 14608 14609 14610 self.emit('style'); 14611 } // if 14612 14613 } 14614 14615 ani_p.progress = percent; 14616 return percent; 14617 } 14618 14619 function valid(start, end) { 14620 if (start == null || end == null) { 14621 return false; 14622 } 14623 14624 if (number(start) && number(end)) { 14625 return true; 14626 } else if (start && end) { 14627 return true; 14628 } 14629 14630 return false; 14631 } 14632 14633 function startAnimation(self, ani, now, isCore) { 14634 var ani_p = ani._private; 14635 ani_p.started = true; 14636 ani_p.startTime = now - ani_p.progress * ani_p.duration; 14637 } 14638 14639 function stepAll(now, cy) { 14640 var eles = cy._private.aniEles; 14641 var doneEles = []; 14642 14643 function stepOne(ele, isCore) { 14644 var _p = ele._private; 14645 var current = _p.animation.current; 14646 var queue = _p.animation.queue; 14647 var ranAnis = false; // cancel all animations on display:none ele 14648 14649 if (!isCore && ele.pstyle('display').value === 'none') { 14650 // put all current and queue animations in this tick's current list 14651 // and empty the lists for the element 14652 current = current.splice(0, current.length).concat(queue.splice(0, queue.length)); // stop all animations 14653 14654 for (var i = 0; i < current.length; i++) { 14655 current[i].stop(); 14656 } 14657 } // if nothing currently animating, get something from the queue 14658 14659 14660 if (current.length === 0) { 14661 var next = queue.shift(); 14662 14663 if (next) { 14664 current.push(next); 14665 } 14666 } 14667 14668 var callbacks = function callbacks(_callbacks) { 14669 for (var j = _callbacks.length - 1; j >= 0; j--) { 14670 var cb = _callbacks[j]; 14671 cb(); 14672 } 14673 14674 _callbacks.splice(0, _callbacks.length); 14675 }; // step and remove if done 14676 14677 14678 for (var _i = current.length - 1; _i >= 0; _i--) { 14679 var ani = current[_i]; 14680 var ani_p = ani._private; 14681 14682 if (ani_p.stopped) { 14683 current.splice(_i, 1); 14684 ani_p.hooked = false; 14685 ani_p.playing = false; 14686 ani_p.started = false; 14687 callbacks(ani_p.frames); 14688 continue; 14689 } 14690 14691 if (!ani_p.playing && !ani_p.applying) { 14692 continue; 14693 } // an apply() while playing shouldn't do anything 14694 14695 14696 if (ani_p.playing && ani_p.applying) { 14697 ani_p.applying = false; 14698 } 14699 14700 if (!ani_p.started) { 14701 startAnimation(ele, ani, now); 14702 } 14703 14704 step(ele, ani, now, isCore); 14705 14706 if (ani_p.applying) { 14707 ani_p.applying = false; 14708 } 14709 14710 callbacks(ani_p.frames); 14711 14712 if (ani_p.step != null) { 14713 ani_p.step(now); 14714 } 14715 14716 if (ani.completed()) { 14717 current.splice(_i, 1); 14718 ani_p.hooked = false; 14719 ani_p.playing = false; 14720 ani_p.started = false; 14721 callbacks(ani_p.completes); 14722 } 14723 14724 ranAnis = true; 14725 } 14726 14727 if (!isCore && current.length === 0 && queue.length === 0) { 14728 doneEles.push(ele); 14729 } 14730 14731 return ranAnis; 14732 } // stepElement 14733 // handle all eles 14734 14735 14736 var ranEleAni = false; 14737 14738 for (var e = 0; e < eles.length; e++) { 14739 var ele = eles[e]; 14740 var handledThisEle = stepOne(ele); 14741 ranEleAni = ranEleAni || handledThisEle; 14742 } // each element 14743 14744 14745 var ranCoreAni = stepOne(cy, true); // notify renderer 14746 14747 if (ranEleAni || ranCoreAni) { 14748 if (eles.length > 0) { 14749 cy.notify('draw', eles); 14750 } else { 14751 cy.notify('draw'); 14752 } 14753 } // remove elements from list of currently animating if its queues are empty 14754 14755 14756 eles.unmerge(doneEles); 14757 cy.emit('step'); 14758 } // stepAll 14759 14760 var corefn$1 = { 14761 // pull in animation functions 14762 animate: define$3.animate(), 14763 animation: define$3.animation(), 14764 animated: define$3.animated(), 14765 clearQueue: define$3.clearQueue(), 14766 delay: define$3.delay(), 14767 delayAnimation: define$3.delayAnimation(), 14768 stop: define$3.stop(), 14769 addToAnimationPool: function addToAnimationPool(eles) { 14770 var cy = this; 14771 14772 if (!cy.styleEnabled()) { 14773 return; 14774 } // save cycles when no style used 14775 14776 14777 cy._private.aniEles.merge(eles); 14778 }, 14779 stopAnimationLoop: function stopAnimationLoop() { 14780 this._private.animationsRunning = false; 14781 }, 14782 startAnimationLoop: function startAnimationLoop() { 14783 var cy = this; 14784 cy._private.animationsRunning = true; 14785 14786 if (!cy.styleEnabled()) { 14787 return; 14788 } // save cycles when no style used 14789 // NB the animation loop will exec in headless environments if style enabled 14790 // and explicit cy.destroy() is necessary to stop the loop 14791 14792 14793 function headlessStep() { 14794 if (!cy._private.animationsRunning) { 14795 return; 14796 } 14797 14798 requestAnimationFrame(function animationStep(now) { 14799 stepAll(now, cy); 14800 headlessStep(); 14801 }); 14802 } 14803 14804 var renderer = cy.renderer(); 14805 14806 if (renderer && renderer.beforeRender) { 14807 // let the renderer schedule animations 14808 renderer.beforeRender(function rendererAnimationStep(willDraw, now) { 14809 stepAll(now, cy); 14810 }, renderer.beforeRenderPriorities.animations); 14811 } else { 14812 // manage the animation loop ourselves 14813 headlessStep(); // first call 14814 } 14815 } 14816 }; 14817 14818 var emitterOptions$1 = { 14819 qualifierCompare: function qualifierCompare(selector1, selector2) { 14820 if (selector1 == null || selector2 == null) { 14821 return selector1 == null && selector2 == null; 14822 } else { 14823 return selector1.sameText(selector2); 14824 } 14825 }, 14826 eventMatches: function eventMatches(cy, listener, eventObj) { 14827 var selector = listener.qualifier; 14828 14829 if (selector != null) { 14830 return cy !== eventObj.target && element(eventObj.target) && selector.matches(eventObj.target); 14831 } 14832 14833 return true; 14834 }, 14835 addEventFields: function addEventFields(cy, evt) { 14836 evt.cy = cy; 14837 evt.target = cy; 14838 }, 14839 callbackContext: function callbackContext(cy, listener, eventObj) { 14840 return listener.qualifier != null ? eventObj.target : cy; 14841 } 14842 }; 14843 14844 var argSelector$1 = function argSelector(arg) { 14845 if (string(arg)) { 14846 return new Selector(arg); 14847 } else { 14848 return arg; 14849 } 14850 }; 14851 14852 var elesfn$v = { 14853 createEmitter: function createEmitter() { 14854 var _p = this._private; 14855 14856 if (!_p.emitter) { 14857 _p.emitter = new Emitter(emitterOptions$1, this); 14858 } 14859 14860 return this; 14861 }, 14862 emitter: function emitter() { 14863 return this._private.emitter; 14864 }, 14865 on: function on(events, selector, callback) { 14866 this.emitter().on(events, argSelector$1(selector), callback); 14867 return this; 14868 }, 14869 removeListener: function removeListener(events, selector, callback) { 14870 this.emitter().removeListener(events, argSelector$1(selector), callback); 14871 return this; 14872 }, 14873 removeAllListeners: function removeAllListeners() { 14874 this.emitter().removeAllListeners(); 14875 return this; 14876 }, 14877 one: function one(events, selector, callback) { 14878 this.emitter().one(events, argSelector$1(selector), callback); 14879 return this; 14880 }, 14881 once: function once(events, selector, callback) { 14882 this.emitter().one(events, argSelector$1(selector), callback); 14883 return this; 14884 }, 14885 emit: function emit(events, extraParams) { 14886 this.emitter().emit(events, extraParams); 14887 return this; 14888 }, 14889 emitAndNotify: function emitAndNotify(event, eles) { 14890 this.emit(event); 14891 this.notify(event, eles); 14892 return this; 14893 } 14894 }; 14895 define$3.eventAliasesOn(elesfn$v); 14896 14897 var corefn$2 = { 14898 png: function png(options) { 14899 var renderer = this._private.renderer; 14900 options = options || {}; 14901 return renderer.png(options); 14902 }, 14903 jpg: function jpg(options) { 14904 var renderer = this._private.renderer; 14905 options = options || {}; 14906 options.bg = options.bg || '#fff'; 14907 return renderer.jpg(options); 14908 } 14909 }; 14910 corefn$2.jpeg = corefn$2.jpg; 14911 14912 var corefn$3 = { 14913 layout: function layout(options) { 14914 var cy = this; 14915 14916 if (options == null) { 14917 error('Layout options must be specified to make a layout'); 14918 return; 14919 } 14920 14921 if (options.name == null) { 14922 error('A `name` must be specified to make a layout'); 14923 return; 14924 } 14925 14926 var name = options.name; 14927 var Layout = cy.extension('layout', name); 14928 14929 if (Layout == null) { 14930 error('No such layout `' + name + '` found. Did you forget to import it and `cytoscape.use()` it?'); 14931 return; 14932 } 14933 14934 var eles; 14935 14936 if (string(options.eles)) { 14937 eles = cy.$(options.eles); 14938 } else { 14939 eles = options.eles != null ? options.eles : cy.$(); 14940 } 14941 14942 var layout = new Layout(extend({}, options, { 14943 cy: cy, 14944 eles: eles 14945 })); 14946 return layout; 14947 } 14948 }; 14949 corefn$3.createLayout = corefn$3.makeLayout = corefn$3.layout; 14950 14951 var corefn$4 = { 14952 notify: function notify(eventName, eventEles) { 14953 var _p = this._private; 14954 14955 if (this.batching()) { 14956 _p.batchNotifications = _p.batchNotifications || {}; 14957 var eles = _p.batchNotifications[eventName] = _p.batchNotifications[eventName] || this.collection(); 14958 14959 if (eventEles != null) { 14960 eles.merge(eventEles); 14961 } 14962 14963 return; // notifications are disabled during batching 14964 } 14965 14966 if (!_p.notificationsEnabled) { 14967 return; 14968 } // exit on disabled 14969 14970 14971 var renderer = this.renderer(); // exit if destroy() called on core or renderer in between frames #1499 #1528 14972 14973 if (this.destroyed() || !renderer) { 14974 return; 14975 } 14976 14977 renderer.notify(eventName, eventEles); 14978 }, 14979 notifications: function notifications(bool) { 14980 var p = this._private; 14981 14982 if (bool === undefined) { 14983 return p.notificationsEnabled; 14984 } else { 14985 p.notificationsEnabled = bool ? true : false; 14986 } 14987 14988 return this; 14989 }, 14990 noNotifications: function noNotifications(callback) { 14991 this.notifications(false); 14992 callback(); 14993 this.notifications(true); 14994 }, 14995 batching: function batching() { 14996 return this._private.batchCount > 0; 14997 }, 14998 startBatch: function startBatch() { 14999 var _p = this._private; 15000 15001 if (_p.batchCount == null) { 15002 _p.batchCount = 0; 15003 } 15004 15005 if (_p.batchCount === 0) { 15006 _p.batchStyleEles = this.collection(); 15007 _p.batchNotifications = {}; 15008 } 15009 15010 _p.batchCount++; 15011 return this; 15012 }, 15013 endBatch: function endBatch() { 15014 var _p = this._private; 15015 15016 if (_p.batchCount === 0) { 15017 return this; 15018 } 15019 15020 _p.batchCount--; 15021 15022 if (_p.batchCount === 0) { 15023 // update style for dirty eles 15024 _p.batchStyleEles.updateStyle(); 15025 15026 var renderer = this.renderer(); // notify the renderer of queued eles and event types 15027 15028 Object.keys(_p.batchNotifications).forEach(function (eventName) { 15029 var eles = _p.batchNotifications[eventName]; 15030 15031 if (eles.empty()) { 15032 renderer.notify(eventName); 15033 } else { 15034 renderer.notify(eventName, eles); 15035 } 15036 }); 15037 } 15038 15039 return this; 15040 }, 15041 batch: function batch(callback) { 15042 this.startBatch(); 15043 callback(); 15044 this.endBatch(); 15045 return this; 15046 }, 15047 // for backwards compatibility 15048 batchData: function batchData(map) { 15049 var cy = this; 15050 return this.batch(function () { 15051 var ids = Object.keys(map); 15052 15053 for (var i = 0; i < ids.length; i++) { 15054 var id = ids[i]; 15055 var data = map[id]; 15056 var ele = cy.getElementById(id); 15057 ele.data(data); 15058 } 15059 }); 15060 } 15061 }; 15062 15063 var rendererDefaults = defaults({ 15064 hideEdgesOnViewport: false, 15065 textureOnViewport: false, 15066 motionBlur: false, 15067 motionBlurOpacity: 0.05, 15068 pixelRatio: undefined, 15069 desktopTapThreshold: 4, 15070 touchTapThreshold: 8, 15071 wheelSensitivity: 1, 15072 debug: false, 15073 showFps: false 15074 }); 15075 var corefn$5 = { 15076 renderTo: function renderTo(context, zoom, pan, pxRatio) { 15077 var r = this._private.renderer; 15078 r.renderTo(context, zoom, pan, pxRatio); 15079 return this; 15080 }, 15081 renderer: function renderer() { 15082 return this._private.renderer; 15083 }, 15084 forceRender: function forceRender() { 15085 this.notify('draw'); 15086 return this; 15087 }, 15088 resize: function resize() { 15089 this.invalidateSize(); 15090 this.emitAndNotify('resize'); 15091 return this; 15092 }, 15093 initRenderer: function initRenderer(options) { 15094 var cy = this; 15095 var RendererProto = cy.extension('renderer', options.name); 15096 15097 if (RendererProto == null) { 15098 error("Can not initialise: No such renderer `".concat(options.name, "` found. Did you forget to import it and `cytoscape.use()` it?")); 15099 return; 15100 } 15101 15102 if (options.wheelSensitivity !== undefined) { 15103 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."); 15104 } 15105 15106 var rOpts = rendererDefaults(options); 15107 rOpts.cy = cy; 15108 cy._private.renderer = new RendererProto(rOpts); 15109 this.notify('init'); 15110 }, 15111 destroyRenderer: function destroyRenderer() { 15112 var cy = this; 15113 cy.notify('destroy'); // destroy the renderer 15114 15115 var domEle = cy.container(); 15116 15117 if (domEle) { 15118 domEle._cyreg = null; 15119 15120 while (domEle.childNodes.length > 0) { 15121 domEle.removeChild(domEle.childNodes[0]); 15122 } 15123 } 15124 15125 cy._private.renderer = null; // to be extra safe, remove the ref 15126 15127 cy.mutableElements().forEach(function (ele) { 15128 var _p = ele._private; 15129 _p.rscratch = {}; 15130 _p.rstyle = {}; 15131 _p.animation.current = []; 15132 _p.animation.queue = []; 15133 }); 15134 }, 15135 onRender: function onRender(fn) { 15136 return this.on('render', fn); 15137 }, 15138 offRender: function offRender(fn) { 15139 return this.off('render', fn); 15140 } 15141 }; 15142 corefn$5.invalidateDimensions = corefn$5.resize; 15143 15144 var corefn$6 = { 15145 // get a collection 15146 // - empty collection on no args 15147 // - collection of elements in the graph on selector arg 15148 // - guarantee a returned collection when elements or collection specified 15149 collection: function collection(eles, opts) { 15150 if (string(eles)) { 15151 return this.$(eles); 15152 } else if (elementOrCollection(eles)) { 15153 return eles.collection(); 15154 } else if (array(eles)) { 15155 return new Collection(this, eles, opts); 15156 } 15157 15158 return new Collection(this); 15159 }, 15160 nodes: function nodes(selector) { 15161 var nodes = this.$(function (ele) { 15162 return ele.isNode(); 15163 }); 15164 15165 if (selector) { 15166 return nodes.filter(selector); 15167 } 15168 15169 return nodes; 15170 }, 15171 edges: function edges(selector) { 15172 var edges = this.$(function (ele) { 15173 return ele.isEdge(); 15174 }); 15175 15176 if (selector) { 15177 return edges.filter(selector); 15178 } 15179 15180 return edges; 15181 }, 15182 // search the graph like jQuery 15183 $: function $(selector) { 15184 var eles = this._private.elements; 15185 15186 if (selector) { 15187 return eles.filter(selector); 15188 } else { 15189 return eles.spawnSelf(); 15190 } 15191 }, 15192 mutableElements: function mutableElements() { 15193 return this._private.elements; 15194 } 15195 }; // aliases 15196 15197 corefn$6.elements = corefn$6.filter = corefn$6.$; 15198 15199 var styfn = {}; // keys for style blocks, e.g. ttfftt 15200 15201 var TRUE = 't'; 15202 var FALSE = 'f'; // (potentially expensive calculation) 15203 // apply the style to the element based on 15204 // - its bypass 15205 // - what selectors match it 15206 15207 styfn.apply = function (eles) { 15208 var self = this; 15209 var _p = self._private; 15210 var cy = _p.cy; 15211 var updatedEles = cy.collection(); 15212 15213 if (_p.newStyle) { 15214 // clear style caches 15215 _p.contextStyles = {}; 15216 _p.propDiffs = {}; 15217 self.cleanElements(eles, true); 15218 } 15219 15220 for (var ie = 0; ie < eles.length; ie++) { 15221 var ele = eles[ie]; 15222 var cxtMeta = self.getContextMeta(ele); 15223 15224 if (cxtMeta.empty) { 15225 continue; 15226 } 15227 15228 var cxtStyle = self.getContextStyle(cxtMeta); 15229 var app = self.applyContextStyle(cxtMeta, cxtStyle, ele); 15230 15231 if (!_p.newStyle) { 15232 self.updateTransitions(ele, app.diffProps); 15233 } 15234 15235 var hintsDiff = self.updateStyleHints(ele); 15236 15237 if (hintsDiff) { 15238 updatedEles.merge(ele); 15239 } 15240 } // for elements 15241 15242 15243 _p.newStyle = false; 15244 return updatedEles; 15245 }; 15246 15247 styfn.getPropertiesDiff = function (oldCxtKey, newCxtKey) { 15248 var self = this; 15249 var cache = self._private.propDiffs = self._private.propDiffs || {}; 15250 var dualCxtKey = oldCxtKey + '-' + newCxtKey; 15251 var cachedVal = cache[dualCxtKey]; 15252 15253 if (cachedVal) { 15254 return cachedVal; 15255 } 15256 15257 var diffProps = []; 15258 var addedProp = {}; 15259 15260 for (var i = 0; i < self.length; i++) { 15261 var cxt = self[i]; 15262 var oldHasCxt = oldCxtKey[i] === TRUE; 15263 var newHasCxt = newCxtKey[i] === TRUE; 15264 var cxtHasDiffed = oldHasCxt !== newHasCxt; 15265 var cxtHasMappedProps = cxt.mappedProperties.length > 0; 15266 15267 if (cxtHasDiffed || newHasCxt && cxtHasMappedProps) { 15268 var props = void 0; 15269 15270 if (cxtHasDiffed && cxtHasMappedProps) { 15271 props = cxt.properties; // suffices b/c mappedProperties is a subset of properties 15272 } else if (cxtHasDiffed) { 15273 props = cxt.properties; // need to check them all 15274 } else if (cxtHasMappedProps) { 15275 props = cxt.mappedProperties; // only need to check mapped 15276 } 15277 15278 for (var j = 0; j < props.length; j++) { 15279 var prop = props[j]; 15280 var name = prop.name; // if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter 15281 // (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result 15282 // is cached) 15283 15284 var laterCxtOverrides = false; 15285 15286 for (var k = i + 1; k < self.length; k++) { 15287 var laterCxt = self[k]; 15288 var hasLaterCxt = newCxtKey[k] === TRUE; 15289 15290 if (!hasLaterCxt) { 15291 continue; 15292 } // can't override unless the context is active 15293 15294 15295 laterCxtOverrides = laterCxt.properties[prop.name] != null; 15296 15297 if (laterCxtOverrides) { 15298 break; 15299 } // exit early as long as one later context overrides 15300 15301 } 15302 15303 if (!addedProp[name] && !laterCxtOverrides) { 15304 addedProp[name] = true; 15305 diffProps.push(name); 15306 } 15307 } // for props 15308 15309 } // if 15310 15311 } // for contexts 15312 15313 15314 cache[dualCxtKey] = diffProps; 15315 return diffProps; 15316 }; 15317 15318 styfn.getContextMeta = function (ele) { 15319 var self = this; 15320 var cxtKey = ''; 15321 var diffProps; 15322 var prevKey = ele._private.styleCxtKey || ''; 15323 15324 if (self._private.newStyle) { 15325 prevKey = ''; // since we need to apply all style if a fresh stylesheet 15326 } // get the cxt key 15327 15328 15329 for (var i = 0; i < self.length; i++) { 15330 var context = self[i]; 15331 var contextSelectorMatches = context.selector && context.selector.matches(ele); // NB: context.selector may be null for 'core' 15332 15333 if (contextSelectorMatches) { 15334 cxtKey += TRUE; 15335 } else { 15336 cxtKey += FALSE; 15337 } 15338 } // for context 15339 15340 15341 diffProps = self.getPropertiesDiff(prevKey, cxtKey); 15342 ele._private.styleCxtKey = cxtKey; 15343 return { 15344 key: cxtKey, 15345 diffPropNames: diffProps, 15346 empty: diffProps.length === 0 15347 }; 15348 }; // gets a computed ele style object based on matched contexts 15349 15350 15351 styfn.getContextStyle = function (cxtMeta) { 15352 var cxtKey = cxtMeta.key; 15353 var self = this; 15354 var cxtStyles = this._private.contextStyles = this._private.contextStyles || {}; // if already computed style, returned cached copy 15355 15356 if (cxtStyles[cxtKey]) { 15357 return cxtStyles[cxtKey]; 15358 } 15359 15360 var style = { 15361 _private: { 15362 key: cxtKey 15363 } 15364 }; 15365 15366 for (var i = 0; i < self.length; i++) { 15367 var cxt = self[i]; 15368 var hasCxt = cxtKey[i] === TRUE; 15369 15370 if (!hasCxt) { 15371 continue; 15372 } 15373 15374 for (var j = 0; j < cxt.properties.length; j++) { 15375 var prop = cxt.properties[j]; 15376 style[prop.name] = prop; 15377 } 15378 } 15379 15380 cxtStyles[cxtKey] = style; 15381 return style; 15382 }; 15383 15384 styfn.applyContextStyle = function (cxtMeta, cxtStyle, ele) { 15385 var self = this; 15386 var diffProps = cxtMeta.diffPropNames; 15387 var retDiffProps = {}; 15388 var types = self.types; 15389 15390 for (var i = 0; i < diffProps.length; i++) { 15391 var diffPropName = diffProps[i]; 15392 var cxtProp = cxtStyle[diffPropName]; 15393 var eleProp = ele.pstyle(diffPropName); 15394 15395 if (!cxtProp) { 15396 // no context prop means delete 15397 if (!eleProp) { 15398 continue; // no existing prop means nothing needs to be removed 15399 // nb affects initial application on mapped values like control-point-distances 15400 } else if (eleProp.bypass) { 15401 cxtProp = { 15402 name: diffPropName, 15403 deleteBypassed: true 15404 }; 15405 } else { 15406 cxtProp = { 15407 name: diffPropName, 15408 "delete": true 15409 }; 15410 } 15411 } // save cycles when the context prop doesn't need to be applied 15412 15413 15414 if (eleProp === cxtProp) { 15415 continue; 15416 } // save cycles when a mapped context prop doesn't need to be applied 15417 15418 15419 if (cxtProp.mapped === types.fn // context prop is function mapper 15420 && eleProp != null // some props can be null even by default (e.g. a prop that overrides another one) 15421 && eleProp.mapping != null // ele prop is a concrete value from from a mapper 15422 && eleProp.mapping.value === cxtProp.value // the current prop on the ele is a flat prop value for the function mapper 15423 ) { 15424 // NB don't write to cxtProp, as it's shared among eles (stored in stylesheet) 15425 var mapping = eleProp.mapping; // can write to mapping, as it's a per-ele copy 15426 15427 var fnValue = mapping.fnValue = cxtProp.value(ele); // temporarily cache the value in case of a miss 15428 15429 if (fnValue === mapping.prevFnValue) { 15430 continue; 15431 } 15432 } 15433 15434 var retDiffProp = retDiffProps[diffPropName] = { 15435 prev: eleProp 15436 }; 15437 self.applyParsedProperty(ele, cxtProp); 15438 retDiffProp.next = ele.pstyle(diffPropName); 15439 15440 if (retDiffProp.next && retDiffProp.next.bypass) { 15441 retDiffProp.next = retDiffProp.next.bypassed; 15442 } 15443 } 15444 15445 return { 15446 diffProps: retDiffProps 15447 }; 15448 }; 15449 15450 styfn.updateStyleHints = function (ele) { 15451 var _p = ele._private; 15452 var self = this; 15453 var propNames = self.propertyGroupNames; 15454 var propGrKeys = self.propertyGroupKeys; 15455 15456 var propHash = function propHash(ele, propNames, seedKey) { 15457 return self.getPropertiesHash(ele, propNames, seedKey); 15458 }; 15459 15460 var oldStyleKey = _p.styleKey; 15461 15462 if (ele.removed()) { 15463 return false; 15464 } 15465 15466 var isNode = _p.group === 'nodes'; // get the style key hashes per prop group 15467 // but lazily -- only use non-default prop values to reduce the number of hashes 15468 // 15469 15470 var overriddenStyles = ele._private.style; 15471 propNames = Object.keys(overriddenStyles); 15472 15473 for (var i = 0; i < propGrKeys.length; i++) { 15474 var grKey = propGrKeys[i]; 15475 _p.styleKeys[grKey] = 0; 15476 } 15477 15478 var updateGrKey = function updateGrKey(val, grKey) { 15479 return _p.styleKeys[grKey] = hashInt(val, _p.styleKeys[grKey]); 15480 }; 15481 15482 var updateGrKeyWStr = function updateGrKeyWStr(strVal, grKey) { 15483 for (var j = 0; j < strVal.length; j++) { 15484 updateGrKey(strVal.charCodeAt(j), grKey); 15485 } 15486 }; // - hashing works on 32 bit ints b/c we use bitwise ops 15487 // - small numbers get cut off (e.g. 0.123 is seen as 0 by the hashing function) 15488 // - raise up small numbers so more significant digits are seen by hashing 15489 // - make small numbers larger than a normal value to avoid collisions 15490 // - works in practice and it's relatively cheap 15491 15492 15493 var N = 2000000000; 15494 15495 var cleanNum = function cleanNum(val) { 15496 return -128 < val && val < 128 && Math.floor(val) !== val ? N - (val * 1024 | 0) : val; 15497 }; 15498 15499 for (var _i = 0; _i < propNames.length; _i++) { 15500 var name = propNames[_i]; 15501 var parsedProp = overriddenStyles[name]; 15502 15503 if (parsedProp == null) { 15504 continue; 15505 } 15506 15507 var propInfo = this.properties[name]; 15508 var type = propInfo.type; 15509 var _grKey = propInfo.groupKey; 15510 var normalizedNumberVal = void 0; 15511 15512 if (propInfo.hashOverride != null) { 15513 normalizedNumberVal = propInfo.hashOverride(ele, parsedProp); 15514 } else if (parsedProp.pfValue != null) { 15515 normalizedNumberVal = parsedProp.pfValue; 15516 } // might not be a number if it allows enums 15517 15518 15519 var numberVal = propInfo.enums == null ? parsedProp.value : null; 15520 var haveNormNum = normalizedNumberVal != null; 15521 var haveUnitedNum = numberVal != null; 15522 var haveNum = haveNormNum || haveUnitedNum; 15523 var units = parsedProp.units; // numbers are cheaper to hash than strings 15524 // 1 hash op vs n hash ops (for length n string) 15525 15526 if (type.number && haveNum) { 15527 var v = haveNormNum ? normalizedNumberVal : numberVal; 15528 15529 if (type.multiple) { 15530 for (var _i2 = 0; _i2 < v.length; _i2++) { 15531 updateGrKey(cleanNum(v[_i2]), _grKey); 15532 } 15533 } else { 15534 updateGrKey(cleanNum(v), _grKey); 15535 } 15536 15537 if (!haveNormNum && units != null) { 15538 updateGrKeyWStr(units, _grKey); 15539 } 15540 } else { 15541 updateGrKeyWStr(parsedProp.strValue, _grKey); 15542 } 15543 } // overall style key 15544 // 15545 15546 15547 var hash = 0; 15548 15549 for (var _i3 = 0; _i3 < propGrKeys.length; _i3++) { 15550 var _grKey2 = propGrKeys[_i3]; 15551 var grHash = _p.styleKeys[_grKey2]; 15552 hash = hashInt(grHash, hash); 15553 } 15554 15555 _p.styleKey = hash; // label dims 15556 // 15557 15558 var labelDimsKey = _p.labelDimsKey = _p.styleKeys.labelDimensions; 15559 _p.labelKey = propHash(ele, ['label'], labelDimsKey); 15560 _p.labelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.labelKey); 15561 15562 if (!isNode) { 15563 _p.sourceLabelKey = propHash(ele, ['source-label'], labelDimsKey); 15564 _p.sourceLabelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.sourceLabelKey); 15565 _p.targetLabelKey = propHash(ele, ['target-label'], labelDimsKey); 15566 _p.targetLabelStyleKey = hashInt(_p.styleKeys.commonLabel, _p.targetLabelKey); 15567 } // node 15568 // 15569 15570 15571 if (isNode) { 15572 var _p$styleKeys = _p.styleKeys, 15573 nodeBody = _p$styleKeys.nodeBody, 15574 nodeBorder = _p$styleKeys.nodeBorder, 15575 backgroundImage = _p$styleKeys.backgroundImage, 15576 compound = _p$styleKeys.compound, 15577 pie = _p$styleKeys.pie; 15578 _p.nodeKey = hashIntsArray([nodeBorder, backgroundImage, compound, pie], nodeBody); 15579 _p.hasPie = pie != 0; 15580 } 15581 15582 return oldStyleKey !== _p.styleKey; 15583 }; 15584 15585 styfn.clearStyleHints = function (ele) { 15586 var _p = ele._private; 15587 _p.styleKeys = {}; 15588 _p.styleKey = null; 15589 _p.labelKey = null; 15590 _p.labelStyleKey = null; 15591 _p.sourceLabelKey = null; 15592 _p.sourceLabelStyleKey = null; 15593 _p.targetLabelKey = null; 15594 _p.targetLabelStyleKey = null; 15595 _p.nodeKey = null; 15596 _p.hasPie = null; 15597 }; // apply a property to the style (for internal use) 15598 // returns whether application was successful 15599 // 15600 // now, this function flattens the property, and here's how: 15601 // 15602 // for parsedProp:{ bypass: true, deleteBypass: true } 15603 // no property is generated, instead the bypass property in the 15604 // element's style is replaced by what's pointed to by the `bypassed` 15605 // field in the bypass property (i.e. restoring the property the 15606 // bypass was overriding) 15607 // 15608 // for parsedProp:{ mapped: truthy } 15609 // the generated flattenedProp:{ mapping: prop } 15610 // 15611 // for parsedProp:{ bypass: true } 15612 // the generated flattenedProp:{ bypassed: parsedProp } 15613 15614 15615 styfn.applyParsedProperty = function (ele, parsedProp) { 15616 var self = this; 15617 var prop = parsedProp; 15618 var style = ele._private.style; 15619 var flatProp; 15620 var types = self.types; 15621 var type = self.properties[prop.name].type; 15622 var propIsBypass = prop.bypass; 15623 var origProp = style[prop.name]; 15624 var origPropIsBypass = origProp && origProp.bypass; 15625 var _p = ele._private; 15626 var flatPropMapping = 'mapping'; 15627 15628 var getVal = function getVal(p) { 15629 if (p == null) { 15630 return null; 15631 } else if (p.pfValue != null) { 15632 return p.pfValue; 15633 } else { 15634 return p.value; 15635 } 15636 }; 15637 15638 var checkTriggers = function checkTriggers() { 15639 var fromVal = getVal(origProp); 15640 var toVal = getVal(prop); 15641 self.checkTriggers(ele, prop.name, fromVal, toVal); 15642 }; // edge sanity checks to prevent the client from making serious mistakes 15643 15644 15645 if (parsedProp.name === 'curve-style' && ele.isEdge() && ( // loops must be bundled beziers 15646 parsedProp.value !== 'bezier' && ele.isLoop() || // edges connected to compound nodes can not be haystacks 15647 parsedProp.value === 'haystack' && (ele.source().isParent() || ele.target().isParent()))) { 15648 prop = parsedProp = this.parse(parsedProp.name, 'bezier', propIsBypass); 15649 } 15650 15651 if (prop["delete"]) { 15652 // delete the property and use the default value on falsey value 15653 style[prop.name] = undefined; 15654 checkTriggers(); 15655 return true; 15656 } 15657 15658 if (prop.deleteBypassed) { 15659 // delete the property that the 15660 if (!origProp) { 15661 checkTriggers(); 15662 return true; // can't delete if no prop 15663 } else if (origProp.bypass) { 15664 // delete bypassed 15665 origProp.bypassed = undefined; 15666 checkTriggers(); 15667 return true; 15668 } else { 15669 return false; // we're unsuccessful deleting the bypassed 15670 } 15671 } // check if we need to delete the current bypass 15672 15673 15674 if (prop.deleteBypass) { 15675 // then this property is just here to indicate we need to delete 15676 if (!origProp) { 15677 checkTriggers(); 15678 return true; // property is already not defined 15679 } else if (origProp.bypass) { 15680 // then replace the bypass property with the original 15681 // because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary) 15682 style[prop.name] = origProp.bypassed; 15683 checkTriggers(); 15684 return true; 15685 } else { 15686 return false; // we're unsuccessful deleting the bypass 15687 } 15688 } 15689 15690 var printMappingErr = function printMappingErr() { 15691 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'); 15692 }; // put the property in the style objects 15693 15694 15695 switch (prop.mapped) { 15696 // flatten the property if mapped 15697 case types.mapData: 15698 { 15699 // flatten the field (e.g. data.foo.bar) 15700 var fields = prop.field.split('.'); 15701 var fieldVal = _p.data; 15702 15703 for (var i = 0; i < fields.length && fieldVal; i++) { 15704 var field = fields[i]; 15705 fieldVal = fieldVal[field]; 15706 } 15707 15708 if (fieldVal == null) { 15709 printMappingErr(); 15710 return false; 15711 } 15712 15713 var percent; 15714 15715 if (!number(fieldVal)) { 15716 // then don't apply and fall back on the existing style 15717 warn('Do not use continuous mappers without specifying numeric data (i.e. `' + prop.field + ': ' + fieldVal + '` for `' + ele.id() + '` is non-numeric)'); 15718 return false; 15719 } else { 15720 var fieldWidth = prop.fieldMax - prop.fieldMin; 15721 15722 if (fieldWidth === 0) { 15723 // safety check -- not strictly necessary as no props of zero range should be passed here 15724 percent = 0; 15725 } else { 15726 percent = (fieldVal - prop.fieldMin) / fieldWidth; 15727 } 15728 } // make sure to bound percent value 15729 15730 15731 if (percent < 0) { 15732 percent = 0; 15733 } else if (percent > 1) { 15734 percent = 1; 15735 } 15736 15737 if (type.color) { 15738 var r1 = prop.valueMin[0]; 15739 var r2 = prop.valueMax[0]; 15740 var g1 = prop.valueMin[1]; 15741 var g2 = prop.valueMax[1]; 15742 var b1 = prop.valueMin[2]; 15743 var b2 = prop.valueMax[2]; 15744 var a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3]; 15745 var a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3]; 15746 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)]; 15747 flatProp = { 15748 // colours are simple, so just create the flat property instead of expensive string parsing 15749 bypass: prop.bypass, 15750 // we're a bypass if the mapping property is a bypass 15751 name: prop.name, 15752 value: clr, 15753 strValue: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')' 15754 }; 15755 } else if (type.number) { 15756 var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent; 15757 flatProp = this.parse(prop.name, calcValue, prop.bypass, flatPropMapping); 15758 } else { 15759 return false; // can only map to colours and numbers 15760 } 15761 15762 if (!flatProp) { 15763 // if we can't flatten the property, then don't apply the property and fall back on the existing style 15764 printMappingErr(); 15765 return false; 15766 } 15767 15768 flatProp.mapping = prop; // keep a reference to the mapping 15769 15770 prop = flatProp; // the flattened (mapped) property is the one we want 15771 15772 break; 15773 } 15774 // direct mapping 15775 15776 case types.data: 15777 { 15778 // flatten the field (e.g. data.foo.bar) 15779 var _fields = prop.field.split('.'); 15780 15781 var _fieldVal = _p.data; 15782 15783 for (var _i4 = 0; _i4 < _fields.length && _fieldVal; _i4++) { 15784 var _field = _fields[_i4]; 15785 _fieldVal = _fieldVal[_field]; 15786 } 15787 15788 if (_fieldVal != null) { 15789 flatProp = this.parse(prop.name, _fieldVal, prop.bypass, flatPropMapping); 15790 } 15791 15792 if (!flatProp) { 15793 // if we can't flatten the property, then don't apply and fall back on the existing style 15794 printMappingErr(); 15795 return false; 15796 } 15797 15798 flatProp.mapping = prop; // keep a reference to the mapping 15799 15800 prop = flatProp; // the flattened (mapped) property is the one we want 15801 15802 break; 15803 } 15804 15805 case types.fn: 15806 { 15807 var fn = prop.value; 15808 var fnRetVal = prop.fnValue != null ? prop.fnValue : fn(ele); // check for cached value before calling function 15809 15810 prop.prevFnValue = fnRetVal; 15811 15812 if (fnRetVal == null) { 15813 warn('Custom function mappers may not return null (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is null)'); 15814 return false; 15815 } 15816 15817 flatProp = this.parse(prop.name, fnRetVal, prop.bypass, flatPropMapping); 15818 15819 if (!flatProp) { 15820 warn('Custom function mappers may not return invalid values for the property type (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is invalid)'); 15821 return false; 15822 } 15823 15824 flatProp.mapping = copy(prop); // keep a reference to the mapping 15825 15826 prop = flatProp; // the flattened (mapped) property is the one we want 15827 15828 break; 15829 } 15830 15831 case undefined: 15832 break; 15833 // just set the property 15834 15835 default: 15836 return false; 15837 // not a valid mapping 15838 } // if the property is a bypass property, then link the resultant property to the original one 15839 15840 15841 if (propIsBypass) { 15842 if (origPropIsBypass) { 15843 // then this bypass overrides the existing one 15844 prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass 15845 } else { 15846 // then link the orig prop to the new bypass 15847 prop.bypassed = origProp; 15848 } 15849 15850 style[prop.name] = prop; // and set 15851 } else { 15852 // prop is not bypass 15853 if (origPropIsBypass) { 15854 // then keep the orig prop (since it's a bypass) and link to the new prop 15855 origProp.bypassed = prop; 15856 } else { 15857 // then just replace the old prop with the new one 15858 style[prop.name] = prop; 15859 } 15860 } 15861 15862 checkTriggers(); 15863 return true; 15864 }; 15865 15866 styfn.cleanElements = function (eles, keepBypasses) { 15867 for (var i = 0; i < eles.length; i++) { 15868 var ele = eles[i]; 15869 this.clearStyleHints(ele); 15870 ele.dirtyCompoundBoundsCache(); 15871 ele.dirtyBoundingBoxCache(); 15872 15873 if (!keepBypasses) { 15874 ele._private.style = {}; 15875 } else { 15876 var style = ele._private.style; 15877 var propNames = Object.keys(style); 15878 15879 for (var j = 0; j < propNames.length; j++) { 15880 var propName = propNames[j]; 15881 var eleProp = style[propName]; 15882 15883 if (eleProp != null) { 15884 if (eleProp.bypass) { 15885 eleProp.bypassed = null; 15886 } else { 15887 style[propName] = null; 15888 } 15889 } 15890 } 15891 } 15892 } 15893 }; // updates the visual style for all elements (useful for manual style modification after init) 15894 15895 15896 styfn.update = function () { 15897 var cy = this._private.cy; 15898 var eles = cy.mutableElements(); 15899 eles.updateStyle(); 15900 }; // diffProps : { name => { prev, next } } 15901 15902 15903 styfn.updateTransitions = function (ele, diffProps) { 15904 var self = this; 15905 var _p = ele._private; 15906 var props = ele.pstyle('transition-property').value; 15907 var duration = ele.pstyle('transition-duration').pfValue; 15908 var delay = ele.pstyle('transition-delay').pfValue; 15909 15910 if (props.length > 0 && duration > 0) { 15911 var style = {}; // build up the style to animate towards 15912 15913 var anyPrev = false; 15914 15915 for (var i = 0; i < props.length; i++) { 15916 var prop = props[i]; 15917 var styProp = ele.pstyle(prop); 15918 var diffProp = diffProps[prop]; 15919 15920 if (!diffProp) { 15921 continue; 15922 } 15923 15924 var prevProp = diffProp.prev; 15925 var fromProp = prevProp; 15926 var toProp = diffProp.next != null ? diffProp.next : styProp; 15927 var diff = false; 15928 var initVal = void 0; 15929 var initDt = 0.000001; // delta time % value for initVal (allows animating out of init zero opacity) 15930 15931 if (!fromProp) { 15932 continue; 15933 } // consider px values 15934 15935 15936 if (number(fromProp.pfValue) && number(toProp.pfValue)) { 15937 diff = toProp.pfValue - fromProp.pfValue; // nonzero is truthy 15938 15939 initVal = fromProp.pfValue + initDt * diff; // consider numerical values 15940 } else if (number(fromProp.value) && number(toProp.value)) { 15941 diff = toProp.value - fromProp.value; // nonzero is truthy 15942 15943 initVal = fromProp.value + initDt * diff; // consider colour values 15944 } else if (array(fromProp.value) && array(toProp.value)) { 15945 diff = fromProp.value[0] !== toProp.value[0] || fromProp.value[1] !== toProp.value[1] || fromProp.value[2] !== toProp.value[2]; 15946 initVal = fromProp.strValue; 15947 } // the previous value is good for an animation only if it's different 15948 15949 15950 if (diff) { 15951 style[prop] = toProp.strValue; // to val 15952 15953 this.applyBypass(ele, prop, initVal); // from val 15954 15955 anyPrev = true; 15956 } 15957 } // end if props allow ani 15958 // can't transition if there's nothing previous to transition from 15959 15960 15961 if (!anyPrev) { 15962 return; 15963 } 15964 15965 _p.transitioning = true; 15966 new Promise$1(function (resolve) { 15967 if (delay > 0) { 15968 ele.delayAnimation(delay).play().promise().then(resolve); 15969 } else { 15970 resolve(); 15971 } 15972 }).then(function () { 15973 return ele.animation({ 15974 style: style, 15975 duration: duration, 15976 easing: ele.pstyle('transition-timing-function').value, 15977 queue: false 15978 }).play().promise(); 15979 }).then(function () { 15980 // if( !isBypass ){ 15981 self.removeBypasses(ele, props); 15982 ele.emitAndNotify('style'); // } 15983 15984 _p.transitioning = false; 15985 }); 15986 } else if (_p.transitioning) { 15987 this.removeBypasses(ele, props); 15988 ele.emitAndNotify('style'); 15989 _p.transitioning = false; 15990 } 15991 }; 15992 15993 styfn.checkTrigger = function (ele, name, fromValue, toValue, getTrigger, onTrigger) { 15994 var prop = this.properties[name]; 15995 var triggerCheck = getTrigger(prop); 15996 15997 if (triggerCheck != null && triggerCheck(fromValue, toValue)) { 15998 onTrigger(prop); 15999 } 16000 }; 16001 16002 styfn.checkZOrderTrigger = function (ele, name, fromValue, toValue) { 16003 var _this = this; 16004 16005 this.checkTrigger(ele, name, fromValue, toValue, function (prop) { 16006 return prop.triggersZOrder; 16007 }, function () { 16008 _this._private.cy.notify('zorder', ele); 16009 }); 16010 }; 16011 16012 styfn.checkBoundsTrigger = function (ele, name, fromValue, toValue) { 16013 this.checkTrigger(ele, name, fromValue, toValue, function (prop) { 16014 return prop.triggersBounds; 16015 }, function (prop) { 16016 ele.dirtyCompoundBoundsCache(); 16017 ele.dirtyBoundingBoxCache(); // if the prop change makes the bb of pll bezier edges invalid, 16018 // then dirty the pll edge bb cache as well 16019 16020 if ( // only for beziers -- so performance of other edges isn't affected 16021 (ele.pstyle('curve-style').value === 'bezier' // already a bezier 16022 // was just now changed to or from a bezier: 16023 || name === 'curve-style' && (fromValue === 'bezier' || toValue === 'bezier')) && prop.triggersBoundsOfParallelBeziers) { 16024 ele.parallelEdges().forEach(function (pllEdge) { 16025 if (pllEdge.isBundledBezier()) { 16026 pllEdge.dirtyBoundingBoxCache(); 16027 } 16028 }); 16029 } 16030 }); 16031 }; 16032 16033 styfn.checkTriggers = function (ele, name, fromValue, toValue) { 16034 ele.dirtyStyleCache(); 16035 this.checkZOrderTrigger(ele, name, fromValue, toValue); 16036 this.checkBoundsTrigger(ele, name, fromValue, toValue); 16037 }; 16038 16039 var styfn$1 = {}; // bypasses are applied to an existing style on an element, and just tacked on temporarily 16040 // returns true iff application was successful for at least 1 specified property 16041 16042 styfn$1.applyBypass = function (eles, name, value, updateTransitions) { 16043 var self = this; 16044 var props = []; 16045 var isBypass = true; // put all the properties (can specify one or many) in an array after parsing them 16046 16047 if (name === '*' || name === '**') { 16048 // apply to all property names 16049 if (value !== undefined) { 16050 for (var i = 0; i < self.properties.length; i++) { 16051 var prop = self.properties[i]; 16052 var _name = prop.name; 16053 var parsedProp = this.parse(_name, value, true); 16054 16055 if (parsedProp) { 16056 props.push(parsedProp); 16057 } 16058 } 16059 } 16060 } else if (string(name)) { 16061 // then parse the single property 16062 var _parsedProp = this.parse(name, value, true); 16063 16064 if (_parsedProp) { 16065 props.push(_parsedProp); 16066 } 16067 } else if (plainObject(name)) { 16068 // then parse each property 16069 var specifiedProps = name; 16070 updateTransitions = value; 16071 var names = Object.keys(specifiedProps); 16072 16073 for (var _i = 0; _i < names.length; _i++) { 16074 var _name2 = names[_i]; 16075 var _value = specifiedProps[_name2]; 16076 16077 if (_value === undefined) { 16078 // try camel case name too 16079 _value = specifiedProps[dash2camel(_name2)]; 16080 } 16081 16082 if (_value !== undefined) { 16083 var _parsedProp2 = this.parse(_name2, _value, true); 16084 16085 if (_parsedProp2) { 16086 props.push(_parsedProp2); 16087 } 16088 } 16089 } 16090 } else { 16091 // can't do anything without well defined properties 16092 return false; 16093 } // we've failed if there are no valid properties 16094 16095 16096 if (props.length === 0) { 16097 return false; 16098 } // now, apply the bypass properties on the elements 16099 16100 16101 var ret = false; // return true if at least one succesful bypass applied 16102 16103 for (var _i2 = 0; _i2 < eles.length; _i2++) { 16104 // for each ele 16105 var ele = eles[_i2]; 16106 var diffProps = {}; 16107 var diffProp = void 0; 16108 16109 for (var j = 0; j < props.length; j++) { 16110 // for each prop 16111 var _prop = props[j]; 16112 16113 if (updateTransitions) { 16114 var prevProp = ele.pstyle(_prop.name); 16115 diffProp = diffProps[_prop.name] = { 16116 prev: prevProp 16117 }; 16118 } 16119 16120 ret = this.applyParsedProperty(ele, _prop) || ret; 16121 16122 if (updateTransitions) { 16123 diffProp.next = ele.pstyle(_prop.name); 16124 } 16125 } // for props 16126 16127 16128 if (ret) { 16129 this.updateStyleHints(ele); 16130 } 16131 16132 if (updateTransitions) { 16133 this.updateTransitions(ele, diffProps, isBypass); 16134 } 16135 } // for eles 16136 16137 16138 return ret; 16139 }; // only useful in specific cases like animation 16140 16141 16142 styfn$1.overrideBypass = function (eles, name, value) { 16143 name = camel2dash(name); 16144 16145 for (var i = 0; i < eles.length; i++) { 16146 var ele = eles[i]; 16147 var prop = ele._private.style[name]; 16148 var type = this.properties[name].type; 16149 var isColor = type.color; 16150 var isMulti = type.mutiple; 16151 var oldValue = !prop ? null : prop.pfValue != null ? prop.pfValue : prop.value; 16152 16153 if (!prop || !prop.bypass) { 16154 // need a bypass if one doesn't exist 16155 this.applyBypass(ele, name, value); 16156 } else { 16157 prop.value = value; 16158 16159 if (prop.pfValue != null) { 16160 prop.pfValue = value; 16161 } 16162 16163 if (isColor) { 16164 prop.strValue = 'rgb(' + value.join(',') + ')'; 16165 } else if (isMulti) { 16166 prop.strValue = value.join(' '); 16167 } else { 16168 prop.strValue = '' + value; 16169 } 16170 16171 this.updateStyleHints(ele); 16172 } 16173 16174 this.checkTriggers(ele, name, oldValue, value); 16175 } 16176 }; 16177 16178 styfn$1.removeAllBypasses = function (eles, updateTransitions) { 16179 return this.removeBypasses(eles, this.propertyNames, updateTransitions); 16180 }; 16181 16182 styfn$1.removeBypasses = function (eles, props, updateTransitions) { 16183 var isBypass = true; 16184 16185 for (var j = 0; j < eles.length; j++) { 16186 var ele = eles[j]; 16187 var diffProps = {}; 16188 16189 for (var i = 0; i < props.length; i++) { 16190 var name = props[i]; 16191 var prop = this.properties[name]; 16192 var prevProp = ele.pstyle(prop.name); 16193 16194 if (!prevProp || !prevProp.bypass) { 16195 // if a bypass doesn't exist for the prop, nothing needs to be removed 16196 continue; 16197 } 16198 16199 var value = ''; // empty => remove bypass 16200 16201 var parsedProp = this.parse(name, value, true); 16202 var diffProp = diffProps[prop.name] = { 16203 prev: prevProp 16204 }; 16205 this.applyParsedProperty(ele, parsedProp); 16206 diffProp.next = ele.pstyle(prop.name); 16207 } // for props 16208 16209 16210 this.updateStyleHints(ele); 16211 16212 if (updateTransitions) { 16213 this.updateTransitions(ele, diffProps, isBypass); 16214 } 16215 } // for eles 16216 16217 }; 16218 16219 var styfn$2 = {}; // gets what an em size corresponds to in pixels relative to a dom element 16220 16221 styfn$2.getEmSizeInPixels = function () { 16222 var px = this.containerCss('font-size'); 16223 16224 if (px != null) { 16225 return parseFloat(px); 16226 } else { 16227 return 1; // for headless 16228 } 16229 }; // gets css property from the core container 16230 16231 16232 styfn$2.containerCss = function (propName) { 16233 var cy = this._private.cy; 16234 var domElement = cy.container(); 16235 16236 if (window$1 && domElement && window$1.getComputedStyle) { 16237 return window$1.getComputedStyle(domElement).getPropertyValue(propName); 16238 } 16239 }; 16240 16241 var styfn$3 = {}; // gets the rendered style for an element 16242 16243 styfn$3.getRenderedStyle = function (ele, prop) { 16244 if (prop) { 16245 return this.getStylePropertyValue(ele, prop, true); 16246 } else { 16247 return this.getRawStyle(ele, true); 16248 } 16249 }; // gets the raw style for an element 16250 16251 16252 styfn$3.getRawStyle = function (ele, isRenderedVal) { 16253 var self = this; 16254 ele = ele[0]; // insure it's an element 16255 16256 if (ele) { 16257 var rstyle = {}; 16258 16259 for (var i = 0; i < self.properties.length; i++) { 16260 var prop = self.properties[i]; 16261 var val = self.getStylePropertyValue(ele, prop.name, isRenderedVal); 16262 16263 if (val != null) { 16264 rstyle[prop.name] = val; 16265 rstyle[dash2camel(prop.name)] = val; 16266 } 16267 } 16268 16269 return rstyle; 16270 } 16271 }; 16272 16273 styfn$3.getIndexedStyle = function (ele, property, subproperty, index) { 16274 var pstyle = ele.pstyle(property)[subproperty][index]; 16275 return pstyle != null ? pstyle : ele.cy().style().getDefaultProperty(property)[subproperty][0]; 16276 }; 16277 16278 styfn$3.getStylePropertyValue = function (ele, propName, isRenderedVal) { 16279 var self = this; 16280 ele = ele[0]; // insure it's an element 16281 16282 if (ele) { 16283 var prop = self.properties[propName]; 16284 16285 if (prop.alias) { 16286 prop = prop.pointsTo; 16287 } 16288 16289 var type = prop.type; 16290 var styleProp = ele.pstyle(prop.name); 16291 16292 if (styleProp) { 16293 var value = styleProp.value, 16294 units = styleProp.units, 16295 strValue = styleProp.strValue; 16296 16297 if (isRenderedVal && type.number && value != null && number(value)) { 16298 var zoom = ele.cy().zoom(); 16299 16300 var getRenderedValue = function getRenderedValue(val) { 16301 return val * zoom; 16302 }; 16303 16304 var getValueStringWithUnits = function getValueStringWithUnits(val, units) { 16305 return getRenderedValue(val) + units; 16306 }; 16307 16308 var isArrayValue = array(value); 16309 var haveUnits = isArrayValue ? units.every(function (u) { 16310 return u != null; 16311 }) : units != null; 16312 16313 if (haveUnits) { 16314 if (isArrayValue) { 16315 return value.map(function (v, i) { 16316 return getValueStringWithUnits(v, units[i]); 16317 }).join(' '); 16318 } else { 16319 return getValueStringWithUnits(value, units); 16320 } 16321 } else { 16322 if (isArrayValue) { 16323 return value.map(function (v) { 16324 return string(v) ? v : '' + getRenderedValue(v); 16325 }).join(' '); 16326 } else { 16327 return '' + getRenderedValue(value); 16328 } 16329 } 16330 } else if (strValue != null) { 16331 return strValue; 16332 } 16333 } 16334 16335 return null; 16336 } 16337 }; 16338 16339 styfn$3.getAnimationStartStyle = function (ele, aniProps) { 16340 var rstyle = {}; 16341 16342 for (var i = 0; i < aniProps.length; i++) { 16343 var aniProp = aniProps[i]; 16344 var name = aniProp.name; 16345 var styleProp = ele.pstyle(name); 16346 16347 if (styleProp !== undefined) { 16348 // then make a prop of it 16349 if (plainObject(styleProp)) { 16350 styleProp = this.parse(name, styleProp.strValue); 16351 } else { 16352 styleProp = this.parse(name, styleProp); 16353 } 16354 } 16355 16356 if (styleProp) { 16357 rstyle[name] = styleProp; 16358 } 16359 } 16360 16361 return rstyle; 16362 }; 16363 16364 styfn$3.getPropsList = function (propsObj) { 16365 var self = this; 16366 var rstyle = []; 16367 var style = propsObj; 16368 var props = self.properties; 16369 16370 if (style) { 16371 var names = Object.keys(style); 16372 16373 for (var i = 0; i < names.length; i++) { 16374 var name = names[i]; 16375 var val = style[name]; 16376 var prop = props[name] || props[camel2dash(name)]; 16377 var styleProp = this.parse(prop.name, val); 16378 16379 if (styleProp) { 16380 rstyle.push(styleProp); 16381 } 16382 } 16383 } 16384 16385 return rstyle; 16386 }; 16387 16388 styfn$3.getNonDefaultPropertiesHash = function (ele, propNames, seed) { 16389 var hash = seed; 16390 var name, val, strVal, chVal; 16391 var i, j; 16392 16393 for (i = 0; i < propNames.length; i++) { 16394 name = propNames[i]; 16395 val = ele.pstyle(name, false); 16396 16397 if (val == null) { 16398 continue; 16399 } else if (val.pfValue != null) { 16400 hash = hashInt(chVal, hash); 16401 } else { 16402 strVal = val.strValue; 16403 16404 for (j = 0; j < strVal.length; j++) { 16405 chVal = strVal.charCodeAt(j); 16406 hash = hashInt(chVal, hash); 16407 } 16408 } 16409 } 16410 16411 return hash; 16412 }; 16413 16414 styfn$3.getPropertiesHash = styfn$3.getNonDefaultPropertiesHash; 16415 16416 var styfn$4 = {}; 16417 16418 styfn$4.appendFromJson = function (json) { 16419 var style = this; 16420 16421 for (var i = 0; i < json.length; i++) { 16422 var context = json[i]; 16423 var selector = context.selector; 16424 var props = context.style || context.css; 16425 var names = Object.keys(props); 16426 style.selector(selector); // apply selector 16427 16428 for (var j = 0; j < names.length; j++) { 16429 var name = names[j]; 16430 var value = props[name]; 16431 style.css(name, value); // apply property 16432 } 16433 } 16434 16435 return style; 16436 }; // accessible cy.style() function 16437 16438 16439 styfn$4.fromJson = function (json) { 16440 var style = this; 16441 style.resetToDefault(); 16442 style.appendFromJson(json); 16443 return style; 16444 }; // get json from cy.style() api 16445 16446 16447 styfn$4.json = function () { 16448 var json = []; 16449 16450 for (var i = this.defaultLength; i < this.length; i++) { 16451 var cxt = this[i]; 16452 var selector = cxt.selector; 16453 var props = cxt.properties; 16454 var css = {}; 16455 16456 for (var j = 0; j < props.length; j++) { 16457 var prop = props[j]; 16458 css[prop.name] = prop.strValue; 16459 } 16460 16461 json.push({ 16462 selector: !selector ? 'core' : selector.toString(), 16463 style: css 16464 }); 16465 } 16466 16467 return json; 16468 }; 16469 16470 var styfn$5 = {}; 16471 16472 styfn$5.appendFromString = function (string) { 16473 var self = this; 16474 var style = this; 16475 var remaining = '' + string; 16476 var selAndBlockStr; 16477 var blockRem; 16478 var propAndValStr; // remove comments from the style string 16479 16480 remaining = remaining.replace(/[/][*](\s|.)+?[*][/]/g, ''); 16481 16482 function removeSelAndBlockFromRemaining() { 16483 // remove the parsed selector and block from the remaining text to parse 16484 if (remaining.length > selAndBlockStr.length) { 16485 remaining = remaining.substr(selAndBlockStr.length); 16486 } else { 16487 remaining = ''; 16488 } 16489 } 16490 16491 function removePropAndValFromRem() { 16492 // remove the parsed property and value from the remaining block text to parse 16493 if (blockRem.length > propAndValStr.length) { 16494 blockRem = blockRem.substr(propAndValStr.length); 16495 } else { 16496 blockRem = ''; 16497 } 16498 } 16499 16500 for (;;) { 16501 var nothingLeftToParse = remaining.match(/^\s*$/); 16502 16503 if (nothingLeftToParse) { 16504 break; 16505 } 16506 16507 var selAndBlock = remaining.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/); 16508 16509 if (!selAndBlock) { 16510 warn('Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: ' + remaining); 16511 break; 16512 } 16513 16514 selAndBlockStr = selAndBlock[0]; // parse the selector 16515 16516 var selectorStr = selAndBlock[1]; 16517 16518 if (selectorStr !== 'core') { 16519 var selector = new Selector(selectorStr); 16520 16521 if (selector.invalid) { 16522 warn('Skipping parsing of block: Invalid selector found in string stylesheet: ' + selectorStr); // skip this selector and block 16523 16524 removeSelAndBlockFromRemaining(); 16525 continue; 16526 } 16527 } // parse the block of properties and values 16528 16529 16530 var blockStr = selAndBlock[2]; 16531 var invalidBlock = false; 16532 blockRem = blockStr; 16533 var props = []; 16534 16535 for (;;) { 16536 var _nothingLeftToParse = blockRem.match(/^\s*$/); 16537 16538 if (_nothingLeftToParse) { 16539 break; 16540 } 16541 16542 var propAndVal = blockRem.match(/^\s*(.+?)\s*:\s*(.+?)\s*;/); 16543 16544 if (!propAndVal) { 16545 warn('Skipping parsing of block: Invalid formatting of style property and value definitions found in:' + blockStr); 16546 invalidBlock = true; 16547 break; 16548 } 16549 16550 propAndValStr = propAndVal[0]; 16551 var propStr = propAndVal[1]; 16552 var valStr = propAndVal[2]; 16553 var prop = self.properties[propStr]; 16554 16555 if (!prop) { 16556 warn('Skipping property: Invalid property name in: ' + propAndValStr); // skip this property in the block 16557 16558 removePropAndValFromRem(); 16559 continue; 16560 } 16561 16562 var parsedProp = style.parse(propStr, valStr); 16563 16564 if (!parsedProp) { 16565 warn('Skipping property: Invalid property definition in: ' + propAndValStr); // skip this property in the block 16566 16567 removePropAndValFromRem(); 16568 continue; 16569 } 16570 16571 props.push({ 16572 name: propStr, 16573 val: valStr 16574 }); 16575 removePropAndValFromRem(); 16576 } 16577 16578 if (invalidBlock) { 16579 removeSelAndBlockFromRemaining(); 16580 break; 16581 } // put the parsed block in the style 16582 16583 16584 style.selector(selectorStr); 16585 16586 for (var i = 0; i < props.length; i++) { 16587 var _prop = props[i]; 16588 style.css(_prop.name, _prop.val); 16589 } 16590 16591 removeSelAndBlockFromRemaining(); 16592 } 16593 16594 return style; 16595 }; 16596 16597 styfn$5.fromString = function (string) { 16598 var style = this; 16599 style.resetToDefault(); 16600 style.appendFromString(string); 16601 return style; 16602 }; 16603 16604 var styfn$6 = {}; 16605 16606 (function () { 16607 var number = number$1; 16608 var rgba = rgbaNoBackRefs; 16609 var hsla = hslaNoBackRefs; 16610 var hex3$1 = hex3; 16611 var hex6$1 = hex6; 16612 16613 var data = function data(prefix) { 16614 return '^' + prefix + '\\s*\\(\\s*([\\w\\.]+)\\s*\\)$'; 16615 }; 16616 16617 var mapData = function mapData(prefix) { 16618 var mapArg = number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3$1 + '|' + hex6$1; 16619 return '^' + prefix + '\\s*\\(([\\w\\.]+)\\s*\\,\\s*(' + number + ')\\s*\\,\\s*(' + number + ')\\s*,\\s*(' + mapArg + ')\\s*\\,\\s*(' + mapArg + ')\\)$'; 16620 }; 16621 16622 var urlRegexes = ['^url\\s*\\(\\s*[\'"]?(.+?)[\'"]?\\s*\\)$', '^(none)$', '^(.+)$']; // each visual style property has a type and needs to be validated according to it 16623 16624 styfn$6.types = { 16625 time: { 16626 number: true, 16627 min: 0, 16628 units: 's|ms', 16629 implicitUnits: 'ms' 16630 }, 16631 percent: { 16632 number: true, 16633 min: 0, 16634 max: 100, 16635 units: '%', 16636 implicitUnits: '%' 16637 }, 16638 percentages: { 16639 number: true, 16640 min: 0, 16641 max: 100, 16642 units: '%', 16643 implicitUnits: '%', 16644 multiple: true 16645 }, 16646 zeroOneNumber: { 16647 number: true, 16648 min: 0, 16649 max: 1, 16650 unitless: true 16651 }, 16652 zeroOneNumbers: { 16653 number: true, 16654 min: 0, 16655 max: 1, 16656 unitless: true, 16657 multiple: true 16658 }, 16659 nOneOneNumber: { 16660 number: true, 16661 min: -1, 16662 max: 1, 16663 unitless: true 16664 }, 16665 nonNegativeInt: { 16666 number: true, 16667 min: 0, 16668 integer: true, 16669 unitless: true 16670 }, 16671 position: { 16672 enums: ['parent', 'origin'] 16673 }, 16674 nodeSize: { 16675 number: true, 16676 min: 0, 16677 enums: ['label'] 16678 }, 16679 number: { 16680 number: true, 16681 unitless: true 16682 }, 16683 numbers: { 16684 number: true, 16685 unitless: true, 16686 multiple: true 16687 }, 16688 positiveNumber: { 16689 number: true, 16690 unitless: true, 16691 min: 0, 16692 strictMin: true 16693 }, 16694 size: { 16695 number: true, 16696 min: 0 16697 }, 16698 bidirectionalSize: { 16699 number: true 16700 }, 16701 // allows negative 16702 bidirectionalSizes: { 16703 number: true, 16704 multiple: true 16705 }, 16706 // allows negative 16707 sizeMaybePercent: { 16708 number: true, 16709 min: 0, 16710 allowPercent: true 16711 }, 16712 axisDirection: { 16713 enums: ['horizontal', 'leftward', 'rightward', 'vertical', 'upward', 'downward', 'auto'] 16714 }, 16715 paddingRelativeTo: { 16716 enums: ['width', 'height', 'average', 'min', 'max'] 16717 }, 16718 bgWH: { 16719 number: true, 16720 min: 0, 16721 allowPercent: true, 16722 enums: ['auto'], 16723 multiple: true 16724 }, 16725 bgPos: { 16726 number: true, 16727 allowPercent: true, 16728 multiple: true 16729 }, 16730 bgRelativeTo: { 16731 enums: ['inner', 'include-padding'], 16732 multiple: true 16733 }, 16734 bgRepeat: { 16735 enums: ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'], 16736 multiple: true 16737 }, 16738 bgFit: { 16739 enums: ['none', 'contain', 'cover'], 16740 multiple: true 16741 }, 16742 bgCrossOrigin: { 16743 enums: ['anonymous', 'use-credentials'], 16744 multiple: true 16745 }, 16746 bgClip: { 16747 enums: ['none', 'node'], 16748 multiple: true 16749 }, 16750 color: { 16751 color: true 16752 }, 16753 colors: { 16754 color: true, 16755 multiple: true 16756 }, 16757 fill: { 16758 enums: ['solid', 'linear-gradient', 'radial-gradient'] 16759 }, 16760 bool: { 16761 enums: ['yes', 'no'] 16762 }, 16763 lineStyle: { 16764 enums: ['solid', 'dotted', 'dashed'] 16765 }, 16766 lineCap: { 16767 enums: ['butt', 'round', 'square'] 16768 }, 16769 borderStyle: { 16770 enums: ['solid', 'dotted', 'dashed', 'double'] 16771 }, 16772 curveStyle: { 16773 enums: ['bezier', 'unbundled-bezier', 'haystack', 'segments', 'straight', 'taxi'] 16774 }, 16775 fontFamily: { 16776 regex: '^([\\w- \\"]+(?:\\s*,\\s*[\\w- \\"]+)*)$' 16777 }, 16778 fontStyle: { 16779 enums: ['italic', 'normal', 'oblique'] 16780 }, 16781 fontWeight: { 16782 enums: ['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '800', '900', 100, 200, 300, 400, 500, 600, 700, 800, 900] 16783 }, 16784 textDecoration: { 16785 enums: ['none', 'underline', 'overline', 'line-through'] 16786 }, 16787 textTransform: { 16788 enums: ['none', 'uppercase', 'lowercase'] 16789 }, 16790 textWrap: { 16791 enums: ['none', 'wrap', 'ellipsis'] 16792 }, 16793 textOverflowWrap: { 16794 enums: ['whitespace', 'anywhere'] 16795 }, 16796 textBackgroundShape: { 16797 enums: ['rectangle', 'roundrectangle', 'round-rectangle'] 16798 }, 16799 nodeShape: { 16800 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'] 16801 }, 16802 compoundIncludeLabels: { 16803 enums: ['include', 'exclude'] 16804 }, 16805 arrowShape: { 16806 enums: ['tee', 'triangle', 'triangle-tee', 'triangle-cross', 'triangle-backcurve', 'vee', 'square', 'circle', 'diamond', 'chevron', 'none'] 16807 }, 16808 arrowFill: { 16809 enums: ['filled', 'hollow'] 16810 }, 16811 display: { 16812 enums: ['element', 'none'] 16813 }, 16814 visibility: { 16815 enums: ['hidden', 'visible'] 16816 }, 16817 zCompoundDepth: { 16818 enums: ['bottom', 'orphan', 'auto', 'top'] 16819 }, 16820 zIndexCompare: { 16821 enums: ['auto', 'manual'] 16822 }, 16823 valign: { 16824 enums: ['top', 'center', 'bottom'] 16825 }, 16826 halign: { 16827 enums: ['left', 'center', 'right'] 16828 }, 16829 justification: { 16830 enums: ['left', 'center', 'right', 'auto'] 16831 }, 16832 text: { 16833 string: true 16834 }, 16835 data: { 16836 mapping: true, 16837 regex: data('data') 16838 }, 16839 layoutData: { 16840 mapping: true, 16841 regex: data('layoutData') 16842 }, 16843 scratch: { 16844 mapping: true, 16845 regex: data('scratch') 16846 }, 16847 mapData: { 16848 mapping: true, 16849 regex: mapData('mapData') 16850 }, 16851 mapLayoutData: { 16852 mapping: true, 16853 regex: mapData('mapLayoutData') 16854 }, 16855 mapScratch: { 16856 mapping: true, 16857 regex: mapData('mapScratch') 16858 }, 16859 fn: { 16860 mapping: true, 16861 fn: true 16862 }, 16863 url: { 16864 regexes: urlRegexes, 16865 singleRegexMatchValue: true 16866 }, 16867 urls: { 16868 regexes: urlRegexes, 16869 singleRegexMatchValue: true, 16870 multiple: true 16871 }, 16872 propList: { 16873 propList: true 16874 }, 16875 angle: { 16876 number: true, 16877 units: 'deg|rad', 16878 implicitUnits: 'rad' 16879 }, 16880 textRotation: { 16881 number: true, 16882 units: 'deg|rad', 16883 implicitUnits: 'rad', 16884 enums: ['none', 'autorotate'] 16885 }, 16886 polygonPointList: { 16887 number: true, 16888 multiple: true, 16889 evenMultiple: true, 16890 min: -1, 16891 max: 1, 16892 unitless: true 16893 }, 16894 edgeDistances: { 16895 enums: ['intersection', 'node-position'] 16896 }, 16897 edgeEndpoint: { 16898 number: true, 16899 multiple: true, 16900 units: '%|px|em|deg|rad', 16901 implicitUnits: 'px', 16902 enums: ['inside-to-node', 'outside-to-node', 'outside-to-node-or-label', 'outside-to-line', 'outside-to-line-or-label'], 16903 singleEnum: true, 16904 validate: function validate(valArr, unitsArr) { 16905 switch (valArr.length) { 16906 case 2: 16907 // can be % or px only 16908 return unitsArr[0] !== 'deg' && unitsArr[0] !== 'rad' && unitsArr[1] !== 'deg' && unitsArr[1] !== 'rad'; 16909 16910 case 1: 16911 // can be enum, deg, or rad only 16912 return string(valArr[0]) || unitsArr[0] === 'deg' || unitsArr[0] === 'rad'; 16913 16914 default: 16915 return false; 16916 } 16917 } 16918 }, 16919 easing: { 16920 regexes: ['^(spring)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$', '^(cubic-bezier)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$'], 16921 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'] 16922 }, 16923 gradientDirection: { 16924 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'] 16925 }, 16926 boundsExpansion: { 16927 number: true, 16928 multiple: true, 16929 min: 0, 16930 validate: function validate(valArr) { 16931 var length = valArr.length; 16932 return length === 1 || length === 2 || length === 4; 16933 } 16934 } 16935 }; 16936 var diff = { 16937 zeroNonZero: function zeroNonZero(val1, val2) { 16938 if ((val1 == null || val2 == null) && val1 !== val2) { 16939 return true; // null cases could represent any value 16940 } 16941 16942 if (val1 == 0 && val2 != 0) { 16943 return true; 16944 } else if (val1 != 0 && val2 == 0) { 16945 return true; 16946 } else { 16947 return false; 16948 } 16949 }, 16950 any: function any(val1, val2) { 16951 return val1 != val2; 16952 } 16953 }; // define visual style properties 16954 // 16955 // - n.b. adding a new group of props may require updates to updateStyleHints() 16956 // - adding new props to an existing group gets handled automatically 16957 16958 var t = styfn$6.types; 16959 var mainLabel = [{ 16960 name: 'label', 16961 type: t.text, 16962 triggersBounds: diff.any 16963 }, { 16964 name: 'text-rotation', 16965 type: t.textRotation, 16966 triggersBounds: diff.any 16967 }, { 16968 name: 'text-margin-x', 16969 type: t.bidirectionalSize, 16970 triggersBounds: diff.any 16971 }, { 16972 name: 'text-margin-y', 16973 type: t.bidirectionalSize, 16974 triggersBounds: diff.any 16975 }]; 16976 var sourceLabel = [{ 16977 name: 'source-label', 16978 type: t.text, 16979 triggersBounds: diff.any 16980 }, { 16981 name: 'source-text-rotation', 16982 type: t.textRotation, 16983 triggersBounds: diff.any 16984 }, { 16985 name: 'source-text-margin-x', 16986 type: t.bidirectionalSize, 16987 triggersBounds: diff.any 16988 }, { 16989 name: 'source-text-margin-y', 16990 type: t.bidirectionalSize, 16991 triggersBounds: diff.any 16992 }, { 16993 name: 'source-text-offset', 16994 type: t.size, 16995 triggersBounds: diff.any 16996 }]; 16997 var targetLabel = [{ 16998 name: 'target-label', 16999 type: t.text, 17000 triggersBounds: diff.any 17001 }, { 17002 name: 'target-text-rotation', 17003 type: t.textRotation, 17004 triggersBounds: diff.any 17005 }, { 17006 name: 'target-text-margin-x', 17007 type: t.bidirectionalSize, 17008 triggersBounds: diff.any 17009 }, { 17010 name: 'target-text-margin-y', 17011 type: t.bidirectionalSize, 17012 triggersBounds: diff.any 17013 }, { 17014 name: 'target-text-offset', 17015 type: t.size, 17016 triggersBounds: diff.any 17017 }]; 17018 var labelDimensions = [{ 17019 name: 'font-family', 17020 type: t.fontFamily, 17021 triggersBounds: diff.any 17022 }, { 17023 name: 'font-style', 17024 type: t.fontStyle, 17025 triggersBounds: diff.any 17026 }, { 17027 name: 'font-weight', 17028 type: t.fontWeight, 17029 triggersBounds: diff.any 17030 }, { 17031 name: 'font-size', 17032 type: t.size, 17033 triggersBounds: diff.any 17034 }, { 17035 name: 'text-transform', 17036 type: t.textTransform, 17037 triggersBounds: diff.any 17038 }, { 17039 name: 'text-wrap', 17040 type: t.textWrap, 17041 triggersBounds: diff.any 17042 }, { 17043 name: 'text-overflow-wrap', 17044 type: t.textOverflowWrap, 17045 triggersBounds: diff.any 17046 }, { 17047 name: 'text-max-width', 17048 type: t.size, 17049 triggersBounds: diff.any 17050 }, { 17051 name: 'text-outline-width', 17052 type: t.size, 17053 triggersBounds: diff.any 17054 }, { 17055 name: 'line-height', 17056 type: t.positiveNumber, 17057 triggersBounds: diff.any 17058 }]; 17059 var commonLabel = [{ 17060 name: 'text-valign', 17061 type: t.valign, 17062 triggersBounds: diff.any 17063 }, { 17064 name: 'text-halign', 17065 type: t.halign, 17066 triggersBounds: diff.any 17067 }, { 17068 name: 'color', 17069 type: t.color 17070 }, { 17071 name: 'text-outline-color', 17072 type: t.color 17073 }, { 17074 name: 'text-outline-opacity', 17075 type: t.zeroOneNumber 17076 }, { 17077 name: 'text-background-color', 17078 type: t.color 17079 }, { 17080 name: 'text-background-opacity', 17081 type: t.zeroOneNumber 17082 }, { 17083 name: 'text-background-padding', 17084 type: t.size, 17085 triggersBounds: diff.any 17086 }, { 17087 name: 'text-border-opacity', 17088 type: t.zeroOneNumber 17089 }, { 17090 name: 'text-border-color', 17091 type: t.color 17092 }, { 17093 name: 'text-border-width', 17094 type: t.size, 17095 triggersBounds: diff.any 17096 }, { 17097 name: 'text-border-style', 17098 type: t.borderStyle, 17099 triggersBounds: diff.any 17100 }, { 17101 name: 'text-background-shape', 17102 type: t.textBackgroundShape, 17103 triggersBounds: diff.any 17104 }, { 17105 name: 'text-justification', 17106 type: t.justification 17107 }]; 17108 var behavior = [{ 17109 name: 'events', 17110 type: t.bool 17111 }, { 17112 name: 'text-events', 17113 type: t.bool 17114 }]; 17115 var visibility = [{ 17116 name: 'display', 17117 type: t.display, 17118 triggersZOrder: diff.any, 17119 triggersBounds: diff.any, 17120 triggersBoundsOfParallelBeziers: true 17121 }, { 17122 name: 'visibility', 17123 type: t.visibility, 17124 triggersZOrder: diff.any 17125 }, { 17126 name: 'opacity', 17127 type: t.zeroOneNumber, 17128 triggersZOrder: diff.zeroNonZero 17129 }, { 17130 name: 'text-opacity', 17131 type: t.zeroOneNumber 17132 }, { 17133 name: 'min-zoomed-font-size', 17134 type: t.size 17135 }, { 17136 name: 'z-compound-depth', 17137 type: t.zCompoundDepth, 17138 triggersZOrder: diff.any 17139 }, { 17140 name: 'z-index-compare', 17141 type: t.zIndexCompare, 17142 triggersZOrder: diff.any 17143 }, { 17144 name: 'z-index', 17145 type: t.nonNegativeInt, 17146 triggersZOrder: diff.any 17147 }]; 17148 var overlay = [{ 17149 name: 'overlay-padding', 17150 type: t.size, 17151 triggersBounds: diff.any 17152 }, { 17153 name: 'overlay-color', 17154 type: t.color 17155 }, { 17156 name: 'overlay-opacity', 17157 type: t.zeroOneNumber, 17158 triggersBounds: diff.zeroNonZero 17159 }]; 17160 var transition = [{ 17161 name: 'transition-property', 17162 type: t.propList 17163 }, { 17164 name: 'transition-duration', 17165 type: t.time 17166 }, { 17167 name: 'transition-delay', 17168 type: t.time 17169 }, { 17170 name: 'transition-timing-function', 17171 type: t.easing 17172 }]; 17173 17174 var nodeSizeHashOverride = function nodeSizeHashOverride(ele, parsedProp) { 17175 if (parsedProp.value === 'label') { 17176 return -ele.poolIndex(); // no hash key hits is using label size (hitrate for perf probably low anyway) 17177 } else { 17178 return parsedProp.pfValue; 17179 } 17180 }; 17181 17182 var nodeBody = [{ 17183 name: 'height', 17184 type: t.nodeSize, 17185 triggersBounds: diff.any, 17186 hashOverride: nodeSizeHashOverride 17187 }, { 17188 name: 'width', 17189 type: t.nodeSize, 17190 triggersBounds: diff.any, 17191 hashOverride: nodeSizeHashOverride 17192 }, { 17193 name: 'shape', 17194 type: t.nodeShape, 17195 triggersBounds: diff.any 17196 }, { 17197 name: 'shape-polygon-points', 17198 type: t.polygonPointList, 17199 triggersBounds: diff.any 17200 }, { 17201 name: 'background-color', 17202 type: t.color 17203 }, { 17204 name: 'background-fill', 17205 type: t.fill 17206 }, { 17207 name: 'background-opacity', 17208 type: t.zeroOneNumber 17209 }, { 17210 name: 'background-blacken', 17211 type: t.nOneOneNumber 17212 }, { 17213 name: 'background-gradient-stop-colors', 17214 type: t.colors 17215 }, { 17216 name: 'background-gradient-stop-positions', 17217 type: t.percentages 17218 }, { 17219 name: 'background-gradient-direction', 17220 type: t.gradientDirection 17221 }, { 17222 name: 'padding', 17223 type: t.sizeMaybePercent, 17224 triggersBounds: diff.any 17225 }, { 17226 name: 'padding-relative-to', 17227 type: t.paddingRelativeTo, 17228 triggersBounds: diff.any 17229 }, { 17230 name: 'bounds-expansion', 17231 type: t.boundsExpansion, 17232 triggersBounds: diff.any 17233 }]; 17234 var nodeBorder = [{ 17235 name: 'border-color', 17236 type: t.color 17237 }, { 17238 name: 'border-opacity', 17239 type: t.zeroOneNumber 17240 }, { 17241 name: 'border-width', 17242 type: t.size, 17243 triggersBounds: diff.any 17244 }, { 17245 name: 'border-style', 17246 type: t.borderStyle 17247 }]; 17248 var backgroundImage = [{ 17249 name: 'background-image', 17250 type: t.urls 17251 }, { 17252 name: 'background-image-crossorigin', 17253 type: t.bgCrossOrigin 17254 }, { 17255 name: 'background-image-opacity', 17256 type: t.zeroOneNumbers 17257 }, { 17258 name: 'background-position-x', 17259 type: t.bgPos 17260 }, { 17261 name: 'background-position-y', 17262 type: t.bgPos 17263 }, { 17264 name: 'background-width-relative-to', 17265 type: t.bgRelativeTo 17266 }, { 17267 name: 'background-height-relative-to', 17268 type: t.bgRelativeTo 17269 }, { 17270 name: 'background-repeat', 17271 type: t.bgRepeat 17272 }, { 17273 name: 'background-fit', 17274 type: t.bgFit 17275 }, { 17276 name: 'background-clip', 17277 type: t.bgClip 17278 }, { 17279 name: 'background-width', 17280 type: t.bgWH 17281 }, { 17282 name: 'background-height', 17283 type: t.bgWH 17284 }, { 17285 name: 'background-offset-x', 17286 type: t.bgPos 17287 }, { 17288 name: 'background-offset-y', 17289 type: t.bgPos 17290 }]; 17291 var compound = [{ 17292 name: 'position', 17293 type: t.position, 17294 triggersBounds: diff.any 17295 }, { 17296 name: 'compound-sizing-wrt-labels', 17297 type: t.compoundIncludeLabels, 17298 triggersBounds: diff.any 17299 }, { 17300 name: 'min-width', 17301 type: t.size, 17302 triggersBounds: diff.any 17303 }, { 17304 name: 'min-width-bias-left', 17305 type: t.sizeMaybePercent, 17306 triggersBounds: diff.any 17307 }, { 17308 name: 'min-width-bias-right', 17309 type: t.sizeMaybePercent, 17310 triggersBounds: diff.any 17311 }, { 17312 name: 'min-height', 17313 type: t.size, 17314 triggersBounds: diff.any 17315 }, { 17316 name: 'min-height-bias-top', 17317 type: t.sizeMaybePercent, 17318 triggersBounds: diff.any 17319 }, { 17320 name: 'min-height-bias-bottom', 17321 type: t.sizeMaybePercent, 17322 triggersBounds: diff.any 17323 }]; 17324 var edgeLine = [{ 17325 name: 'line-style', 17326 type: t.lineStyle 17327 }, { 17328 name: 'line-color', 17329 type: t.color 17330 }, { 17331 name: 'line-fill', 17332 type: t.fill 17333 }, { 17334 name: 'line-cap', 17335 type: t.lineCap 17336 }, { 17337 name: 'line-dash-pattern', 17338 type: t.numbers 17339 }, { 17340 name: 'line-dash-offset', 17341 type: t.number 17342 }, { 17343 name: 'line-gradient-stop-colors', 17344 type: t.colors 17345 }, { 17346 name: 'line-gradient-stop-positions', 17347 type: t.percentages 17348 }, { 17349 name: 'curve-style', 17350 type: t.curveStyle, 17351 triggersBounds: diff.any, 17352 triggersBoundsOfParallelBeziers: true 17353 }, { 17354 name: 'haystack-radius', 17355 type: t.zeroOneNumber, 17356 triggersBounds: diff.any 17357 }, { 17358 name: 'source-endpoint', 17359 type: t.edgeEndpoint, 17360 triggersBounds: diff.any 17361 }, { 17362 name: 'target-endpoint', 17363 type: t.edgeEndpoint, 17364 triggersBounds: diff.any 17365 }, { 17366 name: 'control-point-step-size', 17367 type: t.size, 17368 triggersBounds: diff.any 17369 }, { 17370 name: 'control-point-distances', 17371 type: t.bidirectionalSizes, 17372 triggersBounds: diff.any 17373 }, { 17374 name: 'control-point-weights', 17375 type: t.numbers, 17376 triggersBounds: diff.any 17377 }, { 17378 name: 'segment-distances', 17379 type: t.bidirectionalSizes, 17380 triggersBounds: diff.any 17381 }, { 17382 name: 'segment-weights', 17383 type: t.numbers, 17384 triggersBounds: diff.any 17385 }, { 17386 name: 'taxi-turn', 17387 type: t.sizeMaybePercent, 17388 triggersBounds: diff.any 17389 }, { 17390 name: 'taxi-turn-min-distance', 17391 type: t.size, 17392 triggersBounds: diff.any 17393 }, { 17394 name: 'taxi-direction', 17395 type: t.axisDirection, 17396 triggersBounds: diff.any 17397 }, { 17398 name: 'edge-distances', 17399 type: t.edgeDistances, 17400 triggersBounds: diff.any 17401 }, { 17402 name: 'arrow-scale', 17403 type: t.positiveNumber, 17404 triggersBounds: diff.any 17405 }, { 17406 name: 'loop-direction', 17407 type: t.angle, 17408 triggersBounds: diff.any 17409 }, { 17410 name: 'loop-sweep', 17411 type: t.angle, 17412 triggersBounds: diff.any 17413 }, { 17414 name: 'source-distance-from-node', 17415 type: t.size, 17416 triggersBounds: diff.any 17417 }, { 17418 name: 'target-distance-from-node', 17419 type: t.size, 17420 triggersBounds: diff.any 17421 }]; 17422 var ghost = [{ 17423 name: 'ghost', 17424 type: t.bool, 17425 triggersBounds: diff.any 17426 }, { 17427 name: 'ghost-offset-x', 17428 type: t.bidirectionalSize, 17429 triggersBounds: diff.any 17430 }, { 17431 name: 'ghost-offset-y', 17432 type: t.bidirectionalSize, 17433 triggersBounds: diff.any 17434 }, { 17435 name: 'ghost-opacity', 17436 type: t.zeroOneNumber 17437 }]; 17438 var core = [{ 17439 name: 'selection-box-color', 17440 type: t.color 17441 }, { 17442 name: 'selection-box-opacity', 17443 type: t.zeroOneNumber 17444 }, { 17445 name: 'selection-box-border-color', 17446 type: t.color 17447 }, { 17448 name: 'selection-box-border-width', 17449 type: t.size 17450 }, { 17451 name: 'active-bg-color', 17452 type: t.color 17453 }, { 17454 name: 'active-bg-opacity', 17455 type: t.zeroOneNumber 17456 }, { 17457 name: 'active-bg-size', 17458 type: t.size 17459 }, { 17460 name: 'outside-texture-bg-color', 17461 type: t.color 17462 }, { 17463 name: 'outside-texture-bg-opacity', 17464 type: t.zeroOneNumber 17465 }]; // pie backgrounds for nodes 17466 17467 var pie = []; 17468 styfn$6.pieBackgroundN = 16; // because the pie properties are numbered, give access to a constant N (for renderer use) 17469 17470 pie.push({ 17471 name: 'pie-size', 17472 type: t.sizeMaybePercent 17473 }); 17474 17475 for (var i = 1; i <= styfn$6.pieBackgroundN; i++) { 17476 pie.push({ 17477 name: 'pie-' + i + '-background-color', 17478 type: t.color 17479 }); 17480 pie.push({ 17481 name: 'pie-' + i + '-background-size', 17482 type: t.percent 17483 }); 17484 pie.push({ 17485 name: 'pie-' + i + '-background-opacity', 17486 type: t.zeroOneNumber 17487 }); 17488 } // edge arrows 17489 17490 17491 var edgeArrow = []; 17492 var arrowPrefixes = styfn$6.arrowPrefixes = ['source', 'mid-source', 'target', 'mid-target']; 17493 [{ 17494 name: 'arrow-shape', 17495 type: t.arrowShape, 17496 triggersBounds: diff.any 17497 }, { 17498 name: 'arrow-color', 17499 type: t.color 17500 }, { 17501 name: 'arrow-fill', 17502 type: t.arrowFill 17503 }].forEach(function (prop) { 17504 arrowPrefixes.forEach(function (prefix) { 17505 var name = prefix + '-' + prop.name; 17506 var type = prop.type, 17507 triggersBounds = prop.triggersBounds; 17508 edgeArrow.push({ 17509 name: name, 17510 type: type, 17511 triggersBounds: triggersBounds 17512 }); 17513 }); 17514 }, {}); 17515 var props = styfn$6.properties = [].concat(behavior, transition, visibility, overlay, ghost, commonLabel, labelDimensions, mainLabel, sourceLabel, targetLabel, nodeBody, nodeBorder, backgroundImage, pie, compound, edgeLine, edgeArrow, core); 17516 var propGroups = styfn$6.propertyGroups = { 17517 // common to all eles 17518 behavior: behavior, 17519 transition: transition, 17520 visibility: visibility, 17521 overlay: overlay, 17522 ghost: ghost, 17523 // labels 17524 commonLabel: commonLabel, 17525 labelDimensions: labelDimensions, 17526 mainLabel: mainLabel, 17527 sourceLabel: sourceLabel, 17528 targetLabel: targetLabel, 17529 // node props 17530 nodeBody: nodeBody, 17531 nodeBorder: nodeBorder, 17532 backgroundImage: backgroundImage, 17533 pie: pie, 17534 compound: compound, 17535 // edge props 17536 edgeLine: edgeLine, 17537 edgeArrow: edgeArrow, 17538 core: core 17539 }; 17540 var propGroupNames = styfn$6.propertyGroupNames = {}; 17541 var propGroupKeys = styfn$6.propertyGroupKeys = Object.keys(propGroups); 17542 propGroupKeys.forEach(function (key) { 17543 propGroupNames[key] = propGroups[key].map(function (prop) { 17544 return prop.name; 17545 }); 17546 propGroups[key].forEach(function (prop) { 17547 return prop.groupKey = key; 17548 }); 17549 }); // define aliases 17550 17551 var aliases = styfn$6.aliases = [{ 17552 name: 'content', 17553 pointsTo: 'label' 17554 }, { 17555 name: 'control-point-distance', 17556 pointsTo: 'control-point-distances' 17557 }, { 17558 name: 'control-point-weight', 17559 pointsTo: 'control-point-weights' 17560 }, { 17561 name: 'edge-text-rotation', 17562 pointsTo: 'text-rotation' 17563 }, { 17564 name: 'padding-left', 17565 pointsTo: 'padding' 17566 }, { 17567 name: 'padding-right', 17568 pointsTo: 'padding' 17569 }, { 17570 name: 'padding-top', 17571 pointsTo: 'padding' 17572 }, { 17573 name: 'padding-bottom', 17574 pointsTo: 'padding' 17575 }]; // list of property names 17576 17577 styfn$6.propertyNames = props.map(function (p) { 17578 return p.name; 17579 }); // allow access of properties by name ( e.g. style.properties.height ) 17580 17581 for (var _i = 0; _i < props.length; _i++) { 17582 var prop = props[_i]; 17583 props[prop.name] = prop; // allow lookup by name 17584 } // map aliases 17585 17586 17587 for (var _i2 = 0; _i2 < aliases.length; _i2++) { 17588 var alias = aliases[_i2]; 17589 var pointsToProp = props[alias.pointsTo]; 17590 var aliasProp = { 17591 name: alias.name, 17592 alias: true, 17593 pointsTo: pointsToProp 17594 }; // add alias prop for parsing 17595 17596 props.push(aliasProp); 17597 props[alias.name] = aliasProp; // allow lookup by name 17598 } 17599 })(); 17600 17601 styfn$6.getDefaultProperty = function (name) { 17602 return this.getDefaultProperties()[name]; 17603 }; 17604 17605 styfn$6.getDefaultProperties = function () { 17606 var _p = this._private; 17607 17608 if (_p.defaultProperties != null) { 17609 return _p.defaultProperties; 17610 } 17611 17612 var rawProps = extend({ 17613 // core props 17614 'selection-box-color': '#ddd', 17615 'selection-box-opacity': 0.65, 17616 'selection-box-border-color': '#aaa', 17617 'selection-box-border-width': 1, 17618 'active-bg-color': 'black', 17619 'active-bg-opacity': 0.15, 17620 'active-bg-size': 30, 17621 'outside-texture-bg-color': '#000', 17622 'outside-texture-bg-opacity': 0.125, 17623 // common node/edge props 17624 'events': 'yes', 17625 'text-events': 'no', 17626 'text-valign': 'top', 17627 'text-halign': 'center', 17628 'text-justification': 'auto', 17629 'line-height': 1, 17630 'color': '#000', 17631 'text-outline-color': '#000', 17632 'text-outline-width': 0, 17633 'text-outline-opacity': 1, 17634 'text-opacity': 1, 17635 'text-decoration': 'none', 17636 'text-transform': 'none', 17637 'text-wrap': 'none', 17638 'text-overflow-wrap': 'whitespace', 17639 'text-max-width': 9999, 17640 'text-background-color': '#000', 17641 'text-background-opacity': 0, 17642 'text-background-shape': 'rectangle', 17643 'text-background-padding': 0, 17644 'text-border-opacity': 0, 17645 'text-border-width': 0, 17646 'text-border-style': 'solid', 17647 'text-border-color': '#000', 17648 'font-family': 'Helvetica Neue, Helvetica, sans-serif', 17649 'font-style': 'normal', 17650 'font-weight': 'normal', 17651 'font-size': 16, 17652 'min-zoomed-font-size': 0, 17653 'text-rotation': 'none', 17654 'source-text-rotation': 'none', 17655 'target-text-rotation': 'none', 17656 'visibility': 'visible', 17657 'display': 'element', 17658 'opacity': 1, 17659 'z-compound-depth': 'auto', 17660 'z-index-compare': 'auto', 17661 'z-index': 0, 17662 'label': '', 17663 'text-margin-x': 0, 17664 'text-margin-y': 0, 17665 'source-label': '', 17666 'source-text-offset': 0, 17667 'source-text-margin-x': 0, 17668 'source-text-margin-y': 0, 17669 'target-label': '', 17670 'target-text-offset': 0, 17671 'target-text-margin-x': 0, 17672 'target-text-margin-y': 0, 17673 'overlay-opacity': 0, 17674 'overlay-color': '#000', 17675 'overlay-padding': 10, 17676 'transition-property': 'none', 17677 'transition-duration': 0, 17678 'transition-delay': 0, 17679 'transition-timing-function': 'linear', 17680 // node props 17681 'background-blacken': 0, 17682 'background-color': '#999', 17683 'background-fill': 'solid', 17684 'background-opacity': 1, 17685 'background-image': 'none', 17686 'background-image-crossorigin': 'anonymous', 17687 'background-image-opacity': 1, 17688 'background-position-x': '50%', 17689 'background-position-y': '50%', 17690 'background-offset-x': 0, 17691 'background-offset-y': 0, 17692 'background-width-relative-to': 'include-padding', 17693 'background-height-relative-to': 'include-padding', 17694 'background-repeat': 'no-repeat', 17695 'background-fit': 'none', 17696 'background-clip': 'node', 17697 'background-width': 'auto', 17698 'background-height': 'auto', 17699 'border-color': '#000', 17700 'border-opacity': 1, 17701 'border-width': 0, 17702 'border-style': 'solid', 17703 'height': 30, 17704 'width': 30, 17705 'shape': 'ellipse', 17706 'shape-polygon-points': '-1, -1, 1, -1, 1, 1, -1, 1', 17707 'bounds-expansion': 0, 17708 // node gradient 17709 'background-gradient-direction': 'to-bottom', 17710 'background-gradient-stop-colors': '#999', 17711 'background-gradient-stop-positions': '0%', 17712 // ghost props 17713 'ghost': 'no', 17714 'ghost-offset-y': 0, 17715 'ghost-offset-x': 0, 17716 'ghost-opacity': 0, 17717 // compound props 17718 'padding': 0, 17719 'padding-relative-to': 'width', 17720 'position': 'origin', 17721 'compound-sizing-wrt-labels': 'include', 17722 'min-width': 0, 17723 'min-width-bias-left': 0, 17724 'min-width-bias-right': 0, 17725 'min-height': 0, 17726 'min-height-bias-top': 0, 17727 'min-height-bias-bottom': 0 17728 }, { 17729 // node pie bg 17730 'pie-size': '100%' 17731 }, [{ 17732 name: 'pie-{{i}}-background-color', 17733 value: 'black' 17734 }, { 17735 name: 'pie-{{i}}-background-size', 17736 value: '0%' 17737 }, { 17738 name: 'pie-{{i}}-background-opacity', 17739 value: 1 17740 }].reduce(function (css, prop) { 17741 for (var i = 1; i <= styfn$6.pieBackgroundN; i++) { 17742 var name = prop.name.replace('{{i}}', i); 17743 var val = prop.value; 17744 css[name] = val; 17745 } 17746 17747 return css; 17748 }, {}), { 17749 // edge props 17750 'line-style': 'solid', 17751 'line-color': '#999', 17752 'line-fill': 'solid', 17753 'line-cap': 'butt', 17754 'line-gradient-stop-colors': '#999', 17755 'line-gradient-stop-positions': '0%', 17756 'control-point-step-size': 40, 17757 'control-point-weights': 0.5, 17758 'segment-weights': 0.5, 17759 'segment-distances': 20, 17760 'taxi-turn': '50%', 17761 'taxi-turn-min-distance': 10, 17762 'taxi-direction': 'auto', 17763 'edge-distances': 'intersection', 17764 'curve-style': 'haystack', 17765 'haystack-radius': 0, 17766 'arrow-scale': 1, 17767 'loop-direction': '-45deg', 17768 'loop-sweep': '-90deg', 17769 'source-distance-from-node': 0, 17770 'target-distance-from-node': 0, 17771 'source-endpoint': 'outside-to-node', 17772 'target-endpoint': 'outside-to-node', 17773 'line-dash-pattern': [6, 3], 17774 'line-dash-offset': 0 17775 }, [{ 17776 name: 'arrow-shape', 17777 value: 'none' 17778 }, { 17779 name: 'arrow-color', 17780 value: '#999' 17781 }, { 17782 name: 'arrow-fill', 17783 value: 'filled' 17784 }].reduce(function (css, prop) { 17785 styfn$6.arrowPrefixes.forEach(function (prefix) { 17786 var name = prefix + '-' + prop.name; 17787 var val = prop.value; 17788 css[name] = val; 17789 }); 17790 return css; 17791 }, {})); 17792 var parsedProps = {}; 17793 17794 for (var i = 0; i < this.properties.length; i++) { 17795 var prop = this.properties[i]; 17796 17797 if (prop.pointsTo) { 17798 continue; 17799 } 17800 17801 var name = prop.name; 17802 var val = rawProps[name]; 17803 var parsedProp = this.parse(name, val); 17804 parsedProps[name] = parsedProp; 17805 } 17806 17807 _p.defaultProperties = parsedProps; 17808 return _p.defaultProperties; 17809 }; 17810 17811 styfn$6.addDefaultStylesheet = function () { 17812 this.selector(':parent').css({ 17813 'shape': 'rectangle', 17814 'padding': 10, 17815 'background-color': '#eee', 17816 'border-color': '#ccc', 17817 'border-width': 1 17818 }).selector('edge').css({ 17819 'width': 3 17820 }).selector(':loop').css({ 17821 'curve-style': 'bezier' 17822 }).selector('edge:compound').css({ 17823 'curve-style': 'bezier', 17824 'source-endpoint': 'outside-to-line', 17825 'target-endpoint': 'outside-to-line' 17826 }).selector(':selected').css({ 17827 'background-color': '#0169D9', 17828 'line-color': '#0169D9', 17829 'source-arrow-color': '#0169D9', 17830 'target-arrow-color': '#0169D9', 17831 'mid-source-arrow-color': '#0169D9', 17832 'mid-target-arrow-color': '#0169D9' 17833 }).selector(':parent:selected').css({ 17834 'background-color': '#CCE1F9', 17835 'border-color': '#aec8e5' 17836 }).selector(':active').css({ 17837 'overlay-color': 'black', 17838 'overlay-padding': 10, 17839 'overlay-opacity': 0.25 17840 }); 17841 this.defaultLength = this.length; 17842 }; 17843 17844 var styfn$7 = {}; // a caching layer for property parsing 17845 17846 styfn$7.parse = function (name, value, propIsBypass, propIsFlat) { 17847 var self = this; // function values can't be cached in all cases, and there isn't much benefit of caching them anyway 17848 17849 if (fn(value)) { 17850 return self.parseImplWarn(name, value, propIsBypass, propIsFlat); 17851 } 17852 17853 var flatKey = propIsFlat === 'mapping' || propIsFlat === true || propIsFlat === false || propIsFlat == null ? 'dontcare' : propIsFlat; 17854 var bypassKey = propIsBypass ? 't' : 'f'; 17855 var valueKey = '' + value; 17856 var argHash = hashStrings(name, valueKey, bypassKey, flatKey); 17857 var propCache = self.propCache = self.propCache || []; 17858 var ret; 17859 17860 if (!(ret = propCache[argHash])) { 17861 ret = propCache[argHash] = self.parseImplWarn(name, value, propIsBypass, propIsFlat); 17862 } // - bypasses can't be shared b/c the value can be changed by animations or otherwise overridden 17863 // - mappings can't be shared b/c mappings are per-element 17864 17865 17866 if (propIsBypass || propIsFlat === 'mapping') { 17867 // need a copy since props are mutated later in their lifecycles 17868 ret = copy(ret); 17869 17870 if (ret) { 17871 ret.value = copy(ret.value); // because it could be an array, e.g. colour 17872 } 17873 } 17874 17875 return ret; 17876 }; 17877 17878 styfn$7.parseImplWarn = function (name, value, propIsBypass, propIsFlat) { 17879 var prop = this.parseImpl(name, value, propIsBypass, propIsFlat); 17880 17881 if (!prop && value != null) { 17882 warn("The style property `".concat(name, ": ").concat(value, "` is invalid")); 17883 } 17884 17885 return prop; 17886 }; // parse a property; return null on invalid; return parsed property otherwise 17887 // fields : 17888 // - name : the name of the property 17889 // - value : the parsed, native-typed value of the property 17890 // - strValue : a string value that represents the property value in valid css 17891 // - bypass : true iff the property is a bypass property 17892 17893 17894 styfn$7.parseImpl = function (name, value, propIsBypass, propIsFlat) { 17895 var self = this; 17896 name = camel2dash(name); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName') 17897 17898 var property = self.properties[name]; 17899 var passedValue = value; 17900 var types = self.types; 17901 17902 if (!property) { 17903 return null; 17904 } // return null on property of unknown name 17905 17906 17907 if (value === undefined) { 17908 return null; 17909 } // can't assign undefined 17910 // the property may be an alias 17911 17912 17913 if (property.alias) { 17914 property = property.pointsTo; 17915 name = property.name; 17916 } 17917 17918 var valueIsString = string(value); 17919 17920 if (valueIsString) { 17921 // trim the value to make parsing easier 17922 value = value.trim(); 17923 } 17924 17925 var type = property.type; 17926 17927 if (!type) { 17928 return null; 17929 } // no type, no luck 17930 // check if bypass is null or empty string (i.e. indication to delete bypass property) 17931 17932 17933 if (propIsBypass && (value === '' || value === null)) { 17934 return { 17935 name: name, 17936 value: value, 17937 bypass: true, 17938 deleteBypass: true 17939 }; 17940 } // check if value is a function used as a mapper 17941 17942 17943 if (fn(value)) { 17944 return { 17945 name: name, 17946 value: value, 17947 strValue: 'fn', 17948 mapped: types.fn, 17949 bypass: propIsBypass 17950 }; 17951 } // check if value is mapped 17952 17953 17954 var data, mapData; 17955 17956 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))) { 17957 if (propIsBypass) { 17958 return false; 17959 } // mappers not allowed in bypass 17960 17961 17962 var mapped = types.data; 17963 return { 17964 name: name, 17965 value: data, 17966 strValue: '' + value, 17967 mapped: mapped, 17968 field: data[1], 17969 bypass: propIsBypass 17970 }; 17971 } else if (value.length >= 10 && value[0] === 'm' && (mapData = new RegExp(types.mapData.regex).exec(value))) { 17972 if (propIsBypass) { 17973 return false; 17974 } // mappers not allowed in bypass 17975 17976 17977 if (type.multiple) { 17978 return false; 17979 } // impossible to map to num 17980 17981 17982 var _mapped = types.mapData; // we can map only if the type is a colour or a number 17983 17984 if (!(type.color || type.number)) { 17985 return false; 17986 } 17987 17988 var valueMin = this.parse(name, mapData[4]); // parse to validate 17989 17990 if (!valueMin || valueMin.mapped) { 17991 return false; 17992 } // can't be invalid or mapped 17993 17994 17995 var valueMax = this.parse(name, mapData[5]); // parse to validate 17996 17997 if (!valueMax || valueMax.mapped) { 17998 return false; 17999 } // can't be invalid or mapped 18000 // check if valueMin and valueMax are the same 18001 18002 18003 if (valueMin.pfValue === valueMax.pfValue || valueMin.strValue === valueMax.strValue) { 18004 warn('`' + name + ': ' + value + '` is not a valid mapper because the output range is zero; converting to `' + name + ': ' + valueMin.strValue + '`'); 18005 return this.parse(name, valueMin.strValue); // can't make much of a mapper without a range 18006 } else if (type.color) { 18007 var c1 = valueMin.value; 18008 var c2 = valueMax.value; 18009 var same = c1[0] === c2[0] // red 18010 && c1[1] === c2[1] // green 18011 && c1[2] === c2[2] // blue 18012 && ( // optional alpha 18013 c1[3] === c2[3] // same alpha outright 18014 || (c1[3] == null || c1[3] === 1) && ( // full opacity for colour 1? 18015 c2[3] == null || c2[3] === 1) // full opacity for colour 2? 18016 ); 18017 18018 if (same) { 18019 return false; 18020 } // can't make a mapper without a range 18021 18022 } 18023 18024 return { 18025 name: name, 18026 value: mapData, 18027 strValue: '' + value, 18028 mapped: _mapped, 18029 field: mapData[1], 18030 fieldMin: parseFloat(mapData[2]), 18031 // min & max are numeric 18032 fieldMax: parseFloat(mapData[3]), 18033 valueMin: valueMin.value, 18034 valueMax: valueMax.value, 18035 bypass: propIsBypass 18036 }; 18037 } 18038 18039 if (type.multiple && propIsFlat !== 'multiple') { 18040 var vals; 18041 18042 if (valueIsString) { 18043 vals = value.split(/\s+/); 18044 } else if (array(value)) { 18045 vals = value; 18046 } else { 18047 vals = [value]; 18048 } 18049 18050 if (type.evenMultiple && vals.length % 2 !== 0) { 18051 return null; 18052 } 18053 18054 var valArr = []; 18055 var unitsArr = []; 18056 var pfValArr = []; 18057 var strVal = ''; 18058 var hasEnum = false; 18059 18060 for (var i = 0; i < vals.length; i++) { 18061 var p = self.parse(name, vals[i], propIsBypass, 'multiple'); 18062 hasEnum = hasEnum || string(p.value); 18063 valArr.push(p.value); 18064 pfValArr.push(p.pfValue != null ? p.pfValue : p.value); 18065 unitsArr.push(p.units); 18066 strVal += (i > 0 ? ' ' : '') + p.strValue; 18067 } 18068 18069 if (type.validate && !type.validate(valArr, unitsArr)) { 18070 return null; 18071 } 18072 18073 if (type.singleEnum && hasEnum) { 18074 if (valArr.length === 1 && string(valArr[0])) { 18075 return { 18076 name: name, 18077 value: valArr[0], 18078 strValue: valArr[0], 18079 bypass: propIsBypass 18080 }; 18081 } else { 18082 return null; 18083 } 18084 } 18085 18086 return { 18087 name: name, 18088 value: valArr, 18089 pfValue: pfValArr, 18090 strValue: strVal, 18091 bypass: propIsBypass, 18092 units: unitsArr 18093 }; 18094 } // several types also allow enums 18095 18096 18097 var checkEnums = function checkEnums() { 18098 for (var _i = 0; _i < type.enums.length; _i++) { 18099 var en = type.enums[_i]; 18100 18101 if (en === value) { 18102 return { 18103 name: name, 18104 value: value, 18105 strValue: '' + value, 18106 bypass: propIsBypass 18107 }; 18108 } 18109 } 18110 18111 return null; 18112 }; // check the type and return the appropriate object 18113 18114 18115 if (type.number) { 18116 var units; 18117 var implicitUnits = 'px'; // not set => px 18118 18119 if (type.units) { 18120 // use specified units if set 18121 units = type.units; 18122 } 18123 18124 if (type.implicitUnits) { 18125 implicitUnits = type.implicitUnits; 18126 } 18127 18128 if (!type.unitless) { 18129 if (valueIsString) { 18130 var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : ''); 18131 18132 if (units) { 18133 unitsRegex = units; 18134 } // only allow explicit units if so set 18135 18136 18137 var match = value.match('^(' + number$1 + ')(' + unitsRegex + ')?' + '$'); 18138 18139 if (match) { 18140 value = match[1]; 18141 units = match[2] || implicitUnits; 18142 } 18143 } else if (!units || type.implicitUnits) { 18144 units = implicitUnits; // implicitly px if unspecified 18145 } 18146 } 18147 18148 value = parseFloat(value); // if not a number and enums not allowed, then the value is invalid 18149 18150 if (isNaN(value) && type.enums === undefined) { 18151 return null; 18152 } // check if this number type also accepts special keywords in place of numbers 18153 // (i.e. `left`, `auto`, etc) 18154 18155 18156 if (isNaN(value) && type.enums !== undefined) { 18157 value = passedValue; 18158 return checkEnums(); 18159 } // check if value must be an integer 18160 18161 18162 if (type.integer && !integer(value)) { 18163 return null; 18164 } // check value is within range 18165 18166 18167 if (type.min !== undefined && (value < type.min || type.strictMin && value === type.min) || type.max !== undefined && (value > type.max || type.strictMax && value === type.max)) { 18168 return null; 18169 } 18170 18171 var ret = { 18172 name: name, 18173 value: value, 18174 strValue: '' + value + (units ? units : ''), 18175 units: units, 18176 bypass: propIsBypass 18177 }; // normalise value in pixels 18178 18179 if (type.unitless || units !== 'px' && units !== 'em') { 18180 ret.pfValue = value; 18181 } else { 18182 ret.pfValue = units === 'px' || !units ? value : this.getEmSizeInPixels() * value; 18183 } // normalise value in ms 18184 18185 18186 if (units === 'ms' || units === 's') { 18187 ret.pfValue = units === 'ms' ? value : 1000 * value; 18188 } // normalise value in rad 18189 18190 18191 if (units === 'deg' || units === 'rad') { 18192 ret.pfValue = units === 'rad' ? value : deg2rad(value); 18193 } // normalize value in % 18194 18195 18196 if (units === '%') { 18197 ret.pfValue = value / 100; 18198 } 18199 18200 return ret; 18201 } else if (type.propList) { 18202 var props = []; 18203 var propsStr = '' + value; 18204 18205 if (propsStr === 'none') ; else { 18206 // go over each prop 18207 var propsSplit = propsStr.split(/\s*,\s*|\s+/); 18208 18209 for (var _i2 = 0; _i2 < propsSplit.length; _i2++) { 18210 var propName = propsSplit[_i2].trim(); 18211 18212 if (self.properties[propName]) { 18213 props.push(propName); 18214 } else { 18215 warn('`' + propName + '` is not a valid property name'); 18216 } 18217 } 18218 18219 if (props.length === 0) { 18220 return null; 18221 } 18222 } 18223 18224 return { 18225 name: name, 18226 value: props, 18227 strValue: props.length === 0 ? 'none' : props.join(' '), 18228 bypass: propIsBypass 18229 }; 18230 } else if (type.color) { 18231 var tuple = color2tuple(value); 18232 18233 if (!tuple) { 18234 return null; 18235 } 18236 18237 return { 18238 name: name, 18239 value: tuple, 18240 pfValue: tuple, 18241 strValue: 'rgb(' + tuple[0] + ',' + tuple[1] + ',' + tuple[2] + ')', 18242 // n.b. no spaces b/c of multiple support 18243 bypass: propIsBypass 18244 }; 18245 } else if (type.regex || type.regexes) { 18246 // first check enums 18247 if (type.enums) { 18248 var enumProp = checkEnums(); 18249 18250 if (enumProp) { 18251 return enumProp; 18252 } 18253 } 18254 18255 var regexes = type.regexes ? type.regexes : [type.regex]; 18256 18257 for (var _i3 = 0; _i3 < regexes.length; _i3++) { 18258 var regex = new RegExp(regexes[_i3]); // make a regex from the type string 18259 18260 var m = regex.exec(value); 18261 18262 if (m) { 18263 // regex matches 18264 return { 18265 name: name, 18266 value: type.singleRegexMatchValue ? m[1] : m, 18267 strValue: '' + value, 18268 bypass: propIsBypass 18269 }; 18270 } 18271 } 18272 18273 return null; // didn't match any 18274 } else if (type.string) { 18275 // just return 18276 return { 18277 name: name, 18278 value: '' + value, 18279 strValue: '' + value, 18280 bypass: propIsBypass 18281 }; 18282 } else if (type.enums) { 18283 // check enums last because it's a combo type in others 18284 return checkEnums(); 18285 } else { 18286 return null; // not a type we can handle 18287 } 18288 }; 18289 18290 var Style = function Style(cy) { 18291 if (!(this instanceof Style)) { 18292 return new Style(cy); 18293 } 18294 18295 if (!core(cy)) { 18296 error('A style must have a core reference'); 18297 return; 18298 } 18299 18300 this._private = { 18301 cy: cy, 18302 coreStyle: {} 18303 }; 18304 this.length = 0; 18305 this.resetToDefault(); 18306 }; 18307 18308 var styfn$8 = Style.prototype; 18309 18310 styfn$8.instanceString = function () { 18311 return 'style'; 18312 }; // remove all contexts 18313 18314 18315 styfn$8.clear = function () { 18316 for (var i = 0; i < this.length; i++) { 18317 this[i] = undefined; 18318 } 18319 18320 this.length = 0; 18321 var _p = this._private; 18322 _p.newStyle = true; 18323 return this; // chaining 18324 }; 18325 18326 styfn$8.resetToDefault = function () { 18327 this.clear(); 18328 this.addDefaultStylesheet(); 18329 return this; 18330 }; // builds a style object for the 'core' selector 18331 18332 18333 styfn$8.core = function (propName) { 18334 return this._private.coreStyle[propName] || this.getDefaultProperty(propName); 18335 }; // create a new context from the specified selector string and switch to that context 18336 18337 18338 styfn$8.selector = function (selectorStr) { 18339 // 'core' is a special case and does not need a selector 18340 var selector = selectorStr === 'core' ? null : new Selector(selectorStr); 18341 var i = this.length++; // new context means new index 18342 18343 this[i] = { 18344 selector: selector, 18345 properties: [], 18346 mappedProperties: [], 18347 index: i 18348 }; 18349 return this; // chaining 18350 }; // add one or many css rules to the current context 18351 18352 18353 styfn$8.css = function () { 18354 var self = this; 18355 var args = arguments; 18356 18357 if (args.length === 1) { 18358 var map = args[0]; 18359 18360 for (var i = 0; i < self.properties.length; i++) { 18361 var prop = self.properties[i]; 18362 var mapVal = map[prop.name]; 18363 18364 if (mapVal === undefined) { 18365 mapVal = map[dash2camel(prop.name)]; 18366 } 18367 18368 if (mapVal !== undefined) { 18369 this.cssRule(prop.name, mapVal); 18370 } 18371 } 18372 } else if (args.length === 2) { 18373 this.cssRule(args[0], args[1]); 18374 } // do nothing if args are invalid 18375 18376 18377 return this; // chaining 18378 }; 18379 18380 styfn$8.style = styfn$8.css; // add a single css rule to the current context 18381 18382 styfn$8.cssRule = function (name, value) { 18383 // name-value pair 18384 var property = this.parse(name, value); // add property to current context if valid 18385 18386 if (property) { 18387 var i = this.length - 1; 18388 this[i].properties.push(property); 18389 this[i].properties[property.name] = property; // allow access by name as well 18390 18391 if (property.name.match(/pie-(\d+)-background-size/) && property.value) { 18392 this._private.hasPie = true; 18393 } 18394 18395 if (property.mapped) { 18396 this[i].mappedProperties.push(property); 18397 } // add to core style if necessary 18398 18399 18400 var currentSelectorIsCore = !this[i].selector; 18401 18402 if (currentSelectorIsCore) { 18403 this._private.coreStyle[property.name] = property; 18404 } 18405 } 18406 18407 return this; // chaining 18408 }; 18409 18410 styfn$8.append = function (style) { 18411 if (stylesheet(style)) { 18412 style.appendToStyle(this); 18413 } else if (array(style)) { 18414 this.appendFromJson(style); 18415 } else if (string(style)) { 18416 this.appendFromString(style); 18417 } // you probably wouldn't want to append a Style, since you'd duplicate the default parts 18418 18419 18420 return this; 18421 }; // static function 18422 18423 18424 Style.fromJson = function (cy, json) { 18425 var style = new Style(cy); 18426 style.fromJson(json); 18427 return style; 18428 }; 18429 18430 Style.fromString = function (cy, string) { 18431 return new Style(cy).fromString(string); 18432 }; 18433 18434 [styfn, styfn$1, styfn$2, styfn$3, styfn$4, styfn$5, styfn$6, styfn$7].forEach(function (props) { 18435 extend(styfn$8, props); 18436 }); 18437 Style.types = styfn$8.types; 18438 Style.properties = styfn$8.properties; 18439 Style.propertyGroups = styfn$8.propertyGroups; 18440 Style.propertyGroupNames = styfn$8.propertyGroupNames; 18441 Style.propertyGroupKeys = styfn$8.propertyGroupKeys; 18442 18443 var corefn$7 = { 18444 style: function style(newStyle) { 18445 if (newStyle) { 18446 var s = this.setStyle(newStyle); 18447 s.update(); 18448 } 18449 18450 return this._private.style; 18451 }, 18452 setStyle: function setStyle(style) { 18453 var _p = this._private; 18454 18455 if (stylesheet(style)) { 18456 _p.style = style.generateStyle(this); 18457 } else if (array(style)) { 18458 _p.style = Style.fromJson(this, style); 18459 } else if (string(style)) { 18460 _p.style = Style.fromString(this, style); 18461 } else { 18462 _p.style = Style(this); 18463 } 18464 18465 return _p.style; 18466 } 18467 }; 18468 18469 var defaultSelectionType = 'single'; 18470 var corefn$8 = { 18471 autolock: function autolock(bool) { 18472 if (bool !== undefined) { 18473 this._private.autolock = bool ? true : false; 18474 } else { 18475 return this._private.autolock; 18476 } 18477 18478 return this; // chaining 18479 }, 18480 autoungrabify: function autoungrabify(bool) { 18481 if (bool !== undefined) { 18482 this._private.autoungrabify = bool ? true : false; 18483 } else { 18484 return this._private.autoungrabify; 18485 } 18486 18487 return this; // chaining 18488 }, 18489 autounselectify: function autounselectify(bool) { 18490 if (bool !== undefined) { 18491 this._private.autounselectify = bool ? true : false; 18492 } else { 18493 return this._private.autounselectify; 18494 } 18495 18496 return this; // chaining 18497 }, 18498 selectionType: function selectionType(selType) { 18499 var _p = this._private; 18500 18501 if (_p.selectionType == null) { 18502 _p.selectionType = defaultSelectionType; 18503 } 18504 18505 if (selType !== undefined) { 18506 if (selType === 'additive' || selType === 'single') { 18507 _p.selectionType = selType; 18508 } 18509 } else { 18510 return _p.selectionType; 18511 } 18512 18513 return this; 18514 }, 18515 panningEnabled: function panningEnabled(bool) { 18516 if (bool !== undefined) { 18517 this._private.panningEnabled = bool ? true : false; 18518 } else { 18519 return this._private.panningEnabled; 18520 } 18521 18522 return this; // chaining 18523 }, 18524 userPanningEnabled: function userPanningEnabled(bool) { 18525 if (bool !== undefined) { 18526 this._private.userPanningEnabled = bool ? true : false; 18527 } else { 18528 return this._private.userPanningEnabled; 18529 } 18530 18531 return this; // chaining 18532 }, 18533 zoomingEnabled: function zoomingEnabled(bool) { 18534 if (bool !== undefined) { 18535 this._private.zoomingEnabled = bool ? true : false; 18536 } else { 18537 return this._private.zoomingEnabled; 18538 } 18539 18540 return this; // chaining 18541 }, 18542 userZoomingEnabled: function userZoomingEnabled(bool) { 18543 if (bool !== undefined) { 18544 this._private.userZoomingEnabled = bool ? true : false; 18545 } else { 18546 return this._private.userZoomingEnabled; 18547 } 18548 18549 return this; // chaining 18550 }, 18551 boxSelectionEnabled: function boxSelectionEnabled(bool) { 18552 if (bool !== undefined) { 18553 this._private.boxSelectionEnabled = bool ? true : false; 18554 } else { 18555 return this._private.boxSelectionEnabled; 18556 } 18557 18558 return this; // chaining 18559 }, 18560 pan: function pan() { 18561 var args = arguments; 18562 var pan = this._private.pan; 18563 var dim, val, dims, x, y; 18564 18565 switch (args.length) { 18566 case 0: 18567 // .pan() 18568 return pan; 18569 18570 case 1: 18571 if (string(args[0])) { 18572 // .pan('x') 18573 dim = args[0]; 18574 return pan[dim]; 18575 } else if (plainObject(args[0])) { 18576 // .pan({ x: 0, y: 100 }) 18577 if (!this._private.panningEnabled) { 18578 return this; 18579 } 18580 18581 dims = args[0]; 18582 x = dims.x; 18583 y = dims.y; 18584 18585 if (number(x)) { 18586 pan.x = x; 18587 } 18588 18589 if (number(y)) { 18590 pan.y = y; 18591 } 18592 18593 this.emit('pan viewport'); 18594 } 18595 18596 break; 18597 18598 case 2: 18599 // .pan('x', 100) 18600 if (!this._private.panningEnabled) { 18601 return this; 18602 } 18603 18604 dim = args[0]; 18605 val = args[1]; 18606 18607 if ((dim === 'x' || dim === 'y') && number(val)) { 18608 pan[dim] = val; 18609 } 18610 18611 this.emit('pan viewport'); 18612 break; 18613 // invalid 18614 } 18615 18616 this.notify('viewport'); 18617 return this; // chaining 18618 }, 18619 panBy: function panBy(arg0, arg1) { 18620 var args = arguments; 18621 var pan = this._private.pan; 18622 var dim, val, dims, x, y; 18623 18624 if (!this._private.panningEnabled) { 18625 return this; 18626 } 18627 18628 switch (args.length) { 18629 case 1: 18630 if (plainObject(arg0)) { 18631 // .panBy({ x: 0, y: 100 }) 18632 dims = args[0]; 18633 x = dims.x; 18634 y = dims.y; 18635 18636 if (number(x)) { 18637 pan.x += x; 18638 } 18639 18640 if (number(y)) { 18641 pan.y += y; 18642 } 18643 18644 this.emit('pan viewport'); 18645 } 18646 18647 break; 18648 18649 case 2: 18650 // .panBy('x', 100) 18651 dim = arg0; 18652 val = arg1; 18653 18654 if ((dim === 'x' || dim === 'y') && number(val)) { 18655 pan[dim] += val; 18656 } 18657 18658 this.emit('pan viewport'); 18659 break; 18660 // invalid 18661 } 18662 18663 this.notify('viewport'); 18664 return this; // chaining 18665 }, 18666 fit: function fit(elements, padding) { 18667 var viewportState = this.getFitViewport(elements, padding); 18668 18669 if (viewportState) { 18670 var _p = this._private; 18671 _p.zoom = viewportState.zoom; 18672 _p.pan = viewportState.pan; 18673 this.emit('pan zoom viewport'); 18674 this.notify('viewport'); 18675 } 18676 18677 return this; // chaining 18678 }, 18679 getFitViewport: function getFitViewport(elements, padding) { 18680 if (number(elements) && padding === undefined) { 18681 // elements is optional 18682 padding = elements; 18683 elements = undefined; 18684 } 18685 18686 if (!this._private.panningEnabled || !this._private.zoomingEnabled) { 18687 return; 18688 } 18689 18690 var bb; 18691 18692 if (string(elements)) { 18693 var sel = elements; 18694 elements = this.$(sel); 18695 } else if (boundingBox(elements)) { 18696 // assume bb 18697 var bbe = elements; 18698 bb = { 18699 x1: bbe.x1, 18700 y1: bbe.y1, 18701 x2: bbe.x2, 18702 y2: bbe.y2 18703 }; 18704 bb.w = bb.x2 - bb.x1; 18705 bb.h = bb.y2 - bb.y1; 18706 } else if (!elementOrCollection(elements)) { 18707 elements = this.mutableElements(); 18708 } 18709 18710 if (elementOrCollection(elements) && elements.empty()) { 18711 return; 18712 } // can't fit to nothing 18713 18714 18715 bb = bb || elements.boundingBox(); 18716 var w = this.width(); 18717 var h = this.height(); 18718 var zoom; 18719 padding = number(padding) ? padding : 0; 18720 18721 if (!isNaN(w) && !isNaN(h) && w > 0 && h > 0 && !isNaN(bb.w) && !isNaN(bb.h) && bb.w > 0 && bb.h > 0) { 18722 zoom = Math.min((w - 2 * padding) / bb.w, (h - 2 * padding) / bb.h); // crop zoom 18723 18724 zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; 18725 zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; 18726 var pan = { 18727 // now pan to middle 18728 x: (w - zoom * (bb.x1 + bb.x2)) / 2, 18729 y: (h - zoom * (bb.y1 + bb.y2)) / 2 18730 }; 18731 return { 18732 zoom: zoom, 18733 pan: pan 18734 }; 18735 } 18736 18737 return; 18738 }, 18739 zoomRange: function zoomRange(min, max) { 18740 var _p = this._private; 18741 18742 if (max == null) { 18743 var opts = min; 18744 min = opts.min; 18745 max = opts.max; 18746 } 18747 18748 if (number(min) && number(max) && min <= max) { 18749 _p.minZoom = min; 18750 _p.maxZoom = max; 18751 } else if (number(min) && max === undefined && min <= _p.maxZoom) { 18752 _p.minZoom = min; 18753 } else if (number(max) && min === undefined && max >= _p.minZoom) { 18754 _p.maxZoom = max; 18755 } 18756 18757 return this; 18758 }, 18759 minZoom: function minZoom(zoom) { 18760 if (zoom === undefined) { 18761 return this._private.minZoom; 18762 } else { 18763 return this.zoomRange({ 18764 min: zoom 18765 }); 18766 } 18767 }, 18768 maxZoom: function maxZoom(zoom) { 18769 if (zoom === undefined) { 18770 return this._private.maxZoom; 18771 } else { 18772 return this.zoomRange({ 18773 max: zoom 18774 }); 18775 } 18776 }, 18777 getZoomedViewport: function getZoomedViewport(params) { 18778 var _p = this._private; 18779 var currentPan = _p.pan; 18780 var currentZoom = _p.zoom; 18781 var pos; // in rendered px 18782 18783 var zoom; 18784 var bail = false; 18785 18786 if (!_p.zoomingEnabled) { 18787 // zooming disabled 18788 bail = true; 18789 } 18790 18791 if (number(params)) { 18792 // then set the zoom 18793 zoom = params; 18794 } else if (plainObject(params)) { 18795 // then zoom about a point 18796 zoom = params.level; 18797 18798 if (params.position != null) { 18799 pos = modelToRenderedPosition(params.position, currentZoom, currentPan); 18800 } else if (params.renderedPosition != null) { 18801 pos = params.renderedPosition; 18802 } 18803 18804 if (pos != null && !_p.panningEnabled) { 18805 // panning disabled 18806 bail = true; 18807 } 18808 } // crop zoom 18809 18810 18811 zoom = zoom > _p.maxZoom ? _p.maxZoom : zoom; 18812 zoom = zoom < _p.minZoom ? _p.minZoom : zoom; // can't zoom with invalid params 18813 18814 if (bail || !number(zoom) || zoom === currentZoom || pos != null && (!number(pos.x) || !number(pos.y))) { 18815 return null; 18816 } 18817 18818 if (pos != null) { 18819 // set zoom about position 18820 var pan1 = currentPan; 18821 var zoom1 = currentZoom; 18822 var zoom2 = zoom; 18823 var pan2 = { 18824 x: -zoom2 / zoom1 * (pos.x - pan1.x) + pos.x, 18825 y: -zoom2 / zoom1 * (pos.y - pan1.y) + pos.y 18826 }; 18827 return { 18828 zoomed: true, 18829 panned: true, 18830 zoom: zoom2, 18831 pan: pan2 18832 }; 18833 } else { 18834 // just set the zoom 18835 return { 18836 zoomed: true, 18837 panned: false, 18838 zoom: zoom, 18839 pan: currentPan 18840 }; 18841 } 18842 }, 18843 zoom: function zoom(params) { 18844 if (params === undefined) { 18845 // get 18846 return this._private.zoom; 18847 } else { 18848 // set 18849 var vp = this.getZoomedViewport(params); 18850 var _p = this._private; 18851 18852 if (vp == null || !vp.zoomed) { 18853 return this; 18854 } 18855 18856 _p.zoom = vp.zoom; 18857 18858 if (vp.panned) { 18859 _p.pan.x = vp.pan.x; 18860 _p.pan.y = vp.pan.y; 18861 } 18862 18863 this.emit('zoom' + (vp.panned ? ' pan' : '') + ' viewport'); 18864 this.notify('viewport'); 18865 return this; // chaining 18866 } 18867 }, 18868 viewport: function viewport(opts) { 18869 var _p = this._private; 18870 var zoomDefd = true; 18871 var panDefd = true; 18872 var events = []; // to trigger 18873 18874 var zoomFailed = false; 18875 var panFailed = false; 18876 18877 if (!opts) { 18878 return this; 18879 } 18880 18881 if (!number(opts.zoom)) { 18882 zoomDefd = false; 18883 } 18884 18885 if (!plainObject(opts.pan)) { 18886 panDefd = false; 18887 } 18888 18889 if (!zoomDefd && !panDefd) { 18890 return this; 18891 } 18892 18893 if (zoomDefd) { 18894 var z = opts.zoom; 18895 18896 if (z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled) { 18897 zoomFailed = true; 18898 } else { 18899 _p.zoom = z; 18900 events.push('zoom'); 18901 } 18902 } 18903 18904 if (panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled) { 18905 var p = opts.pan; 18906 18907 if (number(p.x)) { 18908 _p.pan.x = p.x; 18909 panFailed = false; 18910 } 18911 18912 if (number(p.y)) { 18913 _p.pan.y = p.y; 18914 panFailed = false; 18915 } 18916 18917 if (!panFailed) { 18918 events.push('pan'); 18919 } 18920 } 18921 18922 if (events.length > 0) { 18923 events.push('viewport'); 18924 this.emit(events.join(' ')); 18925 this.notify('viewport'); 18926 } 18927 18928 return this; // chaining 18929 }, 18930 center: function center(elements) { 18931 var pan = this.getCenterPan(elements); 18932 18933 if (pan) { 18934 this._private.pan = pan; 18935 this.emit('pan viewport'); 18936 this.notify('viewport'); 18937 } 18938 18939 return this; // chaining 18940 }, 18941 getCenterPan: function getCenterPan(elements, zoom) { 18942 if (!this._private.panningEnabled) { 18943 return; 18944 } 18945 18946 if (string(elements)) { 18947 var selector = elements; 18948 elements = this.mutableElements().filter(selector); 18949 } else if (!elementOrCollection(elements)) { 18950 elements = this.mutableElements(); 18951 } 18952 18953 if (elements.length === 0) { 18954 return; 18955 } // can't centre pan to nothing 18956 18957 18958 var bb = elements.boundingBox(); 18959 var w = this.width(); 18960 var h = this.height(); 18961 zoom = zoom === undefined ? this._private.zoom : zoom; 18962 var pan = { 18963 // middle 18964 x: (w - zoom * (bb.x1 + bb.x2)) / 2, 18965 y: (h - zoom * (bb.y1 + bb.y2)) / 2 18966 }; 18967 return pan; 18968 }, 18969 reset: function reset() { 18970 if (!this._private.panningEnabled || !this._private.zoomingEnabled) { 18971 return this; 18972 } 18973 18974 this.viewport({ 18975 pan: { 18976 x: 0, 18977 y: 0 18978 }, 18979 zoom: 1 18980 }); 18981 return this; // chaining 18982 }, 18983 invalidateSize: function invalidateSize() { 18984 this._private.sizeCache = null; 18985 }, 18986 size: function size() { 18987 var _p = this._private; 18988 var container = _p.container; 18989 return _p.sizeCache = _p.sizeCache || (container ? function () { 18990 var style = window$1.getComputedStyle(container); 18991 18992 var val = function val(name) { 18993 return parseFloat(style.getPropertyValue(name)); 18994 }; 18995 18996 return { 18997 width: container.clientWidth - val('padding-left') - val('padding-right'), 18998 height: container.clientHeight - val('padding-top') - val('padding-bottom') 18999 }; 19000 }() : { 19001 // fallback if no container (not 0 b/c can be used for dividing etc) 19002 width: 1, 19003 height: 1 19004 }); 19005 }, 19006 width: function width() { 19007 return this.size().width; 19008 }, 19009 height: function height() { 19010 return this.size().height; 19011 }, 19012 extent: function extent() { 19013 var pan = this._private.pan; 19014 var zoom = this._private.zoom; 19015 var rb = this.renderedExtent(); 19016 var b = { 19017 x1: (rb.x1 - pan.x) / zoom, 19018 x2: (rb.x2 - pan.x) / zoom, 19019 y1: (rb.y1 - pan.y) / zoom, 19020 y2: (rb.y2 - pan.y) / zoom 19021 }; 19022 b.w = b.x2 - b.x1; 19023 b.h = b.y2 - b.y1; 19024 return b; 19025 }, 19026 renderedExtent: function renderedExtent() { 19027 var width = this.width(); 19028 var height = this.height(); 19029 return { 19030 x1: 0, 19031 y1: 0, 19032 x2: width, 19033 y2: height, 19034 w: width, 19035 h: height 19036 }; 19037 } 19038 }; // aliases 19039 19040 corefn$8.centre = corefn$8.center; // backwards compatibility 19041 19042 corefn$8.autolockNodes = corefn$8.autolock; 19043 corefn$8.autoungrabifyNodes = corefn$8.autoungrabify; 19044 19045 var fn$6 = { 19046 data: define$3.data({ 19047 field: 'data', 19048 bindingEvent: 'data', 19049 allowBinding: true, 19050 allowSetting: true, 19051 settingEvent: 'data', 19052 settingTriggersEvent: true, 19053 triggerFnName: 'trigger', 19054 allowGetting: true 19055 }), 19056 removeData: define$3.removeData({ 19057 field: 'data', 19058 event: 'data', 19059 triggerFnName: 'trigger', 19060 triggerEvent: true 19061 }), 19062 scratch: define$3.data({ 19063 field: 'scratch', 19064 bindingEvent: 'scratch', 19065 allowBinding: true, 19066 allowSetting: true, 19067 settingEvent: 'scratch', 19068 settingTriggersEvent: true, 19069 triggerFnName: 'trigger', 19070 allowGetting: true 19071 }), 19072 removeScratch: define$3.removeData({ 19073 field: 'scratch', 19074 event: 'scratch', 19075 triggerFnName: 'trigger', 19076 triggerEvent: true 19077 }) 19078 }; // aliases 19079 19080 fn$6.attr = fn$6.data; 19081 fn$6.removeAttr = fn$6.removeData; 19082 19083 var Core = function Core(opts) { 19084 var cy = this; 19085 opts = extend({}, opts); 19086 var container = opts.container; // allow for passing a wrapped jquery object 19087 // e.g. cytoscape({ container: $('#cy') }) 19088 19089 if (container && !htmlElement(container) && htmlElement(container[0])) { 19090 container = container[0]; 19091 } 19092 19093 var reg = container ? container._cyreg : null; // e.g. already registered some info (e.g. readies) via jquery 19094 19095 reg = reg || {}; 19096 19097 if (reg && reg.cy) { 19098 reg.cy.destroy(); 19099 reg = {}; // old instance => replace reg completely 19100 } 19101 19102 var readies = reg.readies = reg.readies || []; 19103 19104 if (container) { 19105 container._cyreg = reg; 19106 } // make sure container assoc'd reg points to this cy 19107 19108 19109 reg.cy = cy; 19110 var head = window$1 !== undefined && container !== undefined && !opts.headless; 19111 var options = opts; 19112 options.layout = extend({ 19113 name: head ? 'grid' : 'null' 19114 }, options.layout); 19115 options.renderer = extend({ 19116 name: head ? 'canvas' : 'null' 19117 }, options.renderer); 19118 19119 var defVal = function defVal(def, val, altVal) { 19120 if (val !== undefined) { 19121 return val; 19122 } else if (altVal !== undefined) { 19123 return altVal; 19124 } else { 19125 return def; 19126 } 19127 }; 19128 19129 var _p = this._private = { 19130 container: container, 19131 // html dom ele container 19132 ready: false, 19133 // whether ready has been triggered 19134 options: options, 19135 // cached options 19136 elements: new Collection(this), 19137 // elements in the graph 19138 listeners: [], 19139 // list of listeners 19140 aniEles: new Collection(this), 19141 // elements being animated 19142 data: {}, 19143 // data for the core 19144 scratch: {}, 19145 // scratch object for core 19146 layout: null, 19147 renderer: null, 19148 destroyed: false, 19149 // whether destroy was called 19150 notificationsEnabled: true, 19151 // whether notifications are sent to the renderer 19152 minZoom: 1e-50, 19153 maxZoom: 1e50, 19154 zoomingEnabled: defVal(true, options.zoomingEnabled), 19155 userZoomingEnabled: defVal(true, options.userZoomingEnabled), 19156 panningEnabled: defVal(true, options.panningEnabled), 19157 userPanningEnabled: defVal(true, options.userPanningEnabled), 19158 boxSelectionEnabled: defVal(true, options.boxSelectionEnabled), 19159 autolock: defVal(false, options.autolock, options.autolockNodes), 19160 autoungrabify: defVal(false, options.autoungrabify, options.autoungrabifyNodes), 19161 autounselectify: defVal(false, options.autounselectify), 19162 styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled, 19163 zoom: number(options.zoom) ? options.zoom : 1, 19164 pan: { 19165 x: plainObject(options.pan) && number(options.pan.x) ? options.pan.x : 0, 19166 y: plainObject(options.pan) && number(options.pan.y) ? options.pan.y : 0 19167 }, 19168 animation: { 19169 // object for currently-running animations 19170 current: [], 19171 queue: [] 19172 }, 19173 hasCompoundNodes: false 19174 }; 19175 19176 this.createEmitter(); // set selection type 19177 19178 this.selectionType(options.selectionType); // init zoom bounds 19179 19180 this.zoomRange({ 19181 min: options.minZoom, 19182 max: options.maxZoom 19183 }); 19184 19185 var loadExtData = function loadExtData(extData, next) { 19186 var anyIsPromise = extData.some(promise); 19187 19188 if (anyIsPromise) { 19189 return Promise$1.all(extData).then(next); // load all data asynchronously, then exec rest of init 19190 } else { 19191 next(extData); // exec synchronously for convenience 19192 } 19193 }; // start with the default stylesheet so we have something before loading an external stylesheet 19194 19195 19196 if (_p.styleEnabled) { 19197 cy.setStyle([]); 19198 } // create the renderer 19199 19200 19201 var rendererOptions = extend({}, options, options.renderer); // allow rendering hints in top level options 19202 19203 cy.initRenderer(rendererOptions); 19204 19205 var setElesAndLayout = function setElesAndLayout(elements, onload, ondone) { 19206 cy.notifications(false); // remove old elements 19207 19208 var oldEles = cy.mutableElements(); 19209 19210 if (oldEles.length > 0) { 19211 oldEles.remove(); 19212 } 19213 19214 if (elements != null) { 19215 if (plainObject(elements) || array(elements)) { 19216 cy.add(elements); 19217 } 19218 } 19219 19220 cy.one('layoutready', function (e) { 19221 cy.notifications(true); 19222 cy.emit(e); // we missed this event by turning notifications off, so pass it on 19223 19224 cy.one('load', onload); 19225 cy.emitAndNotify('load'); 19226 }).one('layoutstop', function () { 19227 cy.one('done', ondone); 19228 cy.emit('done'); 19229 }); 19230 var layoutOpts = extend({}, cy._private.options.layout); 19231 layoutOpts.eles = cy.elements(); 19232 cy.layout(layoutOpts).run(); 19233 }; 19234 19235 loadExtData([options.style, options.elements], function (thens) { 19236 var initStyle = thens[0]; 19237 var initEles = thens[1]; // init style 19238 19239 if (_p.styleEnabled) { 19240 cy.style().append(initStyle); 19241 } // initial load 19242 19243 19244 setElesAndLayout(initEles, function () { 19245 // onready 19246 cy.startAnimationLoop(); 19247 _p.ready = true; // if a ready callback is specified as an option, the bind it 19248 19249 if (fn(options.ready)) { 19250 cy.on('ready', options.ready); 19251 } // bind all the ready handlers registered before creating this instance 19252 19253 19254 for (var i = 0; i < readies.length; i++) { 19255 var fn$1 = readies[i]; 19256 cy.on('ready', fn$1); 19257 } 19258 19259 if (reg) { 19260 reg.readies = []; 19261 } // 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 19262 19263 19264 cy.emit('ready'); 19265 }, options.done); 19266 }); 19267 }; 19268 19269 var corefn$9 = Core.prototype; // short alias 19270 19271 extend(corefn$9, { 19272 instanceString: function instanceString() { 19273 return 'core'; 19274 }, 19275 isReady: function isReady() { 19276 return this._private.ready; 19277 }, 19278 destroyed: function destroyed() { 19279 return this._private.destroyed; 19280 }, 19281 ready: function ready(fn) { 19282 if (this.isReady()) { 19283 this.emitter().emit('ready', [], fn); // just calls fn as though triggered via ready event 19284 } else { 19285 this.on('ready', fn); 19286 } 19287 19288 return this; 19289 }, 19290 destroy: function destroy() { 19291 var cy = this; 19292 if (cy.destroyed()) return; 19293 cy.stopAnimationLoop(); 19294 cy.destroyRenderer(); 19295 this.emit('destroy'); 19296 cy._private.destroyed = true; 19297 return cy; 19298 }, 19299 hasElementWithId: function hasElementWithId(id) { 19300 return this._private.elements.hasElementWithId(id); 19301 }, 19302 getElementById: function getElementById(id) { 19303 return this._private.elements.getElementById(id); 19304 }, 19305 hasCompoundNodes: function hasCompoundNodes() { 19306 return this._private.hasCompoundNodes; 19307 }, 19308 headless: function headless() { 19309 return this._private.renderer.isHeadless(); 19310 }, 19311 styleEnabled: function styleEnabled() { 19312 return this._private.styleEnabled; 19313 }, 19314 addToPool: function addToPool(eles) { 19315 this._private.elements.merge(eles); 19316 19317 return this; // chaining 19318 }, 19319 removeFromPool: function removeFromPool(eles) { 19320 this._private.elements.unmerge(eles); 19321 19322 return this; 19323 }, 19324 container: function container() { 19325 return this._private.container || null; 19326 }, 19327 mount: function mount(container) { 19328 if (container == null) { 19329 return; 19330 } 19331 19332 var cy = this; 19333 var _p = cy._private; 19334 var options = _p.options; 19335 19336 if (!htmlElement(container) && htmlElement(container[0])) { 19337 container = container[0]; 19338 } 19339 19340 cy.stopAnimationLoop(); 19341 cy.destroyRenderer(); 19342 _p.container = container; 19343 _p.styleEnabled = true; 19344 cy.invalidateSize(); 19345 cy.initRenderer(extend({}, options, options.renderer, { 19346 // allow custom renderer name to be re-used, otherwise use canvas 19347 name: options.renderer.name === 'null' ? 'canvas' : options.renderer.name 19348 })); 19349 cy.startAnimationLoop(); 19350 cy.style(options.style); 19351 cy.emit('mount'); 19352 return cy; 19353 }, 19354 unmount: function unmount() { 19355 var cy = this; 19356 cy.stopAnimationLoop(); 19357 cy.destroyRenderer(); 19358 cy.initRenderer({ 19359 name: 'null' 19360 }); 19361 cy.emit('unmount'); 19362 return cy; 19363 }, 19364 options: function options() { 19365 return copy(this._private.options); 19366 }, 19367 json: function json(obj) { 19368 var cy = this; 19369 var _p = cy._private; 19370 var eles = cy.mutableElements(); 19371 19372 var getFreshRef = function getFreshRef(ele) { 19373 return cy.getElementById(ele.id()); 19374 }; 19375 19376 if (plainObject(obj)) { 19377 // set 19378 cy.startBatch(); 19379 19380 if (obj.elements) { 19381 var idInJson = {}; 19382 19383 var updateEles = function updateEles(jsons, gr) { 19384 var toAdd = []; 19385 var toMod = []; 19386 19387 for (var i = 0; i < jsons.length; i++) { 19388 var json = jsons[i]; 19389 var id = '' + json.data.id; // id must be string 19390 19391 var ele = cy.getElementById(id); 19392 idInJson[id] = true; 19393 19394 if (ele.length !== 0) { 19395 // existing element should be updated 19396 toMod.push({ 19397 ele: ele, 19398 json: json 19399 }); 19400 } else { 19401 // otherwise should be added 19402 if (gr) { 19403 json.group = gr; 19404 toAdd.push(json); 19405 } else { 19406 toAdd.push(json); 19407 } 19408 } 19409 } 19410 19411 cy.add(toAdd); 19412 19413 for (var _i = 0; _i < toMod.length; _i++) { 19414 var _toMod$_i = toMod[_i], 19415 _ele = _toMod$_i.ele, 19416 _json = _toMod$_i.json; 19417 19418 _ele.json(_json); 19419 } 19420 }; 19421 19422 if (array(obj.elements)) { 19423 // elements: [] 19424 updateEles(obj.elements); 19425 } else { 19426 // elements: { nodes: [], edges: [] } 19427 var grs = ['nodes', 'edges']; 19428 19429 for (var i = 0; i < grs.length; i++) { 19430 var gr = grs[i]; 19431 var elements = obj.elements[gr]; 19432 19433 if (array(elements)) { 19434 updateEles(elements, gr); 19435 } 19436 } 19437 } 19438 19439 var parentsToRemove = cy.collection(); 19440 eles.filter(function (ele) { 19441 return !idInJson[ele.id()]; 19442 }).forEach(function (ele) { 19443 if (ele.isParent()) { 19444 parentsToRemove.merge(ele); 19445 } else { 19446 ele.remove(); 19447 } 19448 }); // so that children are not removed w/parent 19449 19450 parentsToRemove.forEach(function (ele) { 19451 return ele.children().move({ 19452 parent: null 19453 }); 19454 }); // intermediate parents may be moved by prior line, so make sure we remove by fresh refs 19455 19456 parentsToRemove.forEach(function (ele) { 19457 return getFreshRef(ele).remove(); 19458 }); 19459 } 19460 19461 if (obj.style) { 19462 cy.style(obj.style); 19463 } 19464 19465 if (obj.zoom != null && obj.zoom !== _p.zoom) { 19466 cy.zoom(obj.zoom); 19467 } 19468 19469 if (obj.pan) { 19470 if (obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y) { 19471 cy.pan(obj.pan); 19472 } 19473 } 19474 19475 if (obj.data) { 19476 cy.data(obj.data); 19477 } 19478 19479 var fields = ['minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled', 'panningEnabled', 'userPanningEnabled', 'boxSelectionEnabled', 'autolock', 'autoungrabify', 'autounselectify']; 19480 19481 for (var _i2 = 0; _i2 < fields.length; _i2++) { 19482 var f = fields[_i2]; 19483 19484 if (obj[f] != null) { 19485 cy[f](obj[f]); 19486 } 19487 } 19488 19489 cy.endBatch(); 19490 return this; // chaining 19491 } else { 19492 // get 19493 var flat = !!obj; 19494 var json = {}; 19495 19496 if (flat) { 19497 json.elements = this.elements().map(function (ele) { 19498 return ele.json(); 19499 }); 19500 } else { 19501 json.elements = {}; 19502 eles.forEach(function (ele) { 19503 var group = ele.group(); 19504 19505 if (!json.elements[group]) { 19506 json.elements[group] = []; 19507 } 19508 19509 json.elements[group].push(ele.json()); 19510 }); 19511 } 19512 19513 if (this._private.styleEnabled) { 19514 json.style = cy.style().json(); 19515 } 19516 19517 json.data = copy(cy.data()); 19518 var options = _p.options; 19519 json.zoomingEnabled = _p.zoomingEnabled; 19520 json.userZoomingEnabled = _p.userZoomingEnabled; 19521 json.zoom = _p.zoom; 19522 json.minZoom = _p.minZoom; 19523 json.maxZoom = _p.maxZoom; 19524 json.panningEnabled = _p.panningEnabled; 19525 json.userPanningEnabled = _p.userPanningEnabled; 19526 json.pan = copy(_p.pan); 19527 json.boxSelectionEnabled = _p.boxSelectionEnabled; 19528 json.renderer = copy(options.renderer); 19529 json.hideEdgesOnViewport = options.hideEdgesOnViewport; 19530 json.textureOnViewport = options.textureOnViewport; 19531 json.wheelSensitivity = options.wheelSensitivity; 19532 json.motionBlur = options.motionBlur; 19533 return json; 19534 } 19535 } 19536 }); 19537 corefn$9.$id = corefn$9.getElementById; 19538 [corefn, corefn$1, elesfn$v, corefn$2, corefn$3, corefn$4, corefn$5, corefn$6, corefn$7, corefn$8, fn$6].forEach(function (props) { 19539 extend(corefn$9, props); 19540 }); 19541 19542 /* eslint-disable no-unused-vars */ 19543 19544 var defaults$9 = { 19545 fit: true, 19546 // whether to fit the viewport to the graph 19547 directed: false, 19548 // whether the tree is directed downwards (or edges can point in any direction if false) 19549 padding: 30, 19550 // padding on fit 19551 circle: false, 19552 // put depths in concentric circles if true, put depths top down if false 19553 grid: false, 19554 // whether to create an even grid into which the DAG is placed (circle:false only) 19555 spacingFactor: 1.75, 19556 // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap) 19557 boundingBox: undefined, 19558 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19559 avoidOverlap: true, 19560 // prevents node overlap, may overflow boundingBox if not enough space 19561 nodeDimensionsIncludeLabels: false, 19562 // Excludes the label when calculating node bounding boxes for the layout algorithm 19563 roots: undefined, 19564 // the roots of the trees 19565 maximal: false, 19566 // whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only) 19567 animate: false, 19568 // whether to transition the node positions 19569 animationDuration: 500, 19570 // duration of animation in ms if enabled 19571 animationEasing: undefined, 19572 // easing of animation if enabled, 19573 animateFilter: function animateFilter(node, i) { 19574 return true; 19575 }, 19576 // 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 19577 ready: undefined, 19578 // callback on layoutready 19579 stop: undefined, 19580 // callback on layoutstop 19581 transform: function transform(node, position) { 19582 return position; 19583 } // transform a given node position. Useful for changing flow direction in discrete layouts 19584 19585 }; 19586 /* eslint-enable */ 19587 19588 var getInfo = function getInfo(ele) { 19589 return ele.scratch('breadthfirst'); 19590 }; 19591 19592 var setInfo = function setInfo(ele, obj) { 19593 return ele.scratch('breadthfirst', obj); 19594 }; 19595 19596 function BreadthFirstLayout(options) { 19597 this.options = extend({}, defaults$9, options); 19598 } 19599 19600 BreadthFirstLayout.prototype.run = function () { 19601 var params = this.options; 19602 var options = params; 19603 var cy = params.cy; 19604 var eles = options.eles; 19605 var nodes = eles.nodes().filter(function (n) { 19606 return !n.isParent(); 19607 }); 19608 var graph = eles; 19609 var directed = options.directed; 19610 var maximal = options.maximal || options.maximalAdjustments > 0; // maximalAdjustments for compat. w/ old code 19611 19612 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 19613 x1: 0, 19614 y1: 0, 19615 w: cy.width(), 19616 h: cy.height() 19617 }); 19618 var roots; 19619 19620 if (elementOrCollection(options.roots)) { 19621 roots = options.roots; 19622 } else if (array(options.roots)) { 19623 var rootsArray = []; 19624 19625 for (var i = 0; i < options.roots.length; i++) { 19626 var id = options.roots[i]; 19627 var ele = cy.getElementById(id); 19628 rootsArray.push(ele); 19629 } 19630 19631 roots = cy.collection(rootsArray); 19632 } else if (string(options.roots)) { 19633 roots = cy.$(options.roots); 19634 } else { 19635 if (directed) { 19636 roots = nodes.roots(); 19637 } else { 19638 var components = eles.components(); 19639 roots = cy.collection(); 19640 19641 var _loop = function _loop(_i) { 19642 var comp = components[_i]; 19643 var maxDegree = comp.maxDegree(false); 19644 var compRoots = comp.filter(function (ele) { 19645 return ele.degree(false) === maxDegree; 19646 }); 19647 roots = roots.add(compRoots); 19648 }; 19649 19650 for (var _i = 0; _i < components.length; _i++) { 19651 _loop(_i); 19652 } 19653 } 19654 } 19655 19656 var depths = []; 19657 var foundByBfs = {}; 19658 19659 var addToDepth = function addToDepth(ele, d) { 19660 if (depths[d] == null) { 19661 depths[d] = []; 19662 } 19663 19664 var i = depths[d].length; 19665 depths[d].push(ele); 19666 setInfo(ele, { 19667 index: i, 19668 depth: d 19669 }); 19670 }; 19671 19672 var changeDepth = function changeDepth(ele, newDepth) { 19673 var _getInfo = getInfo(ele), 19674 depth = _getInfo.depth, 19675 index = _getInfo.index; 19676 19677 depths[depth][index] = null; 19678 addToDepth(ele, newDepth); 19679 }; // find the depths of the nodes 19680 19681 19682 graph.bfs({ 19683 roots: roots, 19684 directed: options.directed, 19685 visit: function visit(node, edge, pNode, i, depth) { 19686 var ele = node[0]; 19687 var id = ele.id(); 19688 addToDepth(ele, depth); 19689 foundByBfs[id] = true; 19690 } 19691 }); // check for nodes not found by bfs 19692 19693 var orphanNodes = []; 19694 19695 for (var _i2 = 0; _i2 < nodes.length; _i2++) { 19696 var _ele = nodes[_i2]; 19697 19698 if (foundByBfs[_ele.id()]) { 19699 continue; 19700 } else { 19701 orphanNodes.push(_ele); 19702 } 19703 } // assign the nodes a depth and index 19704 19705 19706 var assignDepthsAt = function assignDepthsAt(i) { 19707 var eles = depths[i]; 19708 19709 for (var j = 0; j < eles.length; j++) { 19710 var _ele2 = eles[j]; 19711 19712 if (_ele2 == null) { 19713 eles.splice(j, 1); 19714 j--; 19715 continue; 19716 } 19717 19718 setInfo(_ele2, { 19719 depth: i, 19720 index: j 19721 }); 19722 } 19723 }; 19724 19725 var assignDepths = function assignDepths() { 19726 for (var _i3 = 0; _i3 < depths.length; _i3++) { 19727 assignDepthsAt(_i3); 19728 } 19729 }; 19730 19731 var adjustMaximally = function adjustMaximally(ele, shifted) { 19732 var eInfo = getInfo(ele); 19733 var incomers = ele.incomers().filter(function (el) { 19734 return el.isNode() && eles.has(el); 19735 }); 19736 var maxDepth = -1; 19737 var id = ele.id(); 19738 19739 for (var k = 0; k < incomers.length; k++) { 19740 var incmr = incomers[k]; 19741 var iInfo = getInfo(incmr); 19742 maxDepth = Math.max(maxDepth, iInfo.depth); 19743 } 19744 19745 if (eInfo.depth <= maxDepth) { 19746 if (shifted[id]) { 19747 return null; 19748 } 19749 19750 changeDepth(ele, maxDepth + 1); 19751 shifted[id] = true; 19752 return true; 19753 } 19754 19755 return false; 19756 }; // for the directed case, try to make the edges all go down (i.e. depth i => depth i + 1) 19757 19758 19759 if (directed && maximal) { 19760 var Q = []; 19761 var shifted = {}; 19762 19763 var enqueue = function enqueue(n) { 19764 return Q.push(n); 19765 }; 19766 19767 var dequeue = function dequeue() { 19768 return Q.shift(); 19769 }; 19770 19771 nodes.forEach(function (n) { 19772 return Q.push(n); 19773 }); 19774 19775 while (Q.length > 0) { 19776 var _ele3 = dequeue(); 19777 19778 var didShift = adjustMaximally(_ele3, shifted); 19779 19780 if (didShift) { 19781 _ele3.outgoers().filter(function (el) { 19782 return el.isNode() && eles.has(el); 19783 }).forEach(enqueue); 19784 } else if (didShift === null) { 19785 warn('Detected double maximal shift for node `' + _ele3.id() + '`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.'); 19786 break; // exit on failure 19787 } 19788 } 19789 } 19790 19791 assignDepths(); // clear holes 19792 // find min distance we need to leave between nodes 19793 19794 var minDistance = 0; 19795 19796 if (options.avoidOverlap) { 19797 for (var _i4 = 0; _i4 < nodes.length; _i4++) { 19798 var n = nodes[_i4]; 19799 var nbb = n.layoutDimensions(options); 19800 var w = nbb.w; 19801 var h = nbb.h; 19802 minDistance = Math.max(minDistance, w, h); 19803 } 19804 } // get the weighted percent for an element based on its connectivity to other levels 19805 19806 19807 var cachedWeightedPercent = {}; 19808 19809 var getWeightedPercent = function getWeightedPercent(ele) { 19810 if (cachedWeightedPercent[ele.id()]) { 19811 return cachedWeightedPercent[ele.id()]; 19812 } 19813 19814 var eleDepth = getInfo(ele).depth; 19815 var neighbors = ele.neighborhood(); 19816 var percent = 0; 19817 var samples = 0; 19818 19819 for (var _i5 = 0; _i5 < neighbors.length; _i5++) { 19820 var neighbor = neighbors[_i5]; 19821 19822 if (neighbor.isEdge() || neighbor.isParent() || !nodes.has(neighbor)) { 19823 continue; 19824 } 19825 19826 var bf = getInfo(neighbor); 19827 var index = bf.index; 19828 var depth = bf.depth; // unassigned neighbours shouldn't affect the ordering 19829 19830 if (index == null || depth == null) { 19831 continue; 19832 } 19833 19834 var nDepth = depths[depth].length; 19835 19836 if (depth < eleDepth) { 19837 // only get influenced by elements above 19838 percent += index / nDepth; 19839 samples++; 19840 } 19841 } 19842 19843 samples = Math.max(1, samples); 19844 percent = percent / samples; 19845 19846 if (samples === 0) { 19847 // put lone nodes at the start 19848 percent = 0; 19849 } 19850 19851 cachedWeightedPercent[ele.id()] = percent; 19852 return percent; 19853 }; // rearrange the indices in each depth level based on connectivity 19854 19855 19856 var sortFn = function sortFn(a, b) { 19857 var apct = getWeightedPercent(a); 19858 var bpct = getWeightedPercent(b); 19859 var diff = apct - bpct; 19860 19861 if (diff === 0) { 19862 return ascending(a.id(), b.id()); // make sure sort doesn't have don't-care comparisons 19863 } else { 19864 return diff; 19865 } 19866 }; // sort each level to make connected nodes closer 19867 19868 19869 for (var _i6 = 0; _i6 < depths.length; _i6++) { 19870 depths[_i6].sort(sortFn); 19871 19872 assignDepthsAt(_i6); 19873 } // assign orphan nodes to a new top-level depth 19874 19875 19876 var orphanDepth = []; 19877 19878 for (var _i7 = 0; _i7 < orphanNodes.length; _i7++) { 19879 orphanDepth.push(orphanNodes[_i7]); 19880 } 19881 19882 depths.unshift(orphanDepth); 19883 assignDepths(); 19884 var biggestDepthSize = 0; 19885 19886 for (var _i8 = 0; _i8 < depths.length; _i8++) { 19887 biggestDepthSize = Math.max(depths[_i8].length, biggestDepthSize); 19888 } 19889 19890 var center = { 19891 x: bb.x1 + bb.w / 2, 19892 y: bb.x1 + bb.h / 2 19893 }; 19894 var maxDepthSize = depths.reduce(function (max, eles) { 19895 return Math.max(max, eles.length); 19896 }, 0); 19897 19898 var getPosition = function getPosition(ele) { 19899 var _getInfo2 = getInfo(ele), 19900 depth = _getInfo2.depth, 19901 index = _getInfo2.index; 19902 19903 var depthSize = depths[depth].length; 19904 var distanceX = Math.max(bb.w / ((options.grid ? maxDepthSize : depthSize) + 1), minDistance); 19905 var distanceY = Math.max(bb.h / (depths.length + 1), minDistance); 19906 var radiusStepSize = Math.min(bb.w / 2 / depths.length, bb.h / 2 / depths.length); 19907 radiusStepSize = Math.max(radiusStepSize, minDistance); 19908 19909 if (!options.circle) { 19910 var epos = { 19911 x: center.x + (index + 1 - (depthSize + 1) / 2) * distanceX, 19912 y: (depth + 1) * distanceY 19913 }; 19914 return epos; 19915 } else { 19916 var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize / 2 : 0); 19917 var theta = 2 * Math.PI / depths[depth].length * index; 19918 19919 if (depth === 0 && depths[0].length === 1) { 19920 radius = 1; 19921 } 19922 19923 return { 19924 x: center.x + radius * Math.cos(theta), 19925 y: center.y + radius * Math.sin(theta) 19926 }; 19927 } 19928 }; 19929 19930 nodes.layoutPositions(this, options, getPosition); 19931 return this; // chaining 19932 }; 19933 19934 var defaults$a = { 19935 fit: true, 19936 // whether to fit the viewport to the graph 19937 padding: 30, 19938 // the padding on fit 19939 boundingBox: undefined, 19940 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 19941 avoidOverlap: true, 19942 // prevents node overlap, may overflow boundingBox and radius if not enough space 19943 nodeDimensionsIncludeLabels: false, 19944 // Excludes the label when calculating node bounding boxes for the layout algorithm 19945 spacingFactor: undefined, 19946 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 19947 radius: undefined, 19948 // the radius of the circle 19949 startAngle: 3 / 2 * Math.PI, 19950 // where nodes start in radians 19951 sweep: undefined, 19952 // how many radians should be between the first and last node (defaults to full circle) 19953 clockwise: true, 19954 // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 19955 sort: undefined, 19956 // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 19957 animate: false, 19958 // whether to transition the node positions 19959 animationDuration: 500, 19960 // duration of animation in ms if enabled 19961 animationEasing: undefined, 19962 // easing of animation if enabled 19963 animateFilter: function animateFilter(node, i) { 19964 return true; 19965 }, 19966 // 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 19967 ready: undefined, 19968 // callback on layoutready 19969 stop: undefined, 19970 // callback on layoutstop 19971 transform: function transform(node, position) { 19972 return position; 19973 } // transform a given node position. Useful for changing flow direction in discrete layouts 19974 19975 }; 19976 19977 function CircleLayout(options) { 19978 this.options = extend({}, defaults$a, options); 19979 } 19980 19981 CircleLayout.prototype.run = function () { 19982 var params = this.options; 19983 var options = params; 19984 var cy = params.cy; 19985 var eles = options.eles; 19986 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 19987 var nodes = eles.nodes().not(':parent'); 19988 19989 if (options.sort) { 19990 nodes = nodes.sort(options.sort); 19991 } 19992 19993 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 19994 x1: 0, 19995 y1: 0, 19996 w: cy.width(), 19997 h: cy.height() 19998 }); 19999 var center = { 20000 x: bb.x1 + bb.w / 2, 20001 y: bb.y1 + bb.h / 2 20002 }; 20003 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / nodes.length : options.sweep; 20004 var dTheta = sweep / Math.max(1, nodes.length - 1); 20005 var r; 20006 var minDistance = 0; 20007 20008 for (var i = 0; i < nodes.length; i++) { 20009 var n = nodes[i]; 20010 var nbb = n.layoutDimensions(options); 20011 var w = nbb.w; 20012 var h = nbb.h; 20013 minDistance = Math.max(minDistance, w, h); 20014 } 20015 20016 if (number(options.radius)) { 20017 r = options.radius; 20018 } else if (nodes.length <= 1) { 20019 r = 0; 20020 } else { 20021 r = Math.min(bb.h, bb.w) / 2 - minDistance; 20022 } // calculate the radius 20023 20024 20025 if (nodes.length > 1 && options.avoidOverlap) { 20026 // but only if more than one node (can't overlap) 20027 minDistance *= 1.75; // just to have some nice spacing 20028 20029 var dcos = Math.cos(dTheta) - Math.cos(0); 20030 var dsin = Math.sin(dTheta) - Math.sin(0); 20031 var rMin = Math.sqrt(minDistance * minDistance / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping 20032 20033 r = Math.max(rMin, r); 20034 } 20035 20036 var getPos = function getPos(ele, i) { 20037 var theta = options.startAngle + i * dTheta * (clockwise ? 1 : -1); 20038 var rx = r * Math.cos(theta); 20039 var ry = r * Math.sin(theta); 20040 var pos = { 20041 x: center.x + rx, 20042 y: center.y + ry 20043 }; 20044 return pos; 20045 }; 20046 20047 nodes.layoutPositions(this, options, getPos); 20048 return this; // chaining 20049 }; 20050 20051 var defaults$b = { 20052 fit: true, 20053 // whether to fit the viewport to the graph 20054 padding: 30, 20055 // the padding on fit 20056 startAngle: 3 / 2 * Math.PI, 20057 // where nodes start in radians 20058 sweep: undefined, 20059 // how many radians should be between the first and last node (defaults to full circle) 20060 clockwise: true, 20061 // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 20062 equidistant: false, 20063 // whether levels have an equal radial distance betwen them, may cause bounding box overflow 20064 minNodeSpacing: 10, 20065 // min spacing between outside of nodes (used for radius adjustment) 20066 boundingBox: undefined, 20067 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 20068 avoidOverlap: true, 20069 // prevents node overlap, may overflow boundingBox if not enough space 20070 nodeDimensionsIncludeLabels: false, 20071 // Excludes the label when calculating node bounding boxes for the layout algorithm 20072 height: undefined, 20073 // height of layout area (overrides container height) 20074 width: undefined, 20075 // width of layout area (overrides container width) 20076 spacingFactor: undefined, 20077 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 20078 concentric: function concentric(node) { 20079 // returns numeric value for each node, placing higher nodes in levels towards the centre 20080 return node.degree(); 20081 }, 20082 levelWidth: function levelWidth(nodes) { 20083 // the letiation of concentric values in each level 20084 return nodes.maxDegree() / 4; 20085 }, 20086 animate: false, 20087 // whether to transition the node positions 20088 animationDuration: 500, 20089 // duration of animation in ms if enabled 20090 animationEasing: undefined, 20091 // easing of animation if enabled 20092 animateFilter: function animateFilter(node, i) { 20093 return true; 20094 }, 20095 // 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 20096 ready: undefined, 20097 // callback on layoutready 20098 stop: undefined, 20099 // callback on layoutstop 20100 transform: function transform(node, position) { 20101 return position; 20102 } // transform a given node position. Useful for changing flow direction in discrete layouts 20103 20104 }; 20105 20106 function ConcentricLayout(options) { 20107 this.options = extend({}, defaults$b, options); 20108 } 20109 20110 ConcentricLayout.prototype.run = function () { 20111 var params = this.options; 20112 var options = params; 20113 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 20114 var cy = params.cy; 20115 var eles = options.eles; 20116 var nodes = eles.nodes().not(':parent'); 20117 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 20118 x1: 0, 20119 y1: 0, 20120 w: cy.width(), 20121 h: cy.height() 20122 }); 20123 var center = { 20124 x: bb.x1 + bb.w / 2, 20125 y: bb.y1 + bb.h / 2 20126 }; 20127 var nodeValues = []; // { node, value } 20128 20129 var maxNodeSize = 0; 20130 20131 for (var i = 0; i < nodes.length; i++) { 20132 var node = nodes[i]; 20133 var value = void 0; // calculate the node value 20134 20135 value = options.concentric(node); 20136 nodeValues.push({ 20137 value: value, 20138 node: node 20139 }); // for style mapping 20140 20141 node._private.scratch.concentric = value; 20142 } // in case we used the `concentric` in style 20143 20144 20145 nodes.updateStyle(); // calculate max size now based on potentially updated mappers 20146 20147 for (var _i = 0; _i < nodes.length; _i++) { 20148 var _node = nodes[_i]; 20149 20150 var nbb = _node.layoutDimensions(options); 20151 20152 maxNodeSize = Math.max(maxNodeSize, nbb.w, nbb.h); 20153 } // sort node values in descreasing order 20154 20155 20156 nodeValues.sort(function (a, b) { 20157 return b.value - a.value; 20158 }); 20159 var levelWidth = options.levelWidth(nodes); // put the values into levels 20160 20161 var levels = [[]]; 20162 var currentLevel = levels[0]; 20163 20164 for (var _i2 = 0; _i2 < nodeValues.length; _i2++) { 20165 var val = nodeValues[_i2]; 20166 20167 if (currentLevel.length > 0) { 20168 var diff = Math.abs(currentLevel[0].value - val.value); 20169 20170 if (diff >= levelWidth) { 20171 currentLevel = []; 20172 levels.push(currentLevel); 20173 } 20174 } 20175 20176 currentLevel.push(val); 20177 } // create positions from levels 20178 20179 20180 var minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes 20181 20182 if (!options.avoidOverlap) { 20183 // then strictly constrain to bb 20184 var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; 20185 var maxR = Math.min(bb.w, bb.h) / 2 - minDist; 20186 var rStep = maxR / (levels.length + firstLvlHasMulti ? 1 : 0); 20187 minDist = Math.min(minDist, rStep); 20188 } // find the metrics for each level 20189 20190 20191 var r = 0; 20192 20193 for (var _i3 = 0; _i3 < levels.length; _i3++) { 20194 var level = levels[_i3]; 20195 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / level.length : options.sweep; 20196 var dTheta = level.dTheta = sweep / Math.max(1, level.length - 1); // calculate the radius 20197 20198 if (level.length > 1 && options.avoidOverlap) { 20199 // but only if more than one node (can't overlap) 20200 var dcos = Math.cos(dTheta) - Math.cos(0); 20201 var dsin = Math.sin(dTheta) - Math.sin(0); 20202 var rMin = Math.sqrt(minDist * minDist / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping 20203 20204 r = Math.max(rMin, r); 20205 } 20206 20207 level.r = r; 20208 r += minDist; 20209 } 20210 20211 if (options.equidistant) { 20212 var rDeltaMax = 0; 20213 var _r = 0; 20214 20215 for (var _i4 = 0; _i4 < levels.length; _i4++) { 20216 var _level = levels[_i4]; 20217 var rDelta = _level.r - _r; 20218 rDeltaMax = Math.max(rDeltaMax, rDelta); 20219 } 20220 20221 _r = 0; 20222 20223 for (var _i5 = 0; _i5 < levels.length; _i5++) { 20224 var _level2 = levels[_i5]; 20225 20226 if (_i5 === 0) { 20227 _r = _level2.r; 20228 } 20229 20230 _level2.r = _r; 20231 _r += rDeltaMax; 20232 } 20233 } // calculate the node positions 20234 20235 20236 var pos = {}; // id => position 20237 20238 for (var _i6 = 0; _i6 < levels.length; _i6++) { 20239 var _level3 = levels[_i6]; 20240 var _dTheta = _level3.dTheta; 20241 var _r2 = _level3.r; 20242 20243 for (var j = 0; j < _level3.length; j++) { 20244 var _val = _level3[j]; 20245 var theta = options.startAngle + (clockwise ? 1 : -1) * _dTheta * j; 20246 var p = { 20247 x: center.x + _r2 * Math.cos(theta), 20248 y: center.y + _r2 * Math.sin(theta) 20249 }; 20250 pos[_val.node.id()] = p; 20251 } 20252 } // position the nodes 20253 20254 20255 nodes.layoutPositions(this, options, function (ele) { 20256 var id = ele.id(); 20257 return pos[id]; 20258 }); 20259 return this; // chaining 20260 }; 20261 20262 /* 20263 The CoSE layout was written by Gerardo Huck. 20264 https://www.linkedin.com/in/gerardohuck/ 20265 20266 Based on the following article: 20267 http://dl.acm.org/citation.cfm?id=1498047 20268 20269 Modifications tracked on Github. 20270 */ 20271 var DEBUG; 20272 /** 20273 * @brief : default layout options 20274 */ 20275 20276 var defaults$c = { 20277 // Called on `layoutready` 20278 ready: function ready() {}, 20279 // Called on `layoutstop` 20280 stop: function stop() {}, 20281 // Whether to animate while running the layout 20282 // true : Animate continuously as the layout is running 20283 // false : Just show the end result 20284 // 'end' : Animate with the end result, from the initial positions to the end positions 20285 animate: true, 20286 // Easing of the animation for animate:'end' 20287 animationEasing: undefined, 20288 // The duration of the animation for animate:'end' 20289 animationDuration: undefined, 20290 // A function that determines whether the node should be animated 20291 // All nodes animated by default on animate enabled 20292 // Non-animated nodes are positioned immediately when the layout starts 20293 animateFilter: function animateFilter(node, i) { 20294 return true; 20295 }, 20296 // The layout animates only after this many milliseconds for animate:true 20297 // (prevents flashing on fast runs) 20298 animationThreshold: 250, 20299 // Number of iterations between consecutive screen positions update 20300 refresh: 20, 20301 // Whether to fit the network view after when done 20302 fit: true, 20303 // Padding on fit 20304 padding: 30, 20305 // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 20306 boundingBox: undefined, 20307 // Excludes the label when calculating node bounding boxes for the layout algorithm 20308 nodeDimensionsIncludeLabels: false, 20309 // Randomize the initial positions of the nodes (true) or use existing positions (false) 20310 randomize: false, 20311 // Extra spacing between components in non-compound graphs 20312 componentSpacing: 40, 20313 // Node repulsion (non overlapping) multiplier 20314 nodeRepulsion: function nodeRepulsion(node) { 20315 return 2048; 20316 }, 20317 // Node repulsion (overlapping) multiplier 20318 nodeOverlap: 4, 20319 // Ideal edge (non nested) length 20320 idealEdgeLength: function idealEdgeLength(edge) { 20321 return 32; 20322 }, 20323 // Divisor to compute edge forces 20324 edgeElasticity: function edgeElasticity(edge) { 20325 return 32; 20326 }, 20327 // Nesting factor (multiplier) to compute ideal edge length for nested edges 20328 nestingFactor: 1.2, 20329 // Gravity force (constant) 20330 gravity: 1, 20331 // Maximum number of iterations to perform 20332 numIter: 1000, 20333 // Initial temperature (maximum node displacement) 20334 initialTemp: 1000, 20335 // Cooling factor (how the temperature is reduced between consecutive iterations 20336 coolingFactor: 0.99, 20337 // Lower temperature threshold (below this point the layout will end) 20338 minTemp: 1.0 20339 }; 20340 /** 20341 * @brief : constructor 20342 * @arg options : object containing layout options 20343 */ 20344 20345 function CoseLayout(options) { 20346 this.options = extend({}, defaults$c, options); 20347 this.options.layout = this; 20348 } 20349 /** 20350 * @brief : runs the layout 20351 */ 20352 20353 20354 CoseLayout.prototype.run = function () { 20355 var options = this.options; 20356 var cy = options.cy; 20357 var layout = this; 20358 layout.stopped = false; 20359 20360 if (options.animate === true || options.animate === false) { 20361 layout.emit({ 20362 type: 'layoutstart', 20363 layout: layout 20364 }); 20365 } // Set DEBUG - Global variable 20366 20367 20368 if (true === options.debug) { 20369 DEBUG = true; 20370 } else { 20371 DEBUG = false; 20372 } // Initialize layout info 20373 20374 20375 var layoutInfo = createLayoutInfo(cy, layout, options); // Show LayoutInfo contents if debugging 20376 20377 if (DEBUG) { 20378 printLayoutInfo(layoutInfo); 20379 } // If required, randomize node positions 20380 20381 20382 if (options.randomize) { 20383 randomizePositions(layoutInfo); 20384 } 20385 20386 var startTime = performanceNow(); 20387 20388 var refresh = function refresh() { 20389 refreshPositions(layoutInfo, cy, options); // Fit the graph if necessary 20390 20391 if (true === options.fit) { 20392 cy.fit(options.padding); 20393 } 20394 }; 20395 20396 var mainLoop = function mainLoop(i) { 20397 if (layout.stopped || i >= options.numIter) { 20398 // logDebug("Layout manually stopped. Stopping computation in step " + i); 20399 return false; 20400 } // Do one step in the phisical simulation 20401 20402 20403 step$1(layoutInfo, options); // Update temperature 20404 20405 layoutInfo.temperature = layoutInfo.temperature * options.coolingFactor; // logDebug("New temperature: " + layoutInfo.temperature); 20406 20407 if (layoutInfo.temperature < options.minTemp) { 20408 // logDebug("Temperature drop below minimum threshold. Stopping computation in step " + i); 20409 return false; 20410 } 20411 20412 return true; 20413 }; 20414 20415 var done = function done() { 20416 if (options.animate === true || options.animate === false) { 20417 refresh(); // Layout has finished 20418 20419 layout.one('layoutstop', options.stop); 20420 layout.emit({ 20421 type: 'layoutstop', 20422 layout: layout 20423 }); 20424 } else { 20425 var nodes = options.eles.nodes(); 20426 var getScaledPos = getScaleInBoundsFn(layoutInfo, options, nodes); 20427 nodes.layoutPositions(layout, options, getScaledPos); 20428 } 20429 }; 20430 20431 var i = 0; 20432 var loopRet = true; 20433 20434 if (options.animate === true) { 20435 var frame = function frame() { 20436 var f = 0; 20437 20438 while (loopRet && f < options.refresh) { 20439 loopRet = mainLoop(i); 20440 i++; 20441 f++; 20442 } 20443 20444 if (!loopRet) { 20445 // it's done 20446 separateComponents(layoutInfo, options); 20447 done(); 20448 } else { 20449 var now = performanceNow(); 20450 20451 if (now - startTime >= options.animationThreshold) { 20452 refresh(); 20453 } 20454 20455 requestAnimationFrame(frame); 20456 } 20457 }; 20458 20459 frame(); 20460 } else { 20461 while (loopRet) { 20462 loopRet = mainLoop(i); 20463 i++; 20464 } 20465 20466 separateComponents(layoutInfo, options); 20467 done(); 20468 } 20469 20470 return this; // chaining 20471 }; 20472 /** 20473 * @brief : called on continuous layouts to stop them before they finish 20474 */ 20475 20476 20477 CoseLayout.prototype.stop = function () { 20478 this.stopped = true; 20479 20480 if (this.thread) { 20481 this.thread.stop(); 20482 } 20483 20484 this.emit('layoutstop'); 20485 return this; // chaining 20486 }; 20487 20488 CoseLayout.prototype.destroy = function () { 20489 if (this.thread) { 20490 this.thread.stop(); 20491 } 20492 20493 return this; // chaining 20494 }; 20495 /** 20496 * @brief : Creates an object which is contains all the data 20497 * used in the layout process 20498 * @arg cy : cytoscape.js object 20499 * @return : layoutInfo object initialized 20500 */ 20501 20502 20503 var createLayoutInfo = function createLayoutInfo(cy, layout, options) { 20504 // Shortcut 20505 var edges = options.eles.edges(); 20506 var nodes = options.eles.nodes(); 20507 var layoutInfo = { 20508 isCompound: cy.hasCompoundNodes(), 20509 layoutNodes: [], 20510 idToIndex: {}, 20511 nodeSize: nodes.size(), 20512 graphSet: [], 20513 indexToGraph: [], 20514 layoutEdges: [], 20515 edgeSize: edges.size(), 20516 temperature: options.initialTemp, 20517 clientWidth: cy.width(), 20518 clientHeight: cy.width(), 20519 boundingBox: makeBoundingBox(options.boundingBox ? options.boundingBox : { 20520 x1: 0, 20521 y1: 0, 20522 w: cy.width(), 20523 h: cy.height() 20524 }) 20525 }; 20526 var components = options.eles.components(); 20527 var id2cmptId = {}; 20528 20529 for (var i = 0; i < components.length; i++) { 20530 var component = components[i]; 20531 20532 for (var j = 0; j < component.length; j++) { 20533 var node = component[j]; 20534 id2cmptId[node.id()] = i; 20535 } 20536 } // Iterate over all nodes, creating layout nodes 20537 20538 20539 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20540 var n = nodes[i]; 20541 var nbb = n.layoutDimensions(options); 20542 var tempNode = {}; 20543 tempNode.isLocked = n.locked(); 20544 tempNode.id = n.data('id'); 20545 tempNode.parentId = n.data('parent'); 20546 tempNode.cmptId = id2cmptId[n.id()]; 20547 tempNode.children = []; 20548 tempNode.positionX = n.position('x'); 20549 tempNode.positionY = n.position('y'); 20550 tempNode.offsetX = 0; 20551 tempNode.offsetY = 0; 20552 tempNode.height = nbb.w; 20553 tempNode.width = nbb.h; 20554 tempNode.maxX = tempNode.positionX + tempNode.width / 2; 20555 tempNode.minX = tempNode.positionX - tempNode.width / 2; 20556 tempNode.maxY = tempNode.positionY + tempNode.height / 2; 20557 tempNode.minY = tempNode.positionY - tempNode.height / 2; 20558 tempNode.padLeft = parseFloat(n.style('padding')); 20559 tempNode.padRight = parseFloat(n.style('padding')); 20560 tempNode.padTop = parseFloat(n.style('padding')); 20561 tempNode.padBottom = parseFloat(n.style('padding')); // forces 20562 20563 tempNode.nodeRepulsion = fn(options.nodeRepulsion) ? options.nodeRepulsion(n) : options.nodeRepulsion; // Add new node 20564 20565 layoutInfo.layoutNodes.push(tempNode); // Add entry to id-index map 20566 20567 layoutInfo.idToIndex[tempNode.id] = i; 20568 } // Inline implementation of a queue, used for traversing the graph in BFS order 20569 20570 20571 var queue = []; 20572 var start = 0; // Points to the start the queue 20573 20574 var end = -1; // Points to the end of the queue 20575 20576 var tempGraph = []; // Second pass to add child information and 20577 // initialize queue for hierarchical traversal 20578 20579 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20580 var n = layoutInfo.layoutNodes[i]; 20581 var p_id = n.parentId; // Check if node n has a parent node 20582 20583 if (null != p_id) { 20584 // Add node Id to parent's list of children 20585 layoutInfo.layoutNodes[layoutInfo.idToIndex[p_id]].children.push(n.id); 20586 } else { 20587 // If a node doesn't have a parent, then it's in the root graph 20588 queue[++end] = n.id; 20589 tempGraph.push(n.id); 20590 } 20591 } // Add root graph to graphSet 20592 20593 20594 layoutInfo.graphSet.push(tempGraph); // Traverse the graph, level by level, 20595 20596 while (start <= end) { 20597 // Get the node to visit and remove it from queue 20598 var node_id = queue[start++]; 20599 var node_ix = layoutInfo.idToIndex[node_id]; 20600 var node = layoutInfo.layoutNodes[node_ix]; 20601 var children = node.children; 20602 20603 if (children.length > 0) { 20604 // Add children nodes as a new graph to graph set 20605 layoutInfo.graphSet.push(children); // Add children to que queue to be visited 20606 20607 for (var i = 0; i < children.length; i++) { 20608 queue[++end] = children[i]; 20609 } 20610 } 20611 } // Create indexToGraph map 20612 20613 20614 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 20615 var graph = layoutInfo.graphSet[i]; 20616 20617 for (var j = 0; j < graph.length; j++) { 20618 var index = layoutInfo.idToIndex[graph[j]]; 20619 layoutInfo.indexToGraph[index] = i; 20620 } 20621 } // Iterate over all edges, creating Layout Edges 20622 20623 20624 for (var i = 0; i < layoutInfo.edgeSize; i++) { 20625 var e = edges[i]; 20626 var tempEdge = {}; 20627 tempEdge.id = e.data('id'); 20628 tempEdge.sourceId = e.data('source'); 20629 tempEdge.targetId = e.data('target'); // Compute ideal length 20630 20631 var idealLength = fn(options.idealEdgeLength) ? options.idealEdgeLength(e) : options.idealEdgeLength; 20632 var elasticity = fn(options.edgeElasticity) ? options.edgeElasticity(e) : options.edgeElasticity; // Check if it's an inter graph edge 20633 20634 var sourceIx = layoutInfo.idToIndex[tempEdge.sourceId]; 20635 var targetIx = layoutInfo.idToIndex[tempEdge.targetId]; 20636 var sourceGraph = layoutInfo.indexToGraph[sourceIx]; 20637 var targetGraph = layoutInfo.indexToGraph[targetIx]; 20638 20639 if (sourceGraph != targetGraph) { 20640 // Find lowest common graph ancestor 20641 var lca = findLCA(tempEdge.sourceId, tempEdge.targetId, layoutInfo); // Compute sum of node depths, relative to lca graph 20642 20643 var lcaGraph = layoutInfo.graphSet[lca]; 20644 var depth = 0; // Source depth 20645 20646 var tempNode = layoutInfo.layoutNodes[sourceIx]; 20647 20648 while (-1 === lcaGraph.indexOf(tempNode.id)) { 20649 tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]]; 20650 depth++; 20651 } // Target depth 20652 20653 20654 tempNode = layoutInfo.layoutNodes[targetIx]; 20655 20656 while (-1 === lcaGraph.indexOf(tempNode.id)) { 20657 tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]]; 20658 depth++; 20659 } // logDebug('LCA of nodes ' + tempEdge.sourceId + ' and ' + tempEdge.targetId + 20660 // ". Index: " + lca + " Contents: " + lcaGraph.toString() + 20661 // ". Depth: " + depth); 20662 // Update idealLength 20663 20664 20665 idealLength *= depth * options.nestingFactor; 20666 } 20667 20668 tempEdge.idealLength = idealLength; 20669 tempEdge.elasticity = elasticity; 20670 layoutInfo.layoutEdges.push(tempEdge); 20671 } // Finally, return layoutInfo object 20672 20673 20674 return layoutInfo; 20675 }; 20676 /** 20677 * @brief : This function finds the index of the lowest common 20678 * graph ancestor between 2 nodes in the subtree 20679 * (from the graph hierarchy induced tree) whose 20680 * root is graphIx 20681 * 20682 * @arg node1: node1's ID 20683 * @arg node2: node2's ID 20684 * @arg layoutInfo: layoutInfo object 20685 * 20686 */ 20687 20688 20689 var findLCA = function findLCA(node1, node2, layoutInfo) { 20690 // Find their common ancester, starting from the root graph 20691 var res = findLCA_aux(node1, node2, 0, layoutInfo); 20692 20693 if (2 > res.count) { 20694 // If aux function couldn't find the common ancester, 20695 // then it is the root graph 20696 return 0; 20697 } else { 20698 return res.graph; 20699 } 20700 }; 20701 /** 20702 * @brief : Auxiliary function used for LCA computation 20703 * 20704 * @arg node1 : node1's ID 20705 * @arg node2 : node2's ID 20706 * @arg graphIx : subgraph index 20707 * @arg layoutInfo : layoutInfo object 20708 * 20709 * @return : object of the form {count: X, graph: Y}, where: 20710 * X is the number of ancesters (max: 2) found in 20711 * graphIx (and it's subgraphs), 20712 * Y is the graph index of the lowest graph containing 20713 * all X nodes 20714 */ 20715 20716 20717 var findLCA_aux = function findLCA_aux(node1, node2, graphIx, layoutInfo) { 20718 var graph = layoutInfo.graphSet[graphIx]; // If both nodes belongs to graphIx 20719 20720 if (-1 < graph.indexOf(node1) && -1 < graph.indexOf(node2)) { 20721 return { 20722 count: 2, 20723 graph: graphIx 20724 }; 20725 } // Make recursive calls for all subgraphs 20726 20727 20728 var c = 0; 20729 20730 for (var i = 0; i < graph.length; i++) { 20731 var nodeId = graph[i]; 20732 var nodeIx = layoutInfo.idToIndex[nodeId]; 20733 var children = layoutInfo.layoutNodes[nodeIx].children; // If the node has no child, skip it 20734 20735 if (0 === children.length) { 20736 continue; 20737 } 20738 20739 var childGraphIx = layoutInfo.indexToGraph[layoutInfo.idToIndex[children[0]]]; 20740 var result = findLCA_aux(node1, node2, childGraphIx, layoutInfo); 20741 20742 if (0 === result.count) { 20743 // Neither node1 nor node2 are present in this subgraph 20744 continue; 20745 } else if (1 === result.count) { 20746 // One of (node1, node2) is present in this subgraph 20747 c++; 20748 20749 if (2 === c) { 20750 // We've already found both nodes, no need to keep searching 20751 break; 20752 } 20753 } else { 20754 // Both nodes are present in this subgraph 20755 return result; 20756 } 20757 } 20758 20759 return { 20760 count: c, 20761 graph: graphIx 20762 }; 20763 }; 20764 /** 20765 * @brief: printsLayoutInfo into js console 20766 * Only used for debbuging 20767 */ 20768 20769 20770 if (false) { 20771 var printLayoutInfo; 20772 } 20773 /** 20774 * @brief : Randomizes the position of all nodes 20775 */ 20776 20777 20778 var randomizePositions = function randomizePositions(layoutInfo, cy) { 20779 var width = layoutInfo.clientWidth; 20780 var height = layoutInfo.clientHeight; 20781 20782 for (var i = 0; i < layoutInfo.nodeSize; i++) { 20783 var n = layoutInfo.layoutNodes[i]; // No need to randomize compound nodes or locked nodes 20784 20785 if (0 === n.children.length && !n.isLocked) { 20786 n.positionX = Math.random() * width; 20787 n.positionY = Math.random() * height; 20788 } 20789 } 20790 }; 20791 20792 var getScaleInBoundsFn = function getScaleInBoundsFn(layoutInfo, options, nodes) { 20793 var bb = layoutInfo.boundingBox; 20794 var coseBB = { 20795 x1: Infinity, 20796 x2: -Infinity, 20797 y1: Infinity, 20798 y2: -Infinity 20799 }; 20800 20801 if (options.boundingBox) { 20802 nodes.forEach(function (node) { 20803 var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[node.data('id')]]; 20804 coseBB.x1 = Math.min(coseBB.x1, lnode.positionX); 20805 coseBB.x2 = Math.max(coseBB.x2, lnode.positionX); 20806 coseBB.y1 = Math.min(coseBB.y1, lnode.positionY); 20807 coseBB.y2 = Math.max(coseBB.y2, lnode.positionY); 20808 }); 20809 coseBB.w = coseBB.x2 - coseBB.x1; 20810 coseBB.h = coseBB.y2 - coseBB.y1; 20811 } 20812 20813 return function (ele, i) { 20814 var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[ele.data('id')]]; 20815 20816 if (options.boundingBox) { 20817 // then add extra bounding box constraint 20818 var pctX = (lnode.positionX - coseBB.x1) / coseBB.w; 20819 var pctY = (lnode.positionY - coseBB.y1) / coseBB.h; 20820 return { 20821 x: bb.x1 + pctX * bb.w, 20822 y: bb.y1 + pctY * bb.h 20823 }; 20824 } else { 20825 return { 20826 x: lnode.positionX, 20827 y: lnode.positionY 20828 }; 20829 } 20830 }; 20831 }; 20832 /** 20833 * @brief : Updates the positions of nodes in the network 20834 * @arg layoutInfo : LayoutInfo object 20835 * @arg cy : Cytoscape object 20836 * @arg options : Layout options 20837 */ 20838 20839 20840 var refreshPositions = function refreshPositions(layoutInfo, cy, options) { 20841 // var s = 'Refreshing positions'; 20842 // logDebug(s); 20843 var layout = options.layout; 20844 var nodes = options.eles.nodes(); 20845 var getScaledPos = getScaleInBoundsFn(layoutInfo, options, nodes); 20846 nodes.positions(getScaledPos); // Trigger layoutReady only on first call 20847 20848 if (true !== layoutInfo.ready) { 20849 // s = 'Triggering layoutready'; 20850 // logDebug(s); 20851 layoutInfo.ready = true; 20852 layout.one('layoutready', options.ready); 20853 layout.emit({ 20854 type: 'layoutready', 20855 layout: this 20856 }); 20857 } 20858 }; 20859 /** 20860 * @brief : Logs a debug message in JS console, if DEBUG is ON 20861 */ 20862 // var logDebug = function(text) { 20863 // if (DEBUG) { 20864 // console.debug(text); 20865 // } 20866 // }; 20867 20868 /** 20869 * @brief : Performs one iteration of the physical simulation 20870 * @arg layoutInfo : LayoutInfo object already initialized 20871 * @arg cy : Cytoscape object 20872 * @arg options : Layout options 20873 */ 20874 20875 20876 var step$1 = function step(layoutInfo, options, _step) { 20877 // var s = "\n\n###############################"; 20878 // s += "\nSTEP: " + step; 20879 // s += "\n###############################\n"; 20880 // logDebug(s); 20881 // Calculate node repulsions 20882 calculateNodeForces(layoutInfo, options); // Calculate edge forces 20883 20884 calculateEdgeForces(layoutInfo); // Calculate gravity forces 20885 20886 calculateGravityForces(layoutInfo, options); // Propagate forces from parent to child 20887 20888 propagateForces(layoutInfo); // Update positions based on calculated forces 20889 20890 updatePositions(layoutInfo); 20891 }; 20892 /** 20893 * @brief : Computes the node repulsion forces 20894 */ 20895 20896 20897 var calculateNodeForces = function calculateNodeForces(layoutInfo, options) { 20898 // Go through each of the graphs in graphSet 20899 // Nodes only repel each other if they belong to the same graph 20900 // var s = 'calculateNodeForces'; 20901 // logDebug(s); 20902 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 20903 var graph = layoutInfo.graphSet[i]; 20904 var numNodes = graph.length; // s = "Set: " + graph.toString(); 20905 // logDebug(s); 20906 // Now get all the pairs of nodes 20907 // Only get each pair once, (A, B) = (B, A) 20908 20909 for (var j = 0; j < numNodes; j++) { 20910 var node1 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]]; 20911 20912 for (var k = j + 1; k < numNodes; k++) { 20913 var node2 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[k]]]; 20914 nodeRepulsion(node1, node2, layoutInfo, options); 20915 } 20916 } 20917 } 20918 }; 20919 20920 var randomDistance = function randomDistance(max) { 20921 return -max + 2 * max * Math.random(); 20922 }; 20923 /** 20924 * @brief : Compute the node repulsion forces between a pair of nodes 20925 */ 20926 20927 20928 var nodeRepulsion = function nodeRepulsion(node1, node2, layoutInfo, options) { 20929 // var s = "Node repulsion. Node1: " + node1.id + " Node2: " + node2.id; 20930 var cmptId1 = node1.cmptId; 20931 var cmptId2 = node2.cmptId; 20932 20933 if (cmptId1 !== cmptId2 && !layoutInfo.isCompound) { 20934 return; 20935 } // Get direction of line connecting both node centers 20936 20937 20938 var directionX = node2.positionX - node1.positionX; 20939 var directionY = node2.positionY - node1.positionY; 20940 var maxRandDist = 1; // s += "\ndirectionX: " + directionX + ", directionY: " + directionY; 20941 // If both centers are the same, apply a random force 20942 20943 if (0 === directionX && 0 === directionY) { 20944 directionX = randomDistance(maxRandDist); 20945 directionY = randomDistance(maxRandDist); 20946 } 20947 20948 var overlap = nodesOverlap(node1, node2, directionX, directionY); 20949 20950 if (overlap > 0) { 20951 // s += "\nNodes DO overlap."; 20952 // s += "\nOverlap: " + overlap; 20953 // If nodes overlap, repulsion force is proportional 20954 // to the overlap 20955 var force = options.nodeOverlap * overlap; // Compute the module and components of the force vector 20956 20957 var distance = Math.sqrt(directionX * directionX + directionY * directionY); // s += "\nDistance: " + distance; 20958 20959 var forceX = force * directionX / distance; 20960 var forceY = force * directionY / distance; 20961 } else { 20962 // s += "\nNodes do NOT overlap."; 20963 // If there's no overlap, force is inversely proportional 20964 // to squared distance 20965 // Get clipping points for both nodes 20966 var point1 = findClippingPoint(node1, directionX, directionY); 20967 var point2 = findClippingPoint(node2, -1 * directionX, -1 * directionY); // Use clipping points to compute distance 20968 20969 var distanceX = point2.x - point1.x; 20970 var distanceY = point2.y - point1.y; 20971 var distanceSqr = distanceX * distanceX + distanceY * distanceY; 20972 var distance = Math.sqrt(distanceSqr); // s += "\nDistance: " + distance; 20973 // Compute the module and components of the force vector 20974 20975 var force = (node1.nodeRepulsion + node2.nodeRepulsion) / distanceSqr; 20976 var forceX = force * distanceX / distance; 20977 var forceY = force * distanceY / distance; 20978 } // Apply force 20979 20980 20981 if (!node1.isLocked) { 20982 node1.offsetX -= forceX; 20983 node1.offsetY -= forceY; 20984 } 20985 20986 if (!node2.isLocked) { 20987 node2.offsetX += forceX; 20988 node2.offsetY += forceY; 20989 } // s += "\nForceX: " + forceX + " ForceY: " + forceY; 20990 // logDebug(s); 20991 20992 20993 return; 20994 }; 20995 /** 20996 * @brief : Determines whether two nodes overlap or not 20997 * @return : Amount of overlapping (0 => no overlap) 20998 */ 20999 21000 21001 var nodesOverlap = function nodesOverlap(node1, node2, dX, dY) { 21002 if (dX > 0) { 21003 var overlapX = node1.maxX - node2.minX; 21004 } else { 21005 var overlapX = node2.maxX - node1.minX; 21006 } 21007 21008 if (dY > 0) { 21009 var overlapY = node1.maxY - node2.minY; 21010 } else { 21011 var overlapY = node2.maxY - node1.minY; 21012 } 21013 21014 if (overlapX >= 0 && overlapY >= 0) { 21015 return Math.sqrt(overlapX * overlapX + overlapY * overlapY); 21016 } else { 21017 return 0; 21018 } 21019 }; 21020 /** 21021 * @brief : Finds the point in which an edge (direction dX, dY) intersects 21022 * the rectangular bounding box of it's source/target node 21023 */ 21024 21025 21026 var findClippingPoint = function findClippingPoint(node, dX, dY) { 21027 // Shorcuts 21028 var X = node.positionX; 21029 var Y = node.positionY; 21030 var H = node.height || 1; 21031 var W = node.width || 1; 21032 var dirSlope = dY / dX; 21033 var nodeSlope = H / W; // var s = 'Computing clipping point of node ' + node.id + 21034 // " . Height: " + H + ", Width: " + W + 21035 // "\nDirection " + dX + ", " + dY; 21036 // 21037 // Compute intersection 21038 21039 var res = {}; // Case: Vertical direction (up) 21040 21041 if (0 === dX && 0 < dY) { 21042 res.x = X; // s += "\nUp direction"; 21043 21044 res.y = Y + H / 2; 21045 return res; 21046 } // Case: Vertical direction (down) 21047 21048 21049 if (0 === dX && 0 > dY) { 21050 res.x = X; 21051 res.y = Y + H / 2; // s += "\nDown direction"; 21052 21053 return res; 21054 } // Case: Intersects the right border 21055 21056 21057 if (0 < dX && -1 * nodeSlope <= dirSlope && dirSlope <= nodeSlope) { 21058 res.x = X + W / 2; 21059 res.y = Y + W * dY / 2 / dX; // s += "\nRightborder"; 21060 21061 return res; 21062 } // Case: Intersects the left border 21063 21064 21065 if (0 > dX && -1 * nodeSlope <= dirSlope && dirSlope <= nodeSlope) { 21066 res.x = X - W / 2; 21067 res.y = Y - W * dY / 2 / dX; // s += "\nLeftborder"; 21068 21069 return res; 21070 } // Case: Intersects the top border 21071 21072 21073 if (0 < dY && (dirSlope <= -1 * nodeSlope || dirSlope >= nodeSlope)) { 21074 res.x = X + H * dX / 2 / dY; 21075 res.y = Y + H / 2; // s += "\nTop border"; 21076 21077 return res; 21078 } // Case: Intersects the bottom border 21079 21080 21081 if (0 > dY && (dirSlope <= -1 * nodeSlope || dirSlope >= nodeSlope)) { 21082 res.x = X - H * dX / 2 / dY; 21083 res.y = Y - H / 2; // s += "\nBottom border"; 21084 21085 return res; 21086 } // s += "\nClipping point found at " + res.x + ", " + res.y; 21087 // logDebug(s); 21088 21089 21090 return res; 21091 }; 21092 /** 21093 * @brief : Calculates all edge forces 21094 */ 21095 21096 21097 var calculateEdgeForces = function calculateEdgeForces(layoutInfo, options) { 21098 // Iterate over all edges 21099 for (var i = 0; i < layoutInfo.edgeSize; i++) { 21100 // Get edge, source & target nodes 21101 var edge = layoutInfo.layoutEdges[i]; 21102 var sourceIx = layoutInfo.idToIndex[edge.sourceId]; 21103 var source = layoutInfo.layoutNodes[sourceIx]; 21104 var targetIx = layoutInfo.idToIndex[edge.targetId]; 21105 var target = layoutInfo.layoutNodes[targetIx]; // Get direction of line connecting both node centers 21106 21107 var directionX = target.positionX - source.positionX; 21108 var directionY = target.positionY - source.positionY; // If both centers are the same, do nothing. 21109 // A random force has already been applied as node repulsion 21110 21111 if (0 === directionX && 0 === directionY) { 21112 continue; 21113 } // Get clipping points for both nodes 21114 21115 21116 var point1 = findClippingPoint(source, directionX, directionY); 21117 var point2 = findClippingPoint(target, -1 * directionX, -1 * directionY); 21118 var lx = point2.x - point1.x; 21119 var ly = point2.y - point1.y; 21120 var l = Math.sqrt(lx * lx + ly * ly); 21121 var force = Math.pow(edge.idealLength - l, 2) / edge.elasticity; 21122 21123 if (0 !== l) { 21124 var forceX = force * lx / l; 21125 var forceY = force * ly / l; 21126 } else { 21127 var forceX = 0; 21128 var forceY = 0; 21129 } // Add this force to target and source nodes 21130 21131 21132 if (!source.isLocked) { 21133 source.offsetX += forceX; 21134 source.offsetY += forceY; 21135 } 21136 21137 if (!target.isLocked) { 21138 target.offsetX -= forceX; 21139 target.offsetY -= forceY; 21140 } // var s = 'Edge force between nodes ' + source.id + ' and ' + target.id; 21141 // s += "\nDistance: " + l + " Force: (" + forceX + ", " + forceY + ")"; 21142 // logDebug(s); 21143 21144 } 21145 }; 21146 /** 21147 * @brief : Computes gravity forces for all nodes 21148 */ 21149 21150 21151 var calculateGravityForces = function calculateGravityForces(layoutInfo, options) { 21152 var distThreshold = 1; // var s = 'calculateGravityForces'; 21153 // logDebug(s); 21154 21155 for (var i = 0; i < layoutInfo.graphSet.length; i++) { 21156 var graph = layoutInfo.graphSet[i]; 21157 var numNodes = graph.length; // s = "Set: " + graph.toString(); 21158 // logDebug(s); 21159 // Compute graph center 21160 21161 if (0 === i) { 21162 var centerX = layoutInfo.clientHeight / 2; 21163 var centerY = layoutInfo.clientWidth / 2; 21164 } else { 21165 // Get Parent node for this graph, and use its position as center 21166 var temp = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[0]]]; 21167 var parent = layoutInfo.layoutNodes[layoutInfo.idToIndex[temp.parentId]]; 21168 var centerX = parent.positionX; 21169 var centerY = parent.positionY; 21170 } // s = "Center found at: " + centerX + ", " + centerY; 21171 // logDebug(s); 21172 // Apply force to all nodes in graph 21173 21174 21175 for (var j = 0; j < numNodes; j++) { 21176 var node = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]]; // s = "Node: " + node.id; 21177 21178 if (node.isLocked) { 21179 continue; 21180 } 21181 21182 var dx = centerX - node.positionX; 21183 var dy = centerY - node.positionY; 21184 var d = Math.sqrt(dx * dx + dy * dy); 21185 21186 if (d > distThreshold) { 21187 var fx = options.gravity * dx / d; 21188 var fy = options.gravity * dy / d; 21189 node.offsetX += fx; 21190 node.offsetY += fy; // s += ": Applied force: " + fx + ", " + fy; 21191 } // s += ": skypped since it's too close to center"; 21192 // logDebug(s); 21193 21194 } 21195 } 21196 }; 21197 /** 21198 * @brief : This function propagates the existing offsets from 21199 * parent nodes to its descendents. 21200 * @arg layoutInfo : layoutInfo Object 21201 * @arg cy : cytoscape Object 21202 * @arg options : Layout options 21203 */ 21204 21205 21206 var propagateForces = function propagateForces(layoutInfo, options) { 21207 // Inline implementation of a queue, used for traversing the graph in BFS order 21208 var queue = []; 21209 var start = 0; // Points to the start the queue 21210 21211 var end = -1; // Points to the end of the queue 21212 // logDebug('propagateForces'); 21213 // Start by visiting the nodes in the root graph 21214 21215 queue.push.apply(queue, layoutInfo.graphSet[0]); 21216 end += layoutInfo.graphSet[0].length; // Traverse the graph, level by level, 21217 21218 while (start <= end) { 21219 // Get the node to visit and remove it from queue 21220 var nodeId = queue[start++]; 21221 var nodeIndex = layoutInfo.idToIndex[nodeId]; 21222 var node = layoutInfo.layoutNodes[nodeIndex]; 21223 var children = node.children; // We only need to process the node if it's compound 21224 21225 if (0 < children.length && !node.isLocked) { 21226 var offX = node.offsetX; 21227 var offY = node.offsetY; // var s = "Propagating offset from parent node : " + node.id + 21228 // ". OffsetX: " + offX + ". OffsetY: " + offY; 21229 // s += "\n Children: " + children.toString(); 21230 // logDebug(s); 21231 21232 for (var i = 0; i < children.length; i++) { 21233 var childNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[children[i]]]; // Propagate offset 21234 21235 childNode.offsetX += offX; 21236 childNode.offsetY += offY; // Add children to queue to be visited 21237 21238 queue[++end] = children[i]; 21239 } // Reset parent offsets 21240 21241 21242 node.offsetX = 0; 21243 node.offsetY = 0; 21244 } 21245 } 21246 }; 21247 /** 21248 * @brief : Updates the layout model positions, based on 21249 * the accumulated forces 21250 */ 21251 21252 21253 var updatePositions = function updatePositions(layoutInfo, options) { 21254 // var s = 'Updating positions'; 21255 // logDebug(s); 21256 // Reset boundaries for compound nodes 21257 for (var i = 0; i < layoutInfo.nodeSize; i++) { 21258 var n = layoutInfo.layoutNodes[i]; 21259 21260 if (0 < n.children.length) { 21261 // logDebug("Resetting boundaries of compound node: " + n.id); 21262 n.maxX = undefined; 21263 n.minX = undefined; 21264 n.maxY = undefined; 21265 n.minY = undefined; 21266 } 21267 } 21268 21269 for (var i = 0; i < layoutInfo.nodeSize; i++) { 21270 var n = layoutInfo.layoutNodes[i]; 21271 21272 if (0 < n.children.length || n.isLocked) { 21273 // No need to set compound or locked node position 21274 // logDebug("Skipping position update of node: " + n.id); 21275 continue; 21276 } // s = "Node: " + n.id + " Previous position: (" + 21277 // n.positionX + ", " + n.positionY + ")."; 21278 // Limit displacement in order to improve stability 21279 21280 21281 var tempForce = limitForce(n.offsetX, n.offsetY, layoutInfo.temperature); 21282 n.positionX += tempForce.x; 21283 n.positionY += tempForce.y; 21284 n.offsetX = 0; 21285 n.offsetY = 0; 21286 n.minX = n.positionX - n.width; 21287 n.maxX = n.positionX + n.width; 21288 n.minY = n.positionY - n.height; 21289 n.maxY = n.positionY + n.height; // s += " New Position: (" + n.positionX + ", " + n.positionY + ")."; 21290 // logDebug(s); 21291 // Update ancestry boudaries 21292 21293 updateAncestryBoundaries(n, layoutInfo); 21294 } // Update size, position of compund nodes 21295 21296 21297 for (var i = 0; i < layoutInfo.nodeSize; i++) { 21298 var n = layoutInfo.layoutNodes[i]; 21299 21300 if (0 < n.children.length && !n.isLocked) { 21301 n.positionX = (n.maxX + n.minX) / 2; 21302 n.positionY = (n.maxY + n.minY) / 2; 21303 n.width = n.maxX - n.minX; 21304 n.height = n.maxY - n.minY; // s = "Updating position, size of compound node " + n.id; 21305 // s += "\nPositionX: " + n.positionX + ", PositionY: " + n.positionY; 21306 // s += "\nWidth: " + n.width + ", Height: " + n.height; 21307 // logDebug(s); 21308 } 21309 } 21310 }; 21311 /** 21312 * @brief : Limits a force (forceX, forceY) to be not 21313 * greater (in modulo) than max. 21314 8 Preserves force direction. 21315 */ 21316 21317 21318 var limitForce = function limitForce(forceX, forceY, max) { 21319 // var s = "Limiting force: (" + forceX + ", " + forceY + "). Max: " + max; 21320 var force = Math.sqrt(forceX * forceX + forceY * forceY); 21321 21322 if (force > max) { 21323 var res = { 21324 x: max * forceX / force, 21325 y: max * forceY / force 21326 }; 21327 } else { 21328 var res = { 21329 x: forceX, 21330 y: forceY 21331 }; 21332 } // s += ".\nResult: (" + res.x + ", " + res.y + ")"; 21333 // logDebug(s); 21334 21335 21336 return res; 21337 }; 21338 /** 21339 * @brief : Function used for keeping track of compound node 21340 * sizes, since they should bound all their subnodes. 21341 */ 21342 21343 21344 var updateAncestryBoundaries = function updateAncestryBoundaries(node, layoutInfo) { 21345 // var s = "Propagating new position/size of node " + node.id; 21346 var parentId = node.parentId; 21347 21348 if (null == parentId) { 21349 // If there's no parent, we are done 21350 // s += ". No parent node."; 21351 // logDebug(s); 21352 return; 21353 } // Get Parent Node 21354 21355 21356 var p = layoutInfo.layoutNodes[layoutInfo.idToIndex[parentId]]; 21357 var flag = false; // MaxX 21358 21359 if (null == p.maxX || node.maxX + p.padRight > p.maxX) { 21360 p.maxX = node.maxX + p.padRight; 21361 flag = true; // s += "\nNew maxX for parent node " + p.id + ": " + p.maxX; 21362 } // MinX 21363 21364 21365 if (null == p.minX || node.minX - p.padLeft < p.minX) { 21366 p.minX = node.minX - p.padLeft; 21367 flag = true; // s += "\nNew minX for parent node " + p.id + ": " + p.minX; 21368 } // MaxY 21369 21370 21371 if (null == p.maxY || node.maxY + p.padBottom > p.maxY) { 21372 p.maxY = node.maxY + p.padBottom; 21373 flag = true; // s += "\nNew maxY for parent node " + p.id + ": " + p.maxY; 21374 } // MinY 21375 21376 21377 if (null == p.minY || node.minY - p.padTop < p.minY) { 21378 p.minY = node.minY - p.padTop; 21379 flag = true; // s += "\nNew minY for parent node " + p.id + ": " + p.minY; 21380 } // If updated boundaries, propagate changes upward 21381 21382 21383 if (flag) { 21384 // logDebug(s); 21385 return updateAncestryBoundaries(p, layoutInfo); 21386 } // s += ". No changes in boundaries/position of parent node " + p.id; 21387 // logDebug(s); 21388 21389 21390 return; 21391 }; 21392 21393 var separateComponents = function separateComponents(layoutInfo, options) { 21394 var nodes = layoutInfo.layoutNodes; 21395 var components = []; 21396 21397 for (var i = 0; i < nodes.length; i++) { 21398 var node = nodes[i]; 21399 var cid = node.cmptId; 21400 var component = components[cid] = components[cid] || []; 21401 component.push(node); 21402 } 21403 21404 var totalA = 0; 21405 21406 for (var i = 0; i < components.length; i++) { 21407 var c = components[i]; 21408 21409 if (!c) { 21410 continue; 21411 } 21412 21413 c.x1 = Infinity; 21414 c.x2 = -Infinity; 21415 c.y1 = Infinity; 21416 c.y2 = -Infinity; 21417 21418 for (var j = 0; j < c.length; j++) { 21419 var n = c[j]; 21420 c.x1 = Math.min(c.x1, n.positionX - n.width / 2); 21421 c.x2 = Math.max(c.x2, n.positionX + n.width / 2); 21422 c.y1 = Math.min(c.y1, n.positionY - n.height / 2); 21423 c.y2 = Math.max(c.y2, n.positionY + n.height / 2); 21424 } 21425 21426 c.w = c.x2 - c.x1; 21427 c.h = c.y2 - c.y1; 21428 totalA += c.w * c.h; 21429 } 21430 21431 components.sort(function (c1, c2) { 21432 return c2.w * c2.h - c1.w * c1.h; 21433 }); 21434 var x = 0; 21435 var y = 0; 21436 var usedW = 0; 21437 var rowH = 0; 21438 var maxRowW = Math.sqrt(totalA) * layoutInfo.clientWidth / layoutInfo.clientHeight; 21439 21440 for (var i = 0; i < components.length; i++) { 21441 var c = components[i]; 21442 21443 if (!c) { 21444 continue; 21445 } 21446 21447 for (var j = 0; j < c.length; j++) { 21448 var n = c[j]; 21449 21450 if (!n.isLocked) { 21451 n.positionX += x - c.x1; 21452 n.positionY += y - c.y1; 21453 } 21454 } 21455 21456 x += c.w + options.componentSpacing; 21457 usedW += c.w + options.componentSpacing; 21458 rowH = Math.max(rowH, c.h); 21459 21460 if (usedW > maxRowW) { 21461 y += rowH + options.componentSpacing; 21462 x = 0; 21463 usedW = 0; 21464 rowH = 0; 21465 } 21466 } 21467 }; 21468 21469 var defaults$d = { 21470 fit: true, 21471 // whether to fit the viewport to the graph 21472 padding: 30, 21473 // padding used on fit 21474 boundingBox: undefined, 21475 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 21476 avoidOverlap: true, 21477 // prevents node overlap, may overflow boundingBox if not enough space 21478 avoidOverlapPadding: 10, 21479 // extra spacing around nodes when avoidOverlap: true 21480 nodeDimensionsIncludeLabels: false, 21481 // Excludes the label when calculating node bounding boxes for the layout algorithm 21482 spacingFactor: undefined, 21483 // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 21484 condense: false, 21485 // uses all available space on false, uses minimal space on true 21486 rows: undefined, 21487 // force num of rows in the grid 21488 cols: undefined, 21489 // force num of columns in the grid 21490 position: function position(node) {}, 21491 // returns { row, col } for element 21492 sort: undefined, 21493 // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 21494 animate: false, 21495 // whether to transition the node positions 21496 animationDuration: 500, 21497 // duration of animation in ms if enabled 21498 animationEasing: undefined, 21499 // easing of animation if enabled 21500 animateFilter: function animateFilter(node, i) { 21501 return true; 21502 }, 21503 // 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 21504 ready: undefined, 21505 // callback on layoutready 21506 stop: undefined, 21507 // callback on layoutstop 21508 transform: function transform(node, position) { 21509 return position; 21510 } // transform a given node position. Useful for changing flow direction in discrete layouts 21511 21512 }; 21513 21514 function GridLayout(options) { 21515 this.options = extend({}, defaults$d, options); 21516 } 21517 21518 GridLayout.prototype.run = function () { 21519 var params = this.options; 21520 var options = params; 21521 var cy = params.cy; 21522 var eles = options.eles; 21523 var nodes = eles.nodes().not(':parent'); 21524 21525 if (options.sort) { 21526 nodes = nodes.sort(options.sort); 21527 } 21528 21529 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 21530 x1: 0, 21531 y1: 0, 21532 w: cy.width(), 21533 h: cy.height() 21534 }); 21535 21536 if (bb.h === 0 || bb.w === 0) { 21537 nodes.layoutPositions(this, options, function (ele) { 21538 return { 21539 x: bb.x1, 21540 y: bb.y1 21541 }; 21542 }); 21543 } else { 21544 // width/height * splits^2 = cells where splits is number of times to split width 21545 var cells = nodes.size(); 21546 var splits = Math.sqrt(cells * bb.h / bb.w); 21547 var rows = Math.round(splits); 21548 var cols = Math.round(bb.w / bb.h * splits); 21549 21550 var small = function small(val) { 21551 if (val == null) { 21552 return Math.min(rows, cols); 21553 } else { 21554 var min = Math.min(rows, cols); 21555 21556 if (min == rows) { 21557 rows = val; 21558 } else { 21559 cols = val; 21560 } 21561 } 21562 }; 21563 21564 var large = function large(val) { 21565 if (val == null) { 21566 return Math.max(rows, cols); 21567 } else { 21568 var max = Math.max(rows, cols); 21569 21570 if (max == rows) { 21571 rows = val; 21572 } else { 21573 cols = val; 21574 } 21575 } 21576 }; 21577 21578 var oRows = options.rows; 21579 var oCols = options.cols != null ? options.cols : options.columns; // if rows or columns were set in options, use those values 21580 21581 if (oRows != null && oCols != null) { 21582 rows = oRows; 21583 cols = oCols; 21584 } else if (oRows != null && oCols == null) { 21585 rows = oRows; 21586 cols = Math.ceil(cells / rows); 21587 } else if (oRows == null && oCols != null) { 21588 cols = oCols; 21589 rows = Math.ceil(cells / cols); 21590 } // otherwise use the automatic values and adjust accordingly 21591 // if rounding was up, see if we can reduce rows or columns 21592 else if (cols * rows > cells) { 21593 var sm = small(); 21594 var lg = large(); // reducing the small side takes away the most cells, so try it first 21595 21596 if ((sm - 1) * lg >= cells) { 21597 small(sm - 1); 21598 } else if ((lg - 1) * sm >= cells) { 21599 large(lg - 1); 21600 } 21601 } else { 21602 // if rounding was too low, add rows or columns 21603 while (cols * rows < cells) { 21604 var _sm = small(); 21605 21606 var _lg = large(); // try to add to larger side first (adds less in multiplication) 21607 21608 21609 if ((_lg + 1) * _sm >= cells) { 21610 large(_lg + 1); 21611 } else { 21612 small(_sm + 1); 21613 } 21614 } 21615 } 21616 21617 var cellWidth = bb.w / cols; 21618 var cellHeight = bb.h / rows; 21619 21620 if (options.condense) { 21621 cellWidth = 0; 21622 cellHeight = 0; 21623 } 21624 21625 if (options.avoidOverlap) { 21626 for (var i = 0; i < nodes.length; i++) { 21627 var node = nodes[i]; 21628 var pos = node._private.position; 21629 21630 if (pos.x == null || pos.y == null) { 21631 // for bb 21632 pos.x = 0; 21633 pos.y = 0; 21634 } 21635 21636 var nbb = node.layoutDimensions(options); 21637 var p = options.avoidOverlapPadding; 21638 var w = nbb.w + p; 21639 var h = nbb.h + p; 21640 cellWidth = Math.max(cellWidth, w); 21641 cellHeight = Math.max(cellHeight, h); 21642 } 21643 } 21644 21645 var cellUsed = {}; // e.g. 'c-0-2' => true 21646 21647 var used = function used(row, col) { 21648 return cellUsed['c-' + row + '-' + col] ? true : false; 21649 }; 21650 21651 var use = function use(row, col) { 21652 cellUsed['c-' + row + '-' + col] = true; 21653 }; // to keep track of current cell position 21654 21655 21656 var row = 0; 21657 var col = 0; 21658 21659 var moveToNextCell = function moveToNextCell() { 21660 col++; 21661 21662 if (col >= cols) { 21663 col = 0; 21664 row++; 21665 } 21666 }; // get a cache of all the manual positions 21667 21668 21669 var id2manPos = {}; 21670 21671 for (var _i = 0; _i < nodes.length; _i++) { 21672 var _node = nodes[_i]; 21673 var rcPos = options.position(_node); 21674 21675 if (rcPos && (rcPos.row !== undefined || rcPos.col !== undefined)) { 21676 // must have at least row or col def'd 21677 var _pos = { 21678 row: rcPos.row, 21679 col: rcPos.col 21680 }; 21681 21682 if (_pos.col === undefined) { 21683 // find unused col 21684 _pos.col = 0; 21685 21686 while (used(_pos.row, _pos.col)) { 21687 _pos.col++; 21688 } 21689 } else if (_pos.row === undefined) { 21690 // find unused row 21691 _pos.row = 0; 21692 21693 while (used(_pos.row, _pos.col)) { 21694 _pos.row++; 21695 } 21696 } 21697 21698 id2manPos[_node.id()] = _pos; 21699 use(_pos.row, _pos.col); 21700 } 21701 } 21702 21703 var getPos = function getPos(element, i) { 21704 var x, y; 21705 21706 if (element.locked() || element.isParent()) { 21707 return false; 21708 } // see if we have a manual position set 21709 21710 21711 var rcPos = id2manPos[element.id()]; 21712 21713 if (rcPos) { 21714 x = rcPos.col * cellWidth + cellWidth / 2 + bb.x1; 21715 y = rcPos.row * cellHeight + cellHeight / 2 + bb.y1; 21716 } else { 21717 // otherwise set automatically 21718 while (used(row, col)) { 21719 moveToNextCell(); 21720 } 21721 21722 x = col * cellWidth + cellWidth / 2 + bb.x1; 21723 y = row * cellHeight + cellHeight / 2 + bb.y1; 21724 use(row, col); 21725 moveToNextCell(); 21726 } 21727 21728 return { 21729 x: x, 21730 y: y 21731 }; 21732 }; 21733 21734 nodes.layoutPositions(this, options, getPos); 21735 } 21736 21737 return this; // chaining 21738 }; 21739 21740 var defaults$e = { 21741 ready: function ready() {}, 21742 // on layoutready 21743 stop: function stop() {} // on layoutstop 21744 21745 }; // constructor 21746 // options : object containing layout options 21747 21748 function NullLayout(options) { 21749 this.options = extend({}, defaults$e, options); 21750 } // runs the layout 21751 21752 21753 NullLayout.prototype.run = function () { 21754 var options = this.options; 21755 var eles = options.eles; // elements to consider in the layout 21756 21757 var layout = this; // cy is automatically populated for us in the constructor 21758 // (disable eslint for next line as this serves as example layout code to external developers) 21759 // eslint-disable-next-line no-unused-vars 21760 21761 var cy = options.cy; 21762 layout.emit('layoutstart'); // puts all nodes at (0, 0) 21763 // n.b. most layouts would use layoutPositions(), instead of positions() and manual events 21764 21765 eles.nodes().positions(function () { 21766 return { 21767 x: 0, 21768 y: 0 21769 }; 21770 }); // trigger layoutready when each node has had its position set at least once 21771 21772 layout.one('layoutready', options.ready); 21773 layout.emit('layoutready'); // trigger layoutstop when the layout stops (e.g. finishes) 21774 21775 layout.one('layoutstop', options.stop); 21776 layout.emit('layoutstop'); 21777 return this; // chaining 21778 }; // called on continuous layouts to stop them before they finish 21779 21780 21781 NullLayout.prototype.stop = function () { 21782 return this; // chaining 21783 }; 21784 21785 var defaults$f = { 21786 positions: undefined, 21787 // map of (node id) => (position obj); or function(node){ return somPos; } 21788 zoom: undefined, 21789 // the zoom level to set (prob want fit = false if set) 21790 pan: undefined, 21791 // the pan level to set (prob want fit = false if set) 21792 fit: true, 21793 // whether to fit to viewport 21794 padding: 30, 21795 // padding on fit 21796 animate: false, 21797 // whether to transition the node positions 21798 animationDuration: 500, 21799 // duration of animation in ms if enabled 21800 animationEasing: undefined, 21801 // easing of animation if enabled 21802 animateFilter: function animateFilter(node, i) { 21803 return true; 21804 }, 21805 // 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 21806 ready: undefined, 21807 // callback on layoutready 21808 stop: undefined, 21809 // callback on layoutstop 21810 transform: function transform(node, position) { 21811 return position; 21812 } // transform a given node position. Useful for changing flow direction in discrete layouts 21813 21814 }; 21815 21816 function PresetLayout(options) { 21817 this.options = extend({}, defaults$f, options); 21818 } 21819 21820 PresetLayout.prototype.run = function () { 21821 var options = this.options; 21822 var eles = options.eles; 21823 var nodes = eles.nodes(); 21824 var posIsFn = fn(options.positions); 21825 21826 function getPosition(node) { 21827 if (options.positions == null) { 21828 return copyPosition(node.position()); 21829 } 21830 21831 if (posIsFn) { 21832 return options.positions(node); 21833 } 21834 21835 var pos = options.positions[node._private.data.id]; 21836 21837 if (pos == null) { 21838 return null; 21839 } 21840 21841 return pos; 21842 } 21843 21844 nodes.layoutPositions(this, options, function (node, i) { 21845 var position = getPosition(node); 21846 21847 if (node.locked() || position == null) { 21848 return false; 21849 } 21850 21851 return position; 21852 }); 21853 return this; // chaining 21854 }; 21855 21856 var defaults$g = { 21857 fit: true, 21858 // whether to fit to viewport 21859 padding: 30, 21860 // fit padding 21861 boundingBox: undefined, 21862 // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 21863 animate: false, 21864 // whether to transition the node positions 21865 animationDuration: 500, 21866 // duration of animation in ms if enabled 21867 animationEasing: undefined, 21868 // easing of animation if enabled 21869 animateFilter: function animateFilter(node, i) { 21870 return true; 21871 }, 21872 // 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 21873 ready: undefined, 21874 // callback on layoutready 21875 stop: undefined, 21876 // callback on layoutstop 21877 transform: function transform(node, position) { 21878 return position; 21879 } // transform a given node position. Useful for changing flow direction in discrete layouts 21880 21881 }; 21882 21883 function RandomLayout(options) { 21884 this.options = extend({}, defaults$g, options); 21885 } 21886 21887 RandomLayout.prototype.run = function () { 21888 var options = this.options; 21889 var cy = options.cy; 21890 var eles = options.eles; 21891 var nodes = eles.nodes().not(':parent'); 21892 var bb = makeBoundingBox(options.boundingBox ? options.boundingBox : { 21893 x1: 0, 21894 y1: 0, 21895 w: cy.width(), 21896 h: cy.height() 21897 }); 21898 21899 var getPos = function getPos(node, i) { 21900 return { 21901 x: bb.x1 + Math.round(Math.random() * bb.w), 21902 y: bb.y1 + Math.round(Math.random() * bb.h) 21903 }; 21904 }; 21905 21906 nodes.layoutPositions(this, options, getPos); 21907 return this; // chaining 21908 }; 21909 21910 var layout = [{ 21911 name: 'breadthfirst', 21912 impl: BreadthFirstLayout 21913 }, { 21914 name: 'circle', 21915 impl: CircleLayout 21916 }, { 21917 name: 'concentric', 21918 impl: ConcentricLayout 21919 }, { 21920 name: 'cose', 21921 impl: CoseLayout 21922 }, { 21923 name: 'grid', 21924 impl: GridLayout 21925 }, { 21926 name: 'null', 21927 impl: NullLayout 21928 }, { 21929 name: 'preset', 21930 impl: PresetLayout 21931 }, { 21932 name: 'random', 21933 impl: RandomLayout 21934 }]; 21935 21936 function NullRenderer(options) { 21937 this.options = options; 21938 this.notifications = 0; // for testing 21939 } 21940 21941 var noop$1 = function noop() {}; 21942 21943 var throwImgErr = function throwImgErr() { 21944 throw new Error('A headless instance can not render images'); 21945 }; 21946 21947 NullRenderer.prototype = { 21948 recalculateRenderedStyle: noop$1, 21949 notify: function notify() { 21950 this.notifications++; 21951 }, 21952 init: noop$1, 21953 isHeadless: function isHeadless() { 21954 return true; 21955 }, 21956 png: throwImgErr, 21957 jpg: throwImgErr 21958 }; 21959 21960 var BRp = {}; 21961 BRp.arrowShapeWidth = 0.3; 21962 21963 BRp.registerArrowShapes = function () { 21964 var arrowShapes = this.arrowShapes = {}; 21965 var renderer = this; // Contract for arrow shapes: 21966 // 0, 0 is arrow tip 21967 // (0, 1) is direction towards node 21968 // (1, 0) is right 21969 // 21970 // functional api: 21971 // collide: check x, y in shape 21972 // roughCollide: called before collide, no false negatives 21973 // draw: draw 21974 // spacing: dist(arrowTip, nodeBoundary) 21975 // gap: dist(edgeTip, nodeBoundary), edgeTip may != arrowTip 21976 21977 var bbCollide = function bbCollide(x, y, size, angle, translation, edgeWidth, padding) { 21978 var x1 = translation.x - size / 2 - padding; 21979 var x2 = translation.x + size / 2 + padding; 21980 var y1 = translation.y - size / 2 - padding; 21981 var y2 = translation.y + size / 2 + padding; 21982 var inside = x1 <= x && x <= x2 && y1 <= y && y <= y2; 21983 return inside; 21984 }; 21985 21986 var transform = function transform(x, y, size, angle, translation) { 21987 var xRotated = x * Math.cos(angle) - y * Math.sin(angle); 21988 var yRotated = x * Math.sin(angle) + y * Math.cos(angle); 21989 var xScaled = xRotated * size; 21990 var yScaled = yRotated * size; 21991 var xTranslated = xScaled + translation.x; 21992 var yTranslated = yScaled + translation.y; 21993 return { 21994 x: xTranslated, 21995 y: yTranslated 21996 }; 21997 }; 21998 21999 var transformPoints = function transformPoints(pts, size, angle, translation) { 22000 var retPts = []; 22001 22002 for (var i = 0; i < pts.length; i += 2) { 22003 var x = pts[i]; 22004 var y = pts[i + 1]; 22005 retPts.push(transform(x, y, size, angle, translation)); 22006 } 22007 22008 return retPts; 22009 }; 22010 22011 var pointsToArr = function pointsToArr(pts) { 22012 var ret = []; 22013 22014 for (var i = 0; i < pts.length; i++) { 22015 var p = pts[i]; 22016 ret.push(p.x, p.y); 22017 } 22018 22019 return ret; 22020 }; 22021 22022 var standardGap = function standardGap(edge) { 22023 return edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').pfValue * 2; 22024 }; 22025 22026 var defineArrowShape = function defineArrowShape(name, defn) { 22027 if (string(defn)) { 22028 defn = arrowShapes[defn]; 22029 } 22030 22031 arrowShapes[name] = extend({ 22032 name: name, 22033 points: [-0.15, -0.3, 0.15, -0.3, 0.15, 0.3, -0.15, 0.3], 22034 collide: function collide(x, y, size, angle, translation, padding) { 22035 var points = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 22036 var inside = pointInsidePolygonPoints(x, y, points); 22037 return inside; 22038 }, 22039 roughCollide: bbCollide, 22040 draw: function draw(context, size, angle, translation) { 22041 var points = transformPoints(this.points, size, angle, translation); 22042 renderer.arrowShapeImpl('polygon')(context, points); 22043 }, 22044 spacing: function spacing(edge) { 22045 return 0; 22046 }, 22047 gap: standardGap 22048 }, defn); 22049 }; 22050 22051 defineArrowShape('none', { 22052 collide: falsify, 22053 roughCollide: falsify, 22054 draw: noop, 22055 spacing: zeroify, 22056 gap: zeroify 22057 }); 22058 defineArrowShape('triangle', { 22059 points: [-0.15, -0.3, 0, 0, 0.15, -0.3] 22060 }); 22061 defineArrowShape('arrow', 'triangle'); 22062 defineArrowShape('triangle-backcurve', { 22063 points: arrowShapes['triangle'].points, 22064 controlPoint: [0, -0.15], 22065 roughCollide: bbCollide, 22066 draw: function draw(context, size, angle, translation, edgeWidth) { 22067 var ptsTrans = transformPoints(this.points, size, angle, translation); 22068 var ctrlPt = this.controlPoint; 22069 var ctrlPtTrans = transform(ctrlPt[0], ctrlPt[1], size, angle, translation); 22070 renderer.arrowShapeImpl(this.name)(context, ptsTrans, ctrlPtTrans); 22071 }, 22072 gap: function gap(edge) { 22073 return standardGap(edge) * 0.8; 22074 } 22075 }); 22076 defineArrowShape('triangle-tee', { 22077 points: [0, 0, 0.15, -0.3, -0.15, -0.3, 0, 0], 22078 pointsTee: [-0.15, -0.4, -0.15, -0.5, 0.15, -0.5, 0.15, -0.4], 22079 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 22080 var triPts = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 22081 var teePts = pointsToArr(transformPoints(this.pointsTee, size + 2 * padding, angle, translation)); 22082 var inside = pointInsidePolygonPoints(x, y, triPts) || pointInsidePolygonPoints(x, y, teePts); 22083 return inside; 22084 }, 22085 draw: function draw(context, size, angle, translation, edgeWidth) { 22086 var triPts = transformPoints(this.points, size, angle, translation); 22087 var teePts = transformPoints(this.pointsTee, size, angle, translation); 22088 renderer.arrowShapeImpl(this.name)(context, triPts, teePts); 22089 } 22090 }); 22091 defineArrowShape('triangle-cross', { 22092 points: [0, 0, 0.15, -0.3, -0.15, -0.3, 0, 0], 22093 baseCrossLinePts: [-0.15, -0.4, // first half of the rectangle 22094 -0.15, -0.4, 0.15, -0.4, // second half of the rectangle 22095 0.15, -0.4], 22096 crossLinePts: function crossLinePts(size, edgeWidth) { 22097 // shift points so that the distance between the cross points matches edge width 22098 var p = this.baseCrossLinePts.slice(); 22099 var shiftFactor = edgeWidth / size; 22100 var y0 = 3; 22101 var y1 = 5; 22102 p[y0] = p[y0] - shiftFactor; 22103 p[y1] = p[y1] - shiftFactor; 22104 return p; 22105 }, 22106 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 22107 var triPts = pointsToArr(transformPoints(this.points, size + 2 * padding, angle, translation)); 22108 var teePts = pointsToArr(transformPoints(this.crossLinePts(size, edgeWidth), size + 2 * padding, angle, translation)); 22109 var inside = pointInsidePolygonPoints(x, y, triPts) || pointInsidePolygonPoints(x, y, teePts); 22110 return inside; 22111 }, 22112 draw: function draw(context, size, angle, translation, edgeWidth) { 22113 var triPts = transformPoints(this.points, size, angle, translation); 22114 var crossLinePts = transformPoints(this.crossLinePts(size, edgeWidth), size, angle, translation); 22115 renderer.arrowShapeImpl(this.name)(context, triPts, crossLinePts); 22116 } 22117 }); 22118 defineArrowShape('vee', { 22119 points: [-0.15, -0.3, 0, 0, 0.15, -0.3, 0, -0.15], 22120 gap: function gap(edge) { 22121 return standardGap(edge) * 0.525; 22122 } 22123 }); 22124 defineArrowShape('circle', { 22125 radius: 0.15, 22126 collide: function collide(x, y, size, angle, translation, edgeWidth, padding) { 22127 var t = translation; 22128 var inside = Math.pow(t.x - x, 2) + Math.pow(t.y - y, 2) <= Math.pow((size + 2 * padding) * this.radius, 2); 22129 return inside; 22130 }, 22131 draw: function draw(context, size, angle, translation, edgeWidth) { 22132 renderer.arrowShapeImpl(this.name)(context, translation.x, translation.y, this.radius * size); 22133 }, 22134 spacing: function spacing(edge) { 22135 return renderer.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.radius; 22136 } 22137 }); 22138 defineArrowShape('tee', { 22139 points: [-0.15, 0, -0.15, -0.1, 0.15, -0.1, 0.15, 0], 22140 spacing: function spacing(edge) { 22141 return 1; 22142 }, 22143 gap: function gap(edge) { 22144 return 1; 22145 } 22146 }); 22147 defineArrowShape('square', { 22148 points: [-0.15, 0.00, 0.15, 0.00, 0.15, -0.3, -0.15, -0.3] 22149 }); 22150 defineArrowShape('diamond', { 22151 points: [-0.15, -0.15, 0, -0.3, 0.15, -0.15, 0, 0], 22152 gap: function gap(edge) { 22153 return edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').value; 22154 } 22155 }); 22156 defineArrowShape('chevron', { 22157 points: [0, 0, -0.15, -0.15, -0.1, -0.2, 0, -0.1, 0.1, -0.2, 0.15, -0.15], 22158 gap: function gap(edge) { 22159 return 0.95 * edge.pstyle('width').pfValue * edge.pstyle('arrow-scale').value; 22160 } 22161 }); 22162 }; 22163 22164 var BRp$1 = {}; // Project mouse 22165 22166 BRp$1.projectIntoViewport = function (clientX, clientY) { 22167 var cy = this.cy; 22168 var offsets = this.findContainerClientCoords(); 22169 var offsetLeft = offsets[0]; 22170 var offsetTop = offsets[1]; 22171 var scale = offsets[4]; 22172 var pan = cy.pan(); 22173 var zoom = cy.zoom(); 22174 var x = ((clientX - offsetLeft) / scale - pan.x) / zoom; 22175 var y = ((clientY - offsetTop) / scale - pan.y) / zoom; 22176 return [x, y]; 22177 }; 22178 22179 BRp$1.findContainerClientCoords = function () { 22180 if (this.containerBB) { 22181 return this.containerBB; 22182 } 22183 22184 var container = this.container; 22185 var rect = container.getBoundingClientRect(); 22186 var style = window$1.getComputedStyle(container); 22187 22188 var styleValue = function styleValue(name) { 22189 return parseFloat(style.getPropertyValue(name)); 22190 }; 22191 22192 var padding = { 22193 left: styleValue('padding-left'), 22194 right: styleValue('padding-right'), 22195 top: styleValue('padding-top'), 22196 bottom: styleValue('padding-bottom') 22197 }; 22198 var border = { 22199 left: styleValue('border-left-width'), 22200 right: styleValue('border-right-width'), 22201 top: styleValue('border-top-width'), 22202 bottom: styleValue('border-bottom-width') 22203 }; 22204 var clientWidth = container.clientWidth; 22205 var clientHeight = container.clientHeight; 22206 var paddingHor = padding.left + padding.right; 22207 var paddingVer = padding.top + padding.bottom; 22208 var borderHor = border.left + border.right; 22209 var scale = rect.width / (clientWidth + borderHor); 22210 var unscaledW = clientWidth - paddingHor; 22211 var unscaledH = clientHeight - paddingVer; 22212 var left = rect.left + padding.left + border.left; 22213 var top = rect.top + padding.top + border.top; 22214 return this.containerBB = [left, top, unscaledW, unscaledH, scale]; 22215 }; 22216 22217 BRp$1.invalidateContainerClientCoordsCache = function () { 22218 this.containerBB = null; 22219 }; 22220 22221 BRp$1.findNearestElement = function (x, y, interactiveElementsOnly, isTouch) { 22222 return this.findNearestElements(x, y, interactiveElementsOnly, isTouch)[0]; 22223 }; 22224 22225 BRp$1.findNearestElements = function (x, y, interactiveElementsOnly, isTouch) { 22226 var self = this; 22227 var r = this; 22228 var eles = r.getCachedZSortedEles(); 22229 var near = []; // 1 node max, 1 edge max 22230 22231 var zoom = r.cy.zoom(); 22232 var hasCompounds = r.cy.hasCompoundNodes(); 22233 var edgeThreshold = (isTouch ? 24 : 8) / zoom; 22234 var nodeThreshold = (isTouch ? 8 : 2) / zoom; 22235 var labelThreshold = (isTouch ? 8 : 2) / zoom; 22236 var minSqDist = Infinity; 22237 var nearEdge; 22238 var nearNode; 22239 22240 if (interactiveElementsOnly) { 22241 eles = eles.interactive; 22242 } 22243 22244 function addEle(ele, sqDist) { 22245 if (ele.isNode()) { 22246 if (nearNode) { 22247 return; // can't replace node 22248 } else { 22249 nearNode = ele; 22250 near.push(ele); 22251 } 22252 } 22253 22254 if (ele.isEdge() && (sqDist == null || sqDist < minSqDist)) { 22255 if (nearEdge) { 22256 // then replace existing edge 22257 // can replace only if same z-index 22258 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) { 22259 for (var i = 0; i < near.length; i++) { 22260 if (near[i].isEdge()) { 22261 near[i] = ele; 22262 nearEdge = ele; 22263 minSqDist = sqDist != null ? sqDist : minSqDist; 22264 break; 22265 } 22266 } 22267 } 22268 } else { 22269 near.push(ele); 22270 nearEdge = ele; 22271 minSqDist = sqDist != null ? sqDist : minSqDist; 22272 } 22273 } 22274 } 22275 22276 function checkNode(node) { 22277 var width = node.outerWidth() + 2 * nodeThreshold; 22278 var height = node.outerHeight() + 2 * nodeThreshold; 22279 var hw = width / 2; 22280 var hh = height / 2; 22281 var pos = node.position(); 22282 22283 if (pos.x - hw <= x && x <= pos.x + hw // bb check x 22284 && pos.y - hh <= y && y <= pos.y + hh // bb check y 22285 ) { 22286 var shape = r.nodeShapes[self.getNodeShape(node)]; 22287 22288 if (shape.checkPoint(x, y, 0, width, height, pos.x, pos.y)) { 22289 addEle(node, 0); 22290 return true; 22291 } 22292 } 22293 } 22294 22295 function checkEdge(edge) { 22296 var _p = edge._private; 22297 var rs = _p.rscratch; 22298 var styleWidth = edge.pstyle('width').pfValue; 22299 var scale = edge.pstyle('arrow-scale').value; 22300 var width = styleWidth / 2 + edgeThreshold; // more like a distance radius from centre 22301 22302 var widthSq = width * width; 22303 var width2 = width * 2; 22304 var src = _p.source; 22305 var tgt = _p.target; 22306 var sqDist; 22307 22308 if (rs.edgeType === 'segments' || rs.edgeType === 'straight' || rs.edgeType === 'haystack') { 22309 var pts = rs.allpts; 22310 22311 for (var i = 0; i + 3 < pts.length; i += 2) { 22312 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]))) { 22313 addEle(edge, sqDist); 22314 return true; 22315 } 22316 } 22317 } else if (rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound') { 22318 var pts = rs.allpts; 22319 22320 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 22321 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]))) { 22322 addEle(edge, sqDist); 22323 return true; 22324 } 22325 } 22326 } // if we're close to the edge but didn't hit it, maybe we hit its arrows 22327 22328 22329 var src = src || _p.source; 22330 var tgt = tgt || _p.target; 22331 var arSize = self.getArrowWidth(styleWidth, scale); 22332 var arrows = [{ 22333 name: 'source', 22334 x: rs.arrowStartX, 22335 y: rs.arrowStartY, 22336 angle: rs.srcArrowAngle 22337 }, { 22338 name: 'target', 22339 x: rs.arrowEndX, 22340 y: rs.arrowEndY, 22341 angle: rs.tgtArrowAngle 22342 }, { 22343 name: 'mid-source', 22344 x: rs.midX, 22345 y: rs.midY, 22346 angle: rs.midsrcArrowAngle 22347 }, { 22348 name: 'mid-target', 22349 x: rs.midX, 22350 y: rs.midY, 22351 angle: rs.midtgtArrowAngle 22352 }]; 22353 22354 for (var i = 0; i < arrows.length; i++) { 22355 var ar = arrows[i]; 22356 var shape = r.arrowShapes[edge.pstyle(ar.name + '-arrow-shape').value]; 22357 var edgeWidth = edge.pstyle('width').pfValue; 22358 22359 if (shape.roughCollide(x, y, arSize, ar.angle, { 22360 x: ar.x, 22361 y: ar.y 22362 }, edgeWidth, edgeThreshold) && shape.collide(x, y, arSize, ar.angle, { 22363 x: ar.x, 22364 y: ar.y 22365 }, edgeWidth, edgeThreshold)) { 22366 addEle(edge); 22367 return true; 22368 } 22369 } // for compound graphs, hitting edge may actually want a connected node instead (b/c edge may have greater z-index precedence) 22370 22371 22372 if (hasCompounds && near.length > 0) { 22373 checkNode(src); 22374 checkNode(tgt); 22375 } 22376 } 22377 22378 function preprop(obj, name, pre) { 22379 return getPrefixedProperty(obj, name, pre); 22380 } 22381 22382 function checkLabel(ele, prefix) { 22383 var _p = ele._private; 22384 var th = labelThreshold; 22385 var prefixDash; 22386 22387 if (prefix) { 22388 prefixDash = prefix + '-'; 22389 } else { 22390 prefixDash = ''; 22391 } 22392 22393 ele.boundingBox(); 22394 var bb = _p.labelBounds[prefix || 'main']; 22395 var text = ele.pstyle(prefixDash + 'label').value; 22396 var eventsEnabled = ele.pstyle('text-events').strValue === 'yes'; 22397 22398 if (!eventsEnabled || !text) { 22399 return; 22400 } 22401 22402 var rstyle = _p.rstyle; 22403 var lx = preprop(rstyle, 'labelX', prefix); 22404 var ly = preprop(rstyle, 'labelY', prefix); 22405 var theta = preprop(_p.rscratch, 'labelAngle', prefix); 22406 var lx1 = bb.x1 - th; 22407 var lx2 = bb.x2 + th; 22408 var ly1 = bb.y1 - th; 22409 var ly2 = bb.y2 + th; 22410 22411 if (theta) { 22412 var cos = Math.cos(theta); 22413 var sin = Math.sin(theta); 22414 22415 var rotate = function rotate(x, y) { 22416 x = x - lx; 22417 y = y - ly; 22418 return { 22419 x: x * cos - y * sin + lx, 22420 y: x * sin + y * cos + ly 22421 }; 22422 }; 22423 22424 var px1y1 = rotate(lx1, ly1); 22425 var px1y2 = rotate(lx1, ly2); 22426 var px2y1 = rotate(lx2, ly1); 22427 var px2y2 = rotate(lx2, ly2); 22428 var points = [px1y1.x, px1y1.y, px2y1.x, px2y1.y, px2y2.x, px2y2.y, px1y2.x, px1y2.y]; 22429 22430 if (pointInsidePolygonPoints(x, y, points)) { 22431 addEle(ele); 22432 return true; 22433 } 22434 } else { 22435 // do a cheaper bb check 22436 if (inBoundingBox(bb, x, y)) { 22437 addEle(ele); 22438 return true; 22439 } 22440 } 22441 } 22442 22443 for (var i = eles.length - 1; i >= 0; i--) { 22444 // reverse order for precedence 22445 var ele = eles[i]; 22446 22447 if (ele.isNode()) { 22448 checkNode(ele) || checkLabel(ele); 22449 } else { 22450 // then edge 22451 checkEdge(ele) || checkLabel(ele) || checkLabel(ele, 'source') || checkLabel(ele, 'target'); 22452 } 22453 } 22454 22455 return near; 22456 }; // 'Give me everything from this box' 22457 22458 22459 BRp$1.getAllInBox = function (x1, y1, x2, y2) { 22460 var eles = this.getCachedZSortedEles().interactive; 22461 var box = []; 22462 var x1c = Math.min(x1, x2); 22463 var x2c = Math.max(x1, x2); 22464 var y1c = Math.min(y1, y2); 22465 var y2c = Math.max(y1, y2); 22466 x1 = x1c; 22467 x2 = x2c; 22468 y1 = y1c; 22469 y2 = y2c; 22470 var boxBb = makeBoundingBox({ 22471 x1: x1, 22472 y1: y1, 22473 x2: x2, 22474 y2: y2 22475 }); 22476 22477 for (var e = 0; e < eles.length; e++) { 22478 var ele = eles[e]; 22479 22480 if (ele.isNode()) { 22481 var node = ele; 22482 var nodeBb = node.boundingBox({ 22483 includeNodes: true, 22484 includeEdges: false, 22485 includeLabels: false 22486 }); 22487 22488 if (boundingBoxesIntersect(boxBb, nodeBb) && !boundingBoxInBoundingBox(nodeBb, boxBb)) { 22489 box.push(node); 22490 } 22491 } else { 22492 var edge = ele; 22493 var _p = edge._private; 22494 var rs = _p.rscratch; 22495 22496 if (rs.startX != null && rs.startY != null && !inBoundingBox(boxBb, rs.startX, rs.startY)) { 22497 continue; 22498 } 22499 22500 if (rs.endX != null && rs.endY != null && !inBoundingBox(boxBb, rs.endX, rs.endY)) { 22501 continue; 22502 } 22503 22504 if (rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' || rs.edgeType === 'segments' || rs.edgeType === 'haystack') { 22505 var pts = _p.rstyle.bezierPts || _p.rstyle.linePts || _p.rstyle.haystackPts; 22506 var allInside = true; 22507 22508 for (var i = 0; i < pts.length; i++) { 22509 if (!pointInBoundingBox(boxBb, pts[i])) { 22510 allInside = false; 22511 break; 22512 } 22513 } 22514 22515 if (allInside) { 22516 box.push(edge); 22517 } 22518 } else if (rs.edgeType === 'haystack' || rs.edgeType === 'straight') { 22519 box.push(edge); 22520 } 22521 } 22522 } 22523 22524 return box; 22525 }; 22526 22527 var BRp$2 = {}; 22528 22529 BRp$2.calculateArrowAngles = function (edge) { 22530 var rs = edge._private.rscratch; 22531 var isHaystack = rs.edgeType === 'haystack'; 22532 var isBezier = rs.edgeType === 'bezier'; 22533 var isMultibezier = rs.edgeType === 'multibezier'; 22534 var isSegments = rs.edgeType === 'segments'; 22535 var isCompound = rs.edgeType === 'compound'; 22536 var isSelf = rs.edgeType === 'self'; // Displacement gives direction for arrowhead orientation 22537 22538 var dispX, dispY; 22539 var startX, startY, endX, endY, midX, midY; 22540 22541 if (isHaystack) { 22542 startX = rs.haystackPts[0]; 22543 startY = rs.haystackPts[1]; 22544 endX = rs.haystackPts[2]; 22545 endY = rs.haystackPts[3]; 22546 } else { 22547 startX = rs.arrowStartX; 22548 startY = rs.arrowStartY; 22549 endX = rs.arrowEndX; 22550 endY = rs.arrowEndY; 22551 } 22552 22553 midX = rs.midX; 22554 midY = rs.midY; // source 22555 // 22556 22557 if (isSegments) { 22558 dispX = startX - rs.segpts[0]; 22559 dispY = startY - rs.segpts[1]; 22560 } else if (isMultibezier || isCompound || isSelf || isBezier) { 22561 var pts = rs.allpts; 22562 var bX = qbezierAt(pts[0], pts[2], pts[4], 0.1); 22563 var bY = qbezierAt(pts[1], pts[3], pts[5], 0.1); 22564 dispX = startX - bX; 22565 dispY = startY - bY; 22566 } else { 22567 dispX = startX - midX; 22568 dispY = startY - midY; 22569 } 22570 22571 rs.srcArrowAngle = getAngleFromDisp(dispX, dispY); // mid target 22572 // 22573 22574 var midX = rs.midX; 22575 var midY = rs.midY; 22576 22577 if (isHaystack) { 22578 midX = (startX + endX) / 2; 22579 midY = (startY + endY) / 2; 22580 } 22581 22582 dispX = endX - startX; 22583 dispY = endY - startY; 22584 22585 if (isSegments) { 22586 var pts = rs.allpts; 22587 22588 if (pts.length / 2 % 2 === 0) { 22589 var i2 = pts.length / 2; 22590 var i1 = i2 - 2; 22591 dispX = pts[i2] - pts[i1]; 22592 dispY = pts[i2 + 1] - pts[i1 + 1]; 22593 } else { 22594 var i2 = pts.length / 2 - 1; 22595 var i1 = i2 - 2; 22596 var i3 = i2 + 2; 22597 dispX = pts[i2] - pts[i1]; 22598 dispY = pts[i2 + 1] - pts[i1 + 1]; 22599 } 22600 } else if (isMultibezier || isCompound || isSelf) { 22601 var pts = rs.allpts; 22602 var cpts = rs.ctrlpts; 22603 var bp0x, bp0y; 22604 var bp1x, bp1y; 22605 22606 if (cpts.length / 2 % 2 === 0) { 22607 var p0 = pts.length / 2 - 1; // startpt 22608 22609 var ic = p0 + 2; 22610 var p1 = ic + 2; 22611 bp0x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.0); 22612 bp0y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.0); 22613 bp1x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.0001); 22614 bp1y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.0001); 22615 } else { 22616 var ic = pts.length / 2 - 1; // ctrpt 22617 22618 var p0 = ic - 2; // startpt 22619 22620 var p1 = ic + 2; // endpt 22621 22622 bp0x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.4999); 22623 bp0y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.4999); 22624 bp1x = qbezierAt(pts[p0], pts[ic], pts[p1], 0.5); 22625 bp1y = qbezierAt(pts[p0 + 1], pts[ic + 1], pts[p1 + 1], 0.5); 22626 } 22627 22628 dispX = bp1x - bp0x; 22629 dispY = bp1y - bp0y; 22630 } 22631 22632 rs.midtgtArrowAngle = getAngleFromDisp(dispX, dispY); 22633 rs.midDispX = dispX; 22634 rs.midDispY = dispY; // mid source 22635 // 22636 22637 dispX *= -1; 22638 dispY *= -1; 22639 22640 if (isSegments) { 22641 var pts = rs.allpts; 22642 22643 if (pts.length / 2 % 2 === 0) ; else { 22644 var i2 = pts.length / 2 - 1; 22645 var i3 = i2 + 2; 22646 dispX = -(pts[i3] - pts[i2]); 22647 dispY = -(pts[i3 + 1] - pts[i2 + 1]); 22648 } 22649 } 22650 22651 rs.midsrcArrowAngle = getAngleFromDisp(dispX, dispY); // target 22652 // 22653 22654 if (isSegments) { 22655 dispX = endX - rs.segpts[rs.segpts.length - 2]; 22656 dispY = endY - rs.segpts[rs.segpts.length - 1]; 22657 } else if (isMultibezier || isCompound || isSelf || isBezier) { 22658 var pts = rs.allpts; 22659 var l = pts.length; 22660 var bX = qbezierAt(pts[l - 6], pts[l - 4], pts[l - 2], 0.9); 22661 var bY = qbezierAt(pts[l - 5], pts[l - 3], pts[l - 1], 0.9); 22662 dispX = endX - bX; 22663 dispY = endY - bY; 22664 } else { 22665 dispX = endX - midX; 22666 dispY = endY - midY; 22667 } 22668 22669 rs.tgtArrowAngle = getAngleFromDisp(dispX, dispY); 22670 }; 22671 22672 BRp$2.getArrowWidth = BRp$2.getArrowHeight = function (edgeWidth, scale) { 22673 var cache = this.arrowWidthCache = this.arrowWidthCache || {}; 22674 var cachedVal = cache[edgeWidth + ', ' + scale]; 22675 22676 if (cachedVal) { 22677 return cachedVal; 22678 } 22679 22680 cachedVal = Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29) * scale; 22681 cache[edgeWidth + ', ' + scale] = cachedVal; 22682 return cachedVal; 22683 }; 22684 22685 var BRp$3 = {}; 22686 22687 BRp$3.findHaystackPoints = function (edges) { 22688 for (var i = 0; i < edges.length; i++) { 22689 var edge = edges[i]; 22690 var _p = edge._private; 22691 var rs = _p.rscratch; 22692 22693 if (!rs.haystack) { 22694 var angle = Math.random() * 2 * Math.PI; 22695 rs.source = { 22696 x: Math.cos(angle), 22697 y: Math.sin(angle) 22698 }; 22699 angle = Math.random() * 2 * Math.PI; 22700 rs.target = { 22701 x: Math.cos(angle), 22702 y: Math.sin(angle) 22703 }; 22704 } 22705 22706 var src = _p.source; 22707 var tgt = _p.target; 22708 var srcPos = src.position(); 22709 var tgtPos = tgt.position(); 22710 var srcW = src.width(); 22711 var tgtW = tgt.width(); 22712 var srcH = src.height(); 22713 var tgtH = tgt.height(); 22714 var radius = edge.pstyle('haystack-radius').value; 22715 var halfRadius = radius / 2; // b/c have to half width/height 22716 22717 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]; 22718 rs.midX = (rs.allpts[0] + rs.allpts[2]) / 2; 22719 rs.midY = (rs.allpts[1] + rs.allpts[3]) / 2; // always override as haystack in case set to different type previously 22720 22721 rs.edgeType = 'haystack'; 22722 rs.haystack = true; 22723 this.storeEdgeProjections(edge); 22724 this.calculateArrowAngles(edge); 22725 this.recalculateEdgeLabelProjections(edge); 22726 this.calculateLabelAngles(edge); 22727 } 22728 }; 22729 22730 BRp$3.findSegmentsPoints = function (edge, pairInfo) { 22731 // Segments (multiple straight lines) 22732 var rs = edge._private.rscratch; 22733 var posPts = pairInfo.posPts, 22734 intersectionPts = pairInfo.intersectionPts, 22735 vectorNormInverse = pairInfo.vectorNormInverse; 22736 var edgeDistances = edge.pstyle('edge-distances').value; 22737 var segmentWs = edge.pstyle('segment-weights'); 22738 var segmentDs = edge.pstyle('segment-distances'); 22739 var segmentsN = Math.min(segmentWs.pfValue.length, segmentDs.pfValue.length); 22740 rs.edgeType = 'segments'; 22741 rs.segpts = []; 22742 22743 for (var s = 0; s < segmentsN; s++) { 22744 var w = segmentWs.pfValue[s]; 22745 var d = segmentDs.pfValue[s]; 22746 var w1 = 1 - w; 22747 var w2 = w; 22748 var midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts; 22749 var adjustedMidpt = { 22750 x: midptPts.x1 * w1 + midptPts.x2 * w2, 22751 y: midptPts.y1 * w1 + midptPts.y2 * w2 22752 }; 22753 rs.segpts.push(adjustedMidpt.x + vectorNormInverse.x * d, adjustedMidpt.y + vectorNormInverse.y * d); 22754 } 22755 }; 22756 22757 BRp$3.findLoopPoints = function (edge, pairInfo, i, edgeIsUnbundled) { 22758 // Self-edge 22759 var rs = edge._private.rscratch; 22760 var dirCounts = pairInfo.dirCounts, 22761 srcPos = pairInfo.srcPos; 22762 var ctrlptDists = edge.pstyle('control-point-distances'); 22763 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22764 var loopDir = edge.pstyle('loop-direction').pfValue; 22765 var loopSwp = edge.pstyle('loop-sweep').pfValue; 22766 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22767 rs.edgeType = 'self'; 22768 var j = i; 22769 var loopDist = stepSize; 22770 22771 if (edgeIsUnbundled) { 22772 j = 0; 22773 loopDist = ctrlptDist; 22774 } 22775 22776 var loopAngle = loopDir - Math.PI / 2; 22777 var outAngle = loopAngle - loopSwp / 2; 22778 var inAngle = loopAngle + loopSwp / 2; // increase by step size for overlapping loops, keyed on direction and sweep values 22779 22780 var dc = String(loopDir + '_' + loopSwp); 22781 j = dirCounts[dc] === undefined ? dirCounts[dc] = 0 : ++dirCounts[dc]; 22782 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)]; 22783 }; 22784 22785 BRp$3.findCompoundLoopPoints = function (edge, pairInfo, i, edgeIsUnbundled) { 22786 // Compound edge 22787 var rs = edge._private.rscratch; 22788 rs.edgeType = 'compound'; 22789 var srcPos = pairInfo.srcPos, 22790 tgtPos = pairInfo.tgtPos, 22791 srcW = pairInfo.srcW, 22792 srcH = pairInfo.srcH, 22793 tgtW = pairInfo.tgtW, 22794 tgtH = pairInfo.tgtH; 22795 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22796 var ctrlptDists = edge.pstyle('control-point-distances'); 22797 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22798 var j = i; 22799 var loopDist = stepSize; 22800 22801 if (edgeIsUnbundled) { 22802 j = 0; 22803 loopDist = ctrlptDist; 22804 } 22805 22806 var loopW = 50; 22807 var loopaPos = { 22808 x: srcPos.x - srcW / 2, 22809 y: srcPos.y - srcH / 2 22810 }; 22811 var loopbPos = { 22812 x: tgtPos.x - tgtW / 2, 22813 y: tgtPos.y - tgtH / 2 22814 }; 22815 var loopPos = { 22816 x: Math.min(loopaPos.x, loopbPos.x), 22817 y: Math.min(loopaPos.y, loopbPos.y) 22818 }; // avoids cases with impossible beziers 22819 22820 var minCompoundStretch = 0.5; 22821 var compoundStretchA = Math.max(minCompoundStretch, Math.log(srcW * 0.01)); 22822 var compoundStretchB = Math.max(minCompoundStretch, Math.log(tgtW * 0.01)); 22823 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]; 22824 }; 22825 22826 BRp$3.findStraightEdgePoints = function (edge) { 22827 // Straight edge within bundle 22828 edge._private.rscratch.edgeType = 'straight'; 22829 }; 22830 22831 BRp$3.findBezierPoints = function (edge, pairInfo, i, edgeIsUnbundled, edgeIsSwapped) { 22832 var rs = edge._private.rscratch; 22833 var vectorNormInverse = pairInfo.vectorNormInverse, 22834 posPts = pairInfo.posPts, 22835 intersectionPts = pairInfo.intersectionPts; 22836 var edgeDistances = edge.pstyle('edge-distances').value; 22837 var stepSize = edge.pstyle('control-point-step-size').pfValue; 22838 var ctrlptDists = edge.pstyle('control-point-distances'); 22839 var ctrlptWs = edge.pstyle('control-point-weights'); 22840 var bezierN = ctrlptDists && ctrlptWs ? Math.min(ctrlptDists.value.length, ctrlptWs.value.length) : 1; 22841 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 22842 var ctrlptWeight = ctrlptWs.value[0]; // (Multi)bezier 22843 22844 var multi = edgeIsUnbundled; 22845 rs.edgeType = multi ? 'multibezier' : 'bezier'; 22846 rs.ctrlpts = []; 22847 22848 for (var b = 0; b < bezierN; b++) { 22849 var normctrlptDist = (0.5 - pairInfo.eles.length / 2 + i) * stepSize * (edgeIsSwapped ? -1 : 1); 22850 var manctrlptDist = void 0; 22851 var sign = signum(normctrlptDist); 22852 22853 if (multi) { 22854 ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[b] : stepSize; // fall back on step size 22855 22856 ctrlptWeight = ctrlptWs.value[b]; 22857 } 22858 22859 if (edgeIsUnbundled) { 22860 // multi or single unbundled 22861 manctrlptDist = ctrlptDist; 22862 } else { 22863 manctrlptDist = ctrlptDist !== undefined ? sign * ctrlptDist : undefined; 22864 } 22865 22866 var distanceFromMidpoint = manctrlptDist !== undefined ? manctrlptDist : normctrlptDist; 22867 var w1 = 1 - ctrlptWeight; 22868 var w2 = ctrlptWeight; 22869 var midptPts = edgeDistances === 'node-position' ? posPts : intersectionPts; 22870 var adjustedMidpt = { 22871 x: midptPts.x1 * w1 + midptPts.x2 * w2, 22872 y: midptPts.y1 * w1 + midptPts.y2 * w2 22873 }; 22874 rs.ctrlpts.push(adjustedMidpt.x + vectorNormInverse.x * distanceFromMidpoint, adjustedMidpt.y + vectorNormInverse.y * distanceFromMidpoint); 22875 } 22876 }; 22877 22878 BRp$3.findTaxiPoints = function (edge, pairInfo) { 22879 // Taxicab geometry with two turns maximum 22880 var rs = edge._private.rscratch; 22881 rs.edgeType = 'segments'; 22882 var VERTICAL = 'vertical'; 22883 var HORIZONTAL = 'horizontal'; 22884 var LEFTWARD = 'leftward'; 22885 var RIGHTWARD = 'rightward'; 22886 var DOWNWARD = 'downward'; 22887 var UPWARD = 'upward'; 22888 var AUTO = 'auto'; 22889 var posPts = pairInfo.posPts, 22890 srcW = pairInfo.srcW, 22891 srcH = pairInfo.srcH, 22892 tgtW = pairInfo.tgtW, 22893 tgtH = pairInfo.tgtH; 22894 var edgeDistances = edge.pstyle('edge-distances').value; 22895 var dIncludesNodeBody = edgeDistances !== 'node-position'; 22896 var taxiDir = edge.pstyle('taxi-direction').value; 22897 var rawTaxiDir = taxiDir; // unprocessed value 22898 22899 var taxiTurn = edge.pstyle('taxi-turn'); 22900 var taxiTurnPfVal = taxiTurn.pfValue; 22901 var minD = edge.pstyle('taxi-turn-min-distance').pfValue; 22902 var turnIsPercent = taxiTurn.units === '%'; 22903 var dw = dIncludesNodeBody ? (srcW + tgtW) / 2 : 0; 22904 var dh = dIncludesNodeBody ? (srcH + tgtH) / 2 : 0; 22905 var pdx = posPts.x2 - posPts.x1; 22906 var pdy = posPts.y2 - posPts.y1; // take away the effective w/h from the magnitude of the delta value 22907 22908 var subDWH = function subDWH(dxy, dwh) { 22909 if (dxy > 0) { 22910 return Math.max(dxy - dwh, 0); 22911 } else { 22912 return Math.min(dxy + dwh, 0); 22913 } 22914 }; 22915 22916 var dx = subDWH(pdx, dw); 22917 var dy = subDWH(pdy, dh); 22918 var isExplicitDir = false; 22919 22920 if (taxiDir === AUTO) { 22921 taxiDir = Math.abs(dx) > Math.abs(dy) ? HORIZONTAL : VERTICAL; 22922 } else if (taxiDir === UPWARD || taxiDir === DOWNWARD) { 22923 taxiDir = VERTICAL; 22924 isExplicitDir = true; 22925 } else if (taxiDir === LEFTWARD || taxiDir === RIGHTWARD) { 22926 taxiDir = HORIZONTAL; 22927 isExplicitDir = true; 22928 } 22929 22930 var isVert = taxiDir === VERTICAL; 22931 var l = isVert ? dy : dx; 22932 var pl = isVert ? pdy : pdx; 22933 var sgnL = signum(pl); 22934 var forcedDir = false; 22935 22936 if (!(isExplicitDir && turnIsPercent) // forcing in this case would cause weird growing in the opposite direction 22937 && (rawTaxiDir === DOWNWARD && pl < 0 || rawTaxiDir === UPWARD && pl > 0 || rawTaxiDir === LEFTWARD && pl > 0 || rawTaxiDir === RIGHTWARD && pl < 0)) { 22938 sgnL *= -1; 22939 l = sgnL * Math.abs(l); 22940 forcedDir = true; 22941 } 22942 22943 var d = turnIsPercent ? taxiTurnPfVal * l : taxiTurnPfVal * sgnL; 22944 22945 var getIsTooClose = function getIsTooClose(d) { 22946 return Math.abs(d) < minD || Math.abs(d) >= Math.abs(l); 22947 }; 22948 22949 var isTooCloseSrc = getIsTooClose(d); 22950 var isTooCloseTgt = getIsTooClose(l - d); 22951 var isTooClose = isTooCloseSrc || isTooCloseTgt; 22952 22953 if (isTooClose && !forcedDir) { 22954 // non-ideal routing 22955 if (isVert) { 22956 // vertical fallbacks 22957 var lShapeInsideSrc = Math.abs(pl) <= srcH / 2; 22958 var lShapeInsideTgt = Math.abs(pdx) <= tgtW / 2; 22959 22960 if (lShapeInsideSrc) { 22961 // horizontal Z-shape (direction not respected) 22962 var x = (posPts.x1 + posPts.x2) / 2; 22963 var y1 = posPts.y1, 22964 y2 = posPts.y2; 22965 rs.segpts = [x, y1, x, y2]; 22966 } else if (lShapeInsideTgt) { 22967 // vertical Z-shape (distance not respected) 22968 var y = (posPts.y1 + posPts.y2) / 2; 22969 var x1 = posPts.x1, 22970 x2 = posPts.x2; 22971 rs.segpts = [x1, y, x2, y]; 22972 } else { 22973 // L-shape fallback (turn distance not respected, but works well with tree siblings) 22974 rs.segpts = [posPts.x1, posPts.y2]; 22975 } 22976 } else { 22977 // horizontal fallbacks 22978 var _lShapeInsideSrc = Math.abs(pl) <= srcW / 2; 22979 22980 var _lShapeInsideTgt = Math.abs(pdy) <= tgtH / 2; 22981 22982 if (_lShapeInsideSrc) { 22983 // vertical Z-shape (direction not respected) 22984 var _y = (posPts.y1 + posPts.y2) / 2; 22985 22986 var _x = posPts.x1, 22987 _x2 = posPts.x2; 22988 rs.segpts = [_x, _y, _x2, _y]; 22989 } else if (_lShapeInsideTgt) { 22990 // horizontal Z-shape (turn distance not respected) 22991 var _x3 = (posPts.x1 + posPts.x2) / 2; 22992 22993 var _y2 = posPts.y1, 22994 _y3 = posPts.y2; 22995 rs.segpts = [_x3, _y2, _x3, _y3]; 22996 } else { 22997 // L-shape (turn distance not respected, but works well for tree siblings) 22998 rs.segpts = [posPts.x2, posPts.y1]; 22999 } 23000 } 23001 } else { 23002 // ideal routing 23003 if (isVert) { 23004 var _y4 = posPts.y1 + d + (dIncludesNodeBody ? srcH / 2 * sgnL : 0); 23005 23006 var _x4 = posPts.x1, 23007 _x5 = posPts.x2; 23008 rs.segpts = [_x4, _y4, _x5, _y4]; 23009 } else { 23010 // horizontal 23011 var _x6 = posPts.x1 + d + (dIncludesNodeBody ? srcW / 2 * sgnL : 0); 23012 23013 var _y5 = posPts.y1, 23014 _y6 = posPts.y2; 23015 rs.segpts = [_x6, _y5, _x6, _y6]; 23016 } 23017 } 23018 }; 23019 23020 BRp$3.tryToCorrectInvalidPoints = function (edge, pairInfo) { 23021 var rs = edge._private.rscratch; // can only correct beziers for now... 23022 23023 if (rs.edgeType === 'bezier') { 23024 var srcPos = pairInfo.srcPos, 23025 tgtPos = pairInfo.tgtPos, 23026 srcW = pairInfo.srcW, 23027 srcH = pairInfo.srcH, 23028 tgtW = pairInfo.tgtW, 23029 tgtH = pairInfo.tgtH, 23030 srcShape = pairInfo.srcShape, 23031 tgtShape = pairInfo.tgtShape; 23032 var badStart = !number(rs.startX) || !number(rs.startY); 23033 var badAStart = !number(rs.arrowStartX) || !number(rs.arrowStartY); 23034 var badEnd = !number(rs.endX) || !number(rs.endY); 23035 var badAEnd = !number(rs.arrowEndX) || !number(rs.arrowEndY); 23036 var minCpADistFactor = 3; 23037 var arrowW = this.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.arrowShapeWidth; 23038 var minCpADist = minCpADistFactor * arrowW; 23039 var startACpDist = dist({ 23040 x: rs.ctrlpts[0], 23041 y: rs.ctrlpts[1] 23042 }, { 23043 x: rs.startX, 23044 y: rs.startY 23045 }); 23046 var closeStartACp = startACpDist < minCpADist; 23047 var endACpDist = dist({ 23048 x: rs.ctrlpts[0], 23049 y: rs.ctrlpts[1] 23050 }, { 23051 x: rs.endX, 23052 y: rs.endY 23053 }); 23054 var closeEndACp = endACpDist < minCpADist; 23055 var overlapping = false; 23056 23057 if (badStart || badAStart || closeStartACp) { 23058 overlapping = true; // project control point along line from src centre to outside the src shape 23059 // (otherwise intersection will yield nothing) 23060 23061 var cpD = { 23062 // delta 23063 x: rs.ctrlpts[0] - srcPos.x, 23064 y: rs.ctrlpts[1] - srcPos.y 23065 }; 23066 var cpL = Math.sqrt(cpD.x * cpD.x + cpD.y * cpD.y); // length of line 23067 23068 var cpM = { 23069 // normalised delta 23070 x: cpD.x / cpL, 23071 y: cpD.y / cpL 23072 }; 23073 var radius = Math.max(srcW, srcH); 23074 var cpProj = { 23075 // *2 radius guarantees outside shape 23076 x: rs.ctrlpts[0] + cpM.x * 2 * radius, 23077 y: rs.ctrlpts[1] + cpM.y * 2 * radius 23078 }; 23079 var srcCtrlPtIntn = srcShape.intersectLine(srcPos.x, srcPos.y, srcW, srcH, cpProj.x, cpProj.y, 0); 23080 23081 if (closeStartACp) { 23082 rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - startACpDist); 23083 rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - startACpDist); 23084 } else { 23085 rs.ctrlpts[0] = srcCtrlPtIntn[0] + cpM.x * minCpADist; 23086 rs.ctrlpts[1] = srcCtrlPtIntn[1] + cpM.y * minCpADist; 23087 } 23088 } 23089 23090 if (badEnd || badAEnd || closeEndACp) { 23091 overlapping = true; // project control point along line from tgt centre to outside the tgt shape 23092 // (otherwise intersection will yield nothing) 23093 23094 var _cpD = { 23095 // delta 23096 x: rs.ctrlpts[0] - tgtPos.x, 23097 y: rs.ctrlpts[1] - tgtPos.y 23098 }; 23099 23100 var _cpL = Math.sqrt(_cpD.x * _cpD.x + _cpD.y * _cpD.y); // length of line 23101 23102 23103 var _cpM = { 23104 // normalised delta 23105 x: _cpD.x / _cpL, 23106 y: _cpD.y / _cpL 23107 }; 23108 23109 var _radius = Math.max(srcW, srcH); 23110 23111 var _cpProj = { 23112 // *2 radius guarantees outside shape 23113 x: rs.ctrlpts[0] + _cpM.x * 2 * _radius, 23114 y: rs.ctrlpts[1] + _cpM.y * 2 * _radius 23115 }; 23116 var tgtCtrlPtIntn = tgtShape.intersectLine(tgtPos.x, tgtPos.y, tgtW, tgtH, _cpProj.x, _cpProj.y, 0); 23117 23118 if (closeEndACp) { 23119 rs.ctrlpts[0] = rs.ctrlpts[0] + _cpM.x * (minCpADist - endACpDist); 23120 rs.ctrlpts[1] = rs.ctrlpts[1] + _cpM.y * (minCpADist - endACpDist); 23121 } else { 23122 rs.ctrlpts[0] = tgtCtrlPtIntn[0] + _cpM.x * minCpADist; 23123 rs.ctrlpts[1] = tgtCtrlPtIntn[1] + _cpM.y * minCpADist; 23124 } 23125 } 23126 23127 if (overlapping) { 23128 // recalc endpts 23129 this.findEndpoints(edge); 23130 } 23131 } 23132 }; 23133 23134 BRp$3.storeAllpts = function (edge) { 23135 var rs = edge._private.rscratch; 23136 23137 if (rs.edgeType === 'multibezier' || rs.edgeType === 'bezier' || rs.edgeType === 'self' || rs.edgeType === 'compound') { 23138 rs.allpts = []; 23139 rs.allpts.push(rs.startX, rs.startY); 23140 23141 for (var b = 0; b + 1 < rs.ctrlpts.length; b += 2) { 23142 // ctrl pt itself 23143 rs.allpts.push(rs.ctrlpts[b], rs.ctrlpts[b + 1]); // the midpt between ctrlpts as intermediate destination pts 23144 23145 if (b + 3 < rs.ctrlpts.length) { 23146 rs.allpts.push((rs.ctrlpts[b] + rs.ctrlpts[b + 2]) / 2, (rs.ctrlpts[b + 1] + rs.ctrlpts[b + 3]) / 2); 23147 } 23148 } 23149 23150 rs.allpts.push(rs.endX, rs.endY); 23151 var m, mt; 23152 23153 if (rs.ctrlpts.length / 2 % 2 === 0) { 23154 m = rs.allpts.length / 2 - 1; 23155 rs.midX = rs.allpts[m]; 23156 rs.midY = rs.allpts[m + 1]; 23157 } else { 23158 m = rs.allpts.length / 2 - 3; 23159 mt = 0.5; 23160 rs.midX = qbezierAt(rs.allpts[m], rs.allpts[m + 2], rs.allpts[m + 4], mt); 23161 rs.midY = qbezierAt(rs.allpts[m + 1], rs.allpts[m + 3], rs.allpts[m + 5], mt); 23162 } 23163 } else if (rs.edgeType === 'straight') { 23164 // need to calc these after endpts 23165 rs.allpts = [rs.startX, rs.startY, rs.endX, rs.endY]; // default midpt for labels etc 23166 23167 rs.midX = (rs.startX + rs.endX + rs.arrowStartX + rs.arrowEndX) / 4; 23168 rs.midY = (rs.startY + rs.endY + rs.arrowStartY + rs.arrowEndY) / 4; 23169 } else if (rs.edgeType === 'segments') { 23170 rs.allpts = []; 23171 rs.allpts.push(rs.startX, rs.startY); 23172 rs.allpts.push.apply(rs.allpts, rs.segpts); 23173 rs.allpts.push(rs.endX, rs.endY); 23174 23175 if (rs.segpts.length % 4 === 0) { 23176 var i2 = rs.segpts.length / 2; 23177 var i1 = i2 - 2; 23178 rs.midX = (rs.segpts[i1] + rs.segpts[i2]) / 2; 23179 rs.midY = (rs.segpts[i1 + 1] + rs.segpts[i2 + 1]) / 2; 23180 } else { 23181 var _i = rs.segpts.length / 2 - 1; 23182 23183 rs.midX = rs.segpts[_i]; 23184 rs.midY = rs.segpts[_i + 1]; 23185 } 23186 } 23187 }; 23188 23189 BRp$3.checkForInvalidEdgeWarning = function (edge) { 23190 var rs = edge[0]._private.rscratch; 23191 23192 if (rs.nodesOverlap || number(rs.startX) && number(rs.startY) && number(rs.endX) && number(rs.endY)) { 23193 rs.loggedErr = false; 23194 } else { 23195 if (!rs.loggedErr) { 23196 rs.loggedErr = true; 23197 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.'); 23198 } 23199 } 23200 }; 23201 23202 BRp$3.findEdgeControlPoints = function (edges) { 23203 var _this = this; 23204 23205 if (!edges || edges.length === 0) { 23206 return; 23207 } 23208 23209 var r = this; 23210 var cy = r.cy; 23211 var hasCompounds = cy.hasCompoundNodes(); 23212 var hashTable = { 23213 map: new Map$1(), 23214 get: function get(pairId) { 23215 var map2 = this.map.get(pairId[0]); 23216 23217 if (map2 != null) { 23218 return map2.get(pairId[1]); 23219 } else { 23220 return null; 23221 } 23222 }, 23223 set: function set(pairId, val) { 23224 var map2 = this.map.get(pairId[0]); 23225 23226 if (map2 == null) { 23227 map2 = new Map$1(); 23228 this.map.set(pairId[0], map2); 23229 } 23230 23231 map2.set(pairId[1], val); 23232 } 23233 }; 23234 var pairIds = []; 23235 var haystackEdges = []; // create a table of edge (src, tgt) => list of edges between them 23236 23237 for (var i = 0; i < edges.length; i++) { 23238 var edge = edges[i]; 23239 var _p = edge._private; 23240 var curveStyle = edge.pstyle('curve-style').value; // ignore edges who are not to be displayed 23241 // they shouldn't take up space 23242 23243 if (edge.removed() || !edge.takesUpSpace()) { 23244 continue; 23245 } 23246 23247 if (curveStyle === 'haystack') { 23248 haystackEdges.push(edge); 23249 continue; 23250 } 23251 23252 var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments' || curveStyle === 'straight' || curveStyle === 'taxi'; 23253 var edgeIsBezier = curveStyle === 'unbundled-bezier' || curveStyle === 'bezier'; 23254 var src = _p.source; 23255 var tgt = _p.target; 23256 var srcIndex = src.poolIndex(); 23257 var tgtIndex = tgt.poolIndex(); 23258 var pairId = [srcIndex, tgtIndex].sort(); 23259 var tableEntry = hashTable.get(pairId); 23260 23261 if (tableEntry == null) { 23262 tableEntry = { 23263 eles: [] 23264 }; 23265 hashTable.set(pairId, tableEntry); 23266 pairIds.push(pairId); 23267 } 23268 23269 tableEntry.eles.push(edge); 23270 23271 if (edgeIsUnbundled) { 23272 tableEntry.hasUnbundled = true; 23273 } 23274 23275 if (edgeIsBezier) { 23276 tableEntry.hasBezier = true; 23277 } 23278 } // for each pair (src, tgt), create the ctrl pts 23279 // Nested for loop is OK; total number of iterations for both loops = edgeCount 23280 23281 23282 var _loop = function _loop(p) { 23283 var pairId = pairIds[p]; 23284 var pairInfo = hashTable.get(pairId); 23285 var swappedpairInfo = void 0; 23286 23287 if (!pairInfo.hasUnbundled) { 23288 var pllEdges = pairInfo.eles[0].parallelEdges().filter(function (e) { 23289 return e.isBundledBezier(); 23290 }); 23291 clearArray(pairInfo.eles); 23292 pllEdges.forEach(function (edge) { 23293 return pairInfo.eles.push(edge); 23294 }); // for each pair id, the edges should be sorted by index 23295 23296 pairInfo.eles.sort(function (edge1, edge2) { 23297 return edge1.poolIndex() - edge2.poolIndex(); 23298 }); 23299 } 23300 23301 var firstEdge = pairInfo.eles[0]; 23302 var src = firstEdge.source(); 23303 var tgt = firstEdge.target(); // make sure src/tgt distinction is consistent w.r.t. pairId 23304 23305 if (src.poolIndex() > tgt.poolIndex()) { 23306 var temp = src; 23307 src = tgt; 23308 tgt = temp; 23309 } 23310 23311 var srcPos = pairInfo.srcPos = src.position(); 23312 var tgtPos = pairInfo.tgtPos = tgt.position(); 23313 var srcW = pairInfo.srcW = src.outerWidth(); 23314 var srcH = pairInfo.srcH = src.outerHeight(); 23315 var tgtW = pairInfo.tgtW = tgt.outerWidth(); 23316 var tgtH = pairInfo.tgtH = tgt.outerHeight(); 23317 23318 var srcShape = pairInfo.srcShape = r.nodeShapes[_this.getNodeShape(src)]; 23319 23320 var tgtShape = pairInfo.tgtShape = r.nodeShapes[_this.getNodeShape(tgt)]; 23321 23322 pairInfo.dirCounts = { 23323 'north': 0, 23324 'west': 0, 23325 'south': 0, 23326 'east': 0, 23327 'northwest': 0, 23328 'southwest': 0, 23329 'northeast': 0, 23330 'southeast': 0 23331 }; 23332 23333 for (var _i2 = 0; _i2 < pairInfo.eles.length; _i2++) { 23334 var _edge = pairInfo.eles[_i2]; 23335 var rs = _edge[0]._private.rscratch; 23336 23337 var _curveStyle = _edge.pstyle('curve-style').value; 23338 23339 var _edgeIsUnbundled = _curveStyle === 'unbundled-bezier' || _curveStyle === 'segments' || _curveStyle === 'taxi'; // whether the normalised pair order is the reverse of the edge's src-tgt order 23340 23341 23342 var edgeIsSwapped = !src.same(_edge.source()); 23343 23344 if (!pairInfo.calculatedIntersection && src !== tgt && (pairInfo.hasBezier || pairInfo.hasUnbundled)) { 23345 pairInfo.calculatedIntersection = true; // pt outside src shape to calc distance/displacement from src to tgt 23346 23347 var srcOutside = srcShape.intersectLine(srcPos.x, srcPos.y, srcW, srcH, tgtPos.x, tgtPos.y, 0); 23348 var srcIntn = pairInfo.srcIntn = srcOutside; // pt outside tgt shape to calc distance/displacement from src to tgt 23349 23350 var tgtOutside = tgtShape.intersectLine(tgtPos.x, tgtPos.y, tgtW, tgtH, srcPos.x, srcPos.y, 0); 23351 var tgtIntn = pairInfo.tgtIntn = tgtOutside; 23352 var intersectionPts = pairInfo.intersectionPts = { 23353 x1: srcOutside[0], 23354 x2: tgtOutside[0], 23355 y1: srcOutside[1], 23356 y2: tgtOutside[1] 23357 }; 23358 var posPts = pairInfo.posPts = { 23359 x1: srcPos.x, 23360 x2: tgtPos.x, 23361 y1: srcPos.y, 23362 y2: tgtPos.y 23363 }; 23364 var dy = tgtOutside[1] - srcOutside[1]; 23365 var dx = tgtOutside[0] - srcOutside[0]; 23366 var l = Math.sqrt(dx * dx + dy * dy); 23367 var vector = pairInfo.vector = { 23368 x: dx, 23369 y: dy 23370 }; 23371 var vectorNorm = pairInfo.vectorNorm = { 23372 x: vector.x / l, 23373 y: vector.y / l 23374 }; 23375 var vectorNormInverse = { 23376 x: -vectorNorm.y, 23377 y: vectorNorm.x 23378 }; // if node shapes overlap, then no ctrl pts to draw 23379 23380 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); 23381 pairInfo.vectorNormInverse = vectorNormInverse; 23382 swappedpairInfo = { 23383 nodesOverlap: pairInfo.nodesOverlap, 23384 dirCounts: pairInfo.dirCounts, 23385 calculatedIntersection: true, 23386 hasBezier: pairInfo.hasBezier, 23387 hasUnbundled: pairInfo.hasUnbundled, 23388 eles: pairInfo.eles, 23389 srcPos: tgtPos, 23390 tgtPos: srcPos, 23391 srcW: tgtW, 23392 srcH: tgtH, 23393 tgtW: srcW, 23394 tgtH: srcH, 23395 srcIntn: tgtIntn, 23396 tgtIntn: srcIntn, 23397 srcShape: tgtShape, 23398 tgtShape: srcShape, 23399 posPts: { 23400 x1: posPts.x2, 23401 y1: posPts.y2, 23402 x2: posPts.x1, 23403 y2: posPts.y1 23404 }, 23405 intersectionPts: { 23406 x1: intersectionPts.x2, 23407 y1: intersectionPts.y2, 23408 x2: intersectionPts.x1, 23409 y2: intersectionPts.y1 23410 }, 23411 vector: { 23412 x: -vector.x, 23413 y: -vector.y 23414 }, 23415 vectorNorm: { 23416 x: -vectorNorm.x, 23417 y: -vectorNorm.y 23418 }, 23419 vectorNormInverse: { 23420 x: -vectorNormInverse.x, 23421 y: -vectorNormInverse.y 23422 } 23423 }; 23424 } 23425 23426 var passedPairInfo = edgeIsSwapped ? swappedpairInfo : pairInfo; 23427 rs.nodesOverlap = passedPairInfo.nodesOverlap; 23428 rs.srcIntn = passedPairInfo.srcIntn; 23429 rs.tgtIntn = passedPairInfo.tgtIntn; 23430 23431 if (hasCompounds && (src.isParent() || src.isChild() || tgt.isParent() || tgt.isChild()) && (src.parents().anySame(tgt) || tgt.parents().anySame(src) || src.same(tgt) && src.isParent())) { 23432 _this.findCompoundLoopPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled); 23433 } else if (src === tgt) { 23434 _this.findLoopPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled); 23435 } else if (_curveStyle === 'segments') { 23436 _this.findSegmentsPoints(_edge, passedPairInfo); 23437 } else if (_curveStyle === 'taxi') { 23438 _this.findTaxiPoints(_edge, passedPairInfo); 23439 } else if (_curveStyle === 'straight' || !_edgeIsUnbundled && pairInfo.eles.length % 2 === 1 && _i2 === Math.floor(pairInfo.eles.length / 2)) { 23440 _this.findStraightEdgePoints(_edge); 23441 } else { 23442 _this.findBezierPoints(_edge, passedPairInfo, _i2, _edgeIsUnbundled, edgeIsSwapped); 23443 } 23444 23445 _this.findEndpoints(_edge); 23446 23447 _this.tryToCorrectInvalidPoints(_edge, passedPairInfo); 23448 23449 _this.checkForInvalidEdgeWarning(_edge); 23450 23451 _this.storeAllpts(_edge); 23452 23453 _this.storeEdgeProjections(_edge); 23454 23455 _this.calculateArrowAngles(_edge); 23456 23457 _this.recalculateEdgeLabelProjections(_edge); 23458 23459 _this.calculateLabelAngles(_edge); 23460 } // for pair edges 23461 23462 }; 23463 23464 for (var p = 0; p < pairIds.length; p++) { 23465 _loop(p); 23466 } // for pair ids 23467 // haystacks avoid the expense of pairInfo stuff (intersections etc.) 23468 23469 23470 this.findHaystackPoints(haystackEdges); 23471 }; 23472 23473 function getPts(pts) { 23474 var retPts = []; 23475 23476 if (pts == null) { 23477 return; 23478 } 23479 23480 for (var i = 0; i < pts.length; i += 2) { 23481 var x = pts[i]; 23482 var y = pts[i + 1]; 23483 retPts.push({ 23484 x: x, 23485 y: y 23486 }); 23487 } 23488 23489 return retPts; 23490 } 23491 23492 BRp$3.getSegmentPoints = function (edge) { 23493 var rs = edge[0]._private.rscratch; 23494 var type = rs.edgeType; 23495 23496 if (type === 'segments') { 23497 this.recalculateRenderedStyle(edge); 23498 return getPts(rs.segpts); 23499 } 23500 }; 23501 23502 BRp$3.getControlPoints = function (edge) { 23503 var rs = edge[0]._private.rscratch; 23504 var type = rs.edgeType; 23505 23506 if (type === 'bezier' || type === 'multibezier' || type === 'self' || type === 'compound') { 23507 this.recalculateRenderedStyle(edge); 23508 return getPts(rs.ctrlpts); 23509 } 23510 }; 23511 23512 BRp$3.getEdgeMidpoint = function (edge) { 23513 var rs = edge[0]._private.rscratch; 23514 this.recalculateRenderedStyle(edge); 23515 return { 23516 x: rs.midX, 23517 y: rs.midY 23518 }; 23519 }; 23520 23521 var BRp$4 = {}; 23522 23523 BRp$4.manualEndptToPx = function (node, prop) { 23524 var r = this; 23525 var npos = node.position(); 23526 var w = node.outerWidth(); 23527 var h = node.outerHeight(); 23528 23529 if (prop.value.length === 2) { 23530 var p = [prop.pfValue[0], prop.pfValue[1]]; 23531 23532 if (prop.units[0] === '%') { 23533 p[0] = p[0] * w; 23534 } 23535 23536 if (prop.units[1] === '%') { 23537 p[1] = p[1] * h; 23538 } 23539 23540 p[0] += npos.x; 23541 p[1] += npos.y; 23542 return p; 23543 } else { 23544 var angle = prop.pfValue[0]; 23545 angle = -Math.PI / 2 + angle; // start at 12 o'clock 23546 23547 var l = 2 * Math.max(w, h); 23548 var _p = [npos.x + Math.cos(angle) * l, npos.y + Math.sin(angle) * l]; 23549 return r.nodeShapes[this.getNodeShape(node)].intersectLine(npos.x, npos.y, w, h, _p[0], _p[1], 0); 23550 } 23551 }; 23552 23553 BRp$4.findEndpoints = function (edge) { 23554 var r = this; 23555 var intersect; 23556 var source = edge.source()[0]; 23557 var target = edge.target()[0]; 23558 var srcPos = source.position(); 23559 var tgtPos = target.position(); 23560 var tgtArShape = edge.pstyle('target-arrow-shape').value; 23561 var srcArShape = edge.pstyle('source-arrow-shape').value; 23562 var tgtDist = edge.pstyle('target-distance-from-node').pfValue; 23563 var srcDist = edge.pstyle('source-distance-from-node').pfValue; 23564 var curveStyle = edge.pstyle('curve-style').value; 23565 var rs = edge._private.rscratch; 23566 var et = rs.edgeType; 23567 var taxi = curveStyle === 'taxi'; 23568 var self = et === 'self' || et === 'compound'; 23569 var bezier = et === 'bezier' || et === 'multibezier' || self; 23570 var multi = et !== 'bezier'; 23571 var lines = et === 'straight' || et === 'segments'; 23572 var segments = et === 'segments'; 23573 var hasEndpts = bezier || multi || lines; 23574 var overrideEndpts = self || taxi; 23575 var srcManEndpt = edge.pstyle('source-endpoint'); 23576 var srcManEndptVal = overrideEndpts ? 'outside-to-node' : srcManEndpt.value; 23577 var tgtManEndpt = edge.pstyle('target-endpoint'); 23578 var tgtManEndptVal = overrideEndpts ? 'outside-to-node' : tgtManEndpt.value; 23579 rs.srcManEndpt = srcManEndpt; 23580 rs.tgtManEndpt = tgtManEndpt; 23581 var p1; // last known point of edge on target side 23582 23583 var p2; // last known point of edge on source side 23584 23585 var p1_i; // point to intersect with target shape 23586 23587 var p2_i; // point to intersect with source shape 23588 23589 if (bezier) { 23590 var cpStart = [rs.ctrlpts[0], rs.ctrlpts[1]]; 23591 var cpEnd = multi ? [rs.ctrlpts[rs.ctrlpts.length - 2], rs.ctrlpts[rs.ctrlpts.length - 1]] : cpStart; 23592 p1 = cpEnd; 23593 p2 = cpStart; 23594 } else if (lines) { 23595 var srcArrowFromPt = !segments ? [tgtPos.x, tgtPos.y] : rs.segpts.slice(0, 2); 23596 var tgtArrowFromPt = !segments ? [srcPos.x, srcPos.y] : rs.segpts.slice(rs.segpts.length - 2); 23597 p1 = tgtArrowFromPt; 23598 p2 = srcArrowFromPt; 23599 } 23600 23601 if (tgtManEndptVal === 'inside-to-node') { 23602 intersect = [tgtPos.x, tgtPos.y]; 23603 } else if (tgtManEndpt.units) { 23604 intersect = this.manualEndptToPx(target, tgtManEndpt); 23605 } else if (tgtManEndptVal === 'outside-to-line') { 23606 intersect = rs.tgtIntn; // use cached value from ctrlpt calc 23607 } else { 23608 if (tgtManEndptVal === 'outside-to-node' || tgtManEndptVal === 'outside-to-node-or-label') { 23609 p1_i = p1; 23610 } else if (tgtManEndptVal === 'outside-to-line' || tgtManEndptVal === 'outside-to-line-or-label') { 23611 p1_i = [srcPos.x, srcPos.y]; 23612 } 23613 23614 intersect = r.nodeShapes[this.getNodeShape(target)].intersectLine(tgtPos.x, tgtPos.y, target.outerWidth(), target.outerHeight(), p1_i[0], p1_i[1], 0); 23615 23616 if (tgtManEndptVal === 'outside-to-node-or-label' || tgtManEndptVal === 'outside-to-line-or-label') { 23617 var trs = target._private.rscratch; 23618 var lw = trs.labelWidth; 23619 var lh = trs.labelHeight; 23620 var lx = trs.labelX; 23621 var ly = trs.labelY; 23622 var lw2 = lw / 2; 23623 var lh2 = lh / 2; 23624 var va = target.pstyle('text-valign').value; 23625 23626 if (va === 'top') { 23627 ly -= lh2; 23628 } else if (va === 'bottom') { 23629 ly += lh2; 23630 } 23631 23632 var ha = target.pstyle('text-halign').value; 23633 23634 if (ha === 'left') { 23635 lx -= lw2; 23636 } else if (ha === 'right') { 23637 lx += lw2; 23638 } 23639 23640 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); 23641 23642 if (labelIntersect.length > 0) { 23643 var refPt = srcPos; 23644 var intSqdist = sqdist(refPt, array2point(intersect)); 23645 var labIntSqdist = sqdist(refPt, array2point(labelIntersect)); 23646 var minSqDist = intSqdist; 23647 23648 if (labIntSqdist < intSqdist) { 23649 intersect = labelIntersect; 23650 minSqDist = labIntSqdist; 23651 } 23652 23653 if (labelIntersect.length > 2) { 23654 var labInt2SqDist = sqdist(refPt, { 23655 x: labelIntersect[2], 23656 y: labelIntersect[3] 23657 }); 23658 23659 if (labInt2SqDist < minSqDist) { 23660 intersect = [labelIntersect[2], labelIntersect[3]]; 23661 } 23662 } 23663 } 23664 } 23665 } 23666 23667 var arrowEnd = shortenIntersection(intersect, p1, r.arrowShapes[tgtArShape].spacing(edge) + tgtDist); 23668 var edgeEnd = shortenIntersection(intersect, p1, r.arrowShapes[tgtArShape].gap(edge) + tgtDist); 23669 rs.endX = edgeEnd[0]; 23670 rs.endY = edgeEnd[1]; 23671 rs.arrowEndX = arrowEnd[0]; 23672 rs.arrowEndY = arrowEnd[1]; 23673 23674 if (srcManEndptVal === 'inside-to-node') { 23675 intersect = [srcPos.x, srcPos.y]; 23676 } else if (srcManEndpt.units) { 23677 intersect = this.manualEndptToPx(source, srcManEndpt); 23678 } else if (srcManEndptVal === 'outside-to-line') { 23679 intersect = rs.srcIntn; // use cached value from ctrlpt calc 23680 } else { 23681 if (srcManEndptVal === 'outside-to-node' || srcManEndptVal === 'outside-to-node-or-label') { 23682 p2_i = p2; 23683 } else if (srcManEndptVal === 'outside-to-line' || srcManEndptVal === 'outside-to-line-or-label') { 23684 p2_i = [tgtPos.x, tgtPos.y]; 23685 } 23686 23687 intersect = r.nodeShapes[this.getNodeShape(source)].intersectLine(srcPos.x, srcPos.y, source.outerWidth(), source.outerHeight(), p2_i[0], p2_i[1], 0); 23688 23689 if (srcManEndptVal === 'outside-to-node-or-label' || srcManEndptVal === 'outside-to-line-or-label') { 23690 var srs = source._private.rscratch; 23691 var _lw = srs.labelWidth; 23692 var _lh = srs.labelHeight; 23693 var _lx = srs.labelX; 23694 var _ly = srs.labelY; 23695 23696 var _lw2 = _lw / 2; 23697 23698 var _lh2 = _lh / 2; 23699 23700 var _va = source.pstyle('text-valign').value; 23701 23702 if (_va === 'top') { 23703 _ly -= _lh2; 23704 } else if (_va === 'bottom') { 23705 _ly += _lh2; 23706 } 23707 23708 var _ha = source.pstyle('text-halign').value; 23709 23710 if (_ha === 'left') { 23711 _lx -= _lw2; 23712 } else if (_ha === 'right') { 23713 _lx += _lw2; 23714 } 23715 23716 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); 23717 23718 if (_labelIntersect.length > 0) { 23719 var _refPt = tgtPos; 23720 23721 var _intSqdist = sqdist(_refPt, array2point(intersect)); 23722 23723 var _labIntSqdist = sqdist(_refPt, array2point(_labelIntersect)); 23724 23725 var _minSqDist = _intSqdist; 23726 23727 if (_labIntSqdist < _intSqdist) { 23728 intersect = [_labelIntersect[0], _labelIntersect[1]]; 23729 _minSqDist = _labIntSqdist; 23730 } 23731 23732 if (_labelIntersect.length > 2) { 23733 var _labInt2SqDist = sqdist(_refPt, { 23734 x: _labelIntersect[2], 23735 y: _labelIntersect[3] 23736 }); 23737 23738 if (_labInt2SqDist < _minSqDist) { 23739 intersect = [_labelIntersect[2], _labelIntersect[3]]; 23740 } 23741 } 23742 } 23743 } 23744 } 23745 23746 var arrowStart = shortenIntersection(intersect, p2, r.arrowShapes[srcArShape].spacing(edge) + srcDist); 23747 var edgeStart = shortenIntersection(intersect, p2, r.arrowShapes[srcArShape].gap(edge) + srcDist); 23748 rs.startX = edgeStart[0]; 23749 rs.startY = edgeStart[1]; 23750 rs.arrowStartX = arrowStart[0]; 23751 rs.arrowStartY = arrowStart[1]; 23752 23753 if (hasEndpts) { 23754 if (!number(rs.startX) || !number(rs.startY) || !number(rs.endX) || !number(rs.endY)) { 23755 rs.badLine = true; 23756 } else { 23757 rs.badLine = false; 23758 } 23759 } 23760 }; 23761 23762 BRp$4.getSourceEndpoint = function (edge) { 23763 var rs = edge[0]._private.rscratch; 23764 this.recalculateRenderedStyle(edge); 23765 23766 switch (rs.edgeType) { 23767 case 'haystack': 23768 return { 23769 x: rs.haystackPts[0], 23770 y: rs.haystackPts[1] 23771 }; 23772 23773 default: 23774 return { 23775 x: rs.arrowStartX, 23776 y: rs.arrowStartY 23777 }; 23778 } 23779 }; 23780 23781 BRp$4.getTargetEndpoint = function (edge) { 23782 var rs = edge[0]._private.rscratch; 23783 this.recalculateRenderedStyle(edge); 23784 23785 switch (rs.edgeType) { 23786 case 'haystack': 23787 return { 23788 x: rs.haystackPts[2], 23789 y: rs.haystackPts[3] 23790 }; 23791 23792 default: 23793 return { 23794 x: rs.arrowEndX, 23795 y: rs.arrowEndY 23796 }; 23797 } 23798 }; 23799 23800 var BRp$5 = {}; 23801 23802 function pushBezierPts(r, edge, pts) { 23803 var qbezierAt$1 = function qbezierAt$1(p1, p2, p3, t) { 23804 return qbezierAt(p1, p2, p3, t); 23805 }; 23806 23807 var _p = edge._private; 23808 var bpts = _p.rstyle.bezierPts; 23809 23810 for (var i = 0; i < r.bezierProjPcts.length; i++) { 23811 var p = r.bezierProjPcts[i]; 23812 bpts.push({ 23813 x: qbezierAt$1(pts[0], pts[2], pts[4], p), 23814 y: qbezierAt$1(pts[1], pts[3], pts[5], p) 23815 }); 23816 } 23817 } 23818 23819 BRp$5.storeEdgeProjections = function (edge) { 23820 var _p = edge._private; 23821 var rs = _p.rscratch; 23822 var et = rs.edgeType; // clear the cached points state 23823 23824 _p.rstyle.bezierPts = null; 23825 _p.rstyle.linePts = null; 23826 _p.rstyle.haystackPts = null; 23827 23828 if (et === 'multibezier' || et === 'bezier' || et === 'self' || et === 'compound') { 23829 _p.rstyle.bezierPts = []; 23830 23831 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 23832 pushBezierPts(this, edge, rs.allpts.slice(i, i + 6)); 23833 } 23834 } else if (et === 'segments') { 23835 var lpts = _p.rstyle.linePts = []; 23836 23837 for (var i = 0; i + 1 < rs.allpts.length; i += 2) { 23838 lpts.push({ 23839 x: rs.allpts[i], 23840 y: rs.allpts[i + 1] 23841 }); 23842 } 23843 } else if (et === 'haystack') { 23844 var hpts = rs.haystackPts; 23845 _p.rstyle.haystackPts = [{ 23846 x: hpts[0], 23847 y: hpts[1] 23848 }, { 23849 x: hpts[2], 23850 y: hpts[3] 23851 }]; 23852 } 23853 23854 _p.rstyle.arrowWidth = this.getArrowWidth(edge.pstyle('width').pfValue, edge.pstyle('arrow-scale').value) * this.arrowShapeWidth; 23855 }; 23856 23857 BRp$5.recalculateEdgeProjections = function (edges) { 23858 this.findEdgeControlPoints(edges); 23859 }; 23860 23861 var BRp$6 = {}; 23862 23863 BRp$6.recalculateNodeLabelProjection = function (node) { 23864 var content = node.pstyle('label').strValue; 23865 23866 if (emptyString(content)) { 23867 return; 23868 } 23869 23870 var textX, textY; 23871 var _p = node._private; 23872 var nodeWidth = node.width(); 23873 var nodeHeight = node.height(); 23874 var padding = node.padding(); 23875 var nodePos = node.position(); 23876 var textHalign = node.pstyle('text-halign').strValue; 23877 var textValign = node.pstyle('text-valign').strValue; 23878 var rs = _p.rscratch; 23879 var rstyle = _p.rstyle; 23880 23881 switch (textHalign) { 23882 case 'left': 23883 textX = nodePos.x - nodeWidth / 2 - padding; 23884 break; 23885 23886 case 'right': 23887 textX = nodePos.x + nodeWidth / 2 + padding; 23888 break; 23889 23890 default: 23891 // e.g. center 23892 textX = nodePos.x; 23893 } 23894 23895 switch (textValign) { 23896 case 'top': 23897 textY = nodePos.y - nodeHeight / 2 - padding; 23898 break; 23899 23900 case 'bottom': 23901 textY = nodePos.y + nodeHeight / 2 + padding; 23902 break; 23903 23904 default: 23905 // e.g. middle 23906 textY = nodePos.y; 23907 } 23908 23909 rs.labelX = textX; 23910 rs.labelY = textY; 23911 rstyle.labelX = textX; 23912 rstyle.labelY = textY; 23913 this.applyLabelDimensions(node); 23914 }; 23915 23916 var lineAngleFromDelta = function lineAngleFromDelta(dx, dy) { 23917 var angle = Math.atan(dy / dx); 23918 23919 if (dx === 0 && angle < 0) { 23920 angle = angle * -1; 23921 } 23922 23923 return angle; 23924 }; 23925 23926 var lineAngle = function lineAngle(p0, p1) { 23927 var dx = p1.x - p0.x; 23928 var dy = p1.y - p0.y; 23929 return lineAngleFromDelta(dx, dy); 23930 }; 23931 23932 var bezierAngle = function bezierAngle(p0, p1, p2, t) { 23933 var t0 = bound(0, t - 0.001, 1); 23934 var t1 = bound(0, t + 0.001, 1); 23935 var lp0 = qbezierPtAt(p0, p1, p2, t0); 23936 var lp1 = qbezierPtAt(p0, p1, p2, t1); 23937 return lineAngle(lp0, lp1); 23938 }; 23939 23940 BRp$6.recalculateEdgeLabelProjections = function (edge) { 23941 var p; 23942 var _p = edge._private; 23943 var rs = _p.rscratch; 23944 var r = this; 23945 var content = { 23946 mid: edge.pstyle('label').strValue, 23947 source: edge.pstyle('source-label').strValue, 23948 target: edge.pstyle('target-label').strValue 23949 }; 23950 23951 if (content.mid || content.source || content.target) ; else { 23952 return; // no labels => no calcs 23953 } // add center point to style so bounding box calculations can use it 23954 // 23955 23956 23957 p = { 23958 x: rs.midX, 23959 y: rs.midY 23960 }; 23961 23962 var setRs = function setRs(propName, prefix, value) { 23963 setPrefixedProperty(_p.rscratch, propName, prefix, value); 23964 setPrefixedProperty(_p.rstyle, propName, prefix, value); 23965 }; 23966 23967 setRs('labelX', null, p.x); 23968 setRs('labelY', null, p.y); 23969 var midAngle = lineAngleFromDelta(rs.midDispX, rs.midDispY); 23970 setRs('labelAutoAngle', null, midAngle); 23971 23972 var createControlPointInfo = function createControlPointInfo() { 23973 if (createControlPointInfo.cache) { 23974 return createControlPointInfo.cache; 23975 } // use cache so only 1x per edge 23976 23977 23978 var ctrlpts = []; // store each ctrlpt info init 23979 23980 for (var i = 0; i + 5 < rs.allpts.length; i += 4) { 23981 var p0 = { 23982 x: rs.allpts[i], 23983 y: rs.allpts[i + 1] 23984 }; 23985 var p1 = { 23986 x: rs.allpts[i + 2], 23987 y: rs.allpts[i + 3] 23988 }; // ctrlpt 23989 23990 var p2 = { 23991 x: rs.allpts[i + 4], 23992 y: rs.allpts[i + 5] 23993 }; 23994 ctrlpts.push({ 23995 p0: p0, 23996 p1: p1, 23997 p2: p2, 23998 startDist: 0, 23999 length: 0, 24000 segments: [] 24001 }); 24002 } 24003 24004 var bpts = _p.rstyle.bezierPts; 24005 var nProjs = r.bezierProjPcts.length; 24006 24007 function addSegment(cp, p0, p1, t0, t1) { 24008 var length = dist(p0, p1); 24009 var prevSegment = cp.segments[cp.segments.length - 1]; 24010 var segment = { 24011 p0: p0, 24012 p1: p1, 24013 t0: t0, 24014 t1: t1, 24015 startDist: prevSegment ? prevSegment.startDist + prevSegment.length : 0, 24016 length: length 24017 }; 24018 cp.segments.push(segment); 24019 cp.length += length; 24020 } // update each ctrlpt with segment info 24021 24022 24023 for (var _i = 0; _i < ctrlpts.length; _i++) { 24024 var cp = ctrlpts[_i]; 24025 var prevCp = ctrlpts[_i - 1]; 24026 24027 if (prevCp) { 24028 cp.startDist = prevCp.startDist + prevCp.length; 24029 } 24030 24031 addSegment(cp, cp.p0, bpts[_i * nProjs], 0, r.bezierProjPcts[0]); // first 24032 24033 for (var j = 0; j < nProjs - 1; j++) { 24034 addSegment(cp, bpts[_i * nProjs + j], bpts[_i * nProjs + j + 1], r.bezierProjPcts[j], r.bezierProjPcts[j + 1]); 24035 } 24036 24037 addSegment(cp, bpts[_i * nProjs + nProjs - 1], cp.p2, r.bezierProjPcts[nProjs - 1], 1); // last 24038 } 24039 24040 return createControlPointInfo.cache = ctrlpts; 24041 }; 24042 24043 var calculateEndProjection = function calculateEndProjection(prefix) { 24044 var angle; 24045 var isSrc = prefix === 'source'; 24046 24047 if (!content[prefix]) { 24048 return; 24049 } 24050 24051 var offset = edge.pstyle(prefix + '-text-offset').pfValue; 24052 24053 switch (rs.edgeType) { 24054 case 'self': 24055 case 'compound': 24056 case 'bezier': 24057 case 'multibezier': 24058 { 24059 var cps = createControlPointInfo(); 24060 var selected; 24061 var startDist = 0; 24062 var totalDist = 0; // find the segment we're on 24063 24064 for (var i = 0; i < cps.length; i++) { 24065 var _cp = cps[isSrc ? i : cps.length - 1 - i]; 24066 24067 for (var j = 0; j < _cp.segments.length; j++) { 24068 var _seg = _cp.segments[isSrc ? j : _cp.segments.length - 1 - j]; 24069 var lastSeg = i === cps.length - 1 && j === _cp.segments.length - 1; 24070 startDist = totalDist; 24071 totalDist += _seg.length; 24072 24073 if (totalDist >= offset || lastSeg) { 24074 selected = { 24075 cp: _cp, 24076 segment: _seg 24077 }; 24078 break; 24079 } 24080 } 24081 24082 if (selected) { 24083 break; 24084 } 24085 } 24086 24087 var cp = selected.cp; 24088 var seg = selected.segment; 24089 var tSegment = (offset - startDist) / seg.length; 24090 var segDt = seg.t1 - seg.t0; 24091 var t = isSrc ? seg.t0 + segDt * tSegment : seg.t1 - segDt * tSegment; 24092 t = bound(0, t, 1); 24093 p = qbezierPtAt(cp.p0, cp.p1, cp.p2, t); 24094 angle = bezierAngle(cp.p0, cp.p1, cp.p2, t); 24095 break; 24096 } 24097 24098 case 'straight': 24099 case 'segments': 24100 case 'haystack': 24101 { 24102 var d = 0, 24103 di, 24104 d0; 24105 var p0, p1; 24106 var l = rs.allpts.length; 24107 24108 for (var _i2 = 0; _i2 + 3 < l; _i2 += 2) { 24109 if (isSrc) { 24110 p0 = { 24111 x: rs.allpts[_i2], 24112 y: rs.allpts[_i2 + 1] 24113 }; 24114 p1 = { 24115 x: rs.allpts[_i2 + 2], 24116 y: rs.allpts[_i2 + 3] 24117 }; 24118 } else { 24119 p0 = { 24120 x: rs.allpts[l - 2 - _i2], 24121 y: rs.allpts[l - 1 - _i2] 24122 }; 24123 p1 = { 24124 x: rs.allpts[l - 4 - _i2], 24125 y: rs.allpts[l - 3 - _i2] 24126 }; 24127 } 24128 24129 di = dist(p0, p1); 24130 d0 = d; 24131 d += di; 24132 24133 if (d >= offset) { 24134 break; 24135 } 24136 } 24137 24138 var pD = offset - d0; 24139 24140 var _t = pD / di; 24141 24142 _t = bound(0, _t, 1); 24143 p = lineAt(p0, p1, _t); 24144 angle = lineAngle(p0, p1); 24145 break; 24146 } 24147 } 24148 24149 setRs('labelX', prefix, p.x); 24150 setRs('labelY', prefix, p.y); 24151 setRs('labelAutoAngle', prefix, angle); 24152 }; 24153 24154 calculateEndProjection('source'); 24155 calculateEndProjection('target'); 24156 this.applyLabelDimensions(edge); 24157 }; 24158 24159 BRp$6.applyLabelDimensions = function (ele) { 24160 this.applyPrefixedLabelDimensions(ele); 24161 24162 if (ele.isEdge()) { 24163 this.applyPrefixedLabelDimensions(ele, 'source'); 24164 this.applyPrefixedLabelDimensions(ele, 'target'); 24165 } 24166 }; 24167 24168 BRp$6.applyPrefixedLabelDimensions = function (ele, prefix) { 24169 var _p = ele._private; 24170 var text = this.getLabelText(ele, prefix); 24171 var labelDims = this.calculateLabelDimensions(ele, text); 24172 var lineHeight = ele.pstyle('line-height').pfValue; 24173 var textWrap = ele.pstyle('text-wrap').strValue; 24174 var lines = getPrefixedProperty(_p.rscratch, 'labelWrapCachedLines', prefix) || []; 24175 var numLines = textWrap !== 'wrap' ? 1 : Math.max(lines.length, 1); 24176 var normPerLineHeight = labelDims.height / numLines; 24177 var labelLineHeight = normPerLineHeight * lineHeight; 24178 var width = labelDims.width; 24179 var height = labelDims.height + (numLines - 1) * (lineHeight - 1) * normPerLineHeight; 24180 setPrefixedProperty(_p.rstyle, 'labelWidth', prefix, width); 24181 setPrefixedProperty(_p.rscratch, 'labelWidth', prefix, width); 24182 setPrefixedProperty(_p.rstyle, 'labelHeight', prefix, height); 24183 setPrefixedProperty(_p.rscratch, 'labelHeight', prefix, height); 24184 setPrefixedProperty(_p.rscratch, 'labelLineHeight', prefix, labelLineHeight); 24185 }; 24186 24187 BRp$6.getLabelText = function (ele, prefix) { 24188 var _p = ele._private; 24189 var pfd = prefix ? prefix + '-' : ''; 24190 var text = ele.pstyle(pfd + 'label').strValue; 24191 var textTransform = ele.pstyle('text-transform').value; 24192 24193 var rscratch = function rscratch(propName, value) { 24194 if (value) { 24195 setPrefixedProperty(_p.rscratch, propName, prefix, value); 24196 return value; 24197 } else { 24198 return getPrefixedProperty(_p.rscratch, propName, prefix); 24199 } 24200 }; // for empty text, skip all processing 24201 24202 24203 if (!text) { 24204 return ''; 24205 } 24206 24207 if (textTransform == 'none') ; else if (textTransform == 'uppercase') { 24208 text = text.toUpperCase(); 24209 } else if (textTransform == 'lowercase') { 24210 text = text.toLowerCase(); 24211 } 24212 24213 var wrapStyle = ele.pstyle('text-wrap').value; 24214 24215 if (wrapStyle === 'wrap') { 24216 var labelKey = rscratch('labelKey'); // save recalc if the label is the same as before 24217 24218 if (labelKey != null && rscratch('labelWrapKey') === labelKey) { 24219 return rscratch('labelWrapCachedText'); 24220 } 24221 24222 var zwsp = "\u200B"; 24223 var lines = text.split('\n'); 24224 var maxW = ele.pstyle('text-max-width').pfValue; 24225 var overflow = ele.pstyle('text-overflow-wrap').value; 24226 var overflowAny = overflow === 'anywhere'; 24227 var wrappedLines = []; 24228 var wordsRegex = /[\s\u200b]+/; 24229 var wordSeparator = overflowAny ? '' : ' '; 24230 24231 for (var l = 0; l < lines.length; l++) { 24232 var line = lines[l]; 24233 var lineDims = this.calculateLabelDimensions(ele, line); 24234 var lineW = lineDims.width; 24235 24236 if (overflowAny) { 24237 var processedLine = line.split('').join(zwsp); 24238 line = processedLine; 24239 } 24240 24241 if (lineW > maxW) { 24242 // line is too long 24243 var words = line.split(wordsRegex); 24244 var subline = ''; 24245 24246 for (var w = 0; w < words.length; w++) { 24247 var word = words[w]; 24248 var testLine = subline.length === 0 ? word : subline + wordSeparator + word; 24249 var testDims = this.calculateLabelDimensions(ele, testLine); 24250 var testW = testDims.width; 24251 24252 if (testW <= maxW) { 24253 // word fits on current line 24254 subline += word + wordSeparator; 24255 } else { 24256 // word starts new line 24257 if (subline) { 24258 wrappedLines.push(subline); 24259 } 24260 24261 subline = word + wordSeparator; 24262 } 24263 } // if there's remaining text, put it in a wrapped line 24264 24265 24266 if (!subline.match(/^[\s\u200b]+$/)) { 24267 wrappedLines.push(subline); 24268 } 24269 } else { 24270 // line is already short enough 24271 wrappedLines.push(line); 24272 } 24273 } // for 24274 24275 24276 rscratch('labelWrapCachedLines', wrappedLines); 24277 text = rscratch('labelWrapCachedText', wrappedLines.join('\n')); 24278 rscratch('labelWrapKey', labelKey); 24279 } else if (wrapStyle === 'ellipsis') { 24280 var _maxW = ele.pstyle('text-max-width').pfValue; 24281 var ellipsized = ''; 24282 var ellipsis = "\u2026"; 24283 var incLastCh = false; 24284 24285 for (var i = 0; i < text.length; i++) { 24286 var widthWithNextCh = this.calculateLabelDimensions(ele, ellipsized + text[i] + ellipsis).width; 24287 24288 if (widthWithNextCh > _maxW) { 24289 break; 24290 } 24291 24292 ellipsized += text[i]; 24293 24294 if (i === text.length - 1) { 24295 incLastCh = true; 24296 } 24297 } 24298 24299 if (!incLastCh) { 24300 ellipsized += ellipsis; 24301 } 24302 24303 return ellipsized; 24304 } // if ellipsize 24305 24306 24307 return text; 24308 }; 24309 24310 BRp$6.getLabelJustification = function (ele) { 24311 var justification = ele.pstyle('text-justification').strValue; 24312 var textHalign = ele.pstyle('text-halign').strValue; 24313 24314 if (justification === 'auto') { 24315 if (ele.isNode()) { 24316 switch (textHalign) { 24317 case 'left': 24318 return 'right'; 24319 24320 case 'right': 24321 return 'left'; 24322 24323 default: 24324 return 'center'; 24325 } 24326 } else { 24327 return 'center'; 24328 } 24329 } else { 24330 return justification; 24331 } 24332 }; 24333 24334 BRp$6.calculateLabelDimensions = function (ele, text) { 24335 var r = this; 24336 var cacheKey = hashString(text, ele._private.labelDimsKey); 24337 var cache = r.labelDimCache || (r.labelDimCache = []); 24338 var existingVal = cache[cacheKey]; 24339 24340 if (existingVal != null) { 24341 return existingVal; 24342 } 24343 24344 var sizeMult = 1; // increase the scale to increase accuracy w.r.t. zoomed text 24345 24346 var fStyle = ele.pstyle('font-style').strValue; 24347 var size = sizeMult * ele.pstyle('font-size').pfValue + 'px'; 24348 var family = ele.pstyle('font-family').strValue; 24349 var weight = ele.pstyle('font-weight').strValue; 24350 var div = this.labelCalcDiv; 24351 24352 if (!div) { 24353 div = this.labelCalcDiv = document.createElement('div'); // eslint-disable-line no-undef 24354 24355 document.body.appendChild(div); // eslint-disable-line no-undef 24356 } 24357 24358 var ds = div.style; // from ele style 24359 24360 ds.fontFamily = family; 24361 ds.fontStyle = fStyle; 24362 ds.fontSize = size; 24363 ds.fontWeight = weight; // forced style 24364 24365 ds.position = 'absolute'; 24366 ds.left = '-9999px'; 24367 ds.top = '-9999px'; 24368 ds.zIndex = '-1'; 24369 ds.visibility = 'hidden'; 24370 ds.pointerEvents = 'none'; 24371 ds.padding = '0'; 24372 ds.lineHeight = '1'; // - newlines must be taken into account for text-wrap:wrap 24373 // - since spaces are not collapsed, each space must be taken into account 24374 24375 ds.whiteSpace = 'pre'; // put label content in div 24376 24377 div.textContent = text; 24378 return cache[cacheKey] = { 24379 width: Math.ceil(div.clientWidth / sizeMult), 24380 height: Math.ceil(div.clientHeight / sizeMult) 24381 }; 24382 }; 24383 24384 BRp$6.calculateLabelAngle = function (ele, prefix) { 24385 var _p = ele._private; 24386 var rs = _p.rscratch; 24387 var isEdge = ele.isEdge(); 24388 var prefixDash = prefix ? prefix + '-' : ''; 24389 var rot = ele.pstyle(prefixDash + 'text-rotation'); 24390 var rotStr = rot.strValue; 24391 24392 if (rotStr === 'none') { 24393 return 0; 24394 } else if (isEdge && rotStr === 'autorotate') { 24395 return rs.labelAutoAngle; 24396 } else if (rotStr === 'autorotate') { 24397 return 0; 24398 } else { 24399 return rot.pfValue; 24400 } 24401 }; 24402 24403 BRp$6.calculateLabelAngles = function (ele) { 24404 var r = this; 24405 var isEdge = ele.isEdge(); 24406 var _p = ele._private; 24407 var rs = _p.rscratch; 24408 rs.labelAngle = r.calculateLabelAngle(ele); 24409 24410 if (isEdge) { 24411 rs.sourceLabelAngle = r.calculateLabelAngle(ele, 'source'); 24412 rs.targetLabelAngle = r.calculateLabelAngle(ele, 'target'); 24413 } 24414 }; 24415 24416 var BRp$7 = {}; 24417 var TOO_SMALL_CUT_RECT = 28; 24418 var warnedCutRect = false; 24419 24420 BRp$7.getNodeShape = function (node) { 24421 var r = this; 24422 var shape = node.pstyle('shape').value; 24423 24424 if (shape === 'cutrectangle' && (node.width() < TOO_SMALL_CUT_RECT || node.height() < TOO_SMALL_CUT_RECT)) { 24425 if (!warnedCutRect) { 24426 warn('The `cutrectangle` node shape can not be used at small sizes so `rectangle` is used instead'); 24427 warnedCutRect = true; 24428 } 24429 24430 return 'rectangle'; 24431 } 24432 24433 if (node.isParent()) { 24434 if (shape === 'rectangle' || shape === 'roundrectangle' || shape === 'cutrectangle' || shape === 'barrel') { 24435 return shape; 24436 } else { 24437 return 'rectangle'; 24438 } 24439 } 24440 24441 if (shape === 'polygon') { 24442 var points = node.pstyle('shape-polygon-points').value; 24443 return r.nodeShapes.makePolygon(points).name; 24444 } 24445 24446 return shape; 24447 }; 24448 24449 var BRp$8 = {}; 24450 24451 BRp$8.registerCalculationListeners = function () { 24452 var cy = this.cy; 24453 var elesToUpdate = cy.collection(); 24454 var r = this; 24455 24456 var enqueue = function enqueue(eles) { 24457 var dirtyStyleCaches = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 24458 elesToUpdate.merge(eles); 24459 24460 if (dirtyStyleCaches) { 24461 for (var i = 0; i < eles.length; i++) { 24462 var ele = eles[i]; 24463 var _p = ele._private; 24464 var rstyle = _p.rstyle; 24465 rstyle.clean = false; 24466 rstyle.cleanConnected = false; 24467 } 24468 } 24469 }; 24470 24471 r.binder(cy).on('bounds.* dirty.*', function onDirtyBounds(e) { 24472 var ele = e.target; 24473 enqueue(ele); 24474 }).on('style.* background.*', function onDirtyStyle(e) { 24475 var ele = e.target; 24476 enqueue(ele, false); 24477 }); 24478 24479 var updateEleCalcs = function updateEleCalcs(willDraw) { 24480 if (willDraw) { 24481 var fns = r.onUpdateEleCalcsFns; 24482 24483 for (var i = 0; i < elesToUpdate.length; i++) { 24484 var ele = elesToUpdate[i]; 24485 var rstyle = ele._private.rstyle; 24486 24487 if (ele.isNode() && !rstyle.cleanConnected) { 24488 enqueue(ele.connectedEdges()); 24489 rstyle.cleanConnected = true; 24490 } 24491 } 24492 24493 if (fns) { 24494 for (var i = 0; i < fns.length; i++) { 24495 var fn = fns[i]; 24496 fn(willDraw, elesToUpdate); 24497 } 24498 } 24499 24500 r.recalculateRenderedStyle(elesToUpdate); 24501 elesToUpdate = cy.collection(); 24502 } 24503 }; 24504 24505 r.flushRenderedStyleQueue = function () { 24506 updateEleCalcs(true); 24507 }; 24508 24509 r.beforeRender(updateEleCalcs, r.beforeRenderPriorities.eleCalcs); 24510 }; 24511 24512 BRp$8.onUpdateEleCalcs = function (fn) { 24513 var fns = this.onUpdateEleCalcsFns = this.onUpdateEleCalcsFns || []; 24514 fns.push(fn); 24515 }; 24516 24517 BRp$8.recalculateRenderedStyle = function (eles, useCache) { 24518 var isCleanConnected = function isCleanConnected(ele) { 24519 return ele._private.rstyle.cleanConnected; 24520 }; 24521 24522 var edges = []; 24523 var nodes = []; // the renderer can't be used for calcs when destroyed, e.g. ele.boundingBox() 24524 24525 if (this.destroyed) { 24526 return; 24527 } // use cache by default for perf 24528 24529 24530 if (useCache === undefined) { 24531 useCache = true; 24532 } 24533 24534 for (var i = 0; i < eles.length; i++) { 24535 var ele = eles[i]; 24536 var _p = ele._private; 24537 var rstyle = _p.rstyle; // an edge may be implicitly dirty b/c of one of its connected nodes 24538 // (and a request for recalc may come in between frames) 24539 24540 if (ele.isEdge() && (!isCleanConnected(ele.source()) || !isCleanConnected(ele.target()))) { 24541 rstyle.clean = false; 24542 } // only update if dirty and in graph 24543 24544 24545 if (useCache && rstyle.clean || ele.removed()) { 24546 continue; 24547 } // only update if not display: none 24548 24549 24550 if (ele.pstyle('display').value === 'none') { 24551 continue; 24552 } 24553 24554 if (_p.group === 'nodes') { 24555 nodes.push(ele); 24556 } else { 24557 // edges 24558 edges.push(ele); 24559 } 24560 24561 rstyle.clean = true; 24562 } // update node data from projections 24563 24564 24565 for (var i = 0; i < nodes.length; i++) { 24566 var ele = nodes[i]; 24567 var _p = ele._private; 24568 var rstyle = _p.rstyle; 24569 var pos = ele.position(); 24570 this.recalculateNodeLabelProjection(ele); 24571 rstyle.nodeX = pos.x; 24572 rstyle.nodeY = pos.y; 24573 rstyle.nodeW = ele.pstyle('width').pfValue; 24574 rstyle.nodeH = ele.pstyle('height').pfValue; 24575 } 24576 24577 this.recalculateEdgeProjections(edges); // update edge data from projections 24578 24579 for (var i = 0; i < edges.length; i++) { 24580 var ele = edges[i]; 24581 var _p = ele._private; 24582 var rstyle = _p.rstyle; 24583 var rs = _p.rscratch; // update rstyle positions 24584 24585 rstyle.srcX = rs.arrowStartX; 24586 rstyle.srcY = rs.arrowStartY; 24587 rstyle.tgtX = rs.arrowEndX; 24588 rstyle.tgtY = rs.arrowEndY; 24589 rstyle.midX = rs.midX; 24590 rstyle.midY = rs.midY; 24591 rstyle.labelAngle = rs.labelAngle; 24592 rstyle.sourceLabelAngle = rs.sourceLabelAngle; 24593 rstyle.targetLabelAngle = rs.targetLabelAngle; 24594 } 24595 }; 24596 24597 var BRp$9 = {}; 24598 24599 BRp$9.updateCachedGrabbedEles = function () { 24600 var eles = this.cachedZSortedEles; 24601 24602 if (!eles) { 24603 // just let this be recalculated on the next z sort tick 24604 return; 24605 } 24606 24607 eles.drag = []; 24608 eles.nondrag = []; 24609 var grabTargets = []; 24610 24611 for (var i = 0; i < eles.length; i++) { 24612 var ele = eles[i]; 24613 var rs = ele._private.rscratch; 24614 24615 if (ele.grabbed() && !ele.isParent()) { 24616 grabTargets.push(ele); 24617 } else if (rs.inDragLayer) { 24618 eles.drag.push(ele); 24619 } else { 24620 eles.nondrag.push(ele); 24621 } 24622 } // put the grab target nodes last so it's on top of its neighbourhood 24623 24624 24625 for (var i = 0; i < grabTargets.length; i++) { 24626 var ele = grabTargets[i]; 24627 eles.drag.push(ele); 24628 } 24629 }; 24630 24631 BRp$9.invalidateCachedZSortedEles = function () { 24632 this.cachedZSortedEles = null; 24633 }; 24634 24635 BRp$9.getCachedZSortedEles = function (forceRecalc) { 24636 if (forceRecalc || !this.cachedZSortedEles) { 24637 var eles = this.cy.mutableElements().toArray(); 24638 eles.sort(zIndexSort); 24639 eles.interactive = eles.filter(function (ele) { 24640 return ele.interactive(); 24641 }); 24642 this.cachedZSortedEles = eles; 24643 this.updateCachedGrabbedEles(); 24644 } else { 24645 eles = this.cachedZSortedEles; 24646 } 24647 24648 return eles; 24649 }; 24650 24651 var BRp$a = {}; 24652 [BRp$1, BRp$2, BRp$3, BRp$4, BRp$5, BRp$6, BRp$7, BRp$8, BRp$9].forEach(function (props) { 24653 extend(BRp$a, props); 24654 }); 24655 24656 var BRp$b = {}; 24657 24658 BRp$b.getCachedImage = function (url, crossOrigin, onLoad) { 24659 var r = this; 24660 var imageCache = r.imageCache = r.imageCache || {}; 24661 var cache = imageCache[url]; 24662 24663 if (cache) { 24664 if (!cache.image.complete) { 24665 cache.image.addEventListener('load', onLoad); 24666 } 24667 24668 return cache.image; 24669 } else { 24670 cache = imageCache[url] = imageCache[url] || {}; 24671 var image = cache.image = new Image(); // eslint-disable-line no-undef 24672 24673 image.addEventListener('load', onLoad); 24674 image.addEventListener('error', function () { 24675 image.error = true; 24676 }); // #1582 safari doesn't load data uris with crossOrigin properly 24677 // https://bugs.webkit.org/show_bug.cgi?id=123978 24678 24679 var dataUriPrefix = 'data:'; 24680 var isDataUri = url.substring(0, dataUriPrefix.length).toLowerCase() === dataUriPrefix; 24681 24682 if (!isDataUri) { 24683 image.crossOrigin = crossOrigin; // prevent tainted canvas 24684 } 24685 24686 image.src = url; 24687 return image; 24688 } 24689 }; 24690 24691 var BRp$c = {}; 24692 /* global document, window, ResizeObserver, MutationObserver */ 24693 24694 BRp$c.registerBinding = function (target, event, handler, useCapture) { 24695 // eslint-disable-line no-unused-vars 24696 var args = Array.prototype.slice.apply(arguments, [1]); // copy 24697 24698 var b = this.binder(target); 24699 return b.on.apply(b, args); 24700 }; 24701 24702 BRp$c.binder = function (tgt) { 24703 var r = this; 24704 var tgtIsDom = tgt === window || tgt === document || tgt === document.body || domElement(tgt); 24705 24706 if (r.supportsPassiveEvents == null) { 24707 // from https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection 24708 var supportsPassive = false; 24709 24710 try { 24711 var opts = Object.defineProperty({}, 'passive', { 24712 get: function get() { 24713 supportsPassive = true; 24714 return true; 24715 } 24716 }); 24717 window.addEventListener('test', null, opts); 24718 } catch (err) {// not supported 24719 } 24720 24721 r.supportsPassiveEvents = supportsPassive; 24722 } 24723 24724 var on = function on(event, handler, useCapture) { 24725 var args = Array.prototype.slice.call(arguments); 24726 24727 if (tgtIsDom && r.supportsPassiveEvents) { 24728 // replace useCapture w/ opts obj 24729 args[2] = { 24730 capture: useCapture != null ? useCapture : false, 24731 passive: false, 24732 once: false 24733 }; 24734 } 24735 24736 r.bindings.push({ 24737 target: tgt, 24738 args: args 24739 }); 24740 (tgt.addEventListener || tgt.on).apply(tgt, args); 24741 return this; 24742 }; 24743 24744 return { 24745 on: on, 24746 addEventListener: on, 24747 addListener: on, 24748 bind: on 24749 }; 24750 }; 24751 24752 BRp$c.nodeIsDraggable = function (node) { 24753 return node && node.isNode() && !node.locked() && node.grabbable(); 24754 }; 24755 24756 BRp$c.nodeIsGrabbable = function (node) { 24757 return this.nodeIsDraggable(node) && node.interactive(); 24758 }; 24759 24760 BRp$c.load = function () { 24761 var r = this; 24762 24763 var isSelected = function isSelected(ele) { 24764 return ele.selected(); 24765 }; 24766 24767 var triggerEvents = function triggerEvents(target, names, e, position) { 24768 if (target == null) { 24769 target = r.cy; 24770 } 24771 24772 for (var i = 0; i < names.length; i++) { 24773 var name = names[i]; 24774 target.emit({ 24775 originalEvent: e, 24776 type: name, 24777 position: position 24778 }); 24779 } 24780 }; 24781 24782 var isMultSelKeyDown = function isMultSelKeyDown(e) { 24783 return e.shiftKey || e.metaKey || e.ctrlKey; // maybe e.altKey 24784 }; 24785 24786 var allowPanningPassthrough = function allowPanningPassthrough(down, downs) { 24787 var allowPassthrough = true; 24788 24789 if (r.cy.hasCompoundNodes() && down && down.pannable()) { 24790 // a grabbable compound node below the ele => no passthrough panning 24791 for (var i = 0; downs && i < downs.length; i++) { 24792 var down = downs[i]; 24793 24794 if (down.isNode() && down.isParent()) { 24795 allowPassthrough = false; 24796 break; 24797 } 24798 } 24799 } else { 24800 allowPassthrough = true; 24801 } 24802 24803 return allowPassthrough; 24804 }; 24805 24806 var setGrabbed = function setGrabbed(ele) { 24807 ele[0]._private.grabbed = true; 24808 }; 24809 24810 var setFreed = function setFreed(ele) { 24811 ele[0]._private.grabbed = false; 24812 }; 24813 24814 var setInDragLayer = function setInDragLayer(ele) { 24815 ele[0]._private.rscratch.inDragLayer = true; 24816 }; 24817 24818 var setOutDragLayer = function setOutDragLayer(ele) { 24819 ele[0]._private.rscratch.inDragLayer = false; 24820 }; 24821 24822 var setGrabTarget = function setGrabTarget(ele) { 24823 ele[0]._private.rscratch.isGrabTarget = true; 24824 }; 24825 24826 var removeGrabTarget = function removeGrabTarget(ele) { 24827 ele[0]._private.rscratch.isGrabTarget = false; 24828 }; 24829 24830 var addToDragList = function addToDragList(ele, opts) { 24831 var list = opts.addToList; 24832 var listHasEle = list.has(ele); 24833 24834 if (!listHasEle) { 24835 list.merge(ele); 24836 setGrabbed(ele); 24837 } 24838 }; // helper function to determine which child nodes and inner edges 24839 // of a compound node to be dragged as well as the grabbed and selected nodes 24840 24841 24842 var addDescendantsToDrag = function addDescendantsToDrag(node, opts) { 24843 if (!node.cy().hasCompoundNodes()) { 24844 return; 24845 } 24846 24847 if (opts.inDragLayer == null && opts.addToList == null) { 24848 return; 24849 } // nothing to do 24850 24851 24852 var innerNodes = node.descendants(); 24853 24854 if (opts.inDragLayer) { 24855 innerNodes.forEach(setInDragLayer); 24856 innerNodes.connectedEdges().forEach(setInDragLayer); 24857 } 24858 24859 if (opts.addToList) { 24860 opts.addToList.unmerge(innerNodes); 24861 } 24862 }; // adds the given nodes and its neighbourhood to the drag layer 24863 24864 24865 var addNodesToDrag = function addNodesToDrag(nodes, opts) { 24866 opts = opts || {}; 24867 var hasCompoundNodes = nodes.cy().hasCompoundNodes(); 24868 24869 if (opts.inDragLayer) { 24870 nodes.forEach(setInDragLayer); 24871 nodes.neighborhood().stdFilter(function (ele) { 24872 return !hasCompoundNodes || ele.isEdge(); 24873 }).forEach(setInDragLayer); 24874 } 24875 24876 if (opts.addToList) { 24877 nodes.forEach(function (ele) { 24878 addToDragList(ele, opts); 24879 }); 24880 } 24881 24882 addDescendantsToDrag(nodes, opts); // always add to drag 24883 // also add nodes and edges related to the topmost ancestor 24884 24885 updateAncestorsInDragLayer(nodes, { 24886 inDragLayer: opts.inDragLayer 24887 }); 24888 r.updateCachedGrabbedEles(); 24889 }; 24890 24891 var addNodeToDrag = addNodesToDrag; 24892 24893 var freeDraggedElements = function freeDraggedElements(grabbedEles) { 24894 if (!grabbedEles) { 24895 return; 24896 } // just go over all elements rather than doing a bunch of (possibly expensive) traversals 24897 24898 24899 r.getCachedZSortedEles().forEach(function (ele) { 24900 setFreed(ele); 24901 setOutDragLayer(ele); 24902 removeGrabTarget(ele); 24903 }); 24904 r.updateCachedGrabbedEles(); 24905 }; // helper function to determine which ancestor nodes and edges should go 24906 // to the drag layer (or should be removed from drag layer). 24907 24908 24909 var updateAncestorsInDragLayer = function updateAncestorsInDragLayer(node, opts) { 24910 if (opts.inDragLayer == null && opts.addToList == null) { 24911 return; 24912 } // nothing to do 24913 24914 24915 if (!node.cy().hasCompoundNodes()) { 24916 return; 24917 } // find top-level parent 24918 24919 24920 var parent = node.ancestors().orphans(); // no parent node: no nodes to add to the drag layer 24921 24922 if (parent.same(node)) { 24923 return; 24924 } 24925 24926 var nodes = parent.descendants().spawnSelf().merge(parent).unmerge(node).unmerge(node.descendants()); 24927 var edges = nodes.connectedEdges(); 24928 24929 if (opts.inDragLayer) { 24930 edges.forEach(setInDragLayer); 24931 nodes.forEach(setInDragLayer); 24932 } 24933 24934 if (opts.addToList) { 24935 nodes.forEach(function (ele) { 24936 addToDragList(ele, opts); 24937 }); 24938 } 24939 }; 24940 24941 var blurActiveDomElement = function blurActiveDomElement() { 24942 if (document.activeElement != null && document.activeElement.blur != null) { 24943 document.activeElement.blur(); 24944 } 24945 }; 24946 24947 var haveMutationsApi = typeof MutationObserver !== 'undefined'; 24948 var haveResizeObserverApi = typeof ResizeObserver !== 'undefined'; // watch for when the cy container is removed from the dom 24949 24950 if (haveMutationsApi) { 24951 r.removeObserver = new MutationObserver(function (mutns) { 24952 // eslint-disable-line no-undef 24953 for (var i = 0; i < mutns.length; i++) { 24954 var mutn = mutns[i]; 24955 var rNodes = mutn.removedNodes; 24956 24957 if (rNodes) { 24958 for (var j = 0; j < rNodes.length; j++) { 24959 var rNode = rNodes[j]; 24960 24961 if (rNode === r.container) { 24962 r.destroy(); 24963 break; 24964 } 24965 } 24966 } 24967 } 24968 }); 24969 24970 if (r.container.parentNode) { 24971 r.removeObserver.observe(r.container.parentNode, { 24972 childList: true 24973 }); 24974 } 24975 } else { 24976 r.registerBinding(r.container, 'DOMNodeRemoved', function (e) { 24977 // eslint-disable-line no-unused-vars 24978 r.destroy(); 24979 }); 24980 } 24981 24982 var onResize = lodash_debounce(function () { 24983 r.cy.resize(); 24984 }, 100); 24985 24986 if (haveMutationsApi) { 24987 r.styleObserver = new MutationObserver(onResize); // eslint-disable-line no-undef 24988 24989 r.styleObserver.observe(r.container, { 24990 attributes: true 24991 }); 24992 } // auto resize 24993 24994 24995 r.registerBinding(window, 'resize', onResize); // eslint-disable-line no-undef 24996 24997 if (haveResizeObserverApi) { 24998 r.resizeObserver = new ResizeObserver(onResize); // eslint-disable-line no-undef 24999 25000 r.resizeObserver.observe(r.container); 25001 } 25002 25003 var forEachUp = function forEachUp(domEle, fn) { 25004 while (domEle != null) { 25005 fn(domEle); 25006 domEle = domEle.parentNode; 25007 } 25008 }; 25009 25010 var invalidateCoords = function invalidateCoords() { 25011 r.invalidateContainerClientCoordsCache(); 25012 }; 25013 25014 forEachUp(r.container, function (domEle) { 25015 r.registerBinding(domEle, 'transitionend', invalidateCoords); 25016 r.registerBinding(domEle, 'animationend', invalidateCoords); 25017 r.registerBinding(domEle, 'scroll', invalidateCoords); 25018 }); // stop right click menu from appearing on cy 25019 25020 r.registerBinding(r.container, 'contextmenu', function (e) { 25021 e.preventDefault(); 25022 }); 25023 25024 var inBoxSelection = function inBoxSelection() { 25025 return r.selection[4] !== 0; 25026 }; 25027 25028 var eventInContainer = function eventInContainer(e) { 25029 // save cycles if mouse events aren't to be captured 25030 var containerPageCoords = r.findContainerClientCoords(); 25031 var x = containerPageCoords[0]; 25032 var y = containerPageCoords[1]; 25033 var width = containerPageCoords[2]; 25034 var height = containerPageCoords[3]; 25035 var positions = e.touches ? e.touches : [e]; 25036 var atLeastOnePosInside = false; 25037 25038 for (var i = 0; i < positions.length; i++) { 25039 var p = positions[i]; 25040 25041 if (x <= p.clientX && p.clientX <= x + width && y <= p.clientY && p.clientY <= y + height) { 25042 atLeastOnePosInside = true; 25043 break; 25044 } 25045 } 25046 25047 if (!atLeastOnePosInside) { 25048 return false; 25049 } 25050 25051 var container = r.container; 25052 var target = e.target; 25053 var tParent = target.parentNode; 25054 var containerIsTarget = false; 25055 25056 while (tParent) { 25057 if (tParent === container) { 25058 containerIsTarget = true; 25059 break; 25060 } 25061 25062 tParent = tParent.parentNode; 25063 } 25064 25065 if (!containerIsTarget) { 25066 return false; 25067 } // if target is outisde cy container, then this event is not for us 25068 25069 25070 return true; 25071 }; // Primary key 25072 25073 25074 r.registerBinding(r.container, 'mousedown', function mousedownHandler(e) { 25075 if (!eventInContainer(e)) { 25076 return; 25077 } 25078 25079 e.preventDefault(); 25080 blurActiveDomElement(); 25081 r.hoverData.capture = true; 25082 r.hoverData.which = e.which; 25083 var cy = r.cy; 25084 var gpos = [e.clientX, e.clientY]; 25085 var pos = r.projectIntoViewport(gpos[0], gpos[1]); 25086 var select = r.selection; 25087 var nears = r.findNearestElements(pos[0], pos[1], true, false); 25088 var near = nears[0]; 25089 var draggedElements = r.dragData.possibleDragElements; 25090 r.hoverData.mdownPos = pos; 25091 r.hoverData.mdownGPos = gpos; 25092 25093 var checkForTaphold = function checkForTaphold() { 25094 r.hoverData.tapholdCancelled = false; 25095 clearTimeout(r.hoverData.tapholdTimeout); 25096 r.hoverData.tapholdTimeout = setTimeout(function () { 25097 if (r.hoverData.tapholdCancelled) { 25098 return; 25099 } else { 25100 var ele = r.hoverData.down; 25101 25102 if (ele) { 25103 ele.emit({ 25104 originalEvent: e, 25105 type: 'taphold', 25106 position: { 25107 x: pos[0], 25108 y: pos[1] 25109 } 25110 }); 25111 } else { 25112 cy.emit({ 25113 originalEvent: e, 25114 type: 'taphold', 25115 position: { 25116 x: pos[0], 25117 y: pos[1] 25118 } 25119 }); 25120 } 25121 } 25122 }, r.tapholdDuration); 25123 }; // Right click button 25124 25125 25126 if (e.which == 3) { 25127 r.hoverData.cxtStarted = true; 25128 var cxtEvt = { 25129 originalEvent: e, 25130 type: 'cxttapstart', 25131 position: { 25132 x: pos[0], 25133 y: pos[1] 25134 } 25135 }; 25136 25137 if (near) { 25138 near.activate(); 25139 near.emit(cxtEvt); 25140 r.hoverData.down = near; 25141 } else { 25142 cy.emit(cxtEvt); 25143 } 25144 25145 r.hoverData.downTime = new Date().getTime(); 25146 r.hoverData.cxtDragged = false; // Primary button 25147 } else if (e.which == 1) { 25148 if (near) { 25149 near.activate(); 25150 } // Element dragging 25151 25152 25153 { 25154 // If something is under the cursor and it is draggable, prepare to grab it 25155 if (near != null) { 25156 if (r.nodeIsGrabbable(near)) { 25157 var makeEvent = function makeEvent(type) { 25158 return { 25159 originalEvent: e, 25160 type: type, 25161 position: { 25162 x: pos[0], 25163 y: pos[1] 25164 } 25165 }; 25166 }; 25167 25168 var triggerGrab = function triggerGrab(ele) { 25169 ele.emit(makeEvent('grab')); 25170 }; 25171 25172 setGrabTarget(near); 25173 25174 if (!near.selected()) { 25175 draggedElements = r.dragData.possibleDragElements = cy.collection(); 25176 addNodeToDrag(near, { 25177 addToList: draggedElements 25178 }); 25179 near.emit(makeEvent('grabon')).emit(makeEvent('grab')); 25180 } else { 25181 draggedElements = r.dragData.possibleDragElements = cy.collection(); 25182 var selectedNodes = cy.$(function (ele) { 25183 return ele.isNode() && ele.selected() && r.nodeIsGrabbable(ele); 25184 }); 25185 addNodesToDrag(selectedNodes, { 25186 addToList: draggedElements 25187 }); 25188 near.emit(makeEvent('grabon')); 25189 selectedNodes.forEach(triggerGrab); 25190 } 25191 25192 r.redrawHint('eles', true); 25193 r.redrawHint('drag', true); 25194 } 25195 } 25196 25197 r.hoverData.down = near; 25198 r.hoverData.downs = nears; 25199 r.hoverData.downTime = new Date().getTime(); 25200 } 25201 triggerEvents(near, ['mousedown', 'tapstart', 'vmousedown'], e, { 25202 x: pos[0], 25203 y: pos[1] 25204 }); 25205 25206 if (near == null) { 25207 select[4] = 1; 25208 r.data.bgActivePosistion = { 25209 x: pos[0], 25210 y: pos[1] 25211 }; 25212 r.redrawHint('select', true); 25213 r.redraw(); 25214 } else if (near.pannable()) { 25215 select[4] = 1; // for future pan 25216 } 25217 25218 checkForTaphold(); 25219 } // Initialize selection box coordinates 25220 25221 25222 select[0] = select[2] = pos[0]; 25223 select[1] = select[3] = pos[1]; 25224 }, false); 25225 r.registerBinding(window, 'mousemove', function mousemoveHandler(e) { 25226 // eslint-disable-line no-undef 25227 var capture = r.hoverData.capture; 25228 25229 if (!capture && !eventInContainer(e)) { 25230 return; 25231 } 25232 25233 var preventDefault = false; 25234 var cy = r.cy; 25235 var zoom = cy.zoom(); 25236 var gpos = [e.clientX, e.clientY]; 25237 var pos = r.projectIntoViewport(gpos[0], gpos[1]); 25238 var mdownPos = r.hoverData.mdownPos; 25239 var mdownGPos = r.hoverData.mdownGPos; 25240 var select = r.selection; 25241 var near = null; 25242 25243 if (!r.hoverData.draggingEles && !r.hoverData.dragging && !r.hoverData.selecting) { 25244 near = r.findNearestElement(pos[0], pos[1], true, false); 25245 } 25246 25247 var last = r.hoverData.last; 25248 var down = r.hoverData.down; 25249 var disp = [pos[0] - select[2], pos[1] - select[3]]; 25250 var draggedElements = r.dragData.possibleDragElements; 25251 var isOverThresholdDrag; 25252 25253 if (mdownGPos) { 25254 var dx = gpos[0] - mdownGPos[0]; 25255 var dx2 = dx * dx; 25256 var dy = gpos[1] - mdownGPos[1]; 25257 var dy2 = dy * dy; 25258 var dist2 = dx2 + dy2; 25259 r.hoverData.isOverThresholdDrag = isOverThresholdDrag = dist2 >= r.desktopTapThreshold2; 25260 } 25261 25262 var multSelKeyDown = isMultSelKeyDown(e); 25263 25264 if (isOverThresholdDrag) { 25265 r.hoverData.tapholdCancelled = true; 25266 } 25267 25268 var updateDragDelta = function updateDragDelta() { 25269 var dragDelta = r.hoverData.dragDelta = r.hoverData.dragDelta || []; 25270 25271 if (dragDelta.length === 0) { 25272 dragDelta.push(disp[0]); 25273 dragDelta.push(disp[1]); 25274 } else { 25275 dragDelta[0] += disp[0]; 25276 dragDelta[1] += disp[1]; 25277 } 25278 }; 25279 25280 preventDefault = true; 25281 triggerEvents(near, ['mousemove', 'vmousemove', 'tapdrag'], e, { 25282 x: pos[0], 25283 y: pos[1] 25284 }); 25285 25286 var goIntoBoxMode = function goIntoBoxMode() { 25287 r.data.bgActivePosistion = undefined; 25288 25289 if (!r.hoverData.selecting) { 25290 cy.emit({ 25291 originalEvent: e, 25292 type: 'boxstart', 25293 position: { 25294 x: pos[0], 25295 y: pos[1] 25296 } 25297 }); 25298 } 25299 25300 select[4] = 1; 25301 r.hoverData.selecting = true; 25302 r.redrawHint('select', true); 25303 r.redraw(); 25304 }; // trigger context drag if rmouse down 25305 25306 25307 if (r.hoverData.which === 3) { 25308 // but only if over threshold 25309 if (isOverThresholdDrag) { 25310 var cxtEvt = { 25311 originalEvent: e, 25312 type: 'cxtdrag', 25313 position: { 25314 x: pos[0], 25315 y: pos[1] 25316 } 25317 }; 25318 25319 if (down) { 25320 down.emit(cxtEvt); 25321 } else { 25322 cy.emit(cxtEvt); 25323 } 25324 25325 r.hoverData.cxtDragged = true; 25326 25327 if (!r.hoverData.cxtOver || near !== r.hoverData.cxtOver) { 25328 if (r.hoverData.cxtOver) { 25329 r.hoverData.cxtOver.emit({ 25330 originalEvent: e, 25331 type: 'cxtdragout', 25332 position: { 25333 x: pos[0], 25334 y: pos[1] 25335 } 25336 }); 25337 } 25338 25339 r.hoverData.cxtOver = near; 25340 25341 if (near) { 25342 near.emit({ 25343 originalEvent: e, 25344 type: 'cxtdragover', 25345 position: { 25346 x: pos[0], 25347 y: pos[1] 25348 } 25349 }); 25350 } 25351 } 25352 } // Check if we are drag panning the entire graph 25353 25354 } else if (r.hoverData.dragging) { 25355 preventDefault = true; 25356 25357 if (cy.panningEnabled() && cy.userPanningEnabled()) { 25358 var deltaP; 25359 25360 if (r.hoverData.justStartedPan) { 25361 var mdPos = r.hoverData.mdownPos; 25362 deltaP = { 25363 x: (pos[0] - mdPos[0]) * zoom, 25364 y: (pos[1] - mdPos[1]) * zoom 25365 }; 25366 r.hoverData.justStartedPan = false; 25367 } else { 25368 deltaP = { 25369 x: disp[0] * zoom, 25370 y: disp[1] * zoom 25371 }; 25372 } 25373 25374 cy.panBy(deltaP); 25375 r.hoverData.dragged = true; 25376 } // Needs reproject due to pan changing viewport 25377 25378 25379 pos = r.projectIntoViewport(e.clientX, e.clientY); // Checks primary button down & out of time & mouse not moved much 25380 } else if (select[4] == 1 && (down == null || down.pannable())) { 25381 if (isOverThresholdDrag) { 25382 if (!r.hoverData.dragging && cy.boxSelectionEnabled() && (multSelKeyDown || !cy.panningEnabled() || !cy.userPanningEnabled())) { 25383 goIntoBoxMode(); 25384 } else if (!r.hoverData.selecting && cy.panningEnabled() && cy.userPanningEnabled()) { 25385 var allowPassthrough = allowPanningPassthrough(down, r.hoverData.downs); 25386 25387 if (allowPassthrough) { 25388 r.hoverData.dragging = true; 25389 r.hoverData.justStartedPan = true; 25390 select[4] = 0; 25391 r.data.bgActivePosistion = array2point(mdownPos); 25392 r.redrawHint('select', true); 25393 r.redraw(); 25394 } 25395 } 25396 25397 if (down && down.pannable() && down.active()) { 25398 down.unactivate(); 25399 } 25400 } 25401 } else { 25402 if (down && down.pannable() && down.active()) { 25403 down.unactivate(); 25404 } 25405 25406 if ((!down || !down.grabbed()) && near != last) { 25407 if (last) { 25408 triggerEvents(last, ['mouseout', 'tapdragout'], e, { 25409 x: pos[0], 25410 y: pos[1] 25411 }); 25412 } 25413 25414 if (near) { 25415 triggerEvents(near, ['mouseover', 'tapdragover'], e, { 25416 x: pos[0], 25417 y: pos[1] 25418 }); 25419 } 25420 25421 r.hoverData.last = near; 25422 } 25423 25424 if (down) { 25425 if (isOverThresholdDrag) { 25426 // then we can take action 25427 if (cy.boxSelectionEnabled() && multSelKeyDown) { 25428 // then selection overrides 25429 if (down && down.grabbed()) { 25430 freeDraggedElements(draggedElements); 25431 down.emit('freeon'); 25432 draggedElements.emit('free'); 25433 25434 if (r.dragData.didDrag) { 25435 down.emit('dragfreeon'); 25436 draggedElements.emit('dragfree'); 25437 } 25438 } 25439 25440 goIntoBoxMode(); 25441 } else if (down && down.grabbed() && r.nodeIsDraggable(down)) { 25442 // drag node 25443 var justStartedDrag = !r.dragData.didDrag; 25444 25445 if (justStartedDrag) { 25446 r.redrawHint('eles', true); 25447 } 25448 25449 r.dragData.didDrag = true; // indicate that we actually did drag the node 25450 25451 var toTrigger = cy.collection(); // now, add the elements to the drag layer if not done already 25452 25453 if (!r.hoverData.draggingEles) { 25454 addNodesToDrag(draggedElements, { 25455 inDragLayer: true 25456 }); 25457 } 25458 25459 var totalShift = { 25460 x: 0, 25461 y: 0 25462 }; 25463 25464 if (number(disp[0]) && number(disp[1])) { 25465 totalShift.x += disp[0]; 25466 totalShift.y += disp[1]; 25467 25468 if (justStartedDrag) { 25469 var dragDelta = r.hoverData.dragDelta; 25470 25471 if (dragDelta && number(dragDelta[0]) && number(dragDelta[1])) { 25472 totalShift.x += dragDelta[0]; 25473 totalShift.y += dragDelta[1]; 25474 } 25475 } 25476 } 25477 25478 for (var i = 0; i < draggedElements.length; i++) { 25479 var dEle = draggedElements[i]; 25480 25481 if (r.nodeIsDraggable(dEle) && dEle.grabbed()) { 25482 toTrigger.merge(dEle); 25483 } 25484 } 25485 25486 r.hoverData.draggingEles = true; 25487 toTrigger.silentShift(totalShift).emit('position drag'); 25488 r.redrawHint('drag', true); 25489 r.redraw(); 25490 } 25491 } else { 25492 // otherwise save drag delta for when we actually start dragging so the relative grab pos is constant 25493 updateDragDelta(); 25494 } 25495 } // prevent the dragging from triggering text selection on the page 25496 25497 25498 preventDefault = true; 25499 } 25500 25501 select[2] = pos[0]; 25502 select[3] = pos[1]; 25503 25504 if (preventDefault) { 25505 if (e.stopPropagation) e.stopPropagation(); 25506 if (e.preventDefault) e.preventDefault(); 25507 return false; 25508 } 25509 }, false); 25510 r.registerBinding(window, 'mouseup', function mouseupHandler(e) { 25511 // eslint-disable-line no-undef 25512 var capture = r.hoverData.capture; 25513 25514 if (!capture) { 25515 return; 25516 } 25517 25518 r.hoverData.capture = false; 25519 var cy = r.cy; 25520 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25521 var select = r.selection; 25522 var near = r.findNearestElement(pos[0], pos[1], true, false); 25523 var draggedElements = r.dragData.possibleDragElements; 25524 var down = r.hoverData.down; 25525 var multSelKeyDown = isMultSelKeyDown(e); 25526 25527 if (r.data.bgActivePosistion) { 25528 r.redrawHint('select', true); 25529 r.redraw(); 25530 } 25531 25532 r.hoverData.tapholdCancelled = true; 25533 r.data.bgActivePosistion = undefined; // not active bg now 25534 25535 if (down) { 25536 down.unactivate(); 25537 } 25538 25539 if (r.hoverData.which === 3) { 25540 var cxtEvt = { 25541 originalEvent: e, 25542 type: 'cxttapend', 25543 position: { 25544 x: pos[0], 25545 y: pos[1] 25546 } 25547 }; 25548 25549 if (down) { 25550 down.emit(cxtEvt); 25551 } else { 25552 cy.emit(cxtEvt); 25553 } 25554 25555 if (!r.hoverData.cxtDragged) { 25556 var cxtTap = { 25557 originalEvent: e, 25558 type: 'cxttap', 25559 position: { 25560 x: pos[0], 25561 y: pos[1] 25562 } 25563 }; 25564 25565 if (down) { 25566 down.emit(cxtTap); 25567 } else { 25568 cy.emit(cxtTap); 25569 } 25570 } 25571 25572 r.hoverData.cxtDragged = false; 25573 r.hoverData.which = null; 25574 } else if (r.hoverData.which === 1) { 25575 triggerEvents(near, ['mouseup', 'tapend', 'vmouseup'], e, { 25576 x: pos[0], 25577 y: pos[1] 25578 }); 25579 25580 if (!r.dragData.didDrag // didn't move a node around 25581 && !r.hoverData.dragged // didn't pan 25582 && !r.hoverData.selecting // not box selection 25583 && !r.hoverData.isOverThresholdDrag // didn't move too much 25584 ) { 25585 triggerEvents(down, ['click', 'tap', 'vclick'], e, { 25586 x: pos[0], 25587 y: pos[1] 25588 }); 25589 } // Deselect all elements if nothing is currently under the mouse cursor and we aren't dragging something 25590 25591 25592 if (down == null && // not mousedown on node 25593 !r.dragData.didDrag // didn't move the node around 25594 && !r.hoverData.selecting // not box selection 25595 && !r.hoverData.dragged // didn't pan 25596 && !isMultSelKeyDown(e)) { 25597 cy.$(isSelected).unselect(['tapunselect']); 25598 25599 if (draggedElements.length > 0) { 25600 r.redrawHint('eles', true); 25601 } 25602 25603 r.dragData.possibleDragElements = draggedElements = cy.collection(); 25604 } // Single selection 25605 25606 25607 if (near == down && !r.dragData.didDrag && !r.hoverData.selecting) { 25608 if (near != null && near._private.selectable) { 25609 if (r.hoverData.dragging) ; else if (cy.selectionType() === 'additive' || multSelKeyDown) { 25610 if (near.selected()) { 25611 near.unselect(['tapunselect']); 25612 } else { 25613 near.select(['tapselect']); 25614 } 25615 } else { 25616 if (!multSelKeyDown) { 25617 cy.$(isSelected).unmerge(near).unselect(['tapunselect']); 25618 near.select(['tapselect']); 25619 } 25620 } 25621 25622 r.redrawHint('eles', true); 25623 } 25624 } 25625 25626 if (r.hoverData.selecting) { 25627 var box = cy.collection(r.getAllInBox(select[0], select[1], select[2], select[3])); 25628 r.redrawHint('select', true); 25629 25630 if (box.length > 0) { 25631 r.redrawHint('eles', true); 25632 } 25633 25634 cy.emit({ 25635 type: 'boxend', 25636 originalEvent: e, 25637 position: { 25638 x: pos[0], 25639 y: pos[1] 25640 } 25641 }); 25642 25643 var eleWouldBeSelected = function eleWouldBeSelected(ele) { 25644 return ele.selectable() && !ele.selected(); 25645 }; 25646 25647 if (cy.selectionType() === 'additive') { 25648 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 25649 } else { 25650 if (!multSelKeyDown) { 25651 cy.$(isSelected).unmerge(box).unselect(); 25652 } 25653 25654 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 25655 } // always need redraw in case eles unselectable 25656 25657 25658 r.redraw(); 25659 } // Cancel drag pan 25660 25661 25662 if (r.hoverData.dragging) { 25663 r.hoverData.dragging = false; 25664 r.redrawHint('select', true); 25665 r.redrawHint('eles', true); 25666 r.redraw(); 25667 } 25668 25669 if (!select[4]) { 25670 r.redrawHint('drag', true); 25671 r.redrawHint('eles', true); 25672 var downWasGrabbed = down && down.grabbed(); 25673 freeDraggedElements(draggedElements); 25674 25675 if (downWasGrabbed) { 25676 down.emit('freeon'); 25677 draggedElements.emit('free'); 25678 25679 if (r.dragData.didDrag) { 25680 down.emit('dragfreeon'); 25681 draggedElements.emit('dragfree'); 25682 } 25683 } 25684 } 25685 } // else not right mouse 25686 25687 25688 select[4] = 0; 25689 r.hoverData.down = null; 25690 r.hoverData.cxtStarted = false; 25691 r.hoverData.draggingEles = false; 25692 r.hoverData.selecting = false; 25693 r.hoverData.isOverThresholdDrag = false; 25694 r.dragData.didDrag = false; 25695 r.hoverData.dragged = false; 25696 r.hoverData.dragDelta = []; 25697 r.hoverData.mdownPos = null; 25698 r.hoverData.mdownGPos = null; 25699 }, false); 25700 25701 var wheelHandler = function wheelHandler(e) { 25702 if (r.scrollingPage) { 25703 return; 25704 } // while scrolling, ignore wheel-to-zoom 25705 25706 25707 var cy = r.cy; 25708 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25709 var rpos = [pos[0] * cy.zoom() + cy.pan().x, pos[1] * cy.zoom() + cy.pan().y]; 25710 25711 if (r.hoverData.draggingEles || r.hoverData.dragging || r.hoverData.cxtStarted || inBoxSelection()) { 25712 // if pan dragging or cxt dragging, wheel movements make no zoom 25713 e.preventDefault(); 25714 return; 25715 } 25716 25717 if (cy.panningEnabled() && cy.userPanningEnabled() && cy.zoomingEnabled() && cy.userZoomingEnabled()) { 25718 e.preventDefault(); 25719 r.data.wheelZooming = true; 25720 clearTimeout(r.data.wheelTimeout); 25721 r.data.wheelTimeout = setTimeout(function () { 25722 r.data.wheelZooming = false; 25723 r.redrawHint('eles', true); 25724 r.redraw(); 25725 }, 150); 25726 var diff; 25727 25728 if (e.deltaY != null) { 25729 diff = e.deltaY / -250; 25730 } else if (e.wheelDeltaY != null) { 25731 diff = e.wheelDeltaY / 1000; 25732 } else { 25733 diff = e.wheelDelta / 1000; 25734 } 25735 25736 diff = diff * r.wheelSensitivity; 25737 var needsWheelFix = e.deltaMode === 1; 25738 25739 if (needsWheelFix) { 25740 // fixes slow wheel events on ff/linux and ff/windows 25741 diff *= 33; 25742 } 25743 25744 cy.zoom({ 25745 level: cy.zoom() * Math.pow(10, diff), 25746 renderedPosition: { 25747 x: rpos[0], 25748 y: rpos[1] 25749 } 25750 }); 25751 } 25752 }; // Functions to help with whether mouse wheel should trigger zooming 25753 // -- 25754 25755 25756 r.registerBinding(r.container, 'wheel', wheelHandler, true); // disable nonstandard wheel events 25757 // r.registerBinding(r.container, 'mousewheel', wheelHandler, true); 25758 // r.registerBinding(r.container, 'DOMMouseScroll', wheelHandler, true); 25759 // r.registerBinding(r.container, 'MozMousePixelScroll', wheelHandler, true); // older firefox 25760 25761 r.registerBinding(window, 'scroll', function scrollHandler(e) { 25762 // eslint-disable-line no-unused-vars 25763 r.scrollingPage = true; 25764 clearTimeout(r.scrollingPageTimeout); 25765 r.scrollingPageTimeout = setTimeout(function () { 25766 r.scrollingPage = false; 25767 }, 250); 25768 }, true); // Functions to help with handling mouseout/mouseover on the Cytoscape container 25769 // Handle mouseout on Cytoscape container 25770 25771 r.registerBinding(r.container, 'mouseout', function mouseOutHandler(e) { 25772 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25773 r.cy.emit({ 25774 originalEvent: e, 25775 type: 'mouseout', 25776 position: { 25777 x: pos[0], 25778 y: pos[1] 25779 } 25780 }); 25781 }, false); 25782 r.registerBinding(r.container, 'mouseover', function mouseOverHandler(e) { 25783 var pos = r.projectIntoViewport(e.clientX, e.clientY); 25784 r.cy.emit({ 25785 originalEvent: e, 25786 type: 'mouseover', 25787 position: { 25788 x: pos[0], 25789 y: pos[1] 25790 } 25791 }); 25792 }, false); 25793 var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom 25794 25795 var distance1, distance1Sq; // initial distance between finger 1 and finger 2 for pinch-to-zoom 25796 25797 var center1, modelCenter1; // center point on start pinch to zoom 25798 25799 var offsetLeft, offsetTop; 25800 var containerWidth, containerHeight; 25801 var twoFingersStartInside; 25802 25803 var distance = function distance(x1, y1, x2, y2) { 25804 return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); 25805 }; 25806 25807 var distanceSq = function distanceSq(x1, y1, x2, y2) { 25808 return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); 25809 }; 25810 25811 var touchstartHandler; 25812 r.registerBinding(r.container, 'touchstart', touchstartHandler = function touchstartHandler(e) { 25813 if (!eventInContainer(e)) { 25814 return; 25815 } 25816 25817 blurActiveDomElement(); 25818 r.touchData.capture = true; 25819 r.data.bgActivePosistion = undefined; 25820 var cy = r.cy; 25821 var now = r.touchData.now; 25822 var earlier = r.touchData.earlier; 25823 25824 if (e.touches[0]) { 25825 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 25826 now[0] = pos[0]; 25827 now[1] = pos[1]; 25828 } 25829 25830 if (e.touches[1]) { 25831 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 25832 now[2] = pos[0]; 25833 now[3] = pos[1]; 25834 } 25835 25836 if (e.touches[2]) { 25837 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 25838 now[4] = pos[0]; 25839 now[5] = pos[1]; 25840 } // record starting points for pinch-to-zoom 25841 25842 25843 if (e.touches[1]) { 25844 r.touchData.singleTouchMoved = true; 25845 freeDraggedElements(r.dragData.touchDragEles); 25846 var offsets = r.findContainerClientCoords(); 25847 offsetLeft = offsets[0]; 25848 offsetTop = offsets[1]; 25849 containerWidth = offsets[2]; 25850 containerHeight = offsets[3]; 25851 f1x1 = e.touches[0].clientX - offsetLeft; 25852 f1y1 = e.touches[0].clientY - offsetTop; 25853 f2x1 = e.touches[1].clientX - offsetLeft; 25854 f2y1 = e.touches[1].clientY - offsetTop; 25855 twoFingersStartInside = 0 <= f1x1 && f1x1 <= containerWidth && 0 <= f2x1 && f2x1 <= containerWidth && 0 <= f1y1 && f1y1 <= containerHeight && 0 <= f2y1 && f2y1 <= containerHeight; 25856 var pan = cy.pan(); 25857 var zoom = cy.zoom(); 25858 distance1 = distance(f1x1, f1y1, f2x1, f2y1); 25859 distance1Sq = distanceSq(f1x1, f1y1, f2x1, f2y1); 25860 center1 = [(f1x1 + f2x1) / 2, (f1y1 + f2y1) / 2]; 25861 modelCenter1 = [(center1[0] - pan.x) / zoom, (center1[1] - pan.y) / zoom]; // consider context tap 25862 25863 var cxtDistThreshold = 200; 25864 var cxtDistThresholdSq = cxtDistThreshold * cxtDistThreshold; 25865 25866 if (distance1Sq < cxtDistThresholdSq && !e.touches[2]) { 25867 var near1 = r.findNearestElement(now[0], now[1], true, true); 25868 var near2 = r.findNearestElement(now[2], now[3], true, true); 25869 25870 if (near1 && near1.isNode()) { 25871 near1.activate().emit({ 25872 originalEvent: e, 25873 type: 'cxttapstart', 25874 position: { 25875 x: now[0], 25876 y: now[1] 25877 } 25878 }); 25879 r.touchData.start = near1; 25880 } else if (near2 && near2.isNode()) { 25881 near2.activate().emit({ 25882 originalEvent: e, 25883 type: 'cxttapstart', 25884 position: { 25885 x: now[0], 25886 y: now[1] 25887 } 25888 }); 25889 r.touchData.start = near2; 25890 } else { 25891 cy.emit({ 25892 originalEvent: e, 25893 type: 'cxttapstart', 25894 position: { 25895 x: now[0], 25896 y: now[1] 25897 } 25898 }); 25899 } 25900 25901 if (r.touchData.start) { 25902 r.touchData.start._private.grabbed = false; 25903 } 25904 25905 r.touchData.cxt = true; 25906 r.touchData.cxtDragged = false; 25907 r.data.bgActivePosistion = undefined; 25908 r.redraw(); 25909 return; 25910 } 25911 } 25912 25913 if (e.touches[2]) { 25914 // ignore 25915 // safari on ios pans the page otherwise (normally you should be able to preventdefault on touchmove...) 25916 if (cy.boxSelectionEnabled()) { 25917 e.preventDefault(); 25918 } 25919 } else if (e.touches[1]) ; else if (e.touches[0]) { 25920 var nears = r.findNearestElements(now[0], now[1], true, true); 25921 var near = nears[0]; 25922 25923 if (near != null) { 25924 near.activate(); 25925 r.touchData.start = near; 25926 r.touchData.starts = nears; 25927 25928 if (r.nodeIsGrabbable(near)) { 25929 var draggedEles = r.dragData.touchDragEles = cy.collection(); 25930 var selectedNodes = null; 25931 r.redrawHint('eles', true); 25932 r.redrawHint('drag', true); 25933 25934 if (near.selected()) { 25935 // reset drag elements, since near will be added again 25936 selectedNodes = cy.$(function (ele) { 25937 return ele.selected() && r.nodeIsGrabbable(ele); 25938 }); 25939 addNodesToDrag(selectedNodes, { 25940 addToList: draggedEles 25941 }); 25942 } else { 25943 addNodeToDrag(near, { 25944 addToList: draggedEles 25945 }); 25946 } 25947 25948 setGrabTarget(near); 25949 25950 var makeEvent = function makeEvent(type) { 25951 return { 25952 originalEvent: e, 25953 type: type, 25954 position: { 25955 x: now[0], 25956 y: now[1] 25957 } 25958 }; 25959 }; 25960 25961 near.emit(makeEvent('grabon')); 25962 25963 if (selectedNodes) { 25964 selectedNodes.forEach(function (n) { 25965 n.emit(makeEvent('grab')); 25966 }); 25967 } else { 25968 near.emit(makeEvent('grab')); 25969 } 25970 } 25971 } 25972 25973 triggerEvents(near, ['touchstart', 'tapstart', 'vmousedown'], e, { 25974 x: now[0], 25975 y: now[1] 25976 }); 25977 25978 if (near == null) { 25979 r.data.bgActivePosistion = { 25980 x: pos[0], 25981 y: pos[1] 25982 }; 25983 r.redrawHint('select', true); 25984 r.redraw(); 25985 } // Tap, taphold 25986 // ----- 25987 25988 25989 r.touchData.singleTouchMoved = false; 25990 r.touchData.singleTouchStartTime = +new Date(); 25991 clearTimeout(r.touchData.tapholdTimeout); 25992 r.touchData.tapholdTimeout = setTimeout(function () { 25993 if (r.touchData.singleTouchMoved === false && !r.pinching // if pinching, then taphold unselect shouldn't take effect 25994 && !r.touchData.selecting // box selection shouldn't allow taphold through 25995 ) { 25996 triggerEvents(r.touchData.start, ['taphold'], e, { 25997 x: now[0], 25998 y: now[1] 25999 }); 26000 } 26001 }, r.tapholdDuration); 26002 } 26003 26004 if (e.touches.length >= 1) { 26005 var sPos = r.touchData.startPosition = []; 26006 26007 for (var i = 0; i < now.length; i++) { 26008 sPos[i] = earlier[i] = now[i]; 26009 } 26010 26011 var touch0 = e.touches[0]; 26012 r.touchData.startGPosition = [touch0.clientX, touch0.clientY]; 26013 } 26014 }, false); 26015 var touchmoveHandler; 26016 r.registerBinding(window, 'touchmove', touchmoveHandler = function touchmoveHandler(e) { 26017 // eslint-disable-line no-undef 26018 var capture = r.touchData.capture; 26019 26020 if (!capture && !eventInContainer(e)) { 26021 return; 26022 } 26023 26024 var select = r.selection; 26025 var cy = r.cy; 26026 var now = r.touchData.now; 26027 var earlier = r.touchData.earlier; 26028 var zoom = cy.zoom(); 26029 26030 if (e.touches[0]) { 26031 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 26032 now[0] = pos[0]; 26033 now[1] = pos[1]; 26034 } 26035 26036 if (e.touches[1]) { 26037 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 26038 now[2] = pos[0]; 26039 now[3] = pos[1]; 26040 } 26041 26042 if (e.touches[2]) { 26043 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 26044 now[4] = pos[0]; 26045 now[5] = pos[1]; 26046 } 26047 26048 var startGPos = r.touchData.startGPosition; 26049 var isOverThresholdDrag; 26050 26051 if (capture && e.touches[0] && startGPos) { 26052 var disp = []; 26053 26054 for (var j = 0; j < now.length; j++) { 26055 disp[j] = now[j] - earlier[j]; 26056 } 26057 26058 var dx = e.touches[0].clientX - startGPos[0]; 26059 var dx2 = dx * dx; 26060 var dy = e.touches[0].clientY - startGPos[1]; 26061 var dy2 = dy * dy; 26062 var dist2 = dx2 + dy2; 26063 isOverThresholdDrag = dist2 >= r.touchTapThreshold2; 26064 } // context swipe cancelling 26065 26066 26067 if (capture && r.touchData.cxt) { 26068 e.preventDefault(); 26069 var f1x2 = e.touches[0].clientX - offsetLeft, 26070 f1y2 = e.touches[0].clientY - offsetTop; 26071 var f2x2 = e.touches[1].clientX - offsetLeft, 26072 f2y2 = e.touches[1].clientY - offsetTop; // var distance2 = distance( f1x2, f1y2, f2x2, f2y2 ); 26073 26074 var distance2Sq = distanceSq(f1x2, f1y2, f2x2, f2y2); 26075 var factorSq = distance2Sq / distance1Sq; 26076 var distThreshold = 150; 26077 var distThresholdSq = distThreshold * distThreshold; 26078 var factorThreshold = 1.5; 26079 var factorThresholdSq = factorThreshold * factorThreshold; // cancel ctx gestures if the distance b/t the fingers increases 26080 26081 if (factorSq >= factorThresholdSq || distance2Sq >= distThresholdSq) { 26082 r.touchData.cxt = false; 26083 r.data.bgActivePosistion = undefined; 26084 r.redrawHint('select', true); 26085 var cxtEvt = { 26086 originalEvent: e, 26087 type: 'cxttapend', 26088 position: { 26089 x: now[0], 26090 y: now[1] 26091 } 26092 }; 26093 26094 if (r.touchData.start) { 26095 r.touchData.start.unactivate().emit(cxtEvt); 26096 r.touchData.start = null; 26097 } else { 26098 cy.emit(cxtEvt); 26099 } 26100 } 26101 } // context swipe 26102 26103 26104 if (capture && r.touchData.cxt) { 26105 var cxtEvt = { 26106 originalEvent: e, 26107 type: 'cxtdrag', 26108 position: { 26109 x: now[0], 26110 y: now[1] 26111 } 26112 }; 26113 r.data.bgActivePosistion = undefined; 26114 r.redrawHint('select', true); 26115 26116 if (r.touchData.start) { 26117 r.touchData.start.emit(cxtEvt); 26118 } else { 26119 cy.emit(cxtEvt); 26120 } 26121 26122 if (r.touchData.start) { 26123 r.touchData.start._private.grabbed = false; 26124 } 26125 26126 r.touchData.cxtDragged = true; 26127 var near = r.findNearestElement(now[0], now[1], true, true); 26128 26129 if (!r.touchData.cxtOver || near !== r.touchData.cxtOver) { 26130 if (r.touchData.cxtOver) { 26131 r.touchData.cxtOver.emit({ 26132 originalEvent: e, 26133 type: 'cxtdragout', 26134 position: { 26135 x: now[0], 26136 y: now[1] 26137 } 26138 }); 26139 } 26140 26141 r.touchData.cxtOver = near; 26142 26143 if (near) { 26144 near.emit({ 26145 originalEvent: e, 26146 type: 'cxtdragover', 26147 position: { 26148 x: now[0], 26149 y: now[1] 26150 } 26151 }); 26152 } 26153 } // box selection 26154 26155 } else if (capture && e.touches[2] && cy.boxSelectionEnabled()) { 26156 e.preventDefault(); 26157 r.data.bgActivePosistion = undefined; 26158 this.lastThreeTouch = +new Date(); 26159 26160 if (!r.touchData.selecting) { 26161 cy.emit({ 26162 originalEvent: e, 26163 type: 'boxstart', 26164 position: { 26165 x: now[0], 26166 y: now[1] 26167 } 26168 }); 26169 } 26170 26171 r.touchData.selecting = true; 26172 r.touchData.didSelect = true; 26173 select[4] = 1; 26174 26175 if (!select || select.length === 0 || select[0] === undefined) { 26176 select[0] = (now[0] + now[2] + now[4]) / 3; 26177 select[1] = (now[1] + now[3] + now[5]) / 3; 26178 select[2] = (now[0] + now[2] + now[4]) / 3 + 1; 26179 select[3] = (now[1] + now[3] + now[5]) / 3 + 1; 26180 } else { 26181 select[2] = (now[0] + now[2] + now[4]) / 3; 26182 select[3] = (now[1] + now[3] + now[5]) / 3; 26183 } 26184 26185 r.redrawHint('select', true); 26186 r.redraw(); // pinch to zoom 26187 } else if (capture && e.touches[1] && !r.touchData.didSelect // don't allow box selection to degrade to pinch-to-zoom 26188 && cy.zoomingEnabled() && cy.panningEnabled() && cy.userZoomingEnabled() && cy.userPanningEnabled()) { 26189 // two fingers => pinch to zoom 26190 e.preventDefault(); 26191 r.data.bgActivePosistion = undefined; 26192 r.redrawHint('select', true); 26193 var draggedEles = r.dragData.touchDragEles; 26194 26195 if (draggedEles) { 26196 r.redrawHint('drag', true); 26197 26198 for (var i = 0; i < draggedEles.length; i++) { 26199 var de_p = draggedEles[i]._private; 26200 de_p.grabbed = false; 26201 de_p.rscratch.inDragLayer = false; 26202 } 26203 } 26204 26205 var _start = r.touchData.start; // (x2, y2) for fingers 1 and 2 26206 26207 var f1x2 = e.touches[0].clientX - offsetLeft, 26208 f1y2 = e.touches[0].clientY - offsetTop; 26209 var f2x2 = e.touches[1].clientX - offsetLeft, 26210 f2y2 = e.touches[1].clientY - offsetTop; 26211 var distance2 = distance(f1x2, f1y2, f2x2, f2y2); // var distance2Sq = distanceSq( f1x2, f1y2, f2x2, f2y2 ); 26212 // var factor = Math.sqrt( distance2Sq ) / Math.sqrt( distance1Sq ); 26213 26214 var factor = distance2 / distance1; 26215 26216 if (twoFingersStartInside) { 26217 // delta finger1 26218 var df1x = f1x2 - f1x1; 26219 var df1y = f1y2 - f1y1; // delta finger 2 26220 26221 var df2x = f2x2 - f2x1; 26222 var df2y = f2y2 - f2y1; // translation is the normalised vector of the two fingers movement 26223 // i.e. so pinching cancels out and moving together pans 26224 26225 var tx = (df1x + df2x) / 2; 26226 var ty = (df1y + df2y) / 2; // now calculate the zoom 26227 26228 var zoom1 = cy.zoom(); 26229 var zoom2 = zoom1 * factor; 26230 var pan1 = cy.pan(); // the model center point converted to the current rendered pos 26231 26232 var ctrx = modelCenter1[0] * zoom1 + pan1.x; 26233 var ctry = modelCenter1[1] * zoom1 + pan1.y; 26234 var pan2 = { 26235 x: -zoom2 / zoom1 * (ctrx - pan1.x - tx) + ctrx, 26236 y: -zoom2 / zoom1 * (ctry - pan1.y - ty) + ctry 26237 }; // remove dragged eles 26238 26239 if (_start && _start.active()) { 26240 var draggedEles = r.dragData.touchDragEles; 26241 freeDraggedElements(draggedEles); 26242 r.redrawHint('drag', true); 26243 r.redrawHint('eles', true); 26244 26245 _start.unactivate().emit('freeon'); 26246 26247 draggedEles.emit('free'); 26248 26249 if (r.dragData.didDrag) { 26250 _start.emit('dragfreeon'); 26251 26252 draggedEles.emit('dragfree'); 26253 } 26254 } 26255 26256 cy.viewport({ 26257 zoom: zoom2, 26258 pan: pan2, 26259 cancelOnFailedZoom: true 26260 }); 26261 distance1 = distance2; 26262 f1x1 = f1x2; 26263 f1y1 = f1y2; 26264 f2x1 = f2x2; 26265 f2y1 = f2y2; 26266 r.pinching = true; 26267 } // Re-project 26268 26269 26270 if (e.touches[0]) { 26271 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 26272 now[0] = pos[0]; 26273 now[1] = pos[1]; 26274 } 26275 26276 if (e.touches[1]) { 26277 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 26278 now[2] = pos[0]; 26279 now[3] = pos[1]; 26280 } 26281 26282 if (e.touches[2]) { 26283 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 26284 now[4] = pos[0]; 26285 now[5] = pos[1]; 26286 } 26287 } else if (e.touches[0] && !r.touchData.didSelect // don't allow box selection to degrade to single finger events like panning 26288 ) { 26289 var start = r.touchData.start; 26290 var last = r.touchData.last; 26291 var near; 26292 26293 if (!r.hoverData.draggingEles && !r.swipePanning) { 26294 near = r.findNearestElement(now[0], now[1], true, true); 26295 } 26296 26297 if (capture && start != null) { 26298 e.preventDefault(); 26299 } // dragging nodes 26300 26301 26302 if (capture && start != null && r.nodeIsDraggable(start)) { 26303 if (isOverThresholdDrag) { 26304 // then dragging can happen 26305 var draggedEles = r.dragData.touchDragEles; 26306 var justStartedDrag = !r.dragData.didDrag; 26307 26308 if (justStartedDrag) { 26309 addNodesToDrag(draggedEles, { 26310 inDragLayer: true 26311 }); 26312 } 26313 26314 r.dragData.didDrag = true; 26315 var totalShift = { 26316 x: 0, 26317 y: 0 26318 }; 26319 26320 if (number(disp[0]) && number(disp[1])) { 26321 totalShift.x += disp[0]; 26322 totalShift.y += disp[1]; 26323 26324 if (justStartedDrag) { 26325 r.redrawHint('eles', true); 26326 var dragDelta = r.touchData.dragDelta; 26327 26328 if (dragDelta && number(dragDelta[0]) && number(dragDelta[1])) { 26329 totalShift.x += dragDelta[0]; 26330 totalShift.y += dragDelta[1]; 26331 } 26332 } 26333 } 26334 26335 r.hoverData.draggingEles = true; 26336 draggedEles.silentShift(totalShift).emit('position drag'); 26337 r.redrawHint('drag', true); 26338 26339 if (r.touchData.startPosition[0] == earlier[0] && r.touchData.startPosition[1] == earlier[1]) { 26340 r.redrawHint('eles', true); 26341 } 26342 26343 r.redraw(); 26344 } else { 26345 // otherise keep track of drag delta for later 26346 var dragDelta = r.touchData.dragDelta = r.touchData.dragDelta || []; 26347 26348 if (dragDelta.length === 0) { 26349 dragDelta.push(disp[0]); 26350 dragDelta.push(disp[1]); 26351 } else { 26352 dragDelta[0] += disp[0]; 26353 dragDelta[1] += disp[1]; 26354 } 26355 } 26356 } // touchmove 26357 26358 26359 { 26360 triggerEvents(start || near, ['touchmove', 'tapdrag', 'vmousemove'], e, { 26361 x: now[0], 26362 y: now[1] 26363 }); 26364 26365 if ((!start || !start.grabbed()) && near != last) { 26366 if (last) { 26367 last.emit({ 26368 originalEvent: e, 26369 type: 'tapdragout', 26370 position: { 26371 x: now[0], 26372 y: now[1] 26373 } 26374 }); 26375 } 26376 26377 if (near) { 26378 near.emit({ 26379 originalEvent: e, 26380 type: 'tapdragover', 26381 position: { 26382 x: now[0], 26383 y: now[1] 26384 } 26385 }); 26386 } 26387 } 26388 26389 r.touchData.last = near; 26390 } // check to cancel taphold 26391 26392 if (capture) { 26393 for (var i = 0; i < now.length; i++) { 26394 if (now[i] && r.touchData.startPosition[i] && isOverThresholdDrag) { 26395 r.touchData.singleTouchMoved = true; 26396 } 26397 } 26398 } // panning 26399 26400 26401 if (capture && (start == null || start.pannable()) && cy.panningEnabled() && cy.userPanningEnabled()) { 26402 var allowPassthrough = allowPanningPassthrough(start, r.touchData.starts); 26403 26404 if (allowPassthrough) { 26405 e.preventDefault(); 26406 26407 if (!r.data.bgActivePosistion) { 26408 r.data.bgActivePosistion = array2point(r.touchData.startPosition); 26409 } 26410 26411 if (r.swipePanning) { 26412 cy.panBy({ 26413 x: disp[0] * zoom, 26414 y: disp[1] * zoom 26415 }); 26416 } else if (isOverThresholdDrag) { 26417 r.swipePanning = true; 26418 cy.panBy({ 26419 x: dx * zoom, 26420 y: dy * zoom 26421 }); 26422 26423 if (start) { 26424 start.unactivate(); 26425 r.redrawHint('select', true); 26426 r.touchData.start = null; 26427 } 26428 } 26429 } // Re-project 26430 26431 26432 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 26433 now[0] = pos[0]; 26434 now[1] = pos[1]; 26435 } 26436 } 26437 26438 for (var j = 0; j < now.length; j++) { 26439 earlier[j] = now[j]; 26440 } // the active bg indicator should be removed when making a swipe that is neither for dragging nodes or panning 26441 26442 26443 if (capture && e.touches.length > 0 && !r.hoverData.draggingEles && !r.swipePanning && r.data.bgActivePosistion != null) { 26444 r.data.bgActivePosistion = undefined; 26445 r.redrawHint('select', true); 26446 r.redraw(); 26447 } 26448 }, false); 26449 var touchcancelHandler; 26450 r.registerBinding(window, 'touchcancel', touchcancelHandler = function touchcancelHandler(e) { 26451 // eslint-disable-line no-unused-vars 26452 var start = r.touchData.start; 26453 r.touchData.capture = false; 26454 26455 if (start) { 26456 start.unactivate(); 26457 } 26458 }); 26459 var touchendHandler; 26460 r.registerBinding(window, 'touchend', touchendHandler = function touchendHandler(e) { 26461 // eslint-disable-line no-unused-vars 26462 var start = r.touchData.start; 26463 var capture = r.touchData.capture; 26464 26465 if (capture) { 26466 if (e.touches.length === 0) { 26467 r.touchData.capture = false; 26468 } 26469 26470 e.preventDefault(); 26471 } else { 26472 return; 26473 } 26474 26475 var select = r.selection; 26476 r.swipePanning = false; 26477 r.hoverData.draggingEles = false; 26478 var cy = r.cy; 26479 var zoom = cy.zoom(); 26480 var now = r.touchData.now; 26481 var earlier = r.touchData.earlier; 26482 26483 if (e.touches[0]) { 26484 var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); 26485 now[0] = pos[0]; 26486 now[1] = pos[1]; 26487 } 26488 26489 if (e.touches[1]) { 26490 var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); 26491 now[2] = pos[0]; 26492 now[3] = pos[1]; 26493 } 26494 26495 if (e.touches[2]) { 26496 var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); 26497 now[4] = pos[0]; 26498 now[5] = pos[1]; 26499 } 26500 26501 if (start) { 26502 start.unactivate(); 26503 } 26504 26505 var ctxTapend; 26506 26507 if (r.touchData.cxt) { 26508 ctxTapend = { 26509 originalEvent: e, 26510 type: 'cxttapend', 26511 position: { 26512 x: now[0], 26513 y: now[1] 26514 } 26515 }; 26516 26517 if (start) { 26518 start.emit(ctxTapend); 26519 } else { 26520 cy.emit(ctxTapend); 26521 } 26522 26523 if (!r.touchData.cxtDragged) { 26524 var ctxTap = { 26525 originalEvent: e, 26526 type: 'cxttap', 26527 position: { 26528 x: now[0], 26529 y: now[1] 26530 } 26531 }; 26532 26533 if (start) { 26534 start.emit(ctxTap); 26535 } else { 26536 cy.emit(ctxTap); 26537 } 26538 } 26539 26540 if (r.touchData.start) { 26541 r.touchData.start._private.grabbed = false; 26542 } 26543 26544 r.touchData.cxt = false; 26545 r.touchData.start = null; 26546 r.redraw(); 26547 return; 26548 } // no more box selection if we don't have three fingers 26549 26550 26551 if (!e.touches[2] && cy.boxSelectionEnabled() && r.touchData.selecting) { 26552 r.touchData.selecting = false; 26553 var box = cy.collection(r.getAllInBox(select[0], select[1], select[2], select[3])); 26554 select[0] = undefined; 26555 select[1] = undefined; 26556 select[2] = undefined; 26557 select[3] = undefined; 26558 select[4] = 0; 26559 r.redrawHint('select', true); 26560 cy.emit({ 26561 type: 'boxend', 26562 originalEvent: e, 26563 position: { 26564 x: now[0], 26565 y: now[1] 26566 } 26567 }); 26568 26569 var eleWouldBeSelected = function eleWouldBeSelected(ele) { 26570 return ele.selectable() && !ele.selected(); 26571 }; 26572 26573 box.emit('box').stdFilter(eleWouldBeSelected).select().emit('boxselect'); 26574 26575 if (box.nonempty()) { 26576 r.redrawHint('eles', true); 26577 } 26578 26579 r.redraw(); 26580 } 26581 26582 if (start != null) { 26583 start.unactivate(); 26584 } 26585 26586 if (e.touches[2]) { 26587 r.data.bgActivePosistion = undefined; 26588 r.redrawHint('select', true); 26589 } else if (e.touches[1]) ; else if (e.touches[0]) ; else if (!e.touches[0]) { 26590 r.data.bgActivePosistion = undefined; 26591 r.redrawHint('select', true); 26592 var draggedEles = r.dragData.touchDragEles; 26593 26594 if (start != null) { 26595 var startWasGrabbed = start._private.grabbed; 26596 freeDraggedElements(draggedEles); 26597 r.redrawHint('drag', true); 26598 r.redrawHint('eles', true); 26599 26600 if (startWasGrabbed) { 26601 start.emit('freeon'); 26602 draggedEles.emit('free'); 26603 26604 if (r.dragData.didDrag) { 26605 start.emit('dragfreeon'); 26606 draggedEles.emit('dragfree'); 26607 } 26608 } 26609 26610 triggerEvents(start, ['touchend', 'tapend', 'vmouseup', 'tapdragout'], e, { 26611 x: now[0], 26612 y: now[1] 26613 }); 26614 start.unactivate(); 26615 r.touchData.start = null; 26616 } else { 26617 var near = r.findNearestElement(now[0], now[1], true, true); 26618 triggerEvents(near, ['touchend', 'tapend', 'vmouseup', 'tapdragout'], e, { 26619 x: now[0], 26620 y: now[1] 26621 }); 26622 } 26623 26624 var dx = r.touchData.startPosition[0] - now[0]; 26625 var dx2 = dx * dx; 26626 var dy = r.touchData.startPosition[1] - now[1]; 26627 var dy2 = dy * dy; 26628 var dist2 = dx2 + dy2; 26629 var rdist2 = dist2 * zoom * zoom; // Tap event, roughly same as mouse click event for touch 26630 26631 if (!r.touchData.singleTouchMoved) { 26632 if (!start) { 26633 cy.$(':selected').unselect(['tapunselect']); 26634 } 26635 26636 triggerEvents(start, ['tap', 'vclick'], e, { 26637 x: now[0], 26638 y: now[1] 26639 }); 26640 } // Prepare to select the currently touched node, only if it hasn't been dragged past a certain distance 26641 26642 26643 if (start != null && !r.dragData.didDrag // didn't drag nodes around 26644 && start._private.selectable && rdist2 < r.touchTapThreshold2 && !r.pinching // pinch to zoom should not affect selection 26645 ) { 26646 if (cy.selectionType() === 'single') { 26647 cy.$(isSelected).unmerge(start).unselect(['tapunselect']); 26648 start.select(['tapselect']); 26649 } else { 26650 if (start.selected()) { 26651 start.unselect(['tapunselect']); 26652 } else { 26653 start.select(['tapselect']); 26654 } 26655 } 26656 26657 r.redrawHint('eles', true); 26658 } 26659 26660 r.touchData.singleTouchMoved = true; 26661 } 26662 26663 for (var j = 0; j < now.length; j++) { 26664 earlier[j] = now[j]; 26665 } 26666 26667 r.dragData.didDrag = false; // reset for next touchstart 26668 26669 if (e.touches.length === 0) { 26670 r.touchData.dragDelta = []; 26671 r.touchData.startPosition = null; 26672 r.touchData.startGPosition = null; 26673 r.touchData.didSelect = false; 26674 } 26675 26676 if (e.touches.length < 2) { 26677 if (e.touches.length === 1) { 26678 // the old start global pos'n may not be the same finger that remains 26679 r.touchData.startGPosition = [e.touches[0].clientX, e.touches[0].clientY]; 26680 } 26681 26682 r.pinching = false; 26683 r.redrawHint('eles', true); 26684 r.redraw(); 26685 } //r.redraw(); 26686 26687 }, false); // fallback compatibility layer for ms pointer events 26688 26689 if (typeof TouchEvent === 'undefined') { 26690 var pointers = []; 26691 26692 var makeTouch = function makeTouch(e) { 26693 return { 26694 clientX: e.clientX, 26695 clientY: e.clientY, 26696 force: 1, 26697 identifier: e.pointerId, 26698 pageX: e.pageX, 26699 pageY: e.pageY, 26700 radiusX: e.width / 2, 26701 radiusY: e.height / 2, 26702 screenX: e.screenX, 26703 screenY: e.screenY, 26704 target: e.target 26705 }; 26706 }; 26707 26708 var makePointer = function makePointer(e) { 26709 return { 26710 event: e, 26711 touch: makeTouch(e) 26712 }; 26713 }; 26714 26715 var addPointer = function addPointer(e) { 26716 pointers.push(makePointer(e)); 26717 }; 26718 26719 var removePointer = function removePointer(e) { 26720 for (var i = 0; i < pointers.length; i++) { 26721 var p = pointers[i]; 26722 26723 if (p.event.pointerId === e.pointerId) { 26724 pointers.splice(i, 1); 26725 return; 26726 } 26727 } 26728 }; 26729 26730 var updatePointer = function updatePointer(e) { 26731 var p = pointers.filter(function (p) { 26732 return p.event.pointerId === e.pointerId; 26733 })[0]; 26734 p.event = e; 26735 p.touch = makeTouch(e); 26736 }; 26737 26738 var addTouchesToEvent = function addTouchesToEvent(e) { 26739 e.touches = pointers.map(function (p) { 26740 return p.touch; 26741 }); 26742 }; 26743 26744 var pointerIsMouse = function pointerIsMouse(e) { 26745 return e.pointerType === 'mouse' || e.pointerType === 4; 26746 }; 26747 26748 r.registerBinding(r.container, 'pointerdown', function (e) { 26749 if (pointerIsMouse(e)) { 26750 return; 26751 } // mouse already handled 26752 26753 26754 e.preventDefault(); 26755 addPointer(e); 26756 addTouchesToEvent(e); 26757 touchstartHandler(e); 26758 }); 26759 r.registerBinding(r.container, 'pointerup', function (e) { 26760 if (pointerIsMouse(e)) { 26761 return; 26762 } // mouse already handled 26763 26764 26765 removePointer(e); 26766 addTouchesToEvent(e); 26767 touchendHandler(e); 26768 }); 26769 r.registerBinding(r.container, 'pointercancel', function (e) { 26770 if (pointerIsMouse(e)) { 26771 return; 26772 } // mouse already handled 26773 26774 26775 removePointer(e); 26776 addTouchesToEvent(e); 26777 touchcancelHandler(e); 26778 }); 26779 r.registerBinding(r.container, 'pointermove', function (e) { 26780 if (pointerIsMouse(e)) { 26781 return; 26782 } // mouse already handled 26783 26784 26785 e.preventDefault(); 26786 updatePointer(e); 26787 addTouchesToEvent(e); 26788 touchmoveHandler(e); 26789 }); 26790 } 26791 }; 26792 26793 var BRp$d = {}; 26794 26795 BRp$d.generatePolygon = function (name, points) { 26796 return this.nodeShapes[name] = { 26797 renderer: this, 26798 name: name, 26799 points: points, 26800 draw: function draw(context, centerX, centerY, width, height) { 26801 this.renderer.nodeShapeImpl('polygon', context, centerX, centerY, width, height, this.points); 26802 }, 26803 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26804 return polygonIntersectLine(x, y, this.points, nodeX, nodeY, width / 2, height / 2, padding); 26805 }, 26806 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26807 return pointInsidePolygon(x, y, this.points, centerX, centerY, width, height, [0, -1], padding); 26808 } 26809 }; 26810 }; 26811 26812 BRp$d.generateEllipse = function () { 26813 return this.nodeShapes['ellipse'] = { 26814 renderer: this, 26815 name: 'ellipse', 26816 draw: function draw(context, centerX, centerY, width, height) { 26817 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26818 }, 26819 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26820 return intersectLineEllipse(x, y, nodeX, nodeY, width / 2 + padding, height / 2 + padding); 26821 }, 26822 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26823 return checkInEllipse(x, y, width, height, centerX, centerY, padding); 26824 } 26825 }; 26826 }; 26827 26828 BRp$d.generateRoundPolygon = function (name, points) { 26829 // Pre-compute control points 26830 // Since these points depend on the radius length (which in turns depend on the width/height of the node) we will only pre-compute 26831 // the unit vectors. 26832 // For simplicity the layout will be: 26833 // [ p0, UnitVectorP0P1, p1, UniVectorP1P2, ..., pn, UnitVectorPnP0 ] 26834 var allPoints = new Array(points.length * 2); 26835 26836 for (var i = 0; i < points.length / 2; i++) { 26837 var sourceIndex = i * 2; 26838 var destIndex = void 0; 26839 26840 if (i < points.length / 2 - 1) { 26841 destIndex = (i + 1) * 2; 26842 } else { 26843 destIndex = 0; 26844 } 26845 26846 allPoints[i * 4] = points[sourceIndex]; 26847 allPoints[i * 4 + 1] = points[sourceIndex + 1]; 26848 var xDest = points[destIndex] - points[sourceIndex]; 26849 var yDest = points[destIndex + 1] - points[sourceIndex + 1]; 26850 var norm = Math.sqrt(xDest * xDest + yDest * yDest); 26851 allPoints[i * 4 + 2] = xDest / norm; 26852 allPoints[i * 4 + 3] = yDest / norm; 26853 } 26854 26855 return this.nodeShapes[name] = { 26856 renderer: this, 26857 name: name, 26858 points: allPoints, 26859 draw: function draw(context, centerX, centerY, width, height) { 26860 this.renderer.nodeShapeImpl('round-polygon', context, centerX, centerY, width, height, this.points); 26861 }, 26862 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26863 return roundPolygonIntersectLine(x, y, this.points, nodeX, nodeY, width, height); 26864 }, 26865 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26866 return pointInsideRoundPolygon(x, y, this.points, centerX, centerY, width, height); 26867 } 26868 }; 26869 }; 26870 26871 BRp$d.generateRoundRectangle = function () { 26872 return this.nodeShapes['round-rectangle'] = this.nodeShapes['roundrectangle'] = { 26873 renderer: this, 26874 name: 'round-rectangle', 26875 points: generateUnitNgonPointsFitToSquare(4, 0), 26876 draw: function draw(context, centerX, centerY, width, height) { 26877 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26878 }, 26879 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26880 return roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding); 26881 }, 26882 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26883 var cornerRadius = getRoundRectangleRadius(width, height); 26884 var diam = cornerRadius * 2; // Check hBox 26885 26886 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - diam, [0, -1], padding)) { 26887 return true; 26888 } // Check vBox 26889 26890 26891 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - diam, height, [0, -1], padding)) { 26892 return true; 26893 } // Check top left quarter circle 26894 26895 26896 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY - height / 2 + cornerRadius, padding)) { 26897 return true; 26898 } // Check top right quarter circle 26899 26900 26901 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY - height / 2 + cornerRadius, padding)) { 26902 return true; 26903 } // Check bottom right quarter circle 26904 26905 26906 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26907 return true; 26908 } // Check bottom left quarter circle 26909 26910 26911 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 26912 return true; 26913 } 26914 26915 return false; 26916 } 26917 }; 26918 }; 26919 26920 BRp$d.generateCutRectangle = function () { 26921 return this.nodeShapes['cut-rectangle'] = this.nodeShapes['cutrectangle'] = { 26922 renderer: this, 26923 name: 'cut-rectangle', 26924 cornerLength: getCutRectangleCornerLength(), 26925 points: generateUnitNgonPointsFitToSquare(4, 0), 26926 draw: function draw(context, centerX, centerY, width, height) { 26927 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26928 }, 26929 generateCutTrianglePts: function generateCutTrianglePts(width, height, centerX, centerY) { 26930 var cl = this.cornerLength; 26931 var hh = height / 2; 26932 var hw = width / 2; 26933 var xBegin = centerX - hw; 26934 var xEnd = centerX + hw; 26935 var yBegin = centerY - hh; 26936 var yEnd = centerY + hh; // points are in clockwise order, inner (imaginary) triangle pt on [4, 5] 26937 26938 return { 26939 topLeft: [xBegin, yBegin + cl, xBegin + cl, yBegin, xBegin + cl, yBegin + cl], 26940 topRight: [xEnd - cl, yBegin, xEnd, yBegin + cl, xEnd - cl, yBegin + cl], 26941 bottomRight: [xEnd, yEnd - cl, xEnd - cl, yEnd, xEnd - cl, yEnd - cl], 26942 bottomLeft: [xBegin + cl, yEnd, xBegin, yEnd - cl, xBegin + cl, yEnd - cl] 26943 }; 26944 }, 26945 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26946 var cPts = this.generateCutTrianglePts(width + 2 * padding, height + 2 * padding, nodeX, nodeY); 26947 var pts = [].concat.apply([], [cPts.topLeft.splice(0, 4), cPts.topRight.splice(0, 4), cPts.bottomRight.splice(0, 4), cPts.bottomLeft.splice(0, 4)]); 26948 return polygonIntersectLine(x, y, pts, nodeX, nodeY); 26949 }, 26950 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 26951 // Check hBox 26952 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - 2 * this.cornerLength, [0, -1], padding)) { 26953 return true; 26954 } // Check vBox 26955 26956 26957 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - 2 * this.cornerLength, height, [0, -1], padding)) { 26958 return true; 26959 } 26960 26961 var cutTrianglePts = this.generateCutTrianglePts(width, height, centerX, centerY); 26962 return pointInsidePolygonPoints(x, y, cutTrianglePts.topLeft) || pointInsidePolygonPoints(x, y, cutTrianglePts.topRight) || pointInsidePolygonPoints(x, y, cutTrianglePts.bottomRight) || pointInsidePolygonPoints(x, y, cutTrianglePts.bottomLeft); 26963 } 26964 }; 26965 }; 26966 26967 BRp$d.generateBarrel = function () { 26968 return this.nodeShapes['barrel'] = { 26969 renderer: this, 26970 name: 'barrel', 26971 points: generateUnitNgonPointsFitToSquare(4, 0), 26972 draw: function draw(context, centerX, centerY, width, height) { 26973 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 26974 }, 26975 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 26976 // use two fixed t values for the bezier curve approximation 26977 var t0 = 0.15; 26978 var t1 = 0.5; 26979 var t2 = 0.85; 26980 var bPts = this.generateBarrelBezierPts(width + 2 * padding, height + 2 * padding, nodeX, nodeY); 26981 26982 var approximateBarrelCurvePts = function approximateBarrelCurvePts(pts) { 26983 // approximate curve pts based on the two t values 26984 var m0 = qbezierPtAt({ 26985 x: pts[0], 26986 y: pts[1] 26987 }, { 26988 x: pts[2], 26989 y: pts[3] 26990 }, { 26991 x: pts[4], 26992 y: pts[5] 26993 }, t0); 26994 var m1 = qbezierPtAt({ 26995 x: pts[0], 26996 y: pts[1] 26997 }, { 26998 x: pts[2], 26999 y: pts[3] 27000 }, { 27001 x: pts[4], 27002 y: pts[5] 27003 }, t1); 27004 var m2 = qbezierPtAt({ 27005 x: pts[0], 27006 y: pts[1] 27007 }, { 27008 x: pts[2], 27009 y: pts[3] 27010 }, { 27011 x: pts[4], 27012 y: pts[5] 27013 }, t2); 27014 return [pts[0], pts[1], m0.x, m0.y, m1.x, m1.y, m2.x, m2.y, pts[4], pts[5]]; 27015 }; 27016 27017 var pts = [].concat(approximateBarrelCurvePts(bPts.topLeft), approximateBarrelCurvePts(bPts.topRight), approximateBarrelCurvePts(bPts.bottomRight), approximateBarrelCurvePts(bPts.bottomLeft)); 27018 return polygonIntersectLine(x, y, pts, nodeX, nodeY); 27019 }, 27020 generateBarrelBezierPts: function generateBarrelBezierPts(width, height, centerX, centerY) { 27021 var hh = height / 2; 27022 var hw = width / 2; 27023 var xBegin = centerX - hw; 27024 var xEnd = centerX + hw; 27025 var yBegin = centerY - hh; 27026 var yEnd = centerY + hh; 27027 var curveConstants = getBarrelCurveConstants(width, height); 27028 var hOffset = curveConstants.heightOffset; 27029 var wOffset = curveConstants.widthOffset; 27030 var ctrlPtXOffset = curveConstants.ctrlPtOffsetPct * width; // points are in clockwise order, inner (imaginary) control pt on [4, 5] 27031 27032 var pts = { 27033 topLeft: [xBegin, yBegin + hOffset, xBegin + ctrlPtXOffset, yBegin, xBegin + wOffset, yBegin], 27034 topRight: [xEnd - wOffset, yBegin, xEnd - ctrlPtXOffset, yBegin, xEnd, yBegin + hOffset], 27035 bottomRight: [xEnd, yEnd - hOffset, xEnd - ctrlPtXOffset, yEnd, xEnd - wOffset, yEnd], 27036 bottomLeft: [xBegin + wOffset, yEnd, xBegin + ctrlPtXOffset, yEnd, xBegin, yEnd - hOffset] 27037 }; 27038 pts.topLeft.isTop = true; 27039 pts.topRight.isTop = true; 27040 pts.bottomLeft.isBottom = true; 27041 pts.bottomRight.isBottom = true; 27042 return pts; 27043 }, 27044 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 27045 var curveConstants = getBarrelCurveConstants(width, height); 27046 var hOffset = curveConstants.heightOffset; 27047 var wOffset = curveConstants.widthOffset; // Check hBox 27048 27049 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - 2 * hOffset, [0, -1], padding)) { 27050 return true; 27051 } // Check vBox 27052 27053 27054 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - 2 * wOffset, height, [0, -1], padding)) { 27055 return true; 27056 } 27057 27058 var barrelCurvePts = this.generateBarrelBezierPts(width, height, centerX, centerY); 27059 27060 var getCurveT = function getCurveT(x, y, curvePts) { 27061 var x0 = curvePts[4]; 27062 var x1 = curvePts[2]; 27063 var x2 = curvePts[0]; 27064 var y0 = curvePts[5]; // var y1 = curvePts[ 3 ]; 27065 27066 var y2 = curvePts[1]; 27067 var xMin = Math.min(x0, x2); 27068 var xMax = Math.max(x0, x2); 27069 var yMin = Math.min(y0, y2); 27070 var yMax = Math.max(y0, y2); 27071 27072 if (xMin <= x && x <= xMax && yMin <= y && y <= yMax) { 27073 var coeff = bezierPtsToQuadCoeff(x0, x1, x2); 27074 var roots = solveQuadratic(coeff[0], coeff[1], coeff[2], x); 27075 var validRoots = roots.filter(function (r) { 27076 return 0 <= r && r <= 1; 27077 }); 27078 27079 if (validRoots.length > 0) { 27080 return validRoots[0]; 27081 } 27082 } 27083 27084 return null; 27085 }; 27086 27087 var curveRegions = Object.keys(barrelCurvePts); 27088 27089 for (var i = 0; i < curveRegions.length; i++) { 27090 var corner = curveRegions[i]; 27091 var cornerPts = barrelCurvePts[corner]; 27092 var t = getCurveT(x, y, cornerPts); 27093 27094 if (t == null) { 27095 continue; 27096 } 27097 27098 var y0 = cornerPts[5]; 27099 var y1 = cornerPts[3]; 27100 var y2 = cornerPts[1]; 27101 var bezY = qbezierAt(y0, y1, y2, t); 27102 27103 if (cornerPts.isTop && bezY <= y) { 27104 return true; 27105 } 27106 27107 if (cornerPts.isBottom && y <= bezY) { 27108 return true; 27109 } 27110 } 27111 27112 return false; 27113 } 27114 }; 27115 }; 27116 27117 BRp$d.generateBottomRoundrectangle = function () { 27118 return this.nodeShapes['bottom-round-rectangle'] = this.nodeShapes['bottomroundrectangle'] = { 27119 renderer: this, 27120 name: 'bottom-round-rectangle', 27121 points: generateUnitNgonPointsFitToSquare(4, 0), 27122 draw: function draw(context, centerX, centerY, width, height) { 27123 this.renderer.nodeShapeImpl(this.name, context, centerX, centerY, width, height); 27124 }, 27125 intersectLine: function intersectLine(nodeX, nodeY, width, height, x, y, padding) { 27126 var topStartX = nodeX - (width / 2 + padding); 27127 var topStartY = nodeY - (height / 2 + padding); 27128 var topEndY = topStartY; 27129 var topEndX = nodeX + (width / 2 + padding); 27130 var topIntersections = finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); 27131 27132 if (topIntersections.length > 0) { 27133 return topIntersections; 27134 } 27135 27136 return roundRectangleIntersectLine(x, y, nodeX, nodeY, width, height, padding); 27137 }, 27138 checkPoint: function checkPoint(x, y, padding, width, height, centerX, centerY) { 27139 var cornerRadius = getRoundRectangleRadius(width, height); 27140 var diam = 2 * cornerRadius; // Check hBox 27141 27142 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width, height - diam, [0, -1], padding)) { 27143 return true; 27144 } // Check vBox 27145 27146 27147 if (pointInsidePolygon(x, y, this.points, centerX, centerY, width - diam, height, [0, -1], padding)) { 27148 return true; 27149 } // check non-rounded top side 27150 27151 27152 var outerWidth = width / 2 + 2 * padding; 27153 var outerHeight = height / 2 + 2 * padding; 27154 var points = [centerX - outerWidth, centerY - outerHeight, centerX - outerWidth, centerY, centerX + outerWidth, centerY, centerX + outerWidth, centerY - outerHeight]; 27155 27156 if (pointInsidePolygonPoints(x, y, points)) { 27157 return true; 27158 } // Check bottom right quarter circle 27159 27160 27161 if (checkInEllipse(x, y, diam, diam, centerX + width / 2 - cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 27162 return true; 27163 } // Check bottom left quarter circle 27164 27165 27166 if (checkInEllipse(x, y, diam, diam, centerX - width / 2 + cornerRadius, centerY + height / 2 - cornerRadius, padding)) { 27167 return true; 27168 } 27169 27170 return false; 27171 } 27172 }; 27173 }; 27174 27175 BRp$d.registerNodeShapes = function () { 27176 var nodeShapes = this.nodeShapes = {}; 27177 var renderer = this; 27178 this.generateEllipse(); 27179 this.generatePolygon('triangle', generateUnitNgonPointsFitToSquare(3, 0)); 27180 this.generateRoundPolygon('round-triangle', generateUnitNgonPointsFitToSquare(3, 0)); 27181 this.generatePolygon('rectangle', generateUnitNgonPointsFitToSquare(4, 0)); 27182 nodeShapes['square'] = nodeShapes['rectangle']; 27183 this.generateRoundRectangle(); 27184 this.generateCutRectangle(); 27185 this.generateBarrel(); 27186 this.generateBottomRoundrectangle(); 27187 { 27188 var diamondPoints = [0, 1, 1, 0, 0, -1, -1, 0]; 27189 this.generatePolygon('diamond', diamondPoints); 27190 this.generateRoundPolygon('round-diamond', diamondPoints); 27191 } 27192 this.generatePolygon('pentagon', generateUnitNgonPointsFitToSquare(5, 0)); 27193 this.generateRoundPolygon('round-pentagon', generateUnitNgonPointsFitToSquare(5, 0)); 27194 this.generatePolygon('hexagon', generateUnitNgonPointsFitToSquare(6, 0)); 27195 this.generateRoundPolygon('round-hexagon', generateUnitNgonPointsFitToSquare(6, 0)); 27196 this.generatePolygon('heptagon', generateUnitNgonPointsFitToSquare(7, 0)); 27197 this.generateRoundPolygon('round-heptagon', generateUnitNgonPointsFitToSquare(7, 0)); 27198 this.generatePolygon('octagon', generateUnitNgonPointsFitToSquare(8, 0)); 27199 this.generateRoundPolygon('round-octagon', generateUnitNgonPointsFitToSquare(8, 0)); 27200 var star5Points = new Array(20); 27201 { 27202 var outerPoints = generateUnitNgonPoints(5, 0); 27203 var innerPoints = generateUnitNgonPoints(5, Math.PI / 5); // Outer radius is 1; inner radius of star is smaller 27204 27205 var innerRadius = 0.5 * (3 - Math.sqrt(5)); 27206 innerRadius *= 1.57; 27207 27208 for (var i = 0; i < innerPoints.length / 2; i++) { 27209 innerPoints[i * 2] *= innerRadius; 27210 innerPoints[i * 2 + 1] *= innerRadius; 27211 } 27212 27213 for (var i = 0; i < 20 / 4; i++) { 27214 star5Points[i * 4] = outerPoints[i * 2]; 27215 star5Points[i * 4 + 1] = outerPoints[i * 2 + 1]; 27216 star5Points[i * 4 + 2] = innerPoints[i * 2]; 27217 star5Points[i * 4 + 3] = innerPoints[i * 2 + 1]; 27218 } 27219 } 27220 star5Points = fitPolygonToSquare(star5Points); 27221 this.generatePolygon('star', star5Points); 27222 this.generatePolygon('vee', [-1, -1, 0, -0.333, 1, -1, 0, 1]); 27223 this.generatePolygon('rhomboid', [-1, -1, 0.333, -1, 1, 1, -0.333, 1]); 27224 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]); 27225 { 27226 var tagPoints = [-1, -1, 0.25, -1, 1, 0, 0.25, 1, -1, 1]; 27227 this.generatePolygon('tag', tagPoints); 27228 this.generateRoundPolygon('round-tag', tagPoints); 27229 } 27230 27231 nodeShapes.makePolygon = function (points) { 27232 // use caching on user-specified polygons so they are as fast as native shapes 27233 var key = points.join('$'); 27234 var name = 'polygon-' + key; 27235 var shape; 27236 27237 if (shape = this[name]) { 27238 // got cached shape 27239 return shape; 27240 } // create and cache new shape 27241 27242 27243 return renderer.generatePolygon(name, points); 27244 }; 27245 }; 27246 27247 var BRp$e = {}; 27248 27249 BRp$e.timeToRender = function () { 27250 return this.redrawTotalTime / this.redrawCount; 27251 }; 27252 27253 BRp$e.redraw = function (options) { 27254 options = options || staticEmptyObject(); 27255 var r = this; 27256 27257 if (r.averageRedrawTime === undefined) { 27258 r.averageRedrawTime = 0; 27259 } 27260 27261 if (r.lastRedrawTime === undefined) { 27262 r.lastRedrawTime = 0; 27263 } 27264 27265 if (r.lastDrawTime === undefined) { 27266 r.lastDrawTime = 0; 27267 } 27268 27269 r.requestedFrame = true; 27270 r.renderOptions = options; 27271 }; 27272 27273 BRp$e.beforeRender = function (fn, priority) { 27274 // the renderer can't add tick callbacks when destroyed 27275 if (this.destroyed) { 27276 return; 27277 } 27278 27279 if (priority == null) { 27280 error('Priority is not optional for beforeRender'); 27281 } 27282 27283 var cbs = this.beforeRenderCallbacks; 27284 cbs.push({ 27285 fn: fn, 27286 priority: priority 27287 }); // higher priority callbacks executed first 27288 27289 cbs.sort(function (a, b) { 27290 return b.priority - a.priority; 27291 }); 27292 }; 27293 27294 var beforeRenderCallbacks = function beforeRenderCallbacks(r, willDraw, startTime) { 27295 var cbs = r.beforeRenderCallbacks; 27296 27297 for (var i = 0; i < cbs.length; i++) { 27298 cbs[i].fn(willDraw, startTime); 27299 } 27300 }; 27301 27302 BRp$e.startRenderLoop = function () { 27303 var r = this; 27304 var cy = r.cy; 27305 27306 if (r.renderLoopStarted) { 27307 return; 27308 } else { 27309 r.renderLoopStarted = true; 27310 } 27311 27312 var renderFn = function renderFn(requestTime) { 27313 if (r.destroyed) { 27314 return; 27315 } 27316 27317 if (cy.batching()) ; else if (r.requestedFrame && !r.skipFrame) { 27318 beforeRenderCallbacks(r, true, requestTime); 27319 var startTime = performanceNow(); 27320 r.render(r.renderOptions); 27321 var endTime = r.lastDrawTime = performanceNow(); 27322 27323 if (r.averageRedrawTime === undefined) { 27324 r.averageRedrawTime = endTime - startTime; 27325 } 27326 27327 if (r.redrawCount === undefined) { 27328 r.redrawCount = 0; 27329 } 27330 27331 r.redrawCount++; 27332 27333 if (r.redrawTotalTime === undefined) { 27334 r.redrawTotalTime = 0; 27335 } 27336 27337 var duration = endTime - startTime; 27338 r.redrawTotalTime += duration; 27339 r.lastRedrawTime = duration; // use a weighted average with a bias from the previous average so we don't spike so easily 27340 27341 r.averageRedrawTime = r.averageRedrawTime / 2 + duration / 2; 27342 r.requestedFrame = false; 27343 } else { 27344 beforeRenderCallbacks(r, false, requestTime); 27345 } 27346 27347 r.skipFrame = false; 27348 requestAnimationFrame(renderFn); 27349 }; 27350 27351 requestAnimationFrame(renderFn); 27352 }; 27353 27354 var BaseRenderer = function BaseRenderer(options) { 27355 this.init(options); 27356 }; 27357 27358 var BR = BaseRenderer; 27359 var BRp$f = BR.prototype; 27360 BRp$f.clientFunctions = ['redrawHint', 'render', 'renderTo', 'matchCanvasSize', 'nodeShapeImpl', 'arrowShapeImpl']; 27361 27362 BRp$f.init = function (options) { 27363 var r = this; 27364 r.options = options; 27365 r.cy = options.cy; 27366 var ctr = r.container = options.cy.container(); // prepend a stylesheet in the head such that 27367 27368 if (window$1) { 27369 var document = window$1.document; 27370 var head = document.head; 27371 var stylesheetId = '__________cytoscape_stylesheet'; 27372 var className = '__________cytoscape_container'; 27373 var stylesheetAlreadyExists = document.getElementById(stylesheetId) != null; 27374 27375 if (ctr.className.indexOf(className) < 0) { 27376 ctr.className = (ctr.className || '') + ' ' + className; 27377 } 27378 27379 if (!stylesheetAlreadyExists) { 27380 var stylesheet = document.createElement('style'); 27381 stylesheet.id = stylesheetId; 27382 stylesheet.innerHTML = '.' + className + ' { position: relative; }'; 27383 head.insertBefore(stylesheet, head.children[0]); // first so lowest priority 27384 } 27385 27386 var computedStyle = window$1.getComputedStyle(ctr); 27387 var position = computedStyle.getPropertyValue('position'); 27388 27389 if (position === 'static') { 27390 warn('A Cytoscape container has style position:static and so can not use UI extensions properly'); 27391 } 27392 } 27393 27394 r.selection = [undefined, undefined, undefined, undefined, 0]; // Coordinates for selection box, plus enabled flag 27395 27396 r.bezierProjPcts = [0.05, 0.225, 0.4, 0.5, 0.6, 0.775, 0.95]; //--Pointer-related data 27397 27398 r.hoverData = { 27399 down: null, 27400 last: null, 27401 downTime: null, 27402 triggerMode: null, 27403 dragging: false, 27404 initialPan: [null, null], 27405 capture: false 27406 }; 27407 r.dragData = { 27408 possibleDragElements: [] 27409 }; 27410 r.touchData = { 27411 start: null, 27412 capture: false, 27413 // These 3 fields related to tap, taphold events 27414 startPosition: [null, null, null, null, null, null], 27415 singleTouchStartTime: null, 27416 singleTouchMoved: true, 27417 now: [null, null, null, null, null, null], 27418 earlier: [null, null, null, null, null, null] 27419 }; 27420 r.redraws = 0; 27421 r.showFps = options.showFps; 27422 r.debug = options.debug; 27423 r.hideEdgesOnViewport = options.hideEdgesOnViewport; 27424 r.textureOnViewport = options.textureOnViewport; 27425 r.wheelSensitivity = options.wheelSensitivity; 27426 r.motionBlurEnabled = options.motionBlur; // on by default 27427 27428 r.forcedPixelRatio = number(options.pixelRatio) ? options.pixelRatio : null; 27429 r.motionBlur = options.motionBlur; // for initial kick off 27430 27431 r.motionBlurOpacity = options.motionBlurOpacity; 27432 r.motionBlurTransparency = 1 - r.motionBlurOpacity; 27433 r.motionBlurPxRatio = 1; 27434 r.mbPxRBlurry = 1; //0.8; 27435 27436 r.minMbLowQualFrames = 4; 27437 r.fullQualityMb = false; 27438 r.clearedForMotionBlur = []; 27439 r.desktopTapThreshold = options.desktopTapThreshold; 27440 r.desktopTapThreshold2 = options.desktopTapThreshold * options.desktopTapThreshold; 27441 r.touchTapThreshold = options.touchTapThreshold; 27442 r.touchTapThreshold2 = options.touchTapThreshold * options.touchTapThreshold; 27443 r.tapholdDuration = 500; 27444 r.bindings = []; 27445 r.beforeRenderCallbacks = []; 27446 r.beforeRenderPriorities = { 27447 // higher priority execs before lower one 27448 animations: 400, 27449 eleCalcs: 300, 27450 eleTxrDeq: 200, 27451 lyrTxrDeq: 150, 27452 lyrTxrSkip: 100 27453 }; 27454 r.registerNodeShapes(); 27455 r.registerArrowShapes(); 27456 r.registerCalculationListeners(); 27457 }; 27458 27459 BRp$f.notify = function (eventName, eles) { 27460 var r = this; 27461 var cy = r.cy; // the renderer can't be notified after it's destroyed 27462 27463 if (this.destroyed) { 27464 return; 27465 } 27466 27467 if (eventName === 'init') { 27468 r.load(); 27469 return; 27470 } 27471 27472 if (eventName === 'destroy') { 27473 r.destroy(); 27474 return; 27475 } 27476 27477 if (eventName === 'add' || eventName === 'remove' || eventName === 'move' && cy.hasCompoundNodes() || eventName === 'load' || eventName === 'zorder' || eventName === 'mount') { 27478 r.invalidateCachedZSortedEles(); 27479 } 27480 27481 if (eventName === 'viewport') { 27482 r.redrawHint('select', true); 27483 } 27484 27485 if (eventName === 'load' || eventName === 'resize' || eventName === 'mount') { 27486 r.invalidateContainerClientCoordsCache(); 27487 r.matchCanvasSize(r.container); 27488 } 27489 27490 r.redrawHint('eles', true); 27491 r.redrawHint('drag', true); 27492 this.startRenderLoop(); 27493 this.redraw(); 27494 }; 27495 27496 BRp$f.destroy = function () { 27497 var r = this; 27498 r.destroyed = true; 27499 r.cy.stopAnimationLoop(); 27500 27501 for (var i = 0; i < r.bindings.length; i++) { 27502 var binding = r.bindings[i]; 27503 var b = binding; 27504 var tgt = b.target; 27505 (tgt.off || tgt.removeEventListener).apply(tgt, b.args); 27506 } 27507 27508 r.bindings = []; 27509 r.beforeRenderCallbacks = []; 27510 r.onUpdateEleCalcsFns = []; 27511 27512 if (r.removeObserver) { 27513 r.removeObserver.disconnect(); 27514 } 27515 27516 if (r.styleObserver) { 27517 r.styleObserver.disconnect(); 27518 } 27519 27520 if (r.resizeObserver) { 27521 r.resizeObserver.disconnect(); 27522 } 27523 27524 if (r.labelCalcDiv) { 27525 try { 27526 document.body.removeChild(r.labelCalcDiv); // eslint-disable-line no-undef 27527 } catch (e) {// ie10 issue #1014 27528 } 27529 } 27530 }; 27531 27532 BRp$f.isHeadless = function () { 27533 return false; 27534 }; 27535 27536 [BRp, BRp$a, BRp$b, BRp$c, BRp$d, BRp$e].forEach(function (props) { 27537 extend(BRp$f, props); 27538 }); 27539 27540 var fullFpsTime = 1000 / 60; // assume 60 frames per second 27541 27542 var defs = { 27543 setupDequeueing: function setupDequeueing(opts) { 27544 return function setupDequeueingImpl() { 27545 var self = this; 27546 var r = this.renderer; 27547 27548 if (self.dequeueingSetup) { 27549 return; 27550 } else { 27551 self.dequeueingSetup = true; 27552 } 27553 27554 var queueRedraw = lodash_debounce(function () { 27555 r.redrawHint('eles', true); 27556 r.redrawHint('drag', true); 27557 r.redraw(); 27558 }, opts.deqRedrawThreshold); 27559 27560 var dequeue = function dequeue(willDraw, frameStartTime) { 27561 var startTime = performanceNow(); 27562 var avgRenderTime = r.averageRedrawTime; 27563 var renderTime = r.lastRedrawTime; 27564 var deqd = []; 27565 var extent = r.cy.extent(); 27566 var pixelRatio = r.getPixelRatio(); // if we aren't in a tick that causes a draw, then the rendered style 27567 // queue won't automatically be flushed before dequeueing starts 27568 27569 if (!willDraw) { 27570 r.flushRenderedStyleQueue(); 27571 } 27572 27573 while (true) { 27574 // eslint-disable-line no-constant-condition 27575 var now = performanceNow(); 27576 var duration = now - startTime; 27577 var frameDuration = now - frameStartTime; 27578 27579 if (renderTime < fullFpsTime) { 27580 // if we're rendering faster than the ideal fps, then do dequeueing 27581 // during all of the remaining frame time 27582 var timeAvailable = fullFpsTime - (willDraw ? avgRenderTime : 0); 27583 27584 if (frameDuration >= opts.deqFastCost * timeAvailable) { 27585 break; 27586 } 27587 } else { 27588 if (willDraw) { 27589 if (duration >= opts.deqCost * renderTime || duration >= opts.deqAvgCost * avgRenderTime) { 27590 break; 27591 } 27592 } else if (frameDuration >= opts.deqNoDrawCost * fullFpsTime) { 27593 break; 27594 } 27595 } 27596 27597 var thisDeqd = opts.deq(self, pixelRatio, extent); 27598 27599 if (thisDeqd.length > 0) { 27600 for (var i = 0; i < thisDeqd.length; i++) { 27601 deqd.push(thisDeqd[i]); 27602 } 27603 } else { 27604 break; 27605 } 27606 } // callbacks on dequeue 27607 27608 27609 if (deqd.length > 0) { 27610 opts.onDeqd(self, deqd); 27611 27612 if (!willDraw && opts.shouldRedraw(self, deqd, pixelRatio, extent)) { 27613 queueRedraw(); 27614 } 27615 } 27616 }; 27617 27618 var priority = opts.priority || noop; 27619 r.beforeRender(dequeue, priority(self)); 27620 }; 27621 } 27622 }; 27623 27624 // Uses keys so elements may share the same cache. 27625 27626 var ElementTextureCacheLookup = 27627 /*#__PURE__*/ 27628 function () { 27629 function ElementTextureCacheLookup(getKey) { 27630 var doesEleInvalidateKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : falsify; 27631 27632 _classCallCheck(this, ElementTextureCacheLookup); 27633 27634 this.idsByKey = new Map$1(); 27635 this.keyForId = new Map$1(); 27636 this.cachesByLvl = new Map$1(); 27637 this.lvls = []; 27638 this.getKey = getKey; 27639 this.doesEleInvalidateKey = doesEleInvalidateKey; 27640 } 27641 27642 _createClass(ElementTextureCacheLookup, [{ 27643 key: "getIdsFor", 27644 value: function getIdsFor(key) { 27645 if (key == null) { 27646 error("Can not get id list for null key"); 27647 } 27648 27649 var idsByKey = this.idsByKey; 27650 var ids = this.idsByKey.get(key); 27651 27652 if (!ids) { 27653 ids = new Set$1(); 27654 idsByKey.set(key, ids); 27655 } 27656 27657 return ids; 27658 } 27659 }, { 27660 key: "addIdForKey", 27661 value: function addIdForKey(key, id) { 27662 if (key != null) { 27663 this.getIdsFor(key).add(id); 27664 } 27665 } 27666 }, { 27667 key: "deleteIdForKey", 27668 value: function deleteIdForKey(key, id) { 27669 if (key != null) { 27670 this.getIdsFor(key)["delete"](id); 27671 } 27672 } 27673 }, { 27674 key: "getNumberOfIdsForKey", 27675 value: function getNumberOfIdsForKey(key) { 27676 if (key == null) { 27677 return 0; 27678 } else { 27679 return this.getIdsFor(key).size; 27680 } 27681 } 27682 }, { 27683 key: "updateKeyMappingFor", 27684 value: function updateKeyMappingFor(ele) { 27685 var id = ele.id(); 27686 var prevKey = this.keyForId.get(id); 27687 var currKey = this.getKey(ele); 27688 this.deleteIdForKey(prevKey, id); 27689 this.addIdForKey(currKey, id); 27690 this.keyForId.set(id, currKey); 27691 } 27692 }, { 27693 key: "deleteKeyMappingFor", 27694 value: function deleteKeyMappingFor(ele) { 27695 var id = ele.id(); 27696 var prevKey = this.keyForId.get(id); 27697 this.deleteIdForKey(prevKey, id); 27698 this.keyForId["delete"](id); 27699 } 27700 }, { 27701 key: "keyHasChangedFor", 27702 value: function keyHasChangedFor(ele) { 27703 var id = ele.id(); 27704 var prevKey = this.keyForId.get(id); 27705 var newKey = this.getKey(ele); 27706 return prevKey !== newKey; 27707 } 27708 }, { 27709 key: "isInvalid", 27710 value: function isInvalid(ele) { 27711 return this.keyHasChangedFor(ele) || this.doesEleInvalidateKey(ele); 27712 } 27713 }, { 27714 key: "getCachesAt", 27715 value: function getCachesAt(lvl) { 27716 var cachesByLvl = this.cachesByLvl, 27717 lvls = this.lvls; 27718 var caches = cachesByLvl.get(lvl); 27719 27720 if (!caches) { 27721 caches = new Map$1(); 27722 cachesByLvl.set(lvl, caches); 27723 lvls.push(lvl); 27724 } 27725 27726 return caches; 27727 } 27728 }, { 27729 key: "getCache", 27730 value: function getCache(key, lvl) { 27731 return this.getCachesAt(lvl).get(key); 27732 } 27733 }, { 27734 key: "get", 27735 value: function get(ele, lvl) { 27736 var key = this.getKey(ele); 27737 var cache = this.getCache(key, lvl); // getting for an element may need to add to the id list b/c eles can share keys 27738 27739 if (cache != null) { 27740 this.updateKeyMappingFor(ele); 27741 } 27742 27743 return cache; 27744 } 27745 }, { 27746 key: "getForCachedKey", 27747 value: function getForCachedKey(ele, lvl) { 27748 var key = this.keyForId.get(ele.id()); // n.b. use cached key, not newly computed key 27749 27750 var cache = this.getCache(key, lvl); 27751 return cache; 27752 } 27753 }, { 27754 key: "hasCache", 27755 value: function hasCache(key, lvl) { 27756 return this.getCachesAt(lvl).has(key); 27757 } 27758 }, { 27759 key: "has", 27760 value: function has(ele, lvl) { 27761 var key = this.getKey(ele); 27762 return this.hasCache(key, lvl); 27763 } 27764 }, { 27765 key: "setCache", 27766 value: function setCache(key, lvl, cache) { 27767 cache.key = key; 27768 this.getCachesAt(lvl).set(key, cache); 27769 } 27770 }, { 27771 key: "set", 27772 value: function set(ele, lvl, cache) { 27773 var key = this.getKey(ele); 27774 this.setCache(key, lvl, cache); 27775 this.updateKeyMappingFor(ele); 27776 } 27777 }, { 27778 key: "deleteCache", 27779 value: function deleteCache(key, lvl) { 27780 this.getCachesAt(lvl)["delete"](key); 27781 } 27782 }, { 27783 key: "delete", 27784 value: function _delete(ele, lvl) { 27785 var key = this.getKey(ele); 27786 this.deleteCache(key, lvl); 27787 } 27788 }, { 27789 key: "invalidateKey", 27790 value: function invalidateKey(key) { 27791 var _this = this; 27792 27793 this.lvls.forEach(function (lvl) { 27794 return _this.deleteCache(key, lvl); 27795 }); 27796 } // returns true if no other eles reference the invalidated cache (n.b. other eles may need the cache with the same key) 27797 27798 }, { 27799 key: "invalidate", 27800 value: function invalidate(ele) { 27801 var id = ele.id(); 27802 var key = this.keyForId.get(id); // n.b. use stored key rather than current (potential key) 27803 27804 this.deleteKeyMappingFor(ele); 27805 var entireKeyInvalidated = this.doesEleInvalidateKey(ele); 27806 27807 if (entireKeyInvalidated) { 27808 // clear mapping for current key 27809 this.invalidateKey(key); 27810 } 27811 27812 return entireKeyInvalidated || this.getNumberOfIdsForKey(key) === 0; 27813 } 27814 }]); 27815 27816 return ElementTextureCacheLookup; 27817 }(); 27818 27819 var minTxrH = 25; // the size of the texture cache for small height eles (special case) 27820 27821 var txrStepH = 50; // the min size of the regular cache, and the size it increases with each step up 27822 27823 var minLvl = -4; // when scaling smaller than that we don't need to re-render 27824 27825 var maxLvl = 3; // when larger than this scale just render directly (caching is not helpful) 27826 27827 var maxZoom = 7.99; // beyond this zoom level, layered textures are not used 27828 27829 var eleTxrSpacing = 8; // spacing between elements on textures to avoid blitting overlaps 27830 27831 var defTxrWidth = 1024; // default/minimum texture width 27832 27833 var maxTxrW = 1024; // the maximum width of a texture 27834 27835 var maxTxrH = 1024; // the maximum height of a texture 27836 27837 var minUtility = 0.2; // if usage of texture is less than this, it is retired 27838 27839 var maxFullness = 0.8; // fullness of texture after which queue removal is checked 27840 27841 var maxFullnessChecks = 10; // dequeued after this many checks 27842 27843 var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 27844 27845 var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time 27846 27847 var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 27848 27849 var deqFastCost = 0.9; // % of frame time to be used when >60fps 27850 27851 var deqRedrawThreshold = 100; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 27852 27853 var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch 27854 27855 var getTxrReasons = { 27856 dequeue: 'dequeue', 27857 downscale: 'downscale', 27858 highQuality: 'highQuality' 27859 }; 27860 var initDefaults = defaults({ 27861 getKey: null, 27862 doesEleInvalidateKey: falsify, 27863 drawElement: null, 27864 getBoundingBox: null, 27865 getRotationPoint: null, 27866 getRotationOffset: null, 27867 isVisible: trueify, 27868 allowEdgeTxrCaching: true, 27869 allowParentTxrCaching: true 27870 }); 27871 27872 var ElementTextureCache = function ElementTextureCache(renderer, initOptions) { 27873 var self = this; 27874 self.renderer = renderer; 27875 self.onDequeues = []; 27876 var opts = initDefaults(initOptions); 27877 extend(self, opts); 27878 self.lookup = new ElementTextureCacheLookup(opts.getKey, opts.doesEleInvalidateKey); 27879 self.setupDequeueing(); 27880 }; 27881 27882 var ETCp = ElementTextureCache.prototype; 27883 ETCp.reasons = getTxrReasons; // the list of textures in which new subtextures for elements can be placed 27884 27885 ETCp.getTextureQueue = function (txrH) { 27886 var self = this; 27887 self.eleImgCaches = self.eleImgCaches || {}; 27888 return self.eleImgCaches[txrH] = self.eleImgCaches[txrH] || []; 27889 }; // the list of usused textures which can be recycled (in use in texture queue) 27890 27891 27892 ETCp.getRetiredTextureQueue = function (txrH) { 27893 var self = this; 27894 var rtxtrQs = self.eleImgCaches.retired = self.eleImgCaches.retired || {}; 27895 var rtxtrQ = rtxtrQs[txrH] = rtxtrQs[txrH] || []; 27896 return rtxtrQ; 27897 }; // queue of element draw requests at different scale levels 27898 27899 27900 ETCp.getElementQueue = function () { 27901 var self = this; 27902 var q = self.eleCacheQueue = self.eleCacheQueue || new heap$1(function (a, b) { 27903 return b.reqs - a.reqs; 27904 }); 27905 return q; 27906 }; // queue of element draw requests at different scale levels (element id lookup) 27907 27908 27909 ETCp.getElementKeyToQueue = function () { 27910 var self = this; 27911 var k2q = self.eleKeyToCacheQueue = self.eleKeyToCacheQueue || {}; 27912 return k2q; 27913 }; 27914 27915 ETCp.getElement = function (ele, bb, pxRatio, lvl, reason) { 27916 var self = this; 27917 var r = this.renderer; 27918 var zoom = r.cy.zoom(); 27919 var lookup = this.lookup; 27920 27921 if (bb.w === 0 || bb.h === 0 || isNaN(bb.w) || isNaN(bb.h) || !ele.visible()) { 27922 return null; 27923 } 27924 27925 if (!self.allowEdgeTxrCaching && ele.isEdge() || !self.allowParentTxrCaching && ele.isParent()) { 27926 return null; 27927 } 27928 27929 if (lvl == null) { 27930 lvl = Math.ceil(log2(zoom * pxRatio)); 27931 } 27932 27933 if (lvl < minLvl) { 27934 lvl = minLvl; 27935 } else if (zoom >= maxZoom || lvl > maxLvl) { 27936 return null; 27937 } 27938 27939 var scale = Math.pow(2, lvl); 27940 var eleScaledH = bb.h * scale; 27941 var eleScaledW = bb.w * scale; 27942 var scaledLabelShown = r.eleTextBiggerThanMin(ele, scale); 27943 27944 if (!this.isVisible(ele, scaledLabelShown)) { 27945 return null; 27946 } 27947 27948 var eleCache = lookup.get(ele, lvl); // if this get was on an unused/invalidated cache, then restore the texture usage metric 27949 27950 if (eleCache && eleCache.invalidated) { 27951 eleCache.invalidated = false; 27952 eleCache.texture.invalidatedWidth -= eleCache.width; 27953 } 27954 27955 if (eleCache) { 27956 return eleCache; 27957 } 27958 27959 var txrH; // which texture height this ele belongs to 27960 27961 if (eleScaledH <= minTxrH) { 27962 txrH = minTxrH; 27963 } else if (eleScaledH <= txrStepH) { 27964 txrH = txrStepH; 27965 } else { 27966 txrH = Math.ceil(eleScaledH / txrStepH) * txrStepH; 27967 } 27968 27969 if (eleScaledH > maxTxrH || eleScaledW > maxTxrW) { 27970 return null; // caching large elements is not efficient 27971 } 27972 27973 var txrQ = self.getTextureQueue(txrH); // first try the second last one in case it has space at the end 27974 27975 var txr = txrQ[txrQ.length - 2]; 27976 27977 var addNewTxr = function addNewTxr() { 27978 return self.recycleTexture(txrH, eleScaledW) || self.addTexture(txrH, eleScaledW); 27979 }; // try the last one if there is no second last one 27980 27981 27982 if (!txr) { 27983 txr = txrQ[txrQ.length - 1]; 27984 } // if the last one doesn't exist, we need a first one 27985 27986 27987 if (!txr) { 27988 txr = addNewTxr(); 27989 } // if there's no room in the current texture, we need a new one 27990 27991 27992 if (txr.width - txr.usedWidth < eleScaledW) { 27993 txr = addNewTxr(); 27994 } 27995 27996 var scalableFrom = function scalableFrom(otherCache) { 27997 return otherCache && otherCache.scaledLabelShown === scaledLabelShown; 27998 }; 27999 28000 var deqing = reason && reason === getTxrReasons.dequeue; 28001 var highQualityReq = reason && reason === getTxrReasons.highQuality; 28002 var downscaleReq = reason && reason === getTxrReasons.downscale; 28003 var higherCache; // the nearest cache with a higher level 28004 28005 for (var l = lvl + 1; l <= maxLvl; l++) { 28006 var c = lookup.get(ele, l); 28007 28008 if (c) { 28009 higherCache = c; 28010 break; 28011 } 28012 } 28013 28014 var oneUpCache = higherCache && higherCache.level === lvl + 1 ? higherCache : null; 28015 28016 var downscale = function downscale() { 28017 txr.context.drawImage(oneUpCache.texture.canvas, oneUpCache.x, 0, oneUpCache.width, oneUpCache.height, txr.usedWidth, 0, eleScaledW, eleScaledH); 28018 }; // reset ele area in texture 28019 28020 28021 txr.context.setTransform(1, 0, 0, 1, 0, 0); 28022 txr.context.clearRect(txr.usedWidth, 0, eleScaledW, txrH); 28023 28024 if (scalableFrom(oneUpCache)) { 28025 // then we can relatively cheaply rescale the existing image w/o rerendering 28026 downscale(); 28027 } else if (scalableFrom(higherCache)) { 28028 // then use the higher cache for now and queue the next level down 28029 // to cheaply scale towards the smaller level 28030 if (highQualityReq) { 28031 for (var _l = higherCache.level; _l > lvl; _l--) { 28032 oneUpCache = self.getElement(ele, bb, pxRatio, _l, getTxrReasons.downscale); 28033 } 28034 28035 downscale(); 28036 } else { 28037 self.queueElement(ele, higherCache.level - 1); 28038 return higherCache; 28039 } 28040 } else { 28041 var lowerCache; // the nearest cache with a lower level 28042 28043 if (!deqing && !highQualityReq && !downscaleReq) { 28044 for (var _l2 = lvl - 1; _l2 >= minLvl; _l2--) { 28045 var _c = lookup.get(ele, _l2); 28046 28047 if (_c) { 28048 lowerCache = _c; 28049 break; 28050 } 28051 } 28052 } 28053 28054 if (scalableFrom(lowerCache)) { 28055 // then use the lower quality cache for now and queue the better one for later 28056 self.queueElement(ele, lvl); 28057 return lowerCache; 28058 } 28059 28060 txr.context.translate(txr.usedWidth, 0); 28061 txr.context.scale(scale, scale); 28062 this.drawElement(txr.context, ele, bb, scaledLabelShown, false); 28063 txr.context.scale(1 / scale, 1 / scale); 28064 txr.context.translate(-txr.usedWidth, 0); 28065 } 28066 28067 eleCache = { 28068 x: txr.usedWidth, 28069 texture: txr, 28070 level: lvl, 28071 scale: scale, 28072 width: eleScaledW, 28073 height: eleScaledH, 28074 scaledLabelShown: scaledLabelShown 28075 }; 28076 txr.usedWidth += Math.ceil(eleScaledW + eleTxrSpacing); 28077 txr.eleCaches.push(eleCache); 28078 lookup.set(ele, lvl, eleCache); 28079 self.checkTextureFullness(txr); 28080 return eleCache; 28081 }; 28082 28083 ETCp.invalidateElements = function (eles) { 28084 for (var i = 0; i < eles.length; i++) { 28085 this.invalidateElement(eles[i]); 28086 } 28087 }; 28088 28089 ETCp.invalidateElement = function (ele) { 28090 var self = this; 28091 var lookup = self.lookup; 28092 var caches = []; 28093 var invalid = lookup.isInvalid(ele); 28094 28095 if (!invalid) { 28096 return; // override the invalidation request if the element key has not changed 28097 } 28098 28099 for (var lvl = minLvl; lvl <= maxLvl; lvl++) { 28100 var cache = lookup.getForCachedKey(ele, lvl); 28101 28102 if (cache) { 28103 caches.push(cache); 28104 } 28105 } 28106 28107 var noOtherElesUseCache = lookup.invalidate(ele); 28108 28109 if (noOtherElesUseCache) { 28110 for (var i = 0; i < caches.length; i++) { 28111 var _cache = caches[i]; 28112 var txr = _cache.texture; // remove space from the texture it belongs to 28113 28114 txr.invalidatedWidth += _cache.width; // mark the cache as invalidated 28115 28116 _cache.invalidated = true; // retire the texture if its utility is low 28117 28118 self.checkTextureUtility(txr); 28119 } 28120 } // remove from queue since the old req was for the old state 28121 28122 28123 self.removeFromQueue(ele); 28124 }; 28125 28126 ETCp.checkTextureUtility = function (txr) { 28127 // invalidate all entries in the cache if the cache size is small 28128 if (txr.invalidatedWidth >= minUtility * txr.width) { 28129 this.retireTexture(txr); 28130 } 28131 }; 28132 28133 ETCp.checkTextureFullness = function (txr) { 28134 // if texture has been mostly filled and passed over several times, remove 28135 // it from the queue so we don't need to waste time looking at it to put new things 28136 var self = this; 28137 var txrQ = self.getTextureQueue(txr.height); 28138 28139 if (txr.usedWidth / txr.width > maxFullness && txr.fullnessChecks >= maxFullnessChecks) { 28140 removeFromArray(txrQ, txr); 28141 } else { 28142 txr.fullnessChecks++; 28143 } 28144 }; 28145 28146 ETCp.retireTexture = function (txr) { 28147 var self = this; 28148 var txrH = txr.height; 28149 var txrQ = self.getTextureQueue(txrH); 28150 var lookup = this.lookup; // retire the texture from the active / searchable queue: 28151 28152 removeFromArray(txrQ, txr); 28153 txr.retired = true; // remove the refs from the eles to the caches: 28154 28155 var eleCaches = txr.eleCaches; 28156 28157 for (var i = 0; i < eleCaches.length; i++) { 28158 var eleCache = eleCaches[i]; 28159 lookup.deleteCache(eleCache.key, eleCache.level); 28160 } 28161 28162 clearArray(eleCaches); // add the texture to a retired queue so it can be recycled in future: 28163 28164 var rtxtrQ = self.getRetiredTextureQueue(txrH); 28165 rtxtrQ.push(txr); 28166 }; 28167 28168 ETCp.addTexture = function (txrH, minW) { 28169 var self = this; 28170 var txrQ = self.getTextureQueue(txrH); 28171 var txr = {}; 28172 txrQ.push(txr); 28173 txr.eleCaches = []; 28174 txr.height = txrH; 28175 txr.width = Math.max(defTxrWidth, minW); 28176 txr.usedWidth = 0; 28177 txr.invalidatedWidth = 0; 28178 txr.fullnessChecks = 0; 28179 txr.canvas = self.renderer.makeOffscreenCanvas(txr.width, txr.height); 28180 txr.context = txr.canvas.getContext('2d'); 28181 return txr; 28182 }; 28183 28184 ETCp.recycleTexture = function (txrH, minW) { 28185 var self = this; 28186 var txrQ = self.getTextureQueue(txrH); 28187 var rtxtrQ = self.getRetiredTextureQueue(txrH); 28188 28189 for (var i = 0; i < rtxtrQ.length; i++) { 28190 var txr = rtxtrQ[i]; 28191 28192 if (txr.width >= minW) { 28193 txr.retired = false; 28194 txr.usedWidth = 0; 28195 txr.invalidatedWidth = 0; 28196 txr.fullnessChecks = 0; 28197 clearArray(txr.eleCaches); 28198 txr.context.setTransform(1, 0, 0, 1, 0, 0); 28199 txr.context.clearRect(0, 0, txr.width, txr.height); 28200 removeFromArray(rtxtrQ, txr); 28201 txrQ.push(txr); 28202 return txr; 28203 } 28204 } 28205 }; 28206 28207 ETCp.queueElement = function (ele, lvl) { 28208 var self = this; 28209 var q = self.getElementQueue(); 28210 var k2q = self.getElementKeyToQueue(); 28211 var key = this.getKey(ele); 28212 var existingReq = k2q[key]; 28213 28214 if (existingReq) { 28215 // use the max lvl b/c in between lvls are cheap to make 28216 existingReq.level = Math.max(existingReq.level, lvl); 28217 existingReq.eles.merge(ele); 28218 existingReq.reqs++; 28219 q.updateItem(existingReq); 28220 } else { 28221 var req = { 28222 eles: ele.spawn().merge(ele), 28223 level: lvl, 28224 reqs: 1, 28225 key: key 28226 }; 28227 q.push(req); 28228 k2q[key] = req; 28229 } 28230 }; 28231 28232 ETCp.dequeue = function (pxRatio 28233 /*, extent*/ 28234 ) { 28235 var self = this; 28236 var q = self.getElementQueue(); 28237 var k2q = self.getElementKeyToQueue(); 28238 var dequeued = []; 28239 var lookup = self.lookup; 28240 28241 for (var i = 0; i < maxDeqSize; i++) { 28242 if (q.size() > 0) { 28243 var req = q.pop(); 28244 var key = req.key; 28245 var ele = req.eles[0]; // all eles have the same key 28246 28247 var cacheExists = lookup.hasCache(ele, req.level); // clear out the key to req lookup 28248 28249 k2q[key] = null; // dequeueing isn't necessary with an existing cache 28250 28251 if (cacheExists) { 28252 continue; 28253 } 28254 28255 dequeued.push(req); 28256 var bb = self.getBoundingBox(ele); 28257 self.getElement(ele, bb, pxRatio, req.level, getTxrReasons.dequeue); 28258 } else { 28259 break; 28260 } 28261 } 28262 28263 return dequeued; 28264 }; 28265 28266 ETCp.removeFromQueue = function (ele) { 28267 var self = this; 28268 var q = self.getElementQueue(); 28269 var k2q = self.getElementKeyToQueue(); 28270 var key = this.getKey(ele); 28271 var req = k2q[key]; 28272 28273 if (req != null) { 28274 if (req.eles.length === 1) { 28275 // remove if last ele in the req 28276 // bring to front of queue 28277 req.reqs = MAX_INT; 28278 q.updateItem(req); 28279 q.pop(); // remove from queue 28280 28281 k2q[key] = null; // remove from lookup map 28282 } else { 28283 // otherwise just remove ele from req 28284 req.eles.unmerge(ele); 28285 } 28286 } 28287 }; 28288 28289 ETCp.onDequeue = function (fn) { 28290 this.onDequeues.push(fn); 28291 }; 28292 28293 ETCp.offDequeue = function (fn) { 28294 removeFromArray(this.onDequeues, fn); 28295 }; 28296 28297 ETCp.setupDequeueing = defs.setupDequeueing({ 28298 deqRedrawThreshold: deqRedrawThreshold, 28299 deqCost: deqCost, 28300 deqAvgCost: deqAvgCost, 28301 deqNoDrawCost: deqNoDrawCost, 28302 deqFastCost: deqFastCost, 28303 deq: function deq(self, pxRatio, extent) { 28304 return self.dequeue(pxRatio, extent); 28305 }, 28306 onDeqd: function onDeqd(self, deqd) { 28307 for (var i = 0; i < self.onDequeues.length; i++) { 28308 var fn = self.onDequeues[i]; 28309 fn(deqd); 28310 } 28311 }, 28312 shouldRedraw: function shouldRedraw(self, deqd, pxRatio, extent) { 28313 for (var i = 0; i < deqd.length; i++) { 28314 var eles = deqd[i].eles; 28315 28316 for (var j = 0; j < eles.length; j++) { 28317 var bb = eles[j].boundingBox(); 28318 28319 if (boundingBoxesIntersect(bb, extent)) { 28320 return true; 28321 } 28322 } 28323 } 28324 28325 return false; 28326 }, 28327 priority: function priority(self) { 28328 return self.renderer.beforeRenderPriorities.eleTxrDeq; 28329 } 28330 }); 28331 28332 var defNumLayers = 1; // default number of layers to use 28333 28334 var minLvl$1 = -4; // when scaling smaller than that we don't need to re-render 28335 28336 var maxLvl$1 = 2; // when larger than this scale just render directly (caching is not helpful) 28337 28338 var maxZoom$1 = 3.99; // beyond this zoom level, layered textures are not used 28339 28340 var deqRedrawThreshold$1 = 50; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 28341 28342 var refineEleDebounceTime = 50; // time to debounce sharper ele texture updates 28343 28344 var deqCost$1 = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 28345 28346 var deqAvgCost$1 = 0.1; // % of add'l rendering cost compared to average overall redraw time 28347 28348 var deqNoDrawCost$1 = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 28349 28350 var deqFastCost$1 = 0.9; // % of frame time to be used when >60fps 28351 28352 var maxDeqSize$1 = 1; // number of eles to dequeue and render at higher texture in each batch 28353 28354 var invalidThreshold = 250; // time threshold for disabling b/c of invalidations 28355 28356 var maxLayerArea = 4000 * 4000; // layers can't be bigger than this 28357 28358 var useHighQualityEleTxrReqs = true; // whether to use high quality ele txr requests (generally faster and cheaper in the longterm) 28359 // var log = function(){ console.log.apply( console, arguments ); }; 28360 28361 var LayeredTextureCache = function LayeredTextureCache(renderer) { 28362 var self = this; 28363 var r = self.renderer = renderer; 28364 var cy = r.cy; 28365 self.layersByLevel = {}; // e.g. 2 => [ layer1, layer2, ..., layerN ] 28366 28367 self.firstGet = true; 28368 self.lastInvalidationTime = performanceNow() - 2 * invalidThreshold; 28369 self.skipping = false; 28370 self.eleTxrDeqs = cy.collection(); 28371 self.scheduleElementRefinement = lodash_debounce(function () { 28372 self.refineElementTextures(self.eleTxrDeqs); 28373 self.eleTxrDeqs.unmerge(self.eleTxrDeqs); 28374 }, refineEleDebounceTime); 28375 r.beforeRender(function (willDraw, now) { 28376 if (now - self.lastInvalidationTime <= invalidThreshold) { 28377 self.skipping = true; 28378 } else { 28379 self.skipping = false; 28380 } 28381 }, r.beforeRenderPriorities.lyrTxrSkip); 28382 28383 var qSort = function qSort(a, b) { 28384 return b.reqs - a.reqs; 28385 }; 28386 28387 self.layersQueue = new heap$1(qSort); 28388 self.setupDequeueing(); 28389 }; 28390 28391 var LTCp = LayeredTextureCache.prototype; 28392 var layerIdPool = 0; 28393 var MAX_INT$1 = Math.pow(2, 53) - 1; 28394 28395 LTCp.makeLayer = function (bb, lvl) { 28396 var scale = Math.pow(2, lvl); 28397 var w = Math.ceil(bb.w * scale); 28398 var h = Math.ceil(bb.h * scale); 28399 var canvas = this.renderer.makeOffscreenCanvas(w, h); 28400 var layer = { 28401 id: layerIdPool = ++layerIdPool % MAX_INT$1, 28402 bb: bb, 28403 level: lvl, 28404 width: w, 28405 height: h, 28406 canvas: canvas, 28407 context: canvas.getContext('2d'), 28408 eles: [], 28409 elesQueue: [], 28410 reqs: 0 28411 }; // log('make layer %s with w %s and h %s and lvl %s', layer.id, layer.width, layer.height, layer.level); 28412 28413 var cxt = layer.context; 28414 var dx = -layer.bb.x1; 28415 var dy = -layer.bb.y1; // do the transform on creation to save cycles (it's the same for all eles) 28416 28417 cxt.scale(scale, scale); 28418 cxt.translate(dx, dy); 28419 return layer; 28420 }; 28421 28422 LTCp.getLayers = function (eles, pxRatio, lvl) { 28423 var self = this; 28424 var r = self.renderer; 28425 var cy = r.cy; 28426 var zoom = cy.zoom(); 28427 var firstGet = self.firstGet; 28428 self.firstGet = false; // log('--\nget layers with %s eles', eles.length); 28429 //log eles.map(function(ele){ return ele.id() }) ); 28430 28431 if (lvl == null) { 28432 lvl = Math.ceil(log2(zoom * pxRatio)); 28433 28434 if (lvl < minLvl$1) { 28435 lvl = minLvl$1; 28436 } else if (zoom >= maxZoom$1 || lvl > maxLvl$1) { 28437 return null; 28438 } 28439 } 28440 28441 self.validateLayersElesOrdering(lvl, eles); 28442 var layersByLvl = self.layersByLevel; 28443 var scale = Math.pow(2, lvl); 28444 var layers = layersByLvl[lvl] = layersByLvl[lvl] || []; 28445 var bb; 28446 var lvlComplete = self.levelIsComplete(lvl, eles); 28447 var tmpLayers; 28448 28449 var checkTempLevels = function checkTempLevels() { 28450 var canUseAsTmpLvl = function canUseAsTmpLvl(l) { 28451 self.validateLayersElesOrdering(l, eles); 28452 28453 if (self.levelIsComplete(l, eles)) { 28454 tmpLayers = layersByLvl[l]; 28455 return true; 28456 } 28457 }; 28458 28459 var checkLvls = function checkLvls(dir) { 28460 if (tmpLayers) { 28461 return; 28462 } 28463 28464 for (var l = lvl + dir; minLvl$1 <= l && l <= maxLvl$1; l += dir) { 28465 if (canUseAsTmpLvl(l)) { 28466 break; 28467 } 28468 } 28469 }; 28470 28471 checkLvls(+1); 28472 checkLvls(-1); // remove the invalid layers; they will be replaced as needed later in this function 28473 28474 for (var i = layers.length - 1; i >= 0; i--) { 28475 var layer = layers[i]; 28476 28477 if (layer.invalid) { 28478 removeFromArray(layers, layer); 28479 } 28480 } 28481 }; 28482 28483 if (!lvlComplete) { 28484 // if the current level is incomplete, then use the closest, best quality layerset temporarily 28485 // and later queue the current layerset so we can get the proper quality level soon 28486 checkTempLevels(); 28487 } else { 28488 // log('level complete, using existing layers\n--'); 28489 return layers; 28490 } 28491 28492 var getBb = function getBb() { 28493 if (!bb) { 28494 bb = makeBoundingBox(); 28495 28496 for (var i = 0; i < eles.length; i++) { 28497 updateBoundingBox(bb, eles[i].boundingBox()); 28498 } 28499 } 28500 28501 return bb; 28502 }; 28503 28504 var makeLayer = function makeLayer(opts) { 28505 opts = opts || {}; 28506 var after = opts.after; 28507 getBb(); 28508 var area = bb.w * scale * (bb.h * scale); 28509 28510 if (area > maxLayerArea) { 28511 return null; 28512 } 28513 28514 var layer = self.makeLayer(bb, lvl); 28515 28516 if (after != null) { 28517 var index = layers.indexOf(after) + 1; 28518 layers.splice(index, 0, layer); 28519 } else if (opts.insert === undefined || opts.insert) { 28520 // no after specified => first layer made so put at start 28521 layers.unshift(layer); 28522 } // if( tmpLayers ){ 28523 //self.queueLayer( layer ); 28524 // } 28525 28526 28527 return layer; 28528 }; 28529 28530 if (self.skipping && !firstGet) { 28531 // log('skip layers'); 28532 return null; 28533 } // log('do layers'); 28534 28535 28536 var layer = null; 28537 var maxElesPerLayer = eles.length / defNumLayers; 28538 var allowLazyQueueing = !firstGet; 28539 28540 for (var i = 0; i < eles.length; i++) { 28541 var ele = eles[i]; 28542 var rs = ele._private.rscratch; 28543 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; // log('look at ele', ele.id()); 28544 28545 var existingLayer = caches[lvl]; 28546 28547 if (existingLayer) { 28548 // reuse layer for later eles 28549 // log('reuse layer for', ele.id()); 28550 layer = existingLayer; 28551 continue; 28552 } 28553 28554 if (!layer || layer.eles.length >= maxElesPerLayer || !boundingBoxInBoundingBox(layer.bb, ele.boundingBox())) { 28555 // log('make new layer for ele %s', ele.id()); 28556 layer = makeLayer({ 28557 insert: true, 28558 after: layer 28559 }); // if now layer can be built then we can't use layers at this level 28560 28561 if (!layer) { 28562 return null; 28563 } // log('new layer with id %s', layer.id); 28564 28565 } 28566 28567 if (tmpLayers || allowLazyQueueing) { 28568 // log('queue ele %s in layer %s', ele.id(), layer.id); 28569 self.queueLayer(layer, ele); 28570 } else { 28571 // log('draw ele %s in layer %s', ele.id(), layer.id); 28572 self.drawEleInLayer(layer, ele, lvl, pxRatio); 28573 } 28574 28575 layer.eles.push(ele); 28576 caches[lvl] = layer; 28577 } // log('--'); 28578 28579 28580 if (tmpLayers) { 28581 // then we only queued the current layerset and can't draw it yet 28582 return tmpLayers; 28583 } 28584 28585 if (allowLazyQueueing) { 28586 // log('lazy queue level', lvl); 28587 return null; 28588 } 28589 28590 return layers; 28591 }; // a layer may want to use an ele cache of a higher level to avoid blurriness 28592 // so the layer level might not equal the ele level 28593 28594 28595 LTCp.getEleLevelForLayerLevel = function (lvl, pxRatio) { 28596 return lvl; 28597 }; 28598 28599 LTCp.drawEleInLayer = function (layer, ele, lvl, pxRatio) { 28600 var self = this; 28601 var r = this.renderer; 28602 var context = layer.context; 28603 var bb = ele.boundingBox(); 28604 28605 if (bb.w === 0 || bb.h === 0 || !ele.visible()) { 28606 return; 28607 } 28608 28609 lvl = self.getEleLevelForLayerLevel(lvl, pxRatio); 28610 28611 { 28612 r.setImgSmoothing(context, false); 28613 } 28614 28615 { 28616 r.drawCachedElement(context, ele, null, null, lvl, useHighQualityEleTxrReqs); 28617 } 28618 28619 { 28620 r.setImgSmoothing(context, true); 28621 } 28622 }; 28623 28624 LTCp.levelIsComplete = function (lvl, eles) { 28625 var self = this; 28626 var layers = self.layersByLevel[lvl]; 28627 28628 if (!layers || layers.length === 0) { 28629 return false; 28630 } 28631 28632 var numElesInLayers = 0; 28633 28634 for (var i = 0; i < layers.length; i++) { 28635 var layer = layers[i]; // if there are any eles needed to be drawn yet, the level is not complete 28636 28637 if (layer.reqs > 0) { 28638 return false; 28639 } // if the layer is invalid, the level is not complete 28640 28641 28642 if (layer.invalid) { 28643 return false; 28644 } 28645 28646 numElesInLayers += layer.eles.length; 28647 } // we should have exactly the number of eles passed in to be complete 28648 28649 28650 if (numElesInLayers !== eles.length) { 28651 return false; 28652 } 28653 28654 return true; 28655 }; 28656 28657 LTCp.validateLayersElesOrdering = function (lvl, eles) { 28658 var layers = this.layersByLevel[lvl]; 28659 28660 if (!layers) { 28661 return; 28662 } // if in a layer the eles are not in the same order, then the layer is invalid 28663 // (i.e. there is an ele in between the eles in the layer) 28664 28665 28666 for (var i = 0; i < layers.length; i++) { 28667 var layer = layers[i]; 28668 var offset = -1; // find the offset 28669 28670 for (var j = 0; j < eles.length; j++) { 28671 if (layer.eles[0] === eles[j]) { 28672 offset = j; 28673 break; 28674 } 28675 } 28676 28677 if (offset < 0) { 28678 // then the layer has nonexistant elements and is invalid 28679 this.invalidateLayer(layer); 28680 continue; 28681 } // the eles in the layer must be in the same continuous order, else the layer is invalid 28682 28683 28684 var o = offset; 28685 28686 for (var j = 0; j < layer.eles.length; j++) { 28687 if (layer.eles[j] !== eles[o + j]) { 28688 // log('invalidate based on ordering', layer.id); 28689 this.invalidateLayer(layer); 28690 break; 28691 } 28692 } 28693 } 28694 }; 28695 28696 LTCp.updateElementsInLayers = function (eles, update) { 28697 var self = this; 28698 var isEles = element(eles[0]); // collect udpated elements (cascaded from the layers) and update each 28699 // layer itself along the way 28700 28701 for (var i = 0; i < eles.length; i++) { 28702 var req = isEles ? null : eles[i]; 28703 var ele = isEles ? eles[i] : eles[i].ele; 28704 var rs = ele._private.rscratch; 28705 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; 28706 28707 for (var l = minLvl$1; l <= maxLvl$1; l++) { 28708 var layer = caches[l]; 28709 28710 if (!layer) { 28711 continue; 28712 } // if update is a request from the ele cache, then it affects only 28713 // the matching level 28714 28715 28716 if (req && self.getEleLevelForLayerLevel(layer.level) !== req.level) { 28717 continue; 28718 } 28719 28720 update(layer, ele, req); 28721 } 28722 } 28723 }; 28724 28725 LTCp.haveLayers = function () { 28726 var self = this; 28727 var haveLayers = false; 28728 28729 for (var l = minLvl$1; l <= maxLvl$1; l++) { 28730 var layers = self.layersByLevel[l]; 28731 28732 if (layers && layers.length > 0) { 28733 haveLayers = true; 28734 break; 28735 } 28736 } 28737 28738 return haveLayers; 28739 }; 28740 28741 LTCp.invalidateElements = function (eles) { 28742 var self = this; 28743 28744 if (eles.length === 0) { 28745 return; 28746 } 28747 28748 self.lastInvalidationTime = performanceNow(); // log('update invalidate layer time from eles'); 28749 28750 if (eles.length === 0 || !self.haveLayers()) { 28751 return; 28752 } 28753 28754 self.updateElementsInLayers(eles, function invalAssocLayers(layer, ele, req) { 28755 self.invalidateLayer(layer); 28756 }); 28757 }; 28758 28759 LTCp.invalidateLayer = function (layer) { 28760 // log('update invalidate layer time'); 28761 this.lastInvalidationTime = performanceNow(); 28762 28763 if (layer.invalid) { 28764 return; 28765 } // save cycles 28766 28767 28768 var lvl = layer.level; 28769 var eles = layer.eles; 28770 var layers = this.layersByLevel[lvl]; // log('invalidate layer', layer.id ); 28771 28772 removeFromArray(layers, layer); // layer.eles = []; 28773 28774 layer.elesQueue = []; 28775 layer.invalid = true; 28776 28777 if (layer.replacement) { 28778 layer.replacement.invalid = true; 28779 } 28780 28781 for (var i = 0; i < eles.length; i++) { 28782 var caches = eles[i]._private.rscratch.imgLayerCaches; 28783 28784 if (caches) { 28785 caches[lvl] = null; 28786 } 28787 } 28788 }; 28789 28790 LTCp.refineElementTextures = function (eles) { 28791 var self = this; // log('refine', eles.length); 28792 28793 self.updateElementsInLayers(eles, function refineEachEle(layer, ele, req) { 28794 var rLyr = layer.replacement; 28795 28796 if (!rLyr) { 28797 rLyr = layer.replacement = self.makeLayer(layer.bb, layer.level); 28798 rLyr.replaces = layer; 28799 rLyr.eles = layer.eles; // log('make replacement layer %s for %s with level %s', rLyr.id, layer.id, rLyr.level); 28800 } 28801 28802 if (!rLyr.reqs) { 28803 for (var i = 0; i < rLyr.eles.length; i++) { 28804 self.queueLayer(rLyr, rLyr.eles[i]); 28805 } // log('queue replacement layer refinement', rLyr.id); 28806 28807 } 28808 }); 28809 }; 28810 28811 LTCp.enqueueElementRefinement = function (ele) { 28812 28813 this.eleTxrDeqs.merge(ele); 28814 this.scheduleElementRefinement(); 28815 }; 28816 28817 LTCp.queueLayer = function (layer, ele) { 28818 var self = this; 28819 var q = self.layersQueue; 28820 var elesQ = layer.elesQueue; 28821 var hasId = elesQ.hasId = elesQ.hasId || {}; // if a layer is going to be replaced, queuing is a waste of time 28822 28823 if (layer.replacement) { 28824 return; 28825 } 28826 28827 if (ele) { 28828 if (hasId[ele.id()]) { 28829 return; 28830 } 28831 28832 elesQ.push(ele); 28833 hasId[ele.id()] = true; 28834 } 28835 28836 if (layer.reqs) { 28837 layer.reqs++; 28838 q.updateItem(layer); 28839 } else { 28840 layer.reqs = 1; 28841 q.push(layer); 28842 } 28843 }; 28844 28845 LTCp.dequeue = function (pxRatio) { 28846 var self = this; 28847 var q = self.layersQueue; 28848 var deqd = []; 28849 var eleDeqs = 0; 28850 28851 while (eleDeqs < maxDeqSize$1) { 28852 if (q.size() === 0) { 28853 break; 28854 } 28855 28856 var layer = q.peek(); // if a layer has been or will be replaced, then don't waste time with it 28857 28858 if (layer.replacement) { 28859 // log('layer %s in queue skipped b/c it already has a replacement', layer.id); 28860 q.pop(); 28861 continue; 28862 } // if this is a replacement layer that has been superceded, then forget it 28863 28864 28865 if (layer.replaces && layer !== layer.replaces.replacement) { 28866 // log('layer is no longer the most uptodate replacement; dequeued', layer.id) 28867 q.pop(); 28868 continue; 28869 } 28870 28871 if (layer.invalid) { 28872 // log('replacement layer %s is invalid; dequeued', layer.id); 28873 q.pop(); 28874 continue; 28875 } 28876 28877 var ele = layer.elesQueue.shift(); 28878 28879 if (ele) { 28880 // log('dequeue layer %s', layer.id); 28881 self.drawEleInLayer(layer, ele, layer.level, pxRatio); 28882 eleDeqs++; 28883 } 28884 28885 if (deqd.length === 0) { 28886 // we need only one entry in deqd to queue redrawing etc 28887 deqd.push(true); 28888 } // if the layer has all its eles done, then remove from the queue 28889 28890 28891 if (layer.elesQueue.length === 0) { 28892 q.pop(); 28893 layer.reqs = 0; // log('dequeue of layer %s complete', layer.id); 28894 // when a replacement layer is dequeued, it replaces the old layer in the level 28895 28896 if (layer.replaces) { 28897 self.applyLayerReplacement(layer); 28898 } 28899 28900 self.requestRedraw(); 28901 } 28902 } 28903 28904 return deqd; 28905 }; 28906 28907 LTCp.applyLayerReplacement = function (layer) { 28908 var self = this; 28909 var layersInLevel = self.layersByLevel[layer.level]; 28910 var replaced = layer.replaces; 28911 var index = layersInLevel.indexOf(replaced); // if the replaced layer is not in the active list for the level, then replacing 28912 // refs would be a mistake (i.e. overwriting the true active layer) 28913 28914 if (index < 0 || replaced.invalid) { 28915 // log('replacement layer would have no effect', layer.id); 28916 return; 28917 } 28918 28919 layersInLevel[index] = layer; // replace level ref 28920 // replace refs in eles 28921 28922 for (var i = 0; i < layer.eles.length; i++) { 28923 var _p = layer.eles[i]._private; 28924 var cache = _p.imgLayerCaches = _p.imgLayerCaches || {}; 28925 28926 if (cache) { 28927 cache[layer.level] = layer; 28928 } 28929 } // log('apply replacement layer %s over %s', layer.id, replaced.id); 28930 28931 28932 self.requestRedraw(); 28933 }; 28934 28935 LTCp.requestRedraw = lodash_debounce(function () { 28936 var r = this.renderer; 28937 r.redrawHint('eles', true); 28938 r.redrawHint('drag', true); 28939 r.redraw(); 28940 }, 100); 28941 LTCp.setupDequeueing = defs.setupDequeueing({ 28942 deqRedrawThreshold: deqRedrawThreshold$1, 28943 deqCost: deqCost$1, 28944 deqAvgCost: deqAvgCost$1, 28945 deqNoDrawCost: deqNoDrawCost$1, 28946 deqFastCost: deqFastCost$1, 28947 deq: function deq(self, pxRatio) { 28948 return self.dequeue(pxRatio); 28949 }, 28950 onDeqd: noop, 28951 shouldRedraw: trueify, 28952 priority: function priority(self) { 28953 return self.renderer.beforeRenderPriorities.lyrTxrDeq; 28954 } 28955 }); 28956 28957 var CRp = {}; 28958 var impl; 28959 28960 function polygon(context, points) { 28961 for (var i = 0; i < points.length; i++) { 28962 var pt = points[i]; 28963 context.lineTo(pt.x, pt.y); 28964 } 28965 } 28966 28967 function triangleBackcurve(context, points, controlPoint) { 28968 var firstPt; 28969 28970 for (var i = 0; i < points.length; i++) { 28971 var pt = points[i]; 28972 28973 if (i === 0) { 28974 firstPt = pt; 28975 } 28976 28977 context.lineTo(pt.x, pt.y); 28978 } 28979 28980 context.quadraticCurveTo(controlPoint.x, controlPoint.y, firstPt.x, firstPt.y); 28981 } 28982 28983 function triangleTee(context, trianglePoints, teePoints) { 28984 if (context.beginPath) { 28985 context.beginPath(); 28986 } 28987 28988 var triPts = trianglePoints; 28989 28990 for (var i = 0; i < triPts.length; i++) { 28991 var pt = triPts[i]; 28992 context.lineTo(pt.x, pt.y); 28993 } 28994 28995 var teePts = teePoints; 28996 var firstTeePt = teePoints[0]; 28997 context.moveTo(firstTeePt.x, firstTeePt.y); 28998 28999 for (var i = 1; i < teePts.length; i++) { 29000 var pt = teePts[i]; 29001 context.lineTo(pt.x, pt.y); 29002 } 29003 29004 if (context.closePath) { 29005 context.closePath(); 29006 } 29007 } 29008 29009 function circle(context, rx, ry, r) { 29010 context.arc(rx, ry, r, 0, Math.PI * 2, false); 29011 } 29012 29013 CRp.arrowShapeImpl = function (name) { 29014 return (impl || (impl = { 29015 'polygon': polygon, 29016 'triangle-backcurve': triangleBackcurve, 29017 'triangle-tee': triangleTee, 29018 'triangle-cross': triangleTee, 29019 'circle': circle 29020 }))[name]; 29021 }; 29022 29023 var CRp$1 = {}; 29024 29025 CRp$1.drawElement = function (context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity) { 29026 var r = this; 29027 29028 if (ele.isNode()) { 29029 r.drawNode(context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity); 29030 } else { 29031 r.drawEdge(context, ele, shiftToOriginWithBb, showLabel, showOverlay, showOpacity); 29032 } 29033 }; 29034 29035 CRp$1.drawElementOverlay = function (context, ele) { 29036 var r = this; 29037 29038 if (ele.isNode()) { 29039 r.drawNodeOverlay(context, ele); 29040 } else { 29041 r.drawEdgeOverlay(context, ele); 29042 } 29043 }; 29044 29045 CRp$1.drawCachedElementPortion = function (context, ele, eleTxrCache, pxRatio, lvl, reason, getRotation, getOpacity) { 29046 var r = this; 29047 var bb = eleTxrCache.getBoundingBox(ele); 29048 29049 if (bb.w === 0 || bb.h === 0) { 29050 return; 29051 } // ignore zero size case 29052 29053 29054 var eleCache = eleTxrCache.getElement(ele, bb, pxRatio, lvl, reason); 29055 29056 if (eleCache != null) { 29057 var opacity = getOpacity(r, ele); 29058 29059 if (opacity === 0) { 29060 return; 29061 } 29062 29063 var theta = getRotation(r, ele); 29064 var x1 = bb.x1, 29065 y1 = bb.y1, 29066 w = bb.w, 29067 h = bb.h; 29068 var x, y, sx, sy, smooth; 29069 29070 if (theta !== 0) { 29071 var rotPt = eleTxrCache.getRotationPoint(ele); 29072 sx = rotPt.x; 29073 sy = rotPt.y; 29074 context.translate(sx, sy); 29075 context.rotate(theta); 29076 smooth = r.getImgSmoothing(context); 29077 29078 if (!smooth) { 29079 r.setImgSmoothing(context, true); 29080 } 29081 29082 var off = eleTxrCache.getRotationOffset(ele); 29083 x = off.x; 29084 y = off.y; 29085 } else { 29086 x = x1; 29087 y = y1; 29088 } 29089 29090 var oldGlobalAlpha; 29091 29092 if (opacity !== 1) { 29093 oldGlobalAlpha = context.globalAlpha; 29094 context.globalAlpha = oldGlobalAlpha * opacity; 29095 } 29096 29097 context.drawImage(eleCache.texture.canvas, eleCache.x, 0, eleCache.width, eleCache.height, x, y, w, h); 29098 29099 if (opacity !== 1) { 29100 context.globalAlpha = oldGlobalAlpha; 29101 } 29102 29103 if (theta !== 0) { 29104 context.rotate(-theta); 29105 context.translate(-sx, -sy); 29106 29107 if (!smooth) { 29108 r.setImgSmoothing(context, false); 29109 } 29110 } 29111 } else { 29112 eleTxrCache.drawElement(context, ele); // direct draw fallback 29113 } 29114 }; 29115 29116 var getZeroRotation = function getZeroRotation() { 29117 return 0; 29118 }; 29119 29120 var getLabelRotation = function getLabelRotation(r, ele) { 29121 return r.getTextAngle(ele, null); 29122 }; 29123 29124 var getSourceLabelRotation = function getSourceLabelRotation(r, ele) { 29125 return r.getTextAngle(ele, 'source'); 29126 }; 29127 29128 var getTargetLabelRotation = function getTargetLabelRotation(r, ele) { 29129 return r.getTextAngle(ele, 'target'); 29130 }; 29131 29132 var getOpacity = function getOpacity(r, ele) { 29133 return ele.effectiveOpacity(); 29134 }; 29135 29136 var getTextOpacity = function getTextOpacity(e, ele) { 29137 return ele.pstyle('text-opacity').pfValue * ele.effectiveOpacity(); 29138 }; 29139 29140 CRp$1.drawCachedElement = function (context, ele, pxRatio, extent, lvl, requestHighQuality) { 29141 var r = this; 29142 var _r$data = r.data, 29143 eleTxrCache = _r$data.eleTxrCache, 29144 lblTxrCache = _r$data.lblTxrCache, 29145 slbTxrCache = _r$data.slbTxrCache, 29146 tlbTxrCache = _r$data.tlbTxrCache; 29147 var bb = ele.boundingBox(); 29148 var reason = requestHighQuality === true ? eleTxrCache.reasons.highQuality : null; 29149 29150 if (bb.w === 0 || bb.h === 0 || !ele.visible()) { 29151 return; 29152 } 29153 29154 if (!extent || boundingBoxesIntersect(bb, extent)) { 29155 var isEdge = ele.isEdge(); 29156 29157 var badLine = ele.element()._private.rscratch.badLine; 29158 29159 r.drawCachedElementPortion(context, ele, eleTxrCache, pxRatio, lvl, reason, getZeroRotation, getOpacity); 29160 29161 if (!isEdge || !badLine) { 29162 r.drawCachedElementPortion(context, ele, lblTxrCache, pxRatio, lvl, reason, getLabelRotation, getTextOpacity); 29163 } 29164 29165 if (isEdge && !badLine) { 29166 r.drawCachedElementPortion(context, ele, slbTxrCache, pxRatio, lvl, reason, getSourceLabelRotation, getTextOpacity); 29167 r.drawCachedElementPortion(context, ele, tlbTxrCache, pxRatio, lvl, reason, getTargetLabelRotation, getTextOpacity); 29168 } 29169 29170 r.drawElementOverlay(context, ele); 29171 } 29172 }; 29173 29174 CRp$1.drawElements = function (context, eles) { 29175 var r = this; 29176 29177 for (var i = 0; i < eles.length; i++) { 29178 var ele = eles[i]; 29179 r.drawElement(context, ele); 29180 } 29181 }; 29182 29183 CRp$1.drawCachedElements = function (context, eles, pxRatio, extent) { 29184 var r = this; 29185 29186 for (var i = 0; i < eles.length; i++) { 29187 var ele = eles[i]; 29188 r.drawCachedElement(context, ele, pxRatio, extent); 29189 } 29190 }; 29191 29192 CRp$1.drawCachedNodes = function (context, eles, pxRatio, extent) { 29193 var r = this; 29194 29195 for (var i = 0; i < eles.length; i++) { 29196 var ele = eles[i]; 29197 29198 if (!ele.isNode()) { 29199 continue; 29200 } 29201 29202 r.drawCachedElement(context, ele, pxRatio, extent); 29203 } 29204 }; 29205 29206 CRp$1.drawLayeredElements = function (context, eles, pxRatio, extent) { 29207 var r = this; 29208 var layers = r.data.lyrTxrCache.getLayers(eles, pxRatio); 29209 29210 if (layers) { 29211 for (var i = 0; i < layers.length; i++) { 29212 var layer = layers[i]; 29213 var bb = layer.bb; 29214 29215 if (bb.w === 0 || bb.h === 0) { 29216 continue; 29217 } 29218 29219 context.drawImage(layer.canvas, bb.x1, bb.y1, bb.w, bb.h); 29220 } 29221 } else { 29222 // fall back on plain caching if no layers 29223 r.drawCachedElements(context, eles, pxRatio, extent); 29224 } 29225 }; 29226 29227 /* global Path2D */ 29228 var CRp$2 = {}; 29229 29230 CRp$2.drawEdge = function (context, edge, shiftToOriginWithBb) { 29231 var drawLabel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 29232 var shouldDrawOverlay = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 29233 var shouldDrawOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 29234 var r = this; 29235 var rs = edge._private.rscratch; 29236 29237 if (shouldDrawOpacity && !edge.visible()) { 29238 return; 29239 } // if bezier ctrl pts can not be calculated, then die 29240 29241 29242 if (rs.badLine || rs.allpts == null || isNaN(rs.allpts[0])) { 29243 // isNaN in case edge is impossible and browser bugs (e.g. safari) 29244 return; 29245 } 29246 29247 var bb; 29248 29249 if (shiftToOriginWithBb) { 29250 bb = shiftToOriginWithBb; 29251 context.translate(-bb.x1, -bb.y1); 29252 } 29253 29254 var opacity = shouldDrawOpacity ? edge.pstyle('opacity').value : 1; 29255 var lineStyle = edge.pstyle('line-style').value; 29256 var edgeWidth = edge.pstyle('width').pfValue; 29257 var lineCap = edge.pstyle('line-cap').value; 29258 29259 var drawLine = function drawLine() { 29260 var strokeOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : opacity; 29261 context.lineWidth = edgeWidth; 29262 context.lineCap = lineCap; 29263 r.eleStrokeStyle(context, edge, strokeOpacity); 29264 r.drawEdgePath(edge, context, rs.allpts, lineStyle); 29265 context.lineCap = 'butt'; // reset for other drawing functions 29266 }; 29267 29268 var drawOverlay = function drawOverlay() { 29269 if (!shouldDrawOverlay) { 29270 return; 29271 } 29272 29273 r.drawEdgeOverlay(context, edge); 29274 }; 29275 29276 var drawArrows = function drawArrows() { 29277 var arrowOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : opacity; 29278 r.drawArrowheads(context, edge, arrowOpacity); 29279 }; 29280 29281 var drawText = function drawText() { 29282 r.drawElementText(context, edge, null, drawLabel); 29283 }; 29284 29285 context.lineJoin = 'round'; 29286 var ghost = edge.pstyle('ghost').value === 'yes'; 29287 29288 if (ghost) { 29289 var gx = edge.pstyle('ghost-offset-x').pfValue; 29290 var gy = edge.pstyle('ghost-offset-y').pfValue; 29291 var ghostOpacity = edge.pstyle('ghost-opacity').value; 29292 var effectiveGhostOpacity = opacity * ghostOpacity; 29293 context.translate(gx, gy); 29294 drawLine(effectiveGhostOpacity); 29295 drawArrows(effectiveGhostOpacity); 29296 context.translate(-gx, -gy); 29297 } 29298 29299 drawLine(); 29300 drawArrows(); 29301 drawOverlay(); 29302 drawText(); 29303 29304 if (shiftToOriginWithBb) { 29305 context.translate(bb.x1, bb.y1); 29306 } 29307 }; 29308 29309 CRp$2.drawEdgeOverlay = function (context, edge) { 29310 if (!edge.visible()) { 29311 return; 29312 } 29313 29314 var overlayOpacity = edge.pstyle('overlay-opacity').value; 29315 29316 if (overlayOpacity === 0) { 29317 return; 29318 } 29319 29320 var r = this; 29321 var usePaths = r.usePaths(); 29322 var rs = edge._private.rscratch; 29323 var overlayPadding = edge.pstyle('overlay-padding').pfValue; 29324 var overlayWidth = 2 * overlayPadding; 29325 var overlayColor = edge.pstyle('overlay-color').value; 29326 context.lineWidth = overlayWidth; 29327 29328 if (rs.edgeType === 'self' && !usePaths) { 29329 context.lineCap = 'butt'; 29330 } else { 29331 context.lineCap = 'round'; 29332 } 29333 29334 r.colorStrokeStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); 29335 r.drawEdgePath(edge, context, rs.allpts, 'solid'); 29336 }; 29337 29338 CRp$2.drawEdgePath = function (edge, context, pts, type) { 29339 var rs = edge._private.rscratch; 29340 var canvasCxt = context; 29341 var path; 29342 var pathCacheHit = false; 29343 var usePaths = this.usePaths(); 29344 var lineDashPattern = edge.pstyle('line-dash-pattern').pfValue; 29345 var lineDashOffset = edge.pstyle('line-dash-offset').pfValue; 29346 29347 if (usePaths) { 29348 var pathCacheKey = pts.join('$'); 29349 var keyMatches = rs.pathCacheKey && rs.pathCacheKey === pathCacheKey; 29350 29351 if (keyMatches) { 29352 path = context = rs.pathCache; 29353 pathCacheHit = true; 29354 } else { 29355 path = context = new Path2D(); 29356 rs.pathCacheKey = pathCacheKey; 29357 rs.pathCache = path; 29358 } 29359 } 29360 29361 if (canvasCxt.setLineDash) { 29362 // for very outofdate browsers 29363 switch (type) { 29364 case 'dotted': 29365 canvasCxt.setLineDash([1, 1]); 29366 break; 29367 29368 case 'dashed': 29369 canvasCxt.setLineDash(lineDashPattern); 29370 canvasCxt.lineDashOffset = lineDashOffset; 29371 break; 29372 29373 case 'solid': 29374 canvasCxt.setLineDash([]); 29375 break; 29376 } 29377 } 29378 29379 if (!pathCacheHit && !rs.badLine) { 29380 if (context.beginPath) { 29381 context.beginPath(); 29382 } 29383 29384 context.moveTo(pts[0], pts[1]); 29385 29386 switch (rs.edgeType) { 29387 case 'bezier': 29388 case 'self': 29389 case 'compound': 29390 case 'multibezier': 29391 for (var i = 2; i + 3 < pts.length; i += 4) { 29392 context.quadraticCurveTo(pts[i], pts[i + 1], pts[i + 2], pts[i + 3]); 29393 } 29394 29395 break; 29396 29397 case 'straight': 29398 case 'segments': 29399 case 'haystack': 29400 for (var _i = 2; _i + 1 < pts.length; _i += 2) { 29401 context.lineTo(pts[_i], pts[_i + 1]); 29402 } 29403 29404 break; 29405 } 29406 } 29407 29408 context = canvasCxt; 29409 29410 if (usePaths) { 29411 context.stroke(path); 29412 } else { 29413 context.stroke(); 29414 } // reset any line dashes 29415 29416 29417 if (context.setLineDash) { 29418 // for very outofdate browsers 29419 context.setLineDash([]); 29420 } 29421 }; 29422 29423 CRp$2.drawArrowheads = function (context, edge, opacity) { 29424 var rs = edge._private.rscratch; 29425 var isHaystack = rs.edgeType === 'haystack'; 29426 29427 if (!isHaystack) { 29428 this.drawArrowhead(context, edge, 'source', rs.arrowStartX, rs.arrowStartY, rs.srcArrowAngle, opacity); 29429 } 29430 29431 this.drawArrowhead(context, edge, 'mid-target', rs.midX, rs.midY, rs.midtgtArrowAngle, opacity); 29432 this.drawArrowhead(context, edge, 'mid-source', rs.midX, rs.midY, rs.midsrcArrowAngle, opacity); 29433 29434 if (!isHaystack) { 29435 this.drawArrowhead(context, edge, 'target', rs.arrowEndX, rs.arrowEndY, rs.tgtArrowAngle, opacity); 29436 } 29437 }; 29438 29439 CRp$2.drawArrowhead = function (context, edge, prefix, x, y, angle, opacity) { 29440 if (isNaN(x) || x == null || isNaN(y) || y == null || isNaN(angle) || angle == null) { 29441 return; 29442 } 29443 29444 var self = this; 29445 var arrowShape = edge.pstyle(prefix + '-arrow-shape').value; 29446 29447 if (arrowShape === 'none') { 29448 return; 29449 } 29450 29451 var arrowClearFill = edge.pstyle(prefix + '-arrow-fill').value === 'hollow' ? 'both' : 'filled'; 29452 var arrowFill = edge.pstyle(prefix + '-arrow-fill').value; 29453 var edgeWidth = edge.pstyle('width').pfValue; 29454 var edgeOpacity = edge.pstyle('opacity').value; 29455 29456 if (opacity === undefined) { 29457 opacity = edgeOpacity; 29458 } 29459 29460 var gco = context.globalCompositeOperation; 29461 29462 if (opacity !== 1 || arrowFill === 'hollow') { 29463 // then extra clear is needed 29464 context.globalCompositeOperation = 'destination-out'; 29465 self.colorFillStyle(context, 255, 255, 255, 1); 29466 self.colorStrokeStyle(context, 255, 255, 255, 1); 29467 self.drawArrowShape(edge, context, arrowClearFill, edgeWidth, arrowShape, x, y, angle); 29468 context.globalCompositeOperation = gco; 29469 } // otherwise, the opaque arrow clears it for free :) 29470 29471 29472 var color = edge.pstyle(prefix + '-arrow-color').value; 29473 self.colorFillStyle(context, color[0], color[1], color[2], opacity); 29474 self.colorStrokeStyle(context, color[0], color[1], color[2], opacity); 29475 self.drawArrowShape(edge, context, arrowFill, edgeWidth, arrowShape, x, y, angle); 29476 }; 29477 29478 CRp$2.drawArrowShape = function (edge, context, fill, edgeWidth, shape, x, y, angle) { 29479 var r = this; 29480 var usePaths = this.usePaths() && shape !== 'triangle-cross'; 29481 var pathCacheHit = false; 29482 var path; 29483 var canvasContext = context; 29484 var translation = { 29485 x: x, 29486 y: y 29487 }; 29488 var scale = edge.pstyle('arrow-scale').value; 29489 var size = this.getArrowWidth(edgeWidth, scale); 29490 var shapeImpl = r.arrowShapes[shape]; 29491 29492 if (usePaths) { 29493 var cache = r.arrowPathCache = r.arrowPathCache || []; 29494 var key = hashString(shape); 29495 var cachedPath = cache[key]; 29496 29497 if (cachedPath != null) { 29498 path = context = cachedPath; 29499 pathCacheHit = true; 29500 } else { 29501 path = context = new Path2D(); 29502 cache[key] = path; 29503 } 29504 } 29505 29506 if (!pathCacheHit) { 29507 if (context.beginPath) { 29508 context.beginPath(); 29509 } 29510 29511 if (usePaths) { 29512 // store in the path cache with values easily manipulated later 29513 shapeImpl.draw(context, 1, 0, { 29514 x: 0, 29515 y: 0 29516 }, 1); 29517 } else { 29518 shapeImpl.draw(context, size, angle, translation, edgeWidth); 29519 } 29520 29521 if (context.closePath) { 29522 context.closePath(); 29523 } 29524 } 29525 29526 context = canvasContext; 29527 29528 if (usePaths) { 29529 // set transform to arrow position/orientation 29530 context.translate(x, y); 29531 context.rotate(angle); 29532 context.scale(size, size); 29533 } 29534 29535 if (fill === 'filled' || fill === 'both') { 29536 if (usePaths) { 29537 context.fill(path); 29538 } else { 29539 context.fill(); 29540 } 29541 } 29542 29543 if (fill === 'hollow' || fill === 'both') { 29544 context.lineWidth = (shapeImpl.matchEdgeWidth ? edgeWidth : 1) / (usePaths ? size : 1); 29545 context.lineJoin = 'miter'; 29546 29547 if (usePaths) { 29548 context.stroke(path); 29549 } else { 29550 context.stroke(); 29551 } 29552 } 29553 29554 if (usePaths) { 29555 // reset transform by applying inverse 29556 context.scale(1 / size, 1 / size); 29557 context.rotate(-angle); 29558 context.translate(-x, -y); 29559 } 29560 }; 29561 29562 var CRp$3 = {}; 29563 29564 CRp$3.safeDrawImage = function (context, img, ix, iy, iw, ih, x, y, w, h) { 29565 // detect problematic cases for old browsers with bad images (cheaper than try-catch) 29566 if (iw <= 0 || ih <= 0 || w <= 0 || h <= 0) { 29567 return; 29568 } 29569 29570 context.drawImage(img, ix, iy, iw, ih, x, y, w, h); 29571 }; 29572 29573 CRp$3.drawInscribedImage = function (context, img, node, index, nodeOpacity) { 29574 var r = this; 29575 var pos = node.position(); 29576 var nodeX = pos.x; 29577 var nodeY = pos.y; 29578 var styleObj = node.cy().style(); 29579 var getIndexedStyle = styleObj.getIndexedStyle.bind(styleObj); 29580 var fit = getIndexedStyle(node, 'background-fit', 'value', index); 29581 var repeat = getIndexedStyle(node, 'background-repeat', 'value', index); 29582 var nodeW = node.width(); 29583 var nodeH = node.height(); 29584 var paddingX2 = node.padding() * 2; 29585 var nodeTW = nodeW + (getIndexedStyle(node, 'background-width-relative-to', 'value', index) === 'inner' ? 0 : paddingX2); 29586 var nodeTH = nodeH + (getIndexedStyle(node, 'background-height-relative-to', 'value', index) === 'inner' ? 0 : paddingX2); 29587 var rs = node._private.rscratch; 29588 var clip = getIndexedStyle(node, 'background-clip', 'value', index); 29589 var shouldClip = clip === 'node'; 29590 var imgOpacity = getIndexedStyle(node, 'background-image-opacity', 'value', index) * nodeOpacity; 29591 var imgW = img.width || img.cachedW; 29592 var imgH = img.height || img.cachedH; // workaround for broken browsers like ie 29593 29594 if (null == imgW || null == imgH) { 29595 document.body.appendChild(img); // eslint-disable-line no-undef 29596 29597 imgW = img.cachedW = img.width || img.offsetWidth; 29598 imgH = img.cachedH = img.height || img.offsetHeight; 29599 document.body.removeChild(img); // eslint-disable-line no-undef 29600 } 29601 29602 var w = imgW; 29603 var h = imgH; 29604 29605 if (getIndexedStyle(node, 'background-width', 'value', index) !== 'auto') { 29606 if (getIndexedStyle(node, 'background-width', 'units', index) === '%') { 29607 w = getIndexedStyle(node, 'background-width', 'pfValue', index) * nodeTW; 29608 } else { 29609 w = getIndexedStyle(node, 'background-width', 'pfValue', index); 29610 } 29611 } 29612 29613 if (getIndexedStyle(node, 'background-height', 'value', index) !== 'auto') { 29614 if (getIndexedStyle(node, 'background-height', 'units', index) === '%') { 29615 h = getIndexedStyle(node, 'background-height', 'pfValue', index) * nodeTH; 29616 } else { 29617 h = getIndexedStyle(node, 'background-height', 'pfValue', index); 29618 } 29619 } 29620 29621 if (w === 0 || h === 0) { 29622 return; // no point in drawing empty image (and chrome is broken in this case) 29623 } 29624 29625 if (fit === 'contain') { 29626 var scale = Math.min(nodeTW / w, nodeTH / h); 29627 w *= scale; 29628 h *= scale; 29629 } else if (fit === 'cover') { 29630 var scale = Math.max(nodeTW / w, nodeTH / h); 29631 w *= scale; 29632 h *= scale; 29633 } 29634 29635 var x = nodeX - nodeTW / 2; // left 29636 29637 var posXUnits = getIndexedStyle(node, 'background-position-x', 'units', index); 29638 var posXPfVal = getIndexedStyle(node, 'background-position-x', 'pfValue', index); 29639 29640 if (posXUnits === '%') { 29641 x += (nodeTW - w) * posXPfVal; 29642 } else { 29643 x += posXPfVal; 29644 } 29645 29646 var offXUnits = getIndexedStyle(node, 'background-offset-x', 'units', index); 29647 var offXPfVal = getIndexedStyle(node, 'background-offset-x', 'pfValue', index); 29648 29649 if (offXUnits === '%') { 29650 x += (nodeTW - w) * offXPfVal; 29651 } else { 29652 x += offXPfVal; 29653 } 29654 29655 var y = nodeY - nodeTH / 2; // top 29656 29657 var posYUnits = getIndexedStyle(node, 'background-position-y', 'units', index); 29658 var posYPfVal = getIndexedStyle(node, 'background-position-y', 'pfValue', index); 29659 29660 if (posYUnits === '%') { 29661 y += (nodeTH - h) * posYPfVal; 29662 } else { 29663 y += posYPfVal; 29664 } 29665 29666 var offYUnits = getIndexedStyle(node, 'background-offset-y', 'units', index); 29667 var offYPfVal = getIndexedStyle(node, 'background-offset-y', 'pfValue', index); 29668 29669 if (offYUnits === '%') { 29670 y += (nodeTH - h) * offYPfVal; 29671 } else { 29672 y += offYPfVal; 29673 } 29674 29675 if (rs.pathCache) { 29676 x -= nodeX; 29677 y -= nodeY; 29678 nodeX = 0; 29679 nodeY = 0; 29680 } 29681 29682 var gAlpha = context.globalAlpha; 29683 context.globalAlpha = imgOpacity; 29684 29685 if (repeat === 'no-repeat') { 29686 if (shouldClip) { 29687 context.save(); 29688 29689 if (rs.pathCache) { 29690 context.clip(rs.pathCache); 29691 } else { 29692 r.nodeShapes[r.getNodeShape(node)].draw(context, nodeX, nodeY, nodeTW, nodeTH); 29693 context.clip(); 29694 } 29695 } 29696 29697 r.safeDrawImage(context, img, 0, 0, imgW, imgH, x, y, w, h); 29698 29699 if (shouldClip) { 29700 context.restore(); 29701 } 29702 } else { 29703 var pattern = context.createPattern(img, repeat); 29704 context.fillStyle = pattern; 29705 r.nodeShapes[r.getNodeShape(node)].draw(context, nodeX, nodeY, nodeTW, nodeTH); 29706 context.translate(x, y); 29707 context.fill(); 29708 context.translate(-x, -y); 29709 } 29710 29711 context.globalAlpha = gAlpha; 29712 }; 29713 29714 var CRp$4 = {}; 29715 29716 CRp$4.eleTextBiggerThanMin = function (ele, scale) { 29717 if (!scale) { 29718 var zoom = ele.cy().zoom(); 29719 var pxRatio = this.getPixelRatio(); 29720 var lvl = Math.ceil(log2(zoom * pxRatio)); // the effective texture level 29721 29722 scale = Math.pow(2, lvl); 29723 } 29724 29725 var computedSize = ele.pstyle('font-size').pfValue * scale; 29726 var minSize = ele.pstyle('min-zoomed-font-size').pfValue; 29727 29728 if (computedSize < minSize) { 29729 return false; 29730 } 29731 29732 return true; 29733 }; 29734 29735 CRp$4.drawElementText = function (context, ele, shiftToOriginWithBb, force, prefix) { 29736 var useEleOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 29737 var r = this; 29738 29739 if (force == null) { 29740 if (useEleOpacity && !r.eleTextBiggerThanMin(ele)) { 29741 return; 29742 } 29743 } else if (force === false) { 29744 return; 29745 } 29746 29747 if (ele.isNode()) { 29748 var label = ele.pstyle('label'); 29749 29750 if (!label || !label.value) { 29751 return; 29752 } 29753 29754 var justification = r.getLabelJustification(ele); 29755 context.textAlign = justification; 29756 context.textBaseline = 'bottom'; 29757 } else { 29758 var badLine = ele.element()._private.rscratch.badLine; 29759 29760 var _label = ele.pstyle('label'); 29761 29762 var srcLabel = ele.pstyle('source-label'); 29763 var tgtLabel = ele.pstyle('target-label'); 29764 29765 if (badLine || (!_label || !_label.value) && (!srcLabel || !srcLabel.value) && (!tgtLabel || !tgtLabel.value)) { 29766 return; 29767 } 29768 29769 context.textAlign = 'center'; 29770 context.textBaseline = 'bottom'; 29771 } 29772 29773 var applyRotation = !shiftToOriginWithBb; 29774 var bb; 29775 29776 if (shiftToOriginWithBb) { 29777 bb = shiftToOriginWithBb; 29778 context.translate(-bb.x1, -bb.y1); 29779 } 29780 29781 if (prefix == null) { 29782 r.drawText(context, ele, null, applyRotation, useEleOpacity); 29783 29784 if (ele.isEdge()) { 29785 r.drawText(context, ele, 'source', applyRotation, useEleOpacity); 29786 r.drawText(context, ele, 'target', applyRotation, useEleOpacity); 29787 } 29788 } else { 29789 r.drawText(context, ele, prefix, applyRotation, useEleOpacity); 29790 } 29791 29792 if (shiftToOriginWithBb) { 29793 context.translate(bb.x1, bb.y1); 29794 } 29795 }; 29796 29797 CRp$4.getFontCache = function (context) { 29798 var cache; 29799 this.fontCaches = this.fontCaches || []; 29800 29801 for (var i = 0; i < this.fontCaches.length; i++) { 29802 cache = this.fontCaches[i]; 29803 29804 if (cache.context === context) { 29805 return cache; 29806 } 29807 } 29808 29809 cache = { 29810 context: context 29811 }; 29812 this.fontCaches.push(cache); 29813 return cache; 29814 }; // set up canvas context with font 29815 // returns transformed text string 29816 29817 29818 CRp$4.setupTextStyle = function (context, ele) { 29819 var useEleOpacity = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 29820 // Font style 29821 var labelStyle = ele.pstyle('font-style').strValue; 29822 var labelSize = ele.pstyle('font-size').pfValue + 'px'; 29823 var labelFamily = ele.pstyle('font-family').strValue; 29824 var labelWeight = ele.pstyle('font-weight').strValue; 29825 var opacity = useEleOpacity ? ele.effectiveOpacity() * ele.pstyle('text-opacity').value : 1; 29826 var outlineOpacity = ele.pstyle('text-outline-opacity').value * opacity; 29827 var color = ele.pstyle('color').value; 29828 var outlineColor = ele.pstyle('text-outline-color').value; 29829 context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily; 29830 context.lineJoin = 'round'; // so text outlines aren't jagged 29831 29832 this.colorFillStyle(context, color[0], color[1], color[2], opacity); 29833 this.colorStrokeStyle(context, outlineColor[0], outlineColor[1], outlineColor[2], outlineOpacity); 29834 }; // TODO ensure re-used 29835 29836 29837 function roundRect(ctx, x, y, width, height) { 29838 var radius = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 5; 29839 ctx.beginPath(); 29840 ctx.moveTo(x + radius, y); 29841 ctx.lineTo(x + width - radius, y); 29842 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); 29843 ctx.lineTo(x + width, y + height - radius); 29844 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 29845 ctx.lineTo(x + radius, y + height); 29846 ctx.quadraticCurveTo(x, y + height, x, y + height - radius); 29847 ctx.lineTo(x, y + radius); 29848 ctx.quadraticCurveTo(x, y, x + radius, y); 29849 ctx.closePath(); 29850 ctx.fill(); 29851 } 29852 29853 CRp$4.getTextAngle = function (ele, prefix) { 29854 var theta; 29855 var _p = ele._private; 29856 var rscratch = _p.rscratch; 29857 var pdash = prefix ? prefix + '-' : ''; 29858 var rotation = ele.pstyle(pdash + 'text-rotation'); 29859 var textAngle = getPrefixedProperty(rscratch, 'labelAngle', prefix); 29860 29861 if (rotation.strValue === 'autorotate') { 29862 theta = ele.isEdge() ? textAngle : 0; 29863 } else if (rotation.strValue === 'none') { 29864 theta = 0; 29865 } else { 29866 theta = rotation.pfValue; 29867 } 29868 29869 return theta; 29870 }; 29871 29872 CRp$4.drawText = function (context, ele, prefix) { 29873 var applyRotation = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 29874 var useEleOpacity = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 29875 var _p = ele._private; 29876 var rscratch = _p.rscratch; 29877 var parentOpacity = useEleOpacity ? ele.effectiveOpacity() : 1; 29878 29879 if (useEleOpacity && (parentOpacity === 0 || ele.pstyle('text-opacity').value === 0)) { 29880 return; 29881 } // use 'main' as an alias for the main label (i.e. null prefix) 29882 29883 29884 if (prefix === 'main') { 29885 prefix = null; 29886 } 29887 29888 var textX = getPrefixedProperty(rscratch, 'labelX', prefix); 29889 var textY = getPrefixedProperty(rscratch, 'labelY', prefix); 29890 var orgTextX, orgTextY; // used for rotation 29891 29892 var text = this.getLabelText(ele, prefix); 29893 29894 if (text != null && text !== '' && !isNaN(textX) && !isNaN(textY)) { 29895 this.setupTextStyle(context, ele, useEleOpacity); 29896 var pdash = prefix ? prefix + '-' : ''; 29897 var textW = getPrefixedProperty(rscratch, 'labelWidth', prefix); 29898 var textH = getPrefixedProperty(rscratch, 'labelHeight', prefix); 29899 var marginX = ele.pstyle(pdash + 'text-margin-x').pfValue; 29900 var marginY = ele.pstyle(pdash + 'text-margin-y').pfValue; 29901 var isEdge = ele.isEdge(); 29902 var halign = ele.pstyle('text-halign').value; 29903 var valign = ele.pstyle('text-valign').value; 29904 29905 if (isEdge) { 29906 halign = 'center'; 29907 valign = 'center'; 29908 } 29909 29910 textX += marginX; 29911 textY += marginY; 29912 var theta; 29913 29914 if (!applyRotation) { 29915 theta = 0; 29916 } else { 29917 theta = this.getTextAngle(ele, prefix); 29918 } 29919 29920 if (theta !== 0) { 29921 orgTextX = textX; 29922 orgTextY = textY; 29923 context.translate(orgTextX, orgTextY); 29924 context.rotate(theta); 29925 textX = 0; 29926 textY = 0; 29927 } 29928 29929 switch (valign) { 29930 case 'top': 29931 break; 29932 29933 case 'center': 29934 textY += textH / 2; 29935 break; 29936 29937 case 'bottom': 29938 textY += textH; 29939 break; 29940 } 29941 29942 var backgroundOpacity = ele.pstyle('text-background-opacity').value; 29943 var borderOpacity = ele.pstyle('text-border-opacity').value; 29944 var textBorderWidth = ele.pstyle('text-border-width').pfValue; 29945 var backgroundPadding = ele.pstyle('text-background-padding').pfValue; 29946 29947 if (backgroundOpacity > 0 || textBorderWidth > 0 && borderOpacity > 0) { 29948 var bgX = textX - backgroundPadding; 29949 29950 switch (halign) { 29951 case 'left': 29952 bgX -= textW; 29953 break; 29954 29955 case 'center': 29956 bgX -= textW / 2; 29957 break; 29958 } 29959 29960 var bgY = textY - textH - backgroundPadding; 29961 var bgW = textW + 2 * backgroundPadding; 29962 var bgH = textH + 2 * backgroundPadding; 29963 29964 if (backgroundOpacity > 0) { 29965 var textFill = context.fillStyle; 29966 var textBackgroundColor = ele.pstyle('text-background-color').value; 29967 context.fillStyle = 'rgba(' + textBackgroundColor[0] + ',' + textBackgroundColor[1] + ',' + textBackgroundColor[2] + ',' + backgroundOpacity * parentOpacity + ')'; 29968 var styleShape = ele.pstyle('text-background-shape').strValue; 29969 29970 if (styleShape.indexOf('round') === 0) { 29971 roundRect(context, bgX, bgY, bgW, bgH, 2); 29972 } else { 29973 context.fillRect(bgX, bgY, bgW, bgH); 29974 } 29975 29976 context.fillStyle = textFill; 29977 } 29978 29979 if (textBorderWidth > 0 && borderOpacity > 0) { 29980 var textStroke = context.strokeStyle; 29981 var textLineWidth = context.lineWidth; 29982 var textBorderColor = ele.pstyle('text-border-color').value; 29983 var textBorderStyle = ele.pstyle('text-border-style').value; 29984 context.strokeStyle = 'rgba(' + textBorderColor[0] + ',' + textBorderColor[1] + ',' + textBorderColor[2] + ',' + borderOpacity * parentOpacity + ')'; 29985 context.lineWidth = textBorderWidth; 29986 29987 if (context.setLineDash) { 29988 // for very outofdate browsers 29989 switch (textBorderStyle) { 29990 case 'dotted': 29991 context.setLineDash([1, 1]); 29992 break; 29993 29994 case 'dashed': 29995 context.setLineDash([4, 2]); 29996 break; 29997 29998 case 'double': 29999 context.lineWidth = textBorderWidth / 4; // 50% reserved for white between the two borders 30000 30001 context.setLineDash([]); 30002 break; 30003 30004 case 'solid': 30005 context.setLineDash([]); 30006 break; 30007 } 30008 } 30009 30010 context.strokeRect(bgX, bgY, bgW, bgH); 30011 30012 if (textBorderStyle === 'double') { 30013 var whiteWidth = textBorderWidth / 2; 30014 context.strokeRect(bgX + whiteWidth, bgY + whiteWidth, bgW - whiteWidth * 2, bgH - whiteWidth * 2); 30015 } 30016 30017 if (context.setLineDash) { 30018 // for very outofdate browsers 30019 context.setLineDash([]); 30020 } 30021 30022 context.lineWidth = textLineWidth; 30023 context.strokeStyle = textStroke; 30024 } 30025 } 30026 30027 var lineWidth = 2 * ele.pstyle('text-outline-width').pfValue; // *2 b/c the stroke is drawn centred on the middle 30028 30029 if (lineWidth > 0) { 30030 context.lineWidth = lineWidth; 30031 } 30032 30033 if (ele.pstyle('text-wrap').value === 'wrap') { 30034 var lines = getPrefixedProperty(rscratch, 'labelWrapCachedLines', prefix); 30035 var lineHeight = getPrefixedProperty(rscratch, 'labelLineHeight', prefix); 30036 var halfTextW = textW / 2; 30037 var justification = this.getLabelJustification(ele); 30038 30039 if (justification === 'auto') ; else if (halign === 'left') { 30040 // auto justification : right 30041 if (justification === 'left') { 30042 textX += -textW; 30043 } else if (justification === 'center') { 30044 textX += -halfTextW; 30045 } // else same as auto 30046 30047 } else if (halign === 'center') { 30048 // auto justfication : center 30049 if (justification === 'left') { 30050 textX += -halfTextW; 30051 } else if (justification === 'right') { 30052 textX += halfTextW; 30053 } // else same as auto 30054 30055 } else if (halign === 'right') { 30056 // auto justification : left 30057 if (justification === 'center') { 30058 textX += halfTextW; 30059 } else if (justification === 'right') { 30060 textX += textW; 30061 } // else same as auto 30062 30063 } 30064 30065 switch (valign) { 30066 case 'top': 30067 textY -= (lines.length - 1) * lineHeight; 30068 break; 30069 30070 case 'center': 30071 case 'bottom': 30072 textY -= (lines.length - 1) * lineHeight; 30073 break; 30074 } 30075 30076 for (var l = 0; l < lines.length; l++) { 30077 if (lineWidth > 0) { 30078 context.strokeText(lines[l], textX, textY); 30079 } 30080 30081 context.fillText(lines[l], textX, textY); 30082 textY += lineHeight; 30083 } 30084 } else { 30085 if (lineWidth > 0) { 30086 context.strokeText(text, textX, textY); 30087 } 30088 30089 context.fillText(text, textX, textY); 30090 } 30091 30092 if (theta !== 0) { 30093 context.rotate(-theta); 30094 context.translate(-orgTextX, -orgTextY); 30095 } 30096 } 30097 }; 30098 30099 /* global Path2D */ 30100 var CRp$5 = {}; 30101 30102 CRp$5.drawNode = function (context, node, shiftToOriginWithBb) { 30103 var drawLabel = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 30104 var shouldDrawOverlay = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 30105 var shouldDrawOpacity = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; 30106 var r = this; 30107 var nodeWidth, nodeHeight; 30108 var _p = node._private; 30109 var rs = _p.rscratch; 30110 var pos = node.position(); 30111 30112 if (!number(pos.x) || !number(pos.y)) { 30113 return; // can't draw node with undefined position 30114 } 30115 30116 if (shouldDrawOpacity && !node.visible()) { 30117 return; 30118 } 30119 30120 var eleOpacity = shouldDrawOpacity ? node.effectiveOpacity() : 1; 30121 var usePaths = r.usePaths(); 30122 var path; 30123 var pathCacheHit = false; 30124 var padding = node.padding(); 30125 nodeWidth = node.width() + 2 * padding; 30126 nodeHeight = node.height() + 2 * padding; // 30127 // setup shift 30128 30129 var bb; 30130 30131 if (shiftToOriginWithBb) { 30132 bb = shiftToOriginWithBb; 30133 context.translate(-bb.x1, -bb.y1); 30134 } // 30135 // load bg image 30136 30137 30138 var bgImgProp = node.pstyle('background-image'); 30139 var urls = bgImgProp.value; 30140 var urlDefined = new Array(urls.length); 30141 var image = new Array(urls.length); 30142 var numImages = 0; 30143 30144 for (var i = 0; i < urls.length; i++) { 30145 var url = urls[i]; 30146 var defd = urlDefined[i] = url != null && url !== 'none'; 30147 30148 if (defd) { 30149 var bgImgCrossOrigin = node.cy().style().getIndexedStyle(node, 'background-image-crossorigin', 'value', i); 30150 numImages++; // get image, and if not loaded then ask to redraw when later loaded 30151 30152 image[i] = r.getCachedImage(url, bgImgCrossOrigin, function () { 30153 _p.backgroundTimestamp = Date.now(); 30154 node.emitAndNotify('background'); 30155 }); 30156 } 30157 } // 30158 // setup styles 30159 30160 30161 var darkness = node.pstyle('background-blacken').value; 30162 var borderWidth = node.pstyle('border-width').pfValue; 30163 var bgOpacity = node.pstyle('background-opacity').value * eleOpacity; 30164 var borderColor = node.pstyle('border-color').value; 30165 var borderStyle = node.pstyle('border-style').value; 30166 var borderOpacity = node.pstyle('border-opacity').value * eleOpacity; 30167 context.lineJoin = 'miter'; // so borders are square with the node shape 30168 30169 var setupShapeColor = function setupShapeColor() { 30170 var bgOpy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : bgOpacity; 30171 r.eleFillStyle(context, node, bgOpy); 30172 }; 30173 30174 var setupBorderColor = function setupBorderColor() { 30175 var bdrOpy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : borderOpacity; 30176 r.colorStrokeStyle(context, borderColor[0], borderColor[1], borderColor[2], bdrOpy); 30177 }; // 30178 // setup shape 30179 30180 30181 var styleShape = node.pstyle('shape').strValue; 30182 var shapePts = node.pstyle('shape-polygon-points').pfValue; 30183 30184 if (usePaths) { 30185 context.translate(pos.x, pos.y); 30186 var pathCache = r.nodePathCache = r.nodePathCache || []; 30187 var key = hashStrings(styleShape === 'polygon' ? styleShape + ',' + shapePts.join(',') : styleShape, '' + nodeHeight, '' + nodeWidth); 30188 var cachedPath = pathCache[key]; 30189 30190 if (cachedPath != null) { 30191 path = cachedPath; 30192 pathCacheHit = true; 30193 rs.pathCache = path; 30194 } else { 30195 path = new Path2D(); 30196 pathCache[key] = rs.pathCache = path; 30197 } 30198 } 30199 30200 var drawShape = function drawShape() { 30201 if (!pathCacheHit) { 30202 var npos = pos; 30203 30204 if (usePaths) { 30205 npos = { 30206 x: 0, 30207 y: 0 30208 }; 30209 } 30210 30211 r.nodeShapes[r.getNodeShape(node)].draw(path || context, npos.x, npos.y, nodeWidth, nodeHeight); 30212 } 30213 30214 if (usePaths) { 30215 context.fill(path); 30216 } else { 30217 context.fill(); 30218 } 30219 }; 30220 30221 var drawImages = function drawImages() { 30222 var nodeOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : eleOpacity; 30223 var prevBging = _p.backgrounding; 30224 var totalCompleted = 0; 30225 30226 for (var _i = 0; _i < image.length; _i++) { 30227 if (urlDefined[_i] && image[_i].complete && !image[_i].error) { 30228 totalCompleted++; 30229 r.drawInscribedImage(context, image[_i], node, _i, nodeOpacity); 30230 } 30231 } 30232 30233 _p.backgrounding = !(totalCompleted === numImages); 30234 30235 if (prevBging !== _p.backgrounding) { 30236 // update style b/c :backgrounding state changed 30237 node.updateStyle(false); 30238 } 30239 }; 30240 30241 var drawPie = function drawPie() { 30242 var redrawShape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 30243 var pieOpacity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : eleOpacity; 30244 30245 if (r.hasPie(node)) { 30246 r.drawPie(context, node, pieOpacity); // redraw/restore path if steps after pie need it 30247 30248 if (redrawShape) { 30249 if (!usePaths) { 30250 r.nodeShapes[r.getNodeShape(node)].draw(context, pos.x, pos.y, nodeWidth, nodeHeight); 30251 } 30252 } 30253 } 30254 }; 30255 30256 var darken = function darken() { 30257 var darkenOpacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : eleOpacity; 30258 var opacity = (darkness > 0 ? darkness : -darkness) * darkenOpacity; 30259 var c = darkness > 0 ? 0 : 255; 30260 30261 if (darkness !== 0) { 30262 r.colorFillStyle(context, c, c, c, opacity); 30263 30264 if (usePaths) { 30265 context.fill(path); 30266 } else { 30267 context.fill(); 30268 } 30269 } 30270 }; 30271 30272 var drawBorder = function drawBorder() { 30273 if (borderWidth > 0) { 30274 context.lineWidth = borderWidth; 30275 context.lineCap = 'butt'; 30276 30277 if (context.setLineDash) { 30278 // for very outofdate browsers 30279 switch (borderStyle) { 30280 case 'dotted': 30281 context.setLineDash([1, 1]); 30282 break; 30283 30284 case 'dashed': 30285 context.setLineDash([4, 2]); 30286 break; 30287 30288 case 'solid': 30289 case 'double': 30290 context.setLineDash([]); 30291 break; 30292 } 30293 } 30294 30295 if (usePaths) { 30296 context.stroke(path); 30297 } else { 30298 context.stroke(); 30299 } 30300 30301 if (borderStyle === 'double') { 30302 context.lineWidth = borderWidth / 3; 30303 var gco = context.globalCompositeOperation; 30304 context.globalCompositeOperation = 'destination-out'; 30305 30306 if (usePaths) { 30307 context.stroke(path); 30308 } else { 30309 context.stroke(); 30310 } 30311 30312 context.globalCompositeOperation = gco; 30313 } // reset in case we changed the border style 30314 30315 30316 if (context.setLineDash) { 30317 // for very outofdate browsers 30318 context.setLineDash([]); 30319 } 30320 } 30321 }; 30322 30323 var drawOverlay = function drawOverlay() { 30324 if (shouldDrawOverlay) { 30325 r.drawNodeOverlay(context, node, pos, nodeWidth, nodeHeight); 30326 } 30327 }; 30328 30329 var drawText = function drawText() { 30330 r.drawElementText(context, node, null, drawLabel); 30331 }; 30332 30333 var ghost = node.pstyle('ghost').value === 'yes'; 30334 30335 if (ghost) { 30336 var gx = node.pstyle('ghost-offset-x').pfValue; 30337 var gy = node.pstyle('ghost-offset-y').pfValue; 30338 var ghostOpacity = node.pstyle('ghost-opacity').value; 30339 var effGhostOpacity = ghostOpacity * eleOpacity; 30340 context.translate(gx, gy); 30341 setupShapeColor(ghostOpacity * bgOpacity); 30342 drawShape(); 30343 drawImages(effGhostOpacity); 30344 drawPie(darkness !== 0 || borderWidth !== 0); 30345 darken(effGhostOpacity); 30346 setupBorderColor(ghostOpacity * borderOpacity); 30347 drawBorder(); 30348 context.translate(-gx, -gy); 30349 } 30350 30351 setupShapeColor(); 30352 drawShape(); 30353 drawImages(); 30354 drawPie(darkness !== 0 || borderWidth !== 0); 30355 darken(); 30356 setupBorderColor(); 30357 drawBorder(); 30358 30359 if (usePaths) { 30360 context.translate(-pos.x, -pos.y); 30361 } 30362 30363 drawText(); 30364 drawOverlay(); // 30365 // clean up shift 30366 30367 if (shiftToOriginWithBb) { 30368 context.translate(bb.x1, bb.y1); 30369 } 30370 }; 30371 30372 CRp$5.drawNodeOverlay = function (context, node, pos, nodeWidth, nodeHeight) { 30373 var r = this; 30374 30375 if (!node.visible()) { 30376 return; 30377 } 30378 30379 var overlayPadding = node.pstyle('overlay-padding').pfValue; 30380 var overlayOpacity = node.pstyle('overlay-opacity').value; 30381 var overlayColor = node.pstyle('overlay-color').value; 30382 30383 if (overlayOpacity > 0) { 30384 pos = pos || node.position(); 30385 30386 if (nodeWidth == null || nodeHeight == null) { 30387 var padding = node.padding(); 30388 nodeWidth = node.width() + 2 * padding; 30389 nodeHeight = node.height() + 2 * padding; 30390 } 30391 30392 r.colorFillStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); 30393 r.nodeShapes['roundrectangle'].draw(context, pos.x, pos.y, nodeWidth + overlayPadding * 2, nodeHeight + overlayPadding * 2); 30394 context.fill(); 30395 } 30396 }; // does the node have at least one pie piece? 30397 30398 30399 CRp$5.hasPie = function (node) { 30400 node = node[0]; // ensure ele ref 30401 30402 return node._private.hasPie; 30403 }; 30404 30405 CRp$5.drawPie = function (context, node, nodeOpacity, pos) { 30406 node = node[0]; // ensure ele ref 30407 30408 pos = pos || node.position(); 30409 var cyStyle = node.cy().style(); 30410 var pieSize = node.pstyle('pie-size'); 30411 var x = pos.x; 30412 var y = pos.y; 30413 var nodeW = node.width(); 30414 var nodeH = node.height(); 30415 var radius = Math.min(nodeW, nodeH) / 2; // must fit in node 30416 30417 var lastPercent = 0; // what % to continue drawing pie slices from on [0, 1] 30418 30419 var usePaths = this.usePaths(); 30420 30421 if (usePaths) { 30422 x = 0; 30423 y = 0; 30424 } 30425 30426 if (pieSize.units === '%') { 30427 radius = radius * pieSize.pfValue; 30428 } else if (pieSize.pfValue !== undefined) { 30429 radius = pieSize.pfValue / 2; 30430 } 30431 30432 for (var i = 1; i <= cyStyle.pieBackgroundN; i++) { 30433 // 1..N 30434 var size = node.pstyle('pie-' + i + '-background-size').value; 30435 var color = node.pstyle('pie-' + i + '-background-color').value; 30436 var opacity = node.pstyle('pie-' + i + '-background-opacity').value * nodeOpacity; 30437 var percent = size / 100; // map integer range [0, 100] to [0, 1] 30438 // percent can't push beyond 1 30439 30440 if (percent + lastPercent > 1) { 30441 percent = 1 - lastPercent; 30442 } 30443 30444 var angleStart = 1.5 * Math.PI + 2 * Math.PI * lastPercent; // start at 12 o'clock and go clockwise 30445 30446 var angleDelta = 2 * Math.PI * percent; 30447 var angleEnd = angleStart + angleDelta; // ignore if 30448 // - zero size 30449 // - we're already beyond the full circle 30450 // - adding the current slice would go beyond the full circle 30451 30452 if (size === 0 || lastPercent >= 1 || lastPercent + percent > 1) { 30453 continue; 30454 } 30455 30456 context.beginPath(); 30457 context.moveTo(x, y); 30458 context.arc(x, y, radius, angleStart, angleEnd); 30459 context.closePath(); 30460 this.colorFillStyle(context, color[0], color[1], color[2], opacity); 30461 context.fill(); 30462 lastPercent += percent; 30463 } 30464 }; 30465 30466 var CRp$6 = {}; 30467 var motionBlurDelay = 100; // var isFirefox = typeof InstallTrigger !== 'undefined'; 30468 30469 CRp$6.getPixelRatio = function () { 30470 var context = this.data.contexts[0]; 30471 30472 if (this.forcedPixelRatio != null) { 30473 return this.forcedPixelRatio; 30474 } 30475 30476 var backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; 30477 return (window.devicePixelRatio || 1) / backingStore; // eslint-disable-line no-undef 30478 }; 30479 30480 CRp$6.paintCache = function (context) { 30481 var caches = this.paintCaches = this.paintCaches || []; 30482 var needToCreateCache = true; 30483 var cache; 30484 30485 for (var i = 0; i < caches.length; i++) { 30486 cache = caches[i]; 30487 30488 if (cache.context === context) { 30489 needToCreateCache = false; 30490 break; 30491 } 30492 } 30493 30494 if (needToCreateCache) { 30495 cache = { 30496 context: context 30497 }; 30498 caches.push(cache); 30499 } 30500 30501 return cache; 30502 }; 30503 30504 CRp$6.createGradientStyleFor = function (context, shapeStyleName, ele, fill, opacity) { 30505 var gradientStyle; 30506 var usePaths = this.usePaths(); 30507 var colors = ele.pstyle(shapeStyleName + '-gradient-stop-colors').value, 30508 positions = ele.pstyle(shapeStyleName + '-gradient-stop-positions').pfValue; 30509 30510 if (fill === 'radial-gradient') { 30511 if (ele.isEdge()) { 30512 var start = ele.sourceEndpoint(), 30513 end = ele.targetEndpoint(), 30514 mid = ele.midpoint(); 30515 var d1 = dist(start, mid); 30516 var d2 = dist(end, mid); 30517 gradientStyle = context.createRadialGradient(mid.x, mid.y, 0, mid.x, mid.y, Math.max(d1, d2)); 30518 } else { 30519 var pos = usePaths ? { 30520 x: 0, 30521 y: 0 30522 } : ele.position(), 30523 width = ele.paddedWidth(), 30524 height = ele.paddedHeight(); 30525 gradientStyle = context.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, Math.max(width, height)); 30526 } 30527 } else { 30528 if (ele.isEdge()) { 30529 var _start = ele.sourceEndpoint(), 30530 _end = ele.targetEndpoint(); 30531 30532 gradientStyle = context.createLinearGradient(_start.x, _start.y, _end.x, _end.y); 30533 } else { 30534 var _pos = usePaths ? { 30535 x: 0, 30536 y: 0 30537 } : ele.position(), 30538 _width = ele.paddedWidth(), 30539 _height = ele.paddedHeight(), 30540 halfWidth = _width / 2, 30541 halfHeight = _height / 2; 30542 30543 var direction = ele.pstyle('background-gradient-direction').value; 30544 30545 switch (direction) { 30546 case 'to-bottom': 30547 gradientStyle = context.createLinearGradient(_pos.x, _pos.y - halfHeight, _pos.x, _pos.y + halfHeight); 30548 break; 30549 30550 case 'to-top': 30551 gradientStyle = context.createLinearGradient(_pos.x, _pos.y + halfHeight, _pos.x, _pos.y - halfHeight); 30552 break; 30553 30554 case 'to-left': 30555 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y, _pos.x - halfWidth, _pos.y); 30556 break; 30557 30558 case 'to-right': 30559 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y, _pos.x + halfWidth, _pos.y); 30560 break; 30561 30562 case 'to-bottom-right': 30563 case 'to-right-bottom': 30564 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y - halfHeight, _pos.x + halfWidth, _pos.y + halfHeight); 30565 break; 30566 30567 case 'to-top-right': 30568 case 'to-right-top': 30569 gradientStyle = context.createLinearGradient(_pos.x - halfWidth, _pos.y + halfHeight, _pos.x + halfWidth, _pos.y - halfHeight); 30570 break; 30571 30572 case 'to-bottom-left': 30573 case 'to-left-bottom': 30574 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y - halfHeight, _pos.x - halfWidth, _pos.y + halfHeight); 30575 break; 30576 30577 case 'to-top-left': 30578 case 'to-left-top': 30579 gradientStyle = context.createLinearGradient(_pos.x + halfWidth, _pos.y + halfHeight, _pos.x - halfWidth, _pos.y - halfHeight); 30580 break; 30581 } 30582 } 30583 } 30584 30585 if (!gradientStyle) return null; // invalid gradient style 30586 30587 var hasPositions = positions.length === colors.length; 30588 var length = colors.length; 30589 30590 for (var i = 0; i < length; i++) { 30591 gradientStyle.addColorStop(hasPositions ? positions[i] : i / (length - 1), 'rgba(' + colors[i][0] + ',' + colors[i][1] + ',' + colors[i][2] + ',' + opacity + ')'); 30592 } 30593 30594 return gradientStyle; 30595 }; 30596 30597 CRp$6.gradientFillStyle = function (context, ele, fill, opacity) { 30598 var gradientStyle = this.createGradientStyleFor(context, 'background', ele, fill, opacity); 30599 if (!gradientStyle) return null; // error 30600 30601 context.fillStyle = gradientStyle; 30602 }; 30603 30604 CRp$6.colorFillStyle = function (context, r, g, b, a) { 30605 context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; // turn off for now, seems context does its own caching 30606 // var cache = this.paintCache(context); 30607 // var fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 30608 // if( cache.fillStyle !== fillStyle ){ 30609 // context.fillStyle = cache.fillStyle = fillStyle; 30610 // } 30611 }; 30612 30613 CRp$6.eleFillStyle = function (context, ele, opacity) { 30614 var backgroundFill = ele.pstyle('background-fill').value; 30615 30616 if (backgroundFill === 'linear-gradient' || backgroundFill === 'radial-gradient') { 30617 this.gradientFillStyle(context, ele, backgroundFill, opacity); 30618 } else { 30619 var backgroundColor = ele.pstyle('background-color').value; 30620 this.colorFillStyle(context, backgroundColor[0], backgroundColor[1], backgroundColor[2], opacity); 30621 } 30622 }; 30623 30624 CRp$6.gradientStrokeStyle = function (context, ele, fill, opacity) { 30625 var gradientStyle = this.createGradientStyleFor(context, 'line', ele, fill, opacity); 30626 if (!gradientStyle) return null; // error 30627 30628 context.strokeStyle = gradientStyle; 30629 }; 30630 30631 CRp$6.colorStrokeStyle = function (context, r, g, b, a) { 30632 context.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; // turn off for now, seems context does its own caching 30633 // var cache = this.paintCache(context); 30634 // var strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 30635 // if( cache.strokeStyle !== strokeStyle ){ 30636 // context.strokeStyle = cache.strokeStyle = strokeStyle; 30637 // } 30638 }; 30639 30640 CRp$6.eleStrokeStyle = function (context, ele, opacity) { 30641 var lineFill = ele.pstyle('line-fill').value; 30642 30643 if (lineFill === 'linear-gradient' || lineFill === 'radial-gradient') { 30644 this.gradientStrokeStyle(context, ele, lineFill, opacity); 30645 } else { 30646 var lineColor = ele.pstyle('line-color').value; 30647 this.colorStrokeStyle(context, lineColor[0], lineColor[1], lineColor[2], opacity); 30648 } 30649 }; // Resize canvas 30650 30651 30652 CRp$6.matchCanvasSize = function (container) { 30653 var r = this; 30654 var data = r.data; 30655 var bb = r.findContainerClientCoords(); 30656 var width = bb[2]; 30657 var height = bb[3]; 30658 var pixelRatio = r.getPixelRatio(); 30659 var mbPxRatio = r.motionBlurPxRatio; 30660 30661 if (container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE] || container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]) { 30662 pixelRatio = mbPxRatio; 30663 } 30664 30665 var canvasWidth = width * pixelRatio; 30666 var canvasHeight = height * pixelRatio; 30667 var canvas; 30668 30669 if (canvasWidth === r.canvasWidth && canvasHeight === r.canvasHeight) { 30670 return; // save cycles if same 30671 } 30672 30673 r.fontCaches = null; // resizing resets the style 30674 30675 var canvasContainer = data.canvasContainer; 30676 canvasContainer.style.width = width + 'px'; 30677 canvasContainer.style.height = height + 'px'; 30678 30679 for (var i = 0; i < r.CANVAS_LAYERS; i++) { 30680 canvas = data.canvases[i]; 30681 canvas.width = canvasWidth; 30682 canvas.height = canvasHeight; 30683 canvas.style.width = width + 'px'; 30684 canvas.style.height = height + 'px'; 30685 } 30686 30687 for (var i = 0; i < r.BUFFER_COUNT; i++) { 30688 canvas = data.bufferCanvases[i]; 30689 canvas.width = canvasWidth; 30690 canvas.height = canvasHeight; 30691 canvas.style.width = width + 'px'; 30692 canvas.style.height = height + 'px'; 30693 } 30694 30695 r.textureMult = 1; 30696 30697 if (pixelRatio <= 1) { 30698 canvas = data.bufferCanvases[r.TEXTURE_BUFFER]; 30699 r.textureMult = 2; 30700 canvas.width = canvasWidth * r.textureMult; 30701 canvas.height = canvasHeight * r.textureMult; 30702 } 30703 30704 r.canvasWidth = canvasWidth; 30705 r.canvasHeight = canvasHeight; 30706 }; 30707 30708 CRp$6.renderTo = function (cxt, zoom, pan, pxRatio) { 30709 this.render({ 30710 forcedContext: cxt, 30711 forcedZoom: zoom, 30712 forcedPan: pan, 30713 drawAllLayers: true, 30714 forcedPxRatio: pxRatio 30715 }); 30716 }; 30717 30718 CRp$6.render = function (options) { 30719 options = options || staticEmptyObject(); 30720 var forcedContext = options.forcedContext; 30721 var drawAllLayers = options.drawAllLayers; 30722 var drawOnlyNodeLayer = options.drawOnlyNodeLayer; 30723 var forcedZoom = options.forcedZoom; 30724 var forcedPan = options.forcedPan; 30725 var r = this; 30726 var pixelRatio = options.forcedPxRatio === undefined ? this.getPixelRatio() : options.forcedPxRatio; 30727 var cy = r.cy; 30728 var data = r.data; 30729 var needDraw = data.canvasNeedsRedraw; 30730 var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming); 30731 var motionBlur = options.motionBlur !== undefined ? options.motionBlur : r.motionBlur; 30732 var mbPxRatio = r.motionBlurPxRatio; 30733 var hasCompoundNodes = cy.hasCompoundNodes(); 30734 var inNodeDragGesture = r.hoverData.draggingEles; 30735 var inBoxSelection = r.hoverData.selecting || r.touchData.selecting ? true : false; 30736 motionBlur = motionBlur && !forcedContext && r.motionBlurEnabled && !inBoxSelection; 30737 var motionBlurFadeEffect = motionBlur; 30738 30739 if (!forcedContext) { 30740 if (r.prevPxRatio !== pixelRatio) { 30741 r.invalidateContainerClientCoordsCache(); 30742 r.matchCanvasSize(r.container); 30743 r.redrawHint('eles', true); 30744 r.redrawHint('drag', true); 30745 } 30746 30747 r.prevPxRatio = pixelRatio; 30748 } 30749 30750 if (!forcedContext && r.motionBlurTimeout) { 30751 clearTimeout(r.motionBlurTimeout); 30752 } 30753 30754 if (motionBlur) { 30755 if (r.mbFrames == null) { 30756 r.mbFrames = 0; 30757 } 30758 30759 r.mbFrames++; 30760 30761 if (r.mbFrames < 3) { 30762 // need several frames before even high quality motionblur 30763 motionBlurFadeEffect = false; 30764 } // go to lower quality blurry frames when several m/b frames have been rendered (avoids flashing) 30765 30766 30767 if (r.mbFrames > r.minMbLowQualFrames) { 30768 //r.fullQualityMb = false; 30769 r.motionBlurPxRatio = r.mbPxRBlurry; 30770 } 30771 } 30772 30773 if (r.clearingMotionBlur) { 30774 r.motionBlurPxRatio = 1; 30775 } // b/c drawToContext() may be async w.r.t. redraw(), keep track of last texture frame 30776 // because a rogue async texture frame would clear needDraw 30777 30778 30779 if (r.textureDrawLastFrame && !textureDraw) { 30780 needDraw[r.NODE] = true; 30781 needDraw[r.SELECT_BOX] = true; 30782 } 30783 30784 var style = cy.style(); 30785 var zoom = cy.zoom(); 30786 var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom; 30787 var pan = cy.pan(); 30788 var effectivePan = { 30789 x: pan.x, 30790 y: pan.y 30791 }; 30792 var vp = { 30793 zoom: zoom, 30794 pan: { 30795 x: pan.x, 30796 y: pan.y 30797 } 30798 }; 30799 var prevVp = r.prevViewport; 30800 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) 30801 30802 if (!viewportIsDiff && !(inNodeDragGesture && !hasCompoundNodes)) { 30803 r.motionBlurPxRatio = 1; 30804 } 30805 30806 if (forcedPan) { 30807 effectivePan = forcedPan; 30808 } // apply pixel ratio 30809 30810 30811 effectiveZoom *= pixelRatio; 30812 effectivePan.x *= pixelRatio; 30813 effectivePan.y *= pixelRatio; 30814 var eles = r.getCachedZSortedEles(); 30815 30816 function mbclear(context, x, y, w, h) { 30817 var gco = context.globalCompositeOperation; 30818 context.globalCompositeOperation = 'destination-out'; 30819 r.colorFillStyle(context, 255, 255, 255, r.motionBlurTransparency); 30820 context.fillRect(x, y, w, h); 30821 context.globalCompositeOperation = gco; 30822 } 30823 30824 function setContextTransform(context, clear) { 30825 var ePan, eZoom, w, h; 30826 30827 if (!r.clearingMotionBlur && (context === data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] || context === data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG])) { 30828 ePan = { 30829 x: pan.x * mbPxRatio, 30830 y: pan.y * mbPxRatio 30831 }; 30832 eZoom = zoom * mbPxRatio; 30833 w = r.canvasWidth * mbPxRatio; 30834 h = r.canvasHeight * mbPxRatio; 30835 } else { 30836 ePan = effectivePan; 30837 eZoom = effectiveZoom; 30838 w = r.canvasWidth; 30839 h = r.canvasHeight; 30840 } 30841 30842 context.setTransform(1, 0, 0, 1, 0, 0); 30843 30844 if (clear === 'motionBlur') { 30845 mbclear(context, 0, 0, w, h); 30846 } else if (!forcedContext && (clear === undefined || clear)) { 30847 context.clearRect(0, 0, w, h); 30848 } 30849 30850 if (!drawAllLayers) { 30851 context.translate(ePan.x, ePan.y); 30852 context.scale(eZoom, eZoom); 30853 } 30854 30855 if (forcedPan) { 30856 context.translate(forcedPan.x, forcedPan.y); 30857 } 30858 30859 if (forcedZoom) { 30860 context.scale(forcedZoom, forcedZoom); 30861 } 30862 } 30863 30864 if (!textureDraw) { 30865 r.textureDrawLastFrame = false; 30866 } 30867 30868 if (textureDraw) { 30869 r.textureDrawLastFrame = true; 30870 30871 if (!r.textureCache) { 30872 r.textureCache = {}; 30873 r.textureCache.bb = cy.mutableElements().boundingBox(); 30874 r.textureCache.texture = r.data.bufferCanvases[r.TEXTURE_BUFFER]; 30875 var cxt = r.data.bufferContexts[r.TEXTURE_BUFFER]; 30876 cxt.setTransform(1, 0, 0, 1, 0, 0); 30877 cxt.clearRect(0, 0, r.canvasWidth * r.textureMult, r.canvasHeight * r.textureMult); 30878 r.render({ 30879 forcedContext: cxt, 30880 drawOnlyNodeLayer: true, 30881 forcedPxRatio: pixelRatio * r.textureMult 30882 }); 30883 var vp = r.textureCache.viewport = { 30884 zoom: cy.zoom(), 30885 pan: cy.pan(), 30886 width: r.canvasWidth, 30887 height: r.canvasHeight 30888 }; 30889 vp.mpan = { 30890 x: (0 - vp.pan.x) / vp.zoom, 30891 y: (0 - vp.pan.y) / vp.zoom 30892 }; 30893 } 30894 30895 needDraw[r.DRAG] = false; 30896 needDraw[r.NODE] = false; 30897 var context = data.contexts[r.NODE]; 30898 var texture = r.textureCache.texture; 30899 var vp = r.textureCache.viewport; 30900 context.setTransform(1, 0, 0, 1, 0, 0); 30901 30902 if (motionBlur) { 30903 mbclear(context, 0, 0, vp.width, vp.height); 30904 } else { 30905 context.clearRect(0, 0, vp.width, vp.height); 30906 } 30907 30908 var outsideBgColor = style.core('outside-texture-bg-color').value; 30909 var outsideBgOpacity = style.core('outside-texture-bg-opacity').value; 30910 r.colorFillStyle(context, outsideBgColor[0], outsideBgColor[1], outsideBgColor[2], outsideBgOpacity); 30911 context.fillRect(0, 0, vp.width, vp.height); 30912 var zoom = cy.zoom(); 30913 setContextTransform(context, false); 30914 context.clearRect(vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio); 30915 context.drawImage(texture, vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio); 30916 } else if (r.textureOnViewport && !forcedContext) { 30917 // clear the cache since we don't need it 30918 r.textureCache = null; 30919 } 30920 30921 var extent = cy.extent(); 30922 var vpManip = r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming || r.hoverData.draggingEles || r.cy.animated(); 30923 var hideEdges = r.hideEdgesOnViewport && vpManip; 30924 var needMbClear = []; 30925 needMbClear[r.NODE] = !needDraw[r.NODE] && motionBlur && !r.clearedForMotionBlur[r.NODE] || r.clearingMotionBlur; 30926 30927 if (needMbClear[r.NODE]) { 30928 r.clearedForMotionBlur[r.NODE] = true; 30929 } 30930 30931 needMbClear[r.DRAG] = !needDraw[r.DRAG] && motionBlur && !r.clearedForMotionBlur[r.DRAG] || r.clearingMotionBlur; 30932 30933 if (needMbClear[r.DRAG]) { 30934 r.clearedForMotionBlur[r.DRAG] = true; 30935 } 30936 30937 if (needDraw[r.NODE] || drawAllLayers || drawOnlyNodeLayer || needMbClear[r.NODE]) { 30938 var useBuffer = motionBlur && !needMbClear[r.NODE] && mbPxRatio !== 1; 30939 var context = forcedContext || (useBuffer ? r.data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] : data.contexts[r.NODE]); 30940 var clear = motionBlur && !useBuffer ? 'motionBlur' : undefined; 30941 setContextTransform(context, clear); 30942 30943 if (hideEdges) { 30944 r.drawCachedNodes(context, eles.nondrag, pixelRatio, extent); 30945 } else { 30946 r.drawLayeredElements(context, eles.nondrag, pixelRatio, extent); 30947 } 30948 30949 if (r.debug) { 30950 r.drawDebugPoints(context, eles.nondrag); 30951 } 30952 30953 if (!drawAllLayers && !motionBlur) { 30954 needDraw[r.NODE] = false; 30955 } 30956 } 30957 30958 if (!drawOnlyNodeLayer && (needDraw[r.DRAG] || drawAllLayers || needMbClear[r.DRAG])) { 30959 var useBuffer = motionBlur && !needMbClear[r.DRAG] && mbPxRatio !== 1; 30960 var context = forcedContext || (useBuffer ? r.data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG] : data.contexts[r.DRAG]); 30961 setContextTransform(context, motionBlur && !useBuffer ? 'motionBlur' : undefined); 30962 30963 if (hideEdges) { 30964 r.drawCachedNodes(context, eles.drag, pixelRatio, extent); 30965 } else { 30966 r.drawCachedElements(context, eles.drag, pixelRatio, extent); 30967 } 30968 30969 if (r.debug) { 30970 r.drawDebugPoints(context, eles.drag); 30971 } 30972 30973 if (!drawAllLayers && !motionBlur) { 30974 needDraw[r.DRAG] = false; 30975 } 30976 } 30977 30978 if (r.showFps || !drawOnlyNodeLayer && needDraw[r.SELECT_BOX] && !drawAllLayers) { 30979 var context = forcedContext || data.contexts[r.SELECT_BOX]; 30980 setContextTransform(context); 30981 30982 if (r.selection[4] == 1 && (r.hoverData.selecting || r.touchData.selecting)) { 30983 var zoom = r.cy.zoom(); 30984 var borderWidth = style.core('selection-box-border-width').value / zoom; 30985 context.lineWidth = borderWidth; 30986 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 + ')'; 30987 context.fillRect(r.selection[0], r.selection[1], r.selection[2] - r.selection[0], r.selection[3] - r.selection[1]); 30988 30989 if (borderWidth > 0) { 30990 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 + ')'; 30991 context.strokeRect(r.selection[0], r.selection[1], r.selection[2] - r.selection[0], r.selection[3] - r.selection[1]); 30992 } 30993 } 30994 30995 if (data.bgActivePosistion && !r.hoverData.selecting) { 30996 var zoom = r.cy.zoom(); 30997 var pos = data.bgActivePosistion; 30998 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 + ')'; 30999 context.beginPath(); 31000 context.arc(pos.x, pos.y, style.core('active-bg-size').pfValue / zoom, 0, 2 * Math.PI); 31001 context.fill(); 31002 } 31003 31004 var timeToRender = r.lastRedrawTime; 31005 31006 if (r.showFps && timeToRender) { 31007 timeToRender = Math.round(timeToRender); 31008 var fps = Math.round(1000 / timeToRender); 31009 context.setTransform(1, 0, 0, 1, 0, 0); 31010 context.fillStyle = 'rgba(255, 0, 0, 0.75)'; 31011 context.strokeStyle = 'rgba(255, 0, 0, 0.75)'; 31012 context.lineWidth = 1; 31013 context.fillText('1 frame = ' + timeToRender + ' ms = ' + fps + ' fps', 0, 20); 31014 var maxFps = 60; 31015 context.strokeRect(0, 30, 250, 20); 31016 context.fillRect(0, 30, 250 * Math.min(fps / maxFps, 1), 20); 31017 } 31018 31019 if (!drawAllLayers) { 31020 needDraw[r.SELECT_BOX] = false; 31021 } 31022 } // motionblur: blit rendered blurry frames 31023 31024 31025 if (motionBlur && mbPxRatio !== 1) { 31026 var cxtNode = data.contexts[r.NODE]; 31027 var txtNode = r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE]; 31028 var cxtDrag = data.contexts[r.DRAG]; 31029 var txtDrag = r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]; 31030 31031 var drawMotionBlur = function drawMotionBlur(cxt, txt, needClear) { 31032 cxt.setTransform(1, 0, 0, 1, 0, 0); 31033 31034 if (needClear || !motionBlurFadeEffect) { 31035 cxt.clearRect(0, 0, r.canvasWidth, r.canvasHeight); 31036 } else { 31037 mbclear(cxt, 0, 0, r.canvasWidth, r.canvasHeight); 31038 } 31039 31040 var pxr = mbPxRatio; 31041 cxt.drawImage(txt, // img 31042 0, 0, // sx, sy 31043 r.canvasWidth * pxr, r.canvasHeight * pxr, // sw, sh 31044 0, 0, // x, y 31045 r.canvasWidth, r.canvasHeight // w, h 31046 ); 31047 }; 31048 31049 if (needDraw[r.NODE] || needMbClear[r.NODE]) { 31050 drawMotionBlur(cxtNode, txtNode, needMbClear[r.NODE]); 31051 needDraw[r.NODE] = false; 31052 } 31053 31054 if (needDraw[r.DRAG] || needMbClear[r.DRAG]) { 31055 drawMotionBlur(cxtDrag, txtDrag, needMbClear[r.DRAG]); 31056 needDraw[r.DRAG] = false; 31057 } 31058 } 31059 31060 r.prevViewport = vp; 31061 31062 if (r.clearingMotionBlur) { 31063 r.clearingMotionBlur = false; 31064 r.motionBlurCleared = true; 31065 r.motionBlur = true; 31066 } 31067 31068 if (motionBlur) { 31069 r.motionBlurTimeout = setTimeout(function () { 31070 r.motionBlurTimeout = null; 31071 r.clearedForMotionBlur[r.NODE] = false; 31072 r.clearedForMotionBlur[r.DRAG] = false; 31073 r.motionBlur = false; 31074 r.clearingMotionBlur = !textureDraw; 31075 r.mbFrames = 0; 31076 needDraw[r.NODE] = true; 31077 needDraw[r.DRAG] = true; 31078 r.redraw(); 31079 }, motionBlurDelay); 31080 } 31081 31082 if (!forcedContext) { 31083 cy.emit('render'); 31084 } 31085 }; 31086 31087 var CRp$7 = {}; // @O Polygon drawing 31088 31089 CRp$7.drawPolygonPath = function (context, x, y, width, height, points) { 31090 var halfW = width / 2; 31091 var halfH = height / 2; 31092 31093 if (context.beginPath) { 31094 context.beginPath(); 31095 } 31096 31097 context.moveTo(x + halfW * points[0], y + halfH * points[1]); 31098 31099 for (var i = 1; i < points.length / 2; i++) { 31100 context.lineTo(x + halfW * points[i * 2], y + halfH * points[i * 2 + 1]); 31101 } 31102 31103 context.closePath(); 31104 }; 31105 31106 CRp$7.drawRoundPolygonPath = function (context, x, y, width, height, points) { 31107 var halfW = width / 2; 31108 var halfH = height / 2; 31109 var cornerRadius = getRoundPolygonRadius(width, height); 31110 31111 if (context.beginPath) { 31112 context.beginPath(); 31113 } 31114 31115 for (var _i = 0; _i < points.length / 4; _i++) { 31116 var sourceUv = void 0, 31117 destUv = void 0; 31118 31119 if (_i === 0) { 31120 sourceUv = points.length - 2; 31121 } else { 31122 sourceUv = _i * 4 - 2; 31123 } 31124 31125 destUv = _i * 4 + 2; 31126 var px = x + halfW * points[_i * 4]; 31127 var py = y + halfH * points[_i * 4 + 1]; 31128 var cosTheta = -points[sourceUv] * points[destUv] - points[sourceUv + 1] * points[destUv + 1]; 31129 var offset = cornerRadius / Math.tan(Math.acos(cosTheta) / 2); 31130 var cp0x = px - offset * points[sourceUv]; 31131 var cp0y = py - offset * points[sourceUv + 1]; 31132 var cp1x = px + offset * points[destUv]; 31133 var cp1y = py + offset * points[destUv + 1]; 31134 31135 if (_i === 0) { 31136 context.moveTo(cp0x, cp0y); 31137 } else { 31138 context.lineTo(cp0x, cp0y); 31139 } 31140 31141 context.arcTo(px, py, cp1x, cp1y, cornerRadius); 31142 } 31143 31144 context.closePath(); 31145 }; // Round rectangle drawing 31146 31147 31148 CRp$7.drawRoundRectanglePath = function (context, x, y, width, height) { 31149 var halfWidth = width / 2; 31150 var halfHeight = height / 2; 31151 var cornerRadius = getRoundRectangleRadius(width, height); 31152 31153 if (context.beginPath) { 31154 context.beginPath(); 31155 } // Start at top middle 31156 31157 31158 context.moveTo(x, y - halfHeight); // Arc from middle top to right side 31159 31160 context.arcTo(x + halfWidth, y - halfHeight, x + halfWidth, y, cornerRadius); // Arc from right side to bottom 31161 31162 context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius); // Arc from bottom to left side 31163 31164 context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius); // Arc from left side to topBorder 31165 31166 context.arcTo(x - halfWidth, y - halfHeight, x, y - halfHeight, cornerRadius); // Join line 31167 31168 context.lineTo(x, y - halfHeight); 31169 context.closePath(); 31170 }; 31171 31172 CRp$7.drawBottomRoundRectanglePath = function (context, x, y, width, height) { 31173 var halfWidth = width / 2; 31174 var halfHeight = height / 2; 31175 var cornerRadius = getRoundRectangleRadius(width, height); 31176 31177 if (context.beginPath) { 31178 context.beginPath(); 31179 } // Start at top middle 31180 31181 31182 context.moveTo(x, y - halfHeight); 31183 context.lineTo(x + halfWidth, y - halfHeight); 31184 context.lineTo(x + halfWidth, y); 31185 context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius); 31186 context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius); 31187 context.lineTo(x - halfWidth, y - halfHeight); 31188 context.lineTo(x, y - halfHeight); 31189 context.closePath(); 31190 }; 31191 31192 CRp$7.drawCutRectanglePath = function (context, x, y, width, height) { 31193 var halfWidth = width / 2; 31194 var halfHeight = height / 2; 31195 var cornerLength = getCutRectangleCornerLength(); 31196 31197 if (context.beginPath) { 31198 context.beginPath(); 31199 } 31200 31201 context.moveTo(x - halfWidth + cornerLength, y - halfHeight); 31202 context.lineTo(x + halfWidth - cornerLength, y - halfHeight); 31203 context.lineTo(x + halfWidth, y - halfHeight + cornerLength); 31204 context.lineTo(x + halfWidth, y + halfHeight - cornerLength); 31205 context.lineTo(x + halfWidth - cornerLength, y + halfHeight); 31206 context.lineTo(x - halfWidth + cornerLength, y + halfHeight); 31207 context.lineTo(x - halfWidth, y + halfHeight - cornerLength); 31208 context.lineTo(x - halfWidth, y - halfHeight + cornerLength); 31209 context.closePath(); 31210 }; 31211 31212 CRp$7.drawBarrelPath = function (context, x, y, width, height) { 31213 var halfWidth = width / 2; 31214 var halfHeight = height / 2; 31215 var xBegin = x - halfWidth; 31216 var xEnd = x + halfWidth; 31217 var yBegin = y - halfHeight; 31218 var yEnd = y + halfHeight; 31219 var barrelCurveConstants = getBarrelCurveConstants(width, height); 31220 var wOffset = barrelCurveConstants.widthOffset; 31221 var hOffset = barrelCurveConstants.heightOffset; 31222 var ctrlPtXOffset = barrelCurveConstants.ctrlPtOffsetPct * wOffset; 31223 31224 if (context.beginPath) { 31225 context.beginPath(); 31226 } 31227 31228 context.moveTo(xBegin, yBegin + hOffset); 31229 context.lineTo(xBegin, yEnd - hOffset); 31230 context.quadraticCurveTo(xBegin + ctrlPtXOffset, yEnd, xBegin + wOffset, yEnd); 31231 context.lineTo(xEnd - wOffset, yEnd); 31232 context.quadraticCurveTo(xEnd - ctrlPtXOffset, yEnd, xEnd, yEnd - hOffset); 31233 context.lineTo(xEnd, yBegin + hOffset); 31234 context.quadraticCurveTo(xEnd - ctrlPtXOffset, yBegin, xEnd - wOffset, yBegin); 31235 context.lineTo(xBegin + wOffset, yBegin); 31236 context.quadraticCurveTo(xBegin + ctrlPtXOffset, yBegin, xBegin, yBegin + hOffset); 31237 context.closePath(); 31238 }; 31239 31240 var sin0 = Math.sin(0); 31241 var cos0 = Math.cos(0); 31242 var sin = {}; 31243 var cos = {}; 31244 var ellipseStepSize = Math.PI / 40; 31245 31246 for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize) { 31247 sin[i] = Math.sin(i); 31248 cos[i] = Math.cos(i); 31249 } 31250 31251 CRp$7.drawEllipsePath = function (context, centerX, centerY, width, height) { 31252 if (context.beginPath) { 31253 context.beginPath(); 31254 } 31255 31256 if (context.ellipse) { 31257 context.ellipse(centerX, centerY, width / 2, height / 2, 0, 0, 2 * Math.PI); 31258 } else { 31259 var xPos, yPos; 31260 var rw = width / 2; 31261 var rh = height / 2; 31262 31263 for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize) { 31264 xPos = centerX - rw * sin[i] * sin0 + rw * cos[i] * cos0; 31265 yPos = centerY + rh * cos[i] * sin0 + rh * sin[i] * cos0; 31266 31267 if (i === 0) { 31268 context.moveTo(xPos, yPos); 31269 } else { 31270 context.lineTo(xPos, yPos); 31271 } 31272 } 31273 } 31274 31275 context.closePath(); 31276 }; 31277 31278 /* global atob, ArrayBuffer, Uint8Array, Blob */ 31279 var CRp$8 = {}; 31280 31281 CRp$8.createBuffer = function (w, h) { 31282 var buffer = document.createElement('canvas'); // eslint-disable-line no-undef 31283 31284 buffer.width = w; 31285 buffer.height = h; 31286 return [buffer, buffer.getContext('2d')]; 31287 }; 31288 31289 CRp$8.bufferCanvasImage = function (options) { 31290 var cy = this.cy; 31291 var eles = cy.mutableElements(); 31292 var bb = eles.boundingBox(); 31293 var ctrRect = this.findContainerClientCoords(); 31294 var width = options.full ? Math.ceil(bb.w) : ctrRect[2]; 31295 var height = options.full ? Math.ceil(bb.h) : ctrRect[3]; 31296 var specdMaxDims = number(options.maxWidth) || number(options.maxHeight); 31297 var pxRatio = this.getPixelRatio(); 31298 var scale = 1; 31299 31300 if (options.scale !== undefined) { 31301 width *= options.scale; 31302 height *= options.scale; 31303 scale = options.scale; 31304 } else if (specdMaxDims) { 31305 var maxScaleW = Infinity; 31306 var maxScaleH = Infinity; 31307 31308 if (number(options.maxWidth)) { 31309 maxScaleW = scale * options.maxWidth / width; 31310 } 31311 31312 if (number(options.maxHeight)) { 31313 maxScaleH = scale * options.maxHeight / height; 31314 } 31315 31316 scale = Math.min(maxScaleW, maxScaleH); 31317 width *= scale; 31318 height *= scale; 31319 } 31320 31321 if (!specdMaxDims) { 31322 width *= pxRatio; 31323 height *= pxRatio; 31324 scale *= pxRatio; 31325 } 31326 31327 var buffCanvas = document.createElement('canvas'); // eslint-disable-line no-undef 31328 31329 buffCanvas.width = width; 31330 buffCanvas.height = height; 31331 buffCanvas.style.width = width + 'px'; 31332 buffCanvas.style.height = height + 'px'; 31333 var buffCxt = buffCanvas.getContext('2d'); // Rasterize the layers, but only if container has nonzero size 31334 31335 if (width > 0 && height > 0) { 31336 buffCxt.clearRect(0, 0, width, height); 31337 buffCxt.globalCompositeOperation = 'source-over'; 31338 var zsortedEles = this.getCachedZSortedEles(); 31339 31340 if (options.full) { 31341 // draw the full bounds of the graph 31342 buffCxt.translate(-bb.x1 * scale, -bb.y1 * scale); 31343 buffCxt.scale(scale, scale); 31344 this.drawElements(buffCxt, zsortedEles); 31345 buffCxt.scale(1 / scale, 1 / scale); 31346 buffCxt.translate(bb.x1 * scale, bb.y1 * scale); 31347 } else { 31348 // draw the current view 31349 var pan = cy.pan(); 31350 var translation = { 31351 x: pan.x * scale, 31352 y: pan.y * scale 31353 }; 31354 scale *= cy.zoom(); 31355 buffCxt.translate(translation.x, translation.y); 31356 buffCxt.scale(scale, scale); 31357 this.drawElements(buffCxt, zsortedEles); 31358 buffCxt.scale(1 / scale, 1 / scale); 31359 buffCxt.translate(-translation.x, -translation.y); 31360 } // need to fill bg at end like this in order to fill cleared transparent pixels in jpgs 31361 31362 31363 if (options.bg) { 31364 buffCxt.globalCompositeOperation = 'destination-over'; 31365 buffCxt.fillStyle = options.bg; 31366 buffCxt.rect(0, 0, width, height); 31367 buffCxt.fill(); 31368 } 31369 } 31370 31371 return buffCanvas; 31372 }; 31373 31374 function b64ToBlob(b64, mimeType) { 31375 var bytes = atob(b64); 31376 var buff = new ArrayBuffer(bytes.length); 31377 var buffUint8 = new Uint8Array(buff); 31378 31379 for (var i = 0; i < bytes.length; i++) { 31380 buffUint8[i] = bytes.charCodeAt(i); 31381 } 31382 31383 return new Blob([buff], { 31384 type: mimeType 31385 }); 31386 } 31387 31388 function b64UriToB64(b64uri) { 31389 var i = b64uri.indexOf(','); 31390 return b64uri.substr(i + 1); 31391 } 31392 31393 function output(options, canvas, mimeType) { 31394 var getB64Uri = function getB64Uri() { 31395 return canvas.toDataURL(mimeType, options.quality); 31396 }; 31397 31398 switch (options.output) { 31399 case 'blob-promise': 31400 return new Promise$1(function (resolve, reject) { 31401 try { 31402 canvas.toBlob(function (blob) { 31403 if (blob != null) { 31404 resolve(blob); 31405 } else { 31406 reject(new Error('`canvas.toBlob()` sent a null value in its callback')); 31407 } 31408 }, mimeType, options.quality); 31409 } catch (err) { 31410 reject(err); 31411 } 31412 }); 31413 31414 case 'blob': 31415 return b64ToBlob(b64UriToB64(getB64Uri()), mimeType); 31416 31417 case 'base64': 31418 return b64UriToB64(getB64Uri()); 31419 31420 case 'base64uri': 31421 default: 31422 return getB64Uri(); 31423 } 31424 } 31425 31426 CRp$8.png = function (options) { 31427 return output(options, this.bufferCanvasImage(options), 'image/png'); 31428 }; 31429 31430 CRp$8.jpg = function (options) { 31431 return output(options, this.bufferCanvasImage(options), 'image/jpeg'); 31432 }; 31433 31434 var CRp$9 = {}; 31435 31436 CRp$9.nodeShapeImpl = function (name, context, centerX, centerY, width, height, points) { 31437 switch (name) { 31438 case 'ellipse': 31439 return this.drawEllipsePath(context, centerX, centerY, width, height); 31440 31441 case 'polygon': 31442 return this.drawPolygonPath(context, centerX, centerY, width, height, points); 31443 31444 case 'round-polygon': 31445 return this.drawRoundPolygonPath(context, centerX, centerY, width, height, points); 31446 31447 case 'roundrectangle': 31448 case 'round-rectangle': 31449 return this.drawRoundRectanglePath(context, centerX, centerY, width, height); 31450 31451 case 'cutrectangle': 31452 case 'cut-rectangle': 31453 return this.drawCutRectanglePath(context, centerX, centerY, width, height); 31454 31455 case 'bottomroundrectangle': 31456 case 'bottom-round-rectangle': 31457 return this.drawBottomRoundRectanglePath(context, centerX, centerY, width, height); 31458 31459 case 'barrel': 31460 return this.drawBarrelPath(context, centerX, centerY, width, height); 31461 } 31462 }; 31463 31464 var CR = CanvasRenderer; 31465 var CRp$a = CanvasRenderer.prototype; 31466 CRp$a.CANVAS_LAYERS = 3; // 31467 31468 CRp$a.SELECT_BOX = 0; 31469 CRp$a.DRAG = 1; 31470 CRp$a.NODE = 2; 31471 CRp$a.BUFFER_COUNT = 3; // 31472 31473 CRp$a.TEXTURE_BUFFER = 0; 31474 CRp$a.MOTIONBLUR_BUFFER_NODE = 1; 31475 CRp$a.MOTIONBLUR_BUFFER_DRAG = 2; 31476 31477 function CanvasRenderer(options) { 31478 var r = this; 31479 r.data = { 31480 canvases: new Array(CRp$a.CANVAS_LAYERS), 31481 contexts: new Array(CRp$a.CANVAS_LAYERS), 31482 canvasNeedsRedraw: new Array(CRp$a.CANVAS_LAYERS), 31483 bufferCanvases: new Array(CRp$a.BUFFER_COUNT), 31484 bufferContexts: new Array(CRp$a.CANVAS_LAYERS) 31485 }; 31486 var tapHlOffAttr = '-webkit-tap-highlight-color'; 31487 var tapHlOffStyle = 'rgba(0,0,0,0)'; 31488 r.data.canvasContainer = document.createElement('div'); // eslint-disable-line no-undef 31489 31490 var containerStyle = r.data.canvasContainer.style; 31491 r.data.canvasContainer.style[tapHlOffAttr] = tapHlOffStyle; 31492 containerStyle.position = 'relative'; 31493 containerStyle.zIndex = '0'; 31494 containerStyle.overflow = 'hidden'; 31495 var container = options.cy.container(); 31496 container.appendChild(r.data.canvasContainer); 31497 container.style[tapHlOffAttr] = tapHlOffStyle; 31498 var styleMap = { 31499 '-webkit-user-select': 'none', 31500 '-moz-user-select': '-moz-none', 31501 'user-select': 'none', 31502 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', 31503 'outline-style': 'none' 31504 }; 31505 31506 if (ms()) { 31507 styleMap['-ms-touch-action'] = 'none'; 31508 styleMap['touch-action'] = 'none'; 31509 } 31510 31511 for (var i = 0; i < CRp$a.CANVAS_LAYERS; i++) { 31512 var canvas = r.data.canvases[i] = document.createElement('canvas'); // eslint-disable-line no-undef 31513 31514 r.data.contexts[i] = canvas.getContext('2d'); 31515 Object.keys(styleMap).forEach(function (k) { 31516 canvas.style[k] = styleMap[k]; 31517 }); 31518 canvas.style.position = 'absolute'; 31519 canvas.setAttribute('data-id', 'layer' + i); 31520 canvas.style.zIndex = String(CRp$a.CANVAS_LAYERS - i); 31521 r.data.canvasContainer.appendChild(canvas); 31522 r.data.canvasNeedsRedraw[i] = false; 31523 } 31524 31525 r.data.topCanvas = r.data.canvases[0]; 31526 r.data.canvases[CRp$a.NODE].setAttribute('data-id', 'layer' + CRp$a.NODE + '-node'); 31527 r.data.canvases[CRp$a.SELECT_BOX].setAttribute('data-id', 'layer' + CRp$a.SELECT_BOX + '-selectbox'); 31528 r.data.canvases[CRp$a.DRAG].setAttribute('data-id', 'layer' + CRp$a.DRAG + '-drag'); 31529 31530 for (var i = 0; i < CRp$a.BUFFER_COUNT; i++) { 31531 r.data.bufferCanvases[i] = document.createElement('canvas'); // eslint-disable-line no-undef 31532 31533 r.data.bufferContexts[i] = r.data.bufferCanvases[i].getContext('2d'); 31534 r.data.bufferCanvases[i].style.position = 'absolute'; 31535 r.data.bufferCanvases[i].setAttribute('data-id', 'buffer' + i); 31536 r.data.bufferCanvases[i].style.zIndex = String(-i - 1); 31537 r.data.bufferCanvases[i].style.visibility = 'hidden'; //r.data.canvasContainer.appendChild(r.data.bufferCanvases[i]); 31538 } 31539 31540 r.pathsEnabled = true; 31541 var emptyBb = makeBoundingBox(); 31542 31543 var getBoxCenter = function getBoxCenter(bb) { 31544 return { 31545 x: (bb.x1 + bb.x2) / 2, 31546 y: (bb.y1 + bb.y2) / 2 31547 }; 31548 }; 31549 31550 var getCenterOffset = function getCenterOffset(bb) { 31551 return { 31552 x: -bb.w / 2, 31553 y: -bb.h / 2 31554 }; 31555 }; 31556 31557 var backgroundTimestampHasChanged = function backgroundTimestampHasChanged(ele) { 31558 var _p = ele[0]._private; 31559 var same = _p.oldBackgroundTimestamp === _p.backgroundTimestamp; 31560 return !same; 31561 }; 31562 31563 var getStyleKey = function getStyleKey(ele) { 31564 return ele[0]._private.nodeKey; 31565 }; 31566 31567 var getLabelKey = function getLabelKey(ele) { 31568 return ele[0]._private.labelStyleKey; 31569 }; 31570 31571 var getSourceLabelKey = function getSourceLabelKey(ele) { 31572 return ele[0]._private.sourceLabelStyleKey; 31573 }; 31574 31575 var getTargetLabelKey = function getTargetLabelKey(ele) { 31576 return ele[0]._private.targetLabelStyleKey; 31577 }; 31578 31579 var drawElement = function drawElement(context, ele, bb, scaledLabelShown, useEleOpacity) { 31580 return r.drawElement(context, ele, bb, false, false, useEleOpacity); 31581 }; 31582 31583 var drawLabel = function drawLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 31584 return r.drawElementText(context, ele, bb, scaledLabelShown, 'main', useEleOpacity); 31585 }; 31586 31587 var drawSourceLabel = function drawSourceLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 31588 return r.drawElementText(context, ele, bb, scaledLabelShown, 'source', useEleOpacity); 31589 }; 31590 31591 var drawTargetLabel = function drawTargetLabel(context, ele, bb, scaledLabelShown, useEleOpacity) { 31592 return r.drawElementText(context, ele, bb, scaledLabelShown, 'target', useEleOpacity); 31593 }; 31594 31595 var getElementBox = function getElementBox(ele) { 31596 ele.boundingBox(); 31597 return ele[0]._private.bodyBounds; 31598 }; 31599 31600 var getLabelBox = function getLabelBox(ele) { 31601 ele.boundingBox(); 31602 return ele[0]._private.labelBounds.main || emptyBb; 31603 }; 31604 31605 var getSourceLabelBox = function getSourceLabelBox(ele) { 31606 ele.boundingBox(); 31607 return ele[0]._private.labelBounds.source || emptyBb; 31608 }; 31609 31610 var getTargetLabelBox = function getTargetLabelBox(ele) { 31611 ele.boundingBox(); 31612 return ele[0]._private.labelBounds.target || emptyBb; 31613 }; 31614 31615 var isLabelVisibleAtScale = function isLabelVisibleAtScale(ele, scaledLabelShown) { 31616 return scaledLabelShown; 31617 }; 31618 31619 var getElementRotationPoint = function getElementRotationPoint(ele) { 31620 return getBoxCenter(getElementBox(ele)); 31621 }; 31622 31623 var addTextMargin = function addTextMargin(prefix, pt, ele) { 31624 var pre = prefix ? prefix + '-' : ''; 31625 return { 31626 x: pt.x + ele.pstyle(pre + 'text-margin-x').pfValue, 31627 y: pt.y + ele.pstyle(pre + 'text-margin-y').pfValue 31628 }; 31629 }; 31630 31631 var getRsPt = function getRsPt(ele, x, y) { 31632 var rs = ele[0]._private.rscratch; 31633 return { 31634 x: rs[x], 31635 y: rs[y] 31636 }; 31637 }; 31638 31639 var getLabelRotationPoint = function getLabelRotationPoint(ele) { 31640 return addTextMargin('', getRsPt(ele, 'labelX', 'labelY'), ele); 31641 }; 31642 31643 var getSourceLabelRotationPoint = function getSourceLabelRotationPoint(ele) { 31644 return addTextMargin('source', getRsPt(ele, 'sourceLabelX', 'sourceLabelY'), ele); 31645 }; 31646 31647 var getTargetLabelRotationPoint = function getTargetLabelRotationPoint(ele) { 31648 return addTextMargin('target', getRsPt(ele, 'targetLabelX', 'targetLabelY'), ele); 31649 }; 31650 31651 var getElementRotationOffset = function getElementRotationOffset(ele) { 31652 return getCenterOffset(getElementBox(ele)); 31653 }; 31654 31655 var getSourceLabelRotationOffset = function getSourceLabelRotationOffset(ele) { 31656 return getCenterOffset(getSourceLabelBox(ele)); 31657 }; 31658 31659 var getTargetLabelRotationOffset = function getTargetLabelRotationOffset(ele) { 31660 return getCenterOffset(getTargetLabelBox(ele)); 31661 }; 31662 31663 var getLabelRotationOffset = function getLabelRotationOffset(ele) { 31664 var bb = getLabelBox(ele); 31665 var p = getCenterOffset(getLabelBox(ele)); 31666 31667 if (ele.isNode()) { 31668 switch (ele.pstyle('text-halign').value) { 31669 case 'left': 31670 p.x = -bb.w; 31671 break; 31672 31673 case 'right': 31674 p.x = 0; 31675 break; 31676 } 31677 31678 switch (ele.pstyle('text-valign').value) { 31679 case 'top': 31680 p.y = -bb.h; 31681 break; 31682 31683 case 'bottom': 31684 p.y = 0; 31685 break; 31686 } 31687 } 31688 31689 return p; 31690 }; 31691 31692 var eleTxrCache = r.data.eleTxrCache = new ElementTextureCache(r, { 31693 getKey: getStyleKey, 31694 doesEleInvalidateKey: backgroundTimestampHasChanged, 31695 drawElement: drawElement, 31696 getBoundingBox: getElementBox, 31697 getRotationPoint: getElementRotationPoint, 31698 getRotationOffset: getElementRotationOffset, 31699 allowEdgeTxrCaching: false, 31700 allowParentTxrCaching: false 31701 }); 31702 var lblTxrCache = r.data.lblTxrCache = new ElementTextureCache(r, { 31703 getKey: getLabelKey, 31704 drawElement: drawLabel, 31705 getBoundingBox: getLabelBox, 31706 getRotationPoint: getLabelRotationPoint, 31707 getRotationOffset: getLabelRotationOffset, 31708 isVisible: isLabelVisibleAtScale 31709 }); 31710 var slbTxrCache = r.data.slbTxrCache = new ElementTextureCache(r, { 31711 getKey: getSourceLabelKey, 31712 drawElement: drawSourceLabel, 31713 getBoundingBox: getSourceLabelBox, 31714 getRotationPoint: getSourceLabelRotationPoint, 31715 getRotationOffset: getSourceLabelRotationOffset, 31716 isVisible: isLabelVisibleAtScale 31717 }); 31718 var tlbTxrCache = r.data.tlbTxrCache = new ElementTextureCache(r, { 31719 getKey: getTargetLabelKey, 31720 drawElement: drawTargetLabel, 31721 getBoundingBox: getTargetLabelBox, 31722 getRotationPoint: getTargetLabelRotationPoint, 31723 getRotationOffset: getTargetLabelRotationOffset, 31724 isVisible: isLabelVisibleAtScale 31725 }); 31726 var lyrTxrCache = r.data.lyrTxrCache = new LayeredTextureCache(r); 31727 r.onUpdateEleCalcs(function invalidateTextureCaches(willDraw, eles) { 31728 // each cache should check for sub-key diff to see that the update affects that cache particularly 31729 eleTxrCache.invalidateElements(eles); 31730 lblTxrCache.invalidateElements(eles); 31731 slbTxrCache.invalidateElements(eles); 31732 tlbTxrCache.invalidateElements(eles); // any change invalidates the layers 31733 31734 lyrTxrCache.invalidateElements(eles); // update the old bg timestamp so diffs can be done in the ele txr caches 31735 31736 for (var _i = 0; _i < eles.length; _i++) { 31737 var _p = eles[_i]._private; 31738 _p.oldBackgroundTimestamp = _p.backgroundTimestamp; 31739 } 31740 }); 31741 31742 var refineInLayers = function refineInLayers(reqs) { 31743 for (var i = 0; i < reqs.length; i++) { 31744 lyrTxrCache.enqueueElementRefinement(reqs[i].ele); 31745 } 31746 }; 31747 31748 eleTxrCache.onDequeue(refineInLayers); 31749 lblTxrCache.onDequeue(refineInLayers); 31750 slbTxrCache.onDequeue(refineInLayers); 31751 tlbTxrCache.onDequeue(refineInLayers); 31752 } 31753 31754 CRp$a.redrawHint = function (group, bool) { 31755 var r = this; 31756 31757 switch (group) { 31758 case 'eles': 31759 r.data.canvasNeedsRedraw[CRp$a.NODE] = bool; 31760 break; 31761 31762 case 'drag': 31763 r.data.canvasNeedsRedraw[CRp$a.DRAG] = bool; 31764 break; 31765 31766 case 'select': 31767 r.data.canvasNeedsRedraw[CRp$a.SELECT_BOX] = bool; 31768 break; 31769 } 31770 }; // whether to use Path2D caching for drawing 31771 31772 31773 var pathsImpld = typeof Path2D !== 'undefined'; 31774 31775 CRp$a.path2dEnabled = function (on) { 31776 if (on === undefined) { 31777 return this.pathsEnabled; 31778 } 31779 31780 this.pathsEnabled = on ? true : false; 31781 }; 31782 31783 CRp$a.usePaths = function () { 31784 return pathsImpld && this.pathsEnabled; 31785 }; 31786 31787 CRp$a.setImgSmoothing = function (context, bool) { 31788 if (context.imageSmoothingEnabled != null) { 31789 context.imageSmoothingEnabled = bool; 31790 } else { 31791 context.webkitImageSmoothingEnabled = bool; 31792 context.mozImageSmoothingEnabled = bool; 31793 context.msImageSmoothingEnabled = bool; 31794 } 31795 }; 31796 31797 CRp$a.getImgSmoothing = function (context) { 31798 if (context.imageSmoothingEnabled != null) { 31799 return context.imageSmoothingEnabled; 31800 } else { 31801 return context.webkitImageSmoothingEnabled || context.mozImageSmoothingEnabled || context.msImageSmoothingEnabled; 31802 } 31803 }; 31804 31805 CRp$a.makeOffscreenCanvas = function (width, height) { 31806 var canvas; 31807 31808 if ((typeof OffscreenCanvas === "undefined" ? "undefined" : _typeof(OffscreenCanvas)) !== ( "undefined" )) { 31809 canvas = new OffscreenCanvas(width, height); 31810 } else { 31811 canvas = document.createElement('canvas'); // eslint-disable-line no-undef 31812 31813 canvas.width = width; 31814 canvas.height = height; 31815 } 31816 31817 return canvas; 31818 }; 31819 31820 [CRp, CRp$1, CRp$2, CRp$3, CRp$4, CRp$5, CRp$6, CRp$7, CRp$8, CRp$9].forEach(function (props) { 31821 extend(CRp$a, props); 31822 }); 31823 31824 var renderer = [{ 31825 name: 'null', 31826 impl: NullRenderer 31827 }, { 31828 name: 'base', 31829 impl: BR 31830 }, { 31831 name: 'canvas', 31832 impl: CR 31833 }]; 31834 31835 var incExts = [{ 31836 type: 'layout', 31837 extensions: layout 31838 }, { 31839 type: 'renderer', 31840 extensions: renderer 31841 }]; 31842 31843 var extensions = {}; // registered modules for extensions, indexed by name 31844 31845 var modules = {}; 31846 31847 function setExtension(type, name, registrant) { 31848 var ext = registrant; 31849 31850 var overrideErr = function overrideErr(field) { 31851 error('Can not register `' + name + '` for `' + type + '` since `' + field + '` already exists in the prototype and can not be overridden'); 31852 }; 31853 31854 if (type === 'core') { 31855 if (Core.prototype[name]) { 31856 return overrideErr(name); 31857 } else { 31858 Core.prototype[name] = registrant; 31859 } 31860 } else if (type === 'collection') { 31861 if (Collection.prototype[name]) { 31862 return overrideErr(name); 31863 } else { 31864 Collection.prototype[name] = registrant; 31865 } 31866 } else if (type === 'layout') { 31867 // fill in missing layout functions in the prototype 31868 var Layout = function Layout(options) { 31869 this.options = options; 31870 registrant.call(this, options); // make sure layout has _private for use w/ std apis like .on() 31871 31872 if (!plainObject(this._private)) { 31873 this._private = {}; 31874 } 31875 31876 this._private.cy = options.cy; 31877 this._private.listeners = []; 31878 this.createEmitter(); 31879 }; 31880 31881 var layoutProto = Layout.prototype = Object.create(registrant.prototype); 31882 var optLayoutFns = []; 31883 31884 for (var i = 0; i < optLayoutFns.length; i++) { 31885 var fnName = optLayoutFns[i]; 31886 31887 layoutProto[fnName] = layoutProto[fnName] || function () { 31888 return this; 31889 }; 31890 } // either .start() or .run() is defined, so autogen the other 31891 31892 31893 if (layoutProto.start && !layoutProto.run) { 31894 layoutProto.run = function () { 31895 this.start(); 31896 return this; 31897 }; 31898 } else if (!layoutProto.start && layoutProto.run) { 31899 layoutProto.start = function () { 31900 this.run(); 31901 return this; 31902 }; 31903 } 31904 31905 var regStop = registrant.prototype.stop; 31906 31907 layoutProto.stop = function () { 31908 var opts = this.options; 31909 31910 if (opts && opts.animate) { 31911 var anis = this.animations; 31912 31913 if (anis) { 31914 for (var _i = 0; _i < anis.length; _i++) { 31915 anis[_i].stop(); 31916 } 31917 } 31918 } 31919 31920 if (regStop) { 31921 regStop.call(this); 31922 } else { 31923 this.emit('layoutstop'); 31924 } 31925 31926 return this; 31927 }; 31928 31929 if (!layoutProto.destroy) { 31930 layoutProto.destroy = function () { 31931 return this; 31932 }; 31933 } 31934 31935 layoutProto.cy = function () { 31936 return this._private.cy; 31937 }; 31938 31939 var getCy = function getCy(layout) { 31940 return layout._private.cy; 31941 }; 31942 31943 var emitterOpts = { 31944 addEventFields: function addEventFields(layout, evt) { 31945 evt.layout = layout; 31946 evt.cy = getCy(layout); 31947 evt.target = layout; 31948 }, 31949 bubble: function bubble() { 31950 return true; 31951 }, 31952 parent: function parent(layout) { 31953 return getCy(layout); 31954 } 31955 }; 31956 extend(layoutProto, { 31957 createEmitter: function createEmitter() { 31958 this._private.emitter = new Emitter(emitterOpts, this); 31959 return this; 31960 }, 31961 emitter: function emitter() { 31962 return this._private.emitter; 31963 }, 31964 on: function on(evt, cb) { 31965 this.emitter().on(evt, cb); 31966 return this; 31967 }, 31968 one: function one(evt, cb) { 31969 this.emitter().one(evt, cb); 31970 return this; 31971 }, 31972 once: function once(evt, cb) { 31973 this.emitter().one(evt, cb); 31974 return this; 31975 }, 31976 removeListener: function removeListener(evt, cb) { 31977 this.emitter().removeListener(evt, cb); 31978 return this; 31979 }, 31980 removeAllListeners: function removeAllListeners() { 31981 this.emitter().removeAllListeners(); 31982 return this; 31983 }, 31984 emit: function emit(evt, params) { 31985 this.emitter().emit(evt, params); 31986 return this; 31987 } 31988 }); 31989 define$3.eventAliasesOn(layoutProto); 31990 ext = Layout; // replace with our wrapped layout 31991 } else if (type === 'renderer' && name !== 'null' && name !== 'base') { 31992 // user registered renderers inherit from base 31993 var BaseRenderer = getExtension('renderer', 'base'); 31994 var bProto = BaseRenderer.prototype; 31995 var RegistrantRenderer = registrant; 31996 var rProto = registrant.prototype; 31997 31998 var Renderer = function Renderer() { 31999 BaseRenderer.apply(this, arguments); 32000 RegistrantRenderer.apply(this, arguments); 32001 }; 32002 32003 var proto = Renderer.prototype; 32004 32005 for (var pName in bProto) { 32006 var pVal = bProto[pName]; 32007 var existsInR = rProto[pName] != null; 32008 32009 if (existsInR) { 32010 return overrideErr(pName); 32011 } 32012 32013 proto[pName] = pVal; // take impl from base 32014 } 32015 32016 for (var _pName in rProto) { 32017 proto[_pName] = rProto[_pName]; // take impl from registrant 32018 } 32019 32020 bProto.clientFunctions.forEach(function (name) { 32021 proto[name] = proto[name] || function () { 32022 error('Renderer does not implement `renderer.' + name + '()` on its prototype'); 32023 }; 32024 }); 32025 ext = Renderer; 32026 } 32027 32028 return setMap({ 32029 map: extensions, 32030 keys: [type, name], 32031 value: ext 32032 }); 32033 } 32034 32035 function getExtension(type, name) { 32036 return getMap({ 32037 map: extensions, 32038 keys: [type, name] 32039 }); 32040 } 32041 32042 function setModule(type, name, moduleType, moduleName, registrant) { 32043 return setMap({ 32044 map: modules, 32045 keys: [type, name, moduleType, moduleName], 32046 value: registrant 32047 }); 32048 } 32049 32050 function getModule(type, name, moduleType, moduleName) { 32051 return getMap({ 32052 map: modules, 32053 keys: [type, name, moduleType, moduleName] 32054 }); 32055 } 32056 32057 var extension = function extension() { 32058 // e.g. extension('renderer', 'svg') 32059 if (arguments.length === 2) { 32060 return getExtension.apply(null, arguments); 32061 } // e.g. extension('renderer', 'svg', { ... }) 32062 else if (arguments.length === 3) { 32063 return setExtension.apply(null, arguments); 32064 } // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse') 32065 else if (arguments.length === 4) { 32066 return getModule.apply(null, arguments); 32067 } // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... }) 32068 else if (arguments.length === 5) { 32069 return setModule.apply(null, arguments); 32070 } else { 32071 error('Invalid extension access syntax'); 32072 } 32073 }; // allows a core instance to access extensions internally 32074 32075 32076 Core.prototype.extension = extension; // included extensions 32077 32078 incExts.forEach(function (group) { 32079 group.extensions.forEach(function (ext) { 32080 setExtension(group.type, ext.name, ext.impl); 32081 }); 32082 }); 32083 32084 // (useful for init) 32085 32086 var Stylesheet = function Stylesheet() { 32087 if (!(this instanceof Stylesheet)) { 32088 return new Stylesheet(); 32089 } 32090 32091 this.length = 0; 32092 }; 32093 32094 var sheetfn = Stylesheet.prototype; 32095 32096 sheetfn.instanceString = function () { 32097 return 'stylesheet'; 32098 }; // just store the selector to be parsed later 32099 32100 32101 sheetfn.selector = function (selector) { 32102 var i = this.length++; 32103 this[i] = { 32104 selector: selector, 32105 properties: [] 32106 }; 32107 return this; // chaining 32108 }; // just store the property to be parsed later 32109 32110 32111 sheetfn.css = function (name, value) { 32112 var i = this.length - 1; 32113 32114 if (string(name)) { 32115 this[i].properties.push({ 32116 name: name, 32117 value: value 32118 }); 32119 } else if (plainObject(name)) { 32120 var map = name; 32121 var propNames = Object.keys(map); 32122 32123 for (var j = 0; j < propNames.length; j++) { 32124 var key = propNames[j]; 32125 var mapVal = map[key]; 32126 32127 if (mapVal == null) { 32128 continue; 32129 } 32130 32131 var prop = Style.properties[key] || Style.properties[dash2camel(key)]; 32132 32133 if (prop == null) { 32134 continue; 32135 } 32136 32137 var _name = prop.name; 32138 var _value = mapVal; 32139 this[i].properties.push({ 32140 name: _name, 32141 value: _value 32142 }); 32143 } 32144 } 32145 32146 return this; // chaining 32147 }; 32148 32149 sheetfn.style = sheetfn.css; // generate a real style object from the dummy stylesheet 32150 32151 sheetfn.generateStyle = function (cy) { 32152 var style = new Style(cy); 32153 return this.appendToStyle(style); 32154 }; // append a dummy stylesheet object on a real style object 32155 32156 32157 sheetfn.appendToStyle = function (style) { 32158 for (var i = 0; i < this.length; i++) { 32159 var context = this[i]; 32160 var selector = context.selector; 32161 var props = context.properties; 32162 style.selector(selector); // apply selector 32163 32164 for (var j = 0; j < props.length; j++) { 32165 var prop = props[j]; 32166 style.css(prop.name, prop.value); // apply property 32167 } 32168 } 32169 32170 return style; 32171 }; 32172 32173 var version = "3.13.3"; 32174 32175 var cytoscape = function cytoscape(options) { 32176 // if no options specified, use default 32177 if (options === undefined) { 32178 options = {}; 32179 } // create instance 32180 32181 32182 if (plainObject(options)) { 32183 return new Core(options); 32184 } // allow for registration of extensions 32185 else if (string(options)) { 32186 return extension.apply(extension, arguments); 32187 } 32188 }; // e.g. cytoscape.use( require('cytoscape-foo'), bar ) 32189 32190 32191 cytoscape.use = function (ext) { 32192 var args = Array.prototype.slice.call(arguments, 1); // args to pass to ext 32193 32194 args.unshift(cytoscape); // cytoscape is first arg to ext 32195 32196 ext.apply(null, args); 32197 return this; 32198 }; 32199 32200 cytoscape.warnings = function (bool) { 32201 return warnings(bool); 32202 }; // replaced by build system 32203 32204 32205 cytoscape.version = version; // expose public apis (mostly for extensions) 32206 32207 cytoscape.stylesheet = cytoscape.Stylesheet = Stylesheet; 32208 32209 return cytoscape; 32210 32211 })));