Three fictional rooms: actual Sigma renderer resizing, release and rebuild
HTML for this example
Select the code and use your copy shortcut. Changes in this preview are temporary.
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Three rooms: resize and rebuild the view</title><style>*{box-sizing:border-box}body{margin:0;background:#f5f3ee;color:#263341;font:17px/1.6 system-ui,sans-serif}main{max-width:900px;margin:auto;padding:28px}h1,h2{line-height:1.2}button{font:inherit;padding:8px 12px;border:1px solid #617084;border-radius:4px;background:white;color:#263341}button:disabled{opacity:.45}.controls{display:flex;flex-wrap:wrap;gap:8px;margin:18px 0}#panel{width:100%;max-width:100%;border:1px solid #91969c;background:white}#graph{width:100%;height:360px;position:relative}#status{min-height:3.2em;font-weight:650}table{border-collapse:collapse;width:100%}td,th{padding:7px;text-align:left;border-bottom:1px solid #b5b8bc}code{overflow-wrap:anywhere}@media(max-width:450px){main{padding:18px}#graph{height:320px}}</style><script>!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).graphology=e()}(this,(function(){"use strict";function t(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function e(t,e,n){return(e=function(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var r=n.call(t,e||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:e+""}(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function n(t){return n=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},n(t)}function r(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function i(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(t){}return(i=function(){return!!t})()}function o(t,e){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},o(t,e)}function a(t){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},a(t)}function c(t){var e="function"==typeof Map?new Map:void 0;return c=function(t){if(null===t||!function(t){try{return-1!==Function.toString.call(t).indexOf("[native code]")}catch(e){return"function"==typeof t}}(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,r)}function r(){return function(t,e,n){if(i())return Reflect.construct.apply(null,arguments);var r=[null];r.push.apply(r,e);var a=new(t.bind.apply(t,r));return n&&o(a,n.prototype),a}(t,arguments,n(this).constructor)}return r.prototype=Object.create(t.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),o(r,t)},c(t)}var u=function(){for(var t=arguments[0],e=1,n=arguments.length;e<n;e++)if(arguments[e])for(var r in arguments[e])t[r]=arguments[e][r];return t};function d(t,e,n,r){var i=t._nodes.get(e),o=null;return i?o="mixed"===r?i.out&&i.out[n]||i.undirected&&i.undirected[n]:"directed"===r?i.out&&i.out[n]:i.undirected&&i.undirected[n]:o}function s(t){return"object"===a(t)&&null!==t}function h(t){var e;for(e in t)return!1;return!0}function p(t,e,n){Object.defineProperty(t,e,{enumerable:!1,configurable:!1,writable:!0,value:n})}function f(t,e,n){var r={enumerable:!0,configurable:!0};"function"==typeof n?r.get=n:(r.value=n,r.writable=!1),Object.defineProperty(t,e,r)}function l(t){return!!s(t)&&!(t.attributes&&!Array.isArray(t.attributes))}function g(){var t=arguments,n=null,r=-1;return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){for(var e=null;;){if(null===n){if(++r>=t.length)return{done:!0};n=t[r][Symbol.iterator]()}if(!(e=n.next()).done)break;n=null}return e}))}function y(){return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){return{done:!0}}))}"function"==typeof Object.assign&&(u=Object.assign);var w,v={exports:{}},b="object"==typeof Reflect?Reflect:null,m=b&&"function"==typeof b.apply?b.apply:function(t,e,n){return Function.prototype.apply.call(t,e,n)};w=b&&"function"==typeof b.ownKeys?b.ownKeys:Object.getOwnPropertySymbols?function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:function(t){return Object.getOwnPropertyNames(t)};var k=Number.isNaN||function(t){return t!=t};function _(){_.init.call(this)}v.exports=_,v.exports.once=function(t,e){return new Promise((function(n,r){function i(n){t.removeListener(e,o),r(n)}function o(){"function"==typeof t.removeListener&&t.removeListener("error",i),n([].slice.call(arguments))}j(t,e,o,{once:!0}),"error"!==e&&function(t,e,n){"function"==typeof t.on&&j(t,"error",e,n)}(t,i,{once:!0})}))},_.EventEmitter=_,_.prototype._events=void 0,_.prototype._eventsCount=0,_.prototype._maxListeners=void 0;var G=10;function x(t){if("function"!=typeof t)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof t)}function E(t){return void 0===t._maxListeners?_.defaultMaxListeners:t._maxListeners}function A(t,e,n,r){var i,o,a,c;if(x(n),void 0===(o=t._events)?(o=t._events=Object.create(null),t._eventsCount=0):(void 0!==o.newListener&&(t.emit("newListener",e,n.listener?n.listener:n),o=t._events),a=o[e]),void 0===a)a=o[e]=n,++t._eventsCount;else if("function"==typeof a?a=o[e]=r?[n,a]:[a,n]:r?a.unshift(n):a.push(n),(i=E(t))>0&&a.length>i&&!a.warned){a.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=t,u.type=e,u.count=a.length,c=u,console&&console.warn&&console.warn(c)}return t}function L(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function S(t,e,n){var r={fired:!1,wrapFn:void 0,target:t,type:e,listener:n},i=L.bind(r);return i.listener=n,r.wrapFn=i,i}function D(t,e,n){var r=t._events;if(void 0===r)return[];var i=r[e];return void 0===i?[]:"function"==typeof i?n?[i.listener||i]:[i]:n?function(t){for(var e=new Array(t.length),n=0;n<e.length;++n)e[n]=t[n].listener||t[n];return e}(i):N(i,i.length)}function U(t){var e=this._events;if(void 0!==e){var n=e[t];if("function"==typeof n)return 1;if(void 0!==n)return n.length}return 0}function N(t,e){for(var n=new Array(e),r=0;r<e;++r)n[r]=t[r];return n}function j(t,e,n,r){if("function"==typeof t.on)r.once?t.once(e,n):t.on(e,n);else{if("function"!=typeof t.addEventListener)throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type '+typeof t);t.addEventListener(e,(function i(o){r.once&&t.removeEventListener(e,i),n(o)}))}}Object.defineProperty(_,"defaultMaxListeners",{enumerable:!0,get:function(){return G},set:function(t){if("number"!=typeof t||t<0||k(t))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+t+".");G=t}}),_.init=function(){void 0!==this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},_.prototype.setMaxListeners=function(t){if("number"!=typeof t||t<0||k(t))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+t+".");return this._maxListeners=t,this},_.prototype.getMaxListeners=function(){return E(this)},_.prototype.emit=function(t){for(var e=[],n=1;n<arguments.length;n++)e.push(arguments[n]);var r="error"===t,i=this._events;if(void 0!==i)r=r&&void 0===i.error;else if(!r)return!1;if(r){var o;if(e.length>0&&(o=e[0]),o instanceof Error)throw o;var a=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw a.context=o,a}var c=i[t];if(void 0===c)return!1;if("function"==typeof c)m(c,this,e);else{var u=c.length,d=N(c,u);for(n=0;n<u;++n)m(d[n],this,e)}return!0},_.prototype.addListener=function(t,e){return A(this,t,e,!1)},_.prototype.on=_.prototype.addListener,_.prototype.prependListener=function(t,e){return A(this,t,e,!0)},_.prototype.once=function(t,e){return x(e),this.on(t,S(this,t,e)),this},_.prototype.prependOnceListener=function(t,e){return x(e),this.prependListener(t,S(this,t,e)),this},_.prototype.removeListener=function(t,e){var n,r,i,o,a;if(x(e),void 0===(r=this._events))return this;if(void 0===(n=r[t]))return this;if(n===e||n.listener===e)0==--this._eventsCount?this._events=Object.create(null):(delete r[t],r.removeListener&&this.emit("removeListener",t,n.listener||e));else if("function"!=typeof n){for(i=-1,o=n.length-1;o>=0;o--)if(n[o]===e||n[o].listener===e){a=n[o].listener,i=o;break}if(i<0)return this;0===i?n.shift():function(t,e){for(;e+1<t.length;e++)t[e]=t[e+1];t.pop()}(n,i),1===n.length&&(r[t]=n[0]),void 0!==r.removeListener&&this.emit("removeListener",t,a||e)}return this},_.prototype.off=_.prototype.removeListener,_.prototype.removeAllListeners=function(t){var e,n,r;if(void 0===(n=this._events))return this;if(void 0===n.removeListener)return 0===arguments.length?(this._events=Object.create(null),this._eventsCount=0):void 0!==n[t]&&(0==--this._eventsCount?this._events=Object.create(null):delete n[t]),this;if(0===arguments.length){var i,o=Object.keys(n);for(r=0;r<o.length;++r)"removeListener"!==(i=o[r])&&this.removeAllListeners(i);return this.removeAllListeners("removeListener"),this._events=Object.create(null),this._eventsCount=0,this}if("function"==typeof(e=n[t]))this.removeListener(t,e);else if(void 0!==e)for(r=e.length-1;r>=0;r--)this.removeListener(t,e[r]);return this},_.prototype.listeners=function(t){return D(this,t,!0)},_.prototype.rawListeners=function(t){return D(this,t,!1)},_.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):U.call(t,e)},_.prototype.listenerCount=U,_.prototype.eventNames=function(){return this._eventsCount>0?w(this._events):[]};var O=function(t){function e(e){var n;return(n=t.call(this)||this).name="GraphError",n.message=e,n}return r(e,t),e}(c(Error)),C=function(e){function n(r){var i;return(i=e.call(this,r)||this).name="InvalidArgumentsGraphError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(t(i),n.prototype.constructor),i}return r(n,e),n}(O),M=function(e){function n(r){var i;return(i=e.call(this,r)||this).name="NotFoundGraphError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(t(i),n.prototype.constructor),i}return r(n,e),n}(O),z=function(e){function n(r){var i;return(i=e.call(this,r)||this).name="UsageGraphError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(t(i),n.prototype.constructor),i}return r(n,e),n}(O);function W(t,e){this.key=t,this.attributes=e,this.clear()}function P(t,e){this.key=t,this.attributes=e,this.clear()}function K(t,e){this.key=t,this.attributes=e,this.clear()}function T(t,e,n,r,i){this.key=e,this.attributes=i,this.undirected=t,this.source=n,this.target=r}W.prototype.clear=function(){this.inDegree=0,this.outDegree=0,this.undirectedDegree=0,this.undirectedLoops=0,this.directedLoops=0,this.in={},this.out={},this.undirected={}},P.prototype.clear=function(){this.inDegree=0,this.outDegree=0,this.directedLoops=0,this.in={},this.out={}},K.prototype.clear=function(){this.undirectedDegree=0,this.undirectedLoops=0,this.undirected={}},T.prototype.attach=function(){var t="out",e="in";this.undirected&&(t=e="undirected");var n=this.source.key,r=this.target.key;this.source[t][r]=this,this.undirected&&n===r||(this.target[e][n]=this)},T.prototype.attachMulti=function(){var t="out",e="in",n=this.source.key,r=this.target.key;this.undirected&&(t=e="undirected");var i=this.source[t],o=i[r];if(void 0===o)return i[r]=this,void(this.undirected&&n===r||(this.target[e][n]=this));o.previous=this,this.next=o,i[r]=this,this.target[e][n]=this},T.prototype.detach=function(){var t=this.source.key,e=this.target.key,n="out",r="in";this.undirected&&(n=r="undirected"),delete this.source[n][e],delete this.target[r][t]},T.prototype.detachMulti=function(){var t=this.source.key,e=this.target.key,n="out",r="in";this.undirected&&(n=r="undirected"),void 0===this.previous?void 0===this.next?(delete this.source[n][e],delete this.target[r][t]):(this.next.previous=void 0,this.source[n][e]=this.next,this.target[r][t]=this.next):(this.previous.next=this.next,void 0!==this.next&&(this.next.previous=this.previous))};var I=0,R=1,F=3;function B(t,e,n,r,i,o,a){var c,u,d,s;if(r=""+r,n===I){if(!(c=t._nodes.get(r)))throw new M("Graph.".concat(e,': could not find the "').concat(r,'" node in the graph.'));d=i,s=o}else if(n===F){if(i=""+i,!(u=t._edges.get(i)))throw new M("Graph.".concat(e,': could not find the "').concat(i,'" edge in the graph.'));var h=u.source.key,p=u.target.key;if(r===h)c=u.target;else{if(r!==p)throw new M("Graph.".concat(e,': the "').concat(r,'" node is not attached to the "').concat(i,'" edge (').concat(h,", ").concat(p,")."));c=u.source}d=o,s=a}else{if(!(u=t._edges.get(r)))throw new M("Graph.".concat(e,': could not find the "').concat(r,'" edge in the graph.'));c=n===R?u.source:u.target,d=i,s=o}return[c,d,s]}var Y=[{name:function(t){return"get".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o=B(this,e,n,t,r,i),a=o[0],c=o[1];return a.attributes[c]}}},{name:function(t){return"get".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r){return B(this,e,n,t,r)[0].attributes}}},{name:function(t){return"has".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o=B(this,e,n,t,r,i),a=o[0],c=o[1];return a.attributes.hasOwnProperty(c)}}},{name:function(t){return"set".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i,o){var a=B(this,e,n,t,r,i,o),c=a[0],u=a[1],d=a[2];return c.attributes[u]=d,this.emit("nodeAttributesUpdated",{key:c.key,type:"set",attributes:c.attributes,name:u}),this}}},{name:function(t){return"update".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i,o){var a=B(this,e,n,t,r,i,o),c=a[0],u=a[1],d=a[2];if("function"!=typeof d)throw new C("Graph.".concat(e,": updater should be a function."));var s=c.attributes,h=d(s[u]);return s[u]=h,this.emit("nodeAttributesUpdated",{key:c.key,type:"set",attributes:c.attributes,name:u}),this}}},{name:function(t){return"remove".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o=B(this,e,n,t,r,i),a=o[0],c=o[1];return delete a.attributes[c],this.emit("nodeAttributesUpdated",{key:a.key,type:"remove",attributes:a.attributes,name:c}),this}}},{name:function(t){return"replace".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o=B(this,e,n,t,r,i),a=o[0],c=o[1];if(!s(c))throw new C("Graph.".concat(e,": provided attributes are not a plain object."));return a.attributes=c,this.emit("nodeAttributesUpdated",{key:a.key,type:"replace",attributes:a.attributes}),this}}},{name:function(t){return"merge".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o=B(this,e,n,t,r,i),a=o[0],c=o[1];if(!s(c))throw new C("Graph.".concat(e,": provided attributes are not a plain object."));return u(a.attributes,c),this.emit("nodeAttributesUpdated",{key:a.key,type:"merge",attributes:a.attributes,data:c}),this}}},{name:function(t){return"update".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o=B(this,e,n,t,r,i),a=o[0],c=o[1];if("function"!=typeof c)throw new C("Graph.".concat(e,": provided updater is not a function."));return a.attributes=c(a.attributes),this.emit("nodeAttributesUpdated",{key:a.key,type:"update",attributes:a.attributes}),this}}}];var J=[{name:function(t){return"get".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r){var i;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>2){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var o=""+t,a=""+r;if(r=arguments[2],!(i=d(this,o,a,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(o,'" - "').concat(a,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(i=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}return i.attributes[r]}}},{name:function(t){return"get".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t){var r;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>1){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var i=""+t,o=""+arguments[1];if(!(r=d(this,i,o,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(i,'" - "').concat(o,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(r=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}return r.attributes}}},{name:function(t){return"has".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r){var i;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>2){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var o=""+t,a=""+r;if(r=arguments[2],!(i=d(this,o,a,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(o,'" - "').concat(a,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(i=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}return i.attributes.hasOwnProperty(r)}}},{name:function(t){return"set".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>3){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var a=""+t,c=""+r;if(r=arguments[2],i=arguments[3],!(o=d(this,a,c,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(a,'" - "').concat(c,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(o=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}return o.attributes[r]=i,this.emit("edgeAttributesUpdated",{key:o.key,type:"set",attributes:o.attributes,name:r}),this}}},{name:function(t){return"update".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r,i){var o;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>3){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var a=""+t,c=""+r;if(r=arguments[2],i=arguments[3],!(o=d(this,a,c,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(a,'" - "').concat(c,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(o=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}if("function"!=typeof i)throw new C("Graph.".concat(e,": updater should be a function."));return o.attributes[r]=i(o.attributes[r]),this.emit("edgeAttributesUpdated",{key:o.key,type:"set",attributes:o.attributes,name:r}),this}}},{name:function(t){return"remove".concat(t,"Attribute")},attacher:function(t,e,n){t.prototype[e]=function(t,r){var i;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>2){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var o=""+t,a=""+r;if(r=arguments[2],!(i=d(this,o,a,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(o,'" - "').concat(a,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(i=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}return delete i.attributes[r],this.emit("edgeAttributesUpdated",{key:i.key,type:"remove",attributes:i.attributes,name:r}),this}}},{name:function(t){return"replace".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r){var i;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>2){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var o=""+t,a=""+r;if(r=arguments[2],!(i=d(this,o,a,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(o,'" - "').concat(a,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(i=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}if(!s(r))throw new C("Graph.".concat(e,": provided attributes are not a plain object."));return i.attributes=r,this.emit("edgeAttributesUpdated",{key:i.key,type:"replace",attributes:i.attributes}),this}}},{name:function(t){return"merge".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r){var i;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>2){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var o=""+t,a=""+r;if(r=arguments[2],!(i=d(this,o,a,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(o,'" - "').concat(a,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(i=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}if(!s(r))throw new C("Graph.".concat(e,": provided attributes are not a plain object."));return u(i.attributes,r),this.emit("edgeAttributesUpdated",{key:i.key,type:"merge",attributes:i.attributes,data:r}),this}}},{name:function(t){return"update".concat(t,"Attributes")},attacher:function(t,e,n){t.prototype[e]=function(t,r){var i;if("mixed"!==this.type&&"mixed"!==n&&n!==this.type)throw new z("Graph.".concat(e,": cannot find this type of edges in your ").concat(this.type," graph."));if(arguments.length>2){if(this.multi)throw new z("Graph.".concat(e,": cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about."));var o=""+t,a=""+r;if(r=arguments[2],!(i=d(this,o,a,n)))throw new M("Graph.".concat(e,': could not find an edge for the given path ("').concat(o,'" - "').concat(a,'").'))}else{if("mixed"!==n)throw new z("Graph.".concat(e,": calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type."));if(t=""+t,!(i=this._edges.get(t)))throw new M("Graph.".concat(e,': could not find the "').concat(t,'" edge in the graph.'))}if("function"!=typeof r)throw new C("Graph.".concat(e,": provided updater is not a function."));return i.attributes=r(i.attributes),this.emit("edgeAttributesUpdated",{key:i.key,type:"update",attributes:i.attributes}),this}}}];var q=[{name:"edges",type:"mixed"},{name:"inEdges",type:"directed",direction:"in"},{name:"outEdges",type:"directed",direction:"out"},{name:"inboundEdges",type:"mixed",direction:"in"},{name:"outboundEdges",type:"mixed",direction:"out"},{name:"directedEdges",type:"directed"},{name:"undirectedEdges",type:"undirected"}];function H(t,e,n,r){var i=!1;for(var o in e)if(o!==r){var a=e[o];if(i=n(a.key,a.attributes,a.source.key,a.target.key,a.source.attributes,a.target.attributes,a.undirected),t&&i)return a.key}}function Q(t,e,n,r){var i,o,a,c=!1;for(var u in e)if(u!==r){i=e[u];do{if(o=i.source,a=i.target,c=n(i.key,i.attributes,o.key,a.key,o.attributes,a.attributes,i.undirected),t&&c)return i.key;i=i.next}while(void 0!==i)}}function V(t,n){var r,i=Object.keys(t),o=i.length,a=0;return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){do{if(r)r=r.next;else{if(a>=o)return{done:!0};var e=i[a++];if(e===n){r=void 0;continue}r=t[e]}}while(!r);return{done:!1,value:{edge:r.key,attributes:r.attributes,source:r.source.key,target:r.target.key,sourceAttributes:r.source.attributes,targetAttributes:r.target.attributes,undirected:r.undirected}}}))}function X(t,e,n,r){var i=e[n];if(i){var o=i.source,a=i.target;return r(i.key,i.attributes,o.key,a.key,o.attributes,a.attributes,i.undirected)&&t?i.key:void 0}}function Z(t,e,n,r){var i=e[n];if(i){var o=!1;do{if(o=r(i.key,i.attributes,i.source.key,i.target.key,i.source.attributes,i.target.attributes,i.undirected),t&&o)return i.key;i=i.next}while(void 0!==i)}}function $(t,n){var r=t[n];if(void 0!==r.next)return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){if(!r)return{done:!0};var t={edge:r.key,attributes:r.attributes,source:r.source.key,target:r.target.key,sourceAttributes:r.source.attributes,targetAttributes:r.target.attributes,undirected:r.undirected};return r=r.next,{done:!1,value:t}}));var i=!1;return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){return!0===i?{done:!0}:(i=!0,{done:!1,value:{edge:r.key,attributes:r.attributes,source:r.source.key,target:r.target.key,sourceAttributes:r.source.attributes,targetAttributes:r.target.attributes,undirected:r.undirected}})}))}function tt(t,e,n,r){if(0!==e.size)for(var i,o,a="mixed"!==n&&n!==e.type,c="undirected"===n,u=!1,d=e._edges.values();!0!==(i=d.next()).done;)if(o=i.value,!a||o.undirected===c){var s=o,h=s.key,p=s.attributes,f=s.source,l=s.target;if(u=r(h,p,f.key,l.key,f.attributes,l.attributes,o.undirected),t&&u)return h}}function et(t,e,n,r,i,o){var a,c=e?Q:H;if("undirected"!==n){if("out"!==r&&(a=c(t,i.in,o),t&&a))return a;if("in"!==r&&(a=c(t,i.out,o,r?void 0:i.key),t&&a))return a}if("directed"!==n&&(a=c(t,i.undirected,o),t&&a))return a}function nt(t,e,n,r,i,o,a){var c,u=n?Z:X;if("undirected"!==e){if(void 0!==i.in&&"out"!==r&&(c=u(t,i.in,o,a),t&&c))return c;if(void 0!==i.out&&"in"!==r&&(r||i.key!==o)&&(c=u(t,i.out,o,a),t&&c))return c}if("directed"!==e&&void 0!==i.undirected&&(c=u(t,i.undirected,o,a),t&&c))return c}function rt(t,e){var n=e.name,r=e.type,i=e.direction;t.prototype[n]=function(t,e){if("mixed"!==r&&"mixed"!==this.type&&r!==this.type)return[];if(!arguments.length)return function(t,e){if(0===t.size)return[];if("mixed"===e||e===t.type)return Array.from(t._edges.keys());for(var n,r,i="undirected"===e?t.undirectedSize:t.directedSize,o=new Array(i),a="undirected"===e,c=t._edges.values(),u=0;!0!==(n=c.next()).done;)(r=n.value).undirected===a&&(o[u++]=r.key);return o}(this,r);if(1===arguments.length){t=""+t;var o=this._nodes.get(t);if(void 0===o)throw new M("Graph.".concat(n,': could not find the "').concat(t,'" node in the graph.'));return function(t,e,n,r){var i=[];return et(!1,t,e,n,r,(function(t){i.push(t)})),i}(this.multi,"mixed"===r?this.type:r,i,o)}if(2===arguments.length){t=""+t,e=""+e;var a=this._nodes.get(t);if(!a)throw new M("Graph.".concat(n,': could not find the "').concat(t,'" source node in the graph.'));if(!this._nodes.has(e))throw new M("Graph.".concat(n,': could not find the "').concat(e,'" target node in the graph.'));return function(t,e,n,r,i){var o=[];return nt(!1,t,e,n,r,i,(function(t){o.push(t)})),o}(r,this.multi,i,a,e)}throw new C("Graph.".concat(n,": too many arguments (expecting 0, 1 or 2 and got ").concat(arguments.length,")."))}}function it(t,n){var r=n.name,i=n.type,o=n.direction,a=r.slice(0,-1)+"Entries";t.prototype[a]=function(t,n){if("mixed"!==i&&"mixed"!==this.type&&i!==this.type)return y();if(!arguments.length)return function(t,n){if(0===t.size)return y();var r="mixed"!==n&&n!==t.type,i="undirected"===n,o=t._edges.values();return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){for(var t,e;;){if((t=o.next()).done)return t;if(e=t.value,!r||e.undirected===i)break}return{value:{edge:e.key,attributes:e.attributes,source:e.source.key,target:e.target.key,sourceAttributes:e.source.attributes,targetAttributes:e.target.attributes,undirected:e.undirected},done:!1}}))}(this,i);if(1===arguments.length){t=""+t;var r=this._nodes.get(t);if(!r)throw new M("Graph.".concat(a,': could not find the "').concat(t,'" node in the graph.'));return function(t,e,n){var r=y();return"undirected"!==t&&("out"!==e&&void 0!==n.in&&(r=g(r,V(n.in))),"in"!==e&&void 0!==n.out&&(r=g(r,V(n.out,e?void 0:n.key)))),"directed"!==t&&void 0!==n.undirected&&(r=g(r,V(n.undirected))),r}(i,o,r)}if(2===arguments.length){t=""+t,n=""+n;var c=this._nodes.get(t);if(!c)throw new M("Graph.".concat(a,': could not find the "').concat(t,'" source node in the graph.'));if(!this._nodes.has(n))throw new M("Graph.".concat(a,': could not find the "').concat(n,'" target node in the graph.'));return function(t,e,n,r){var i=y();return"undirected"!==t&&(void 0!==n.in&&"out"!==e&&r in n.in&&(i=g(i,$(n.in,r))),void 0!==n.out&&"in"!==e&&r in n.out&&(e||n.key!==r)&&(i=g(i,$(n.out,r)))),"directed"!==t&&void 0!==n.undirected&&r in n.undirected&&(i=g(i,$(n.undirected,r))),i}(i,o,c,n)}throw new C("Graph.".concat(a,": too many arguments (expecting 0, 1 or 2 and got ").concat(arguments.length,")."))}}var ot=[{name:"neighbors",type:"mixed"},{name:"inNeighbors",type:"directed",direction:"in"},{name:"outNeighbors",type:"directed",direction:"out"},{name:"inboundNeighbors",type:"mixed",direction:"in"},{name:"outboundNeighbors",type:"mixed",direction:"out"},{name:"directedNeighbors",type:"directed"},{name:"undirectedNeighbors",type:"undirected"}];function at(){this.A=null,this.B=null}function ct(t,e,n,r,i){for(var o in r){var a=r[o],c=a.source,u=a.target,d=c===n?u:c;if(!e||!e.has(d.key)){var s=i(d.key,d.attributes);if(t&&s)return d.key}}}function ut(t,e,n,r,i){if("mixed"!==e){if("undirected"===e)return ct(t,null,r,r.undirected,i);if("string"==typeof n)return ct(t,null,r,r[n],i)}var o,a=new at;if("undirected"!==e){if("out"!==n){if(o=ct(t,null,r,r.in,i),t&&o)return o;a.wrap(r.in)}if("in"!==n){if(o=ct(t,a,r,r.out,i),t&&o)return o;a.wrap(r.out)}}if("directed"!==e&&(o=ct(t,a,r,r.undirected,i),t&&o))return o}function dt(t,n,r){var i=Object.keys(r),o=i.length,a=0;return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){var e=null;do{if(a>=o)return t&&t.wrap(r),{done:!0};var c=r[i[a++]],u=c.source,d=c.target;e=u===n?d:u,t&&t.has(e.key)&&(e=null)}while(null===e);return{done:!1,value:{neighbor:e.key,attributes:e.attributes}}}))}function st(t,e){var n=e.name,r=e.type,i=e.direction;t.prototype[n]=function(t){if("mixed"!==r&&"mixed"!==this.type&&r!==this.type)return[];t=""+t;var e=this._nodes.get(t);if(void 0===e)throw new M("Graph.".concat(n,': could not find the "').concat(t,'" node in the graph.'));return function(t,e,n){if("mixed"!==t){if("undirected"===t)return Object.keys(n.undirected);if("string"==typeof e)return Object.keys(n[e])}var r=[];return ut(!1,t,e,n,(function(t){r.push(t)})),r}("mixed"===r?this.type:r,i,e)}}function ht(t,e){var n=e.name,r=e.type,i=e.direction,o=n.slice(0,-1)+"Entries";t.prototype[o]=function(t){if("mixed"!==r&&"mixed"!==this.type&&r!==this.type)return y();t=""+t;var e=this._nodes.get(t);if(void 0===e)throw new M("Graph.".concat(o,': could not find the "').concat(t,'" node in the graph.'));return function(t,e,n){if("mixed"!==t){if("undirected"===t)return dt(null,n,n.undirected);if("string"==typeof e)return dt(null,n,n[e])}var r=y(),i=new at;return"undirected"!==t&&("out"!==e&&(r=g(r,dt(i,n,n.in))),"in"!==e&&(r=g(r,dt(i,n,n.out)))),"directed"!==t&&(r=g(r,dt(i,n,n.undirected))),r}("mixed"===r?this.type:r,i,e)}}function pt(t,e,n,r,i){for(var o,a,c,u,d,s,h,p=r._nodes.values(),f=r.type;!0!==(o=p.next()).done;){var l=!1;if(a=o.value,"undirected"!==f)for(c in u=a.out){d=u[c];do{if(s=d.target,l=!0,h=i(a.key,s.key,a.attributes,s.attributes,d.key,d.attributes,d.undirected),t&&h)return d;d=d.next}while(d)}if("directed"!==f)for(c in u=a.undirected)if(!(e&&a.key>c)){d=u[c];do{if((s=d.target).key!==c&&(s=d.source),l=!0,h=i(a.key,s.key,a.attributes,s.attributes,d.key,d.attributes,d.undirected),t&&h)return d;d=d.next}while(d)}if(n&&!l&&(h=i(a.key,null,a.attributes,null,null,null,null),t&&h))return null}}function ft(t){if(!s(t))throw new C('Graph.import: invalid serialized node. A serialized node should be a plain object with at least a "key" property.');if(!("key"in t))throw new C("Graph.import: serialized node is missing its key.");if("attributes"in t&&(!s(t.attributes)||null===t.attributes))throw new C("Graph.import: invalid attributes. Attributes should be a plain object, null or omitted.")}function lt(t){if(!s(t))throw new C('Graph.import: invalid serialized edge. A serialized edge should be a plain object with at least a "source" & "target" property.');if(!("source"in t))throw new C("Graph.import: serialized edge is missing its source.");if(!("target"in t))throw new C("Graph.import: serialized edge is missing its target.");if("attributes"in t&&(!s(t.attributes)||null===t.attributes))throw new C("Graph.import: invalid attributes. Attributes should be a plain object, null or omitted.");if("undirected"in t&&"boolean"!=typeof t.undirected)throw new C("Graph.import: invalid undirectedness information. Undirected should be boolean or omitted.")}at.prototype.wrap=function(t){null===this.A?this.A=t:null===this.B&&(this.B=t)},at.prototype.has=function(t){return null!==this.A&&t in this.A||null!==this.B&&t in this.B};var gt,yt=(gt=255&Math.floor(256*Math.random()),function(){return gt++}),wt=new Set(["directed","undirected","mixed"]),vt=new Set(["domain","_events","_eventsCount","_maxListeners"]),bt={allowSelfLoops:!0,multi:!1,type:"mixed"};function mt(t,e,n){var r=new t.NodeDataClass(e,n);return t._nodes.set(e,r),t.emit("nodeAdded",{key:e,attributes:n}),r}function kt(t,e,n,r,i,o,a,c){if(!r&&"undirected"===t.type)throw new z("Graph.".concat(e,": you cannot add a directed edge to an undirected graph. Use the #.addEdge or #.addUndirectedEdge instead."));if(r&&"directed"===t.type)throw new z("Graph.".concat(e,": you cannot add an undirected edge to a directed graph. Use the #.addEdge or #.addDirectedEdge instead."));if(c&&!s(c))throw new C("Graph.".concat(e,': invalid attributes. Expecting an object but got "').concat(c,'"'));if(o=""+o,a=""+a,c=c||{},!t.allowSelfLoops&&o===a)throw new z("Graph.".concat(e,': source & target are the same ("').concat(o,"\"), thus creating a loop explicitly forbidden by this graph 'allowSelfLoops' option set to false."));var u=t._nodes.get(o),d=t._nodes.get(a);if(!u)throw new M("Graph.".concat(e,': source node "').concat(o,'" not found.'));if(!d)throw new M("Graph.".concat(e,': target node "').concat(a,'" not found.'));var h={key:null,undirected:r,source:o,target:a,attributes:c};if(n)i=t._edgeKeyGenerator();else if(i=""+i,t._edges.has(i))throw new z("Graph.".concat(e,': the "').concat(i,'" edge already exists in the graph.'));if(!t.multi&&(r?void 0!==u.undirected[a]:void 0!==u.out[a]))throw new z("Graph.".concat(e,': an edge linking "').concat(o,'" to "').concat(a,"\" already exists. If you really want to add multiple edges linking those nodes, you should create a multi graph by using the 'multi' option."));var p=new T(r,i,u,d,c);t._edges.set(i,p);var f=o===a;return r?(u.undirectedDegree++,d.undirectedDegree++,f&&(u.undirectedLoops++,t._undirectedSelfLoopCount++)):(u.outDegree++,d.inDegree++,f&&(u.directedLoops++,t._directedSelfLoopCount++)),t.multi?p.attachMulti():p.attach(),r?t._undirectedSize++:t._directedSize++,h.key=i,t.emit("edgeAdded",h),i}function _t(t,e,n,r,i,o,a,c,d){if(!r&&"undirected"===t.type)throw new z("Graph.".concat(e,": you cannot merge/update a directed edge to an undirected graph. Use the #.mergeEdge/#.updateEdge or #.addUndirectedEdge instead."));if(r&&"directed"===t.type)throw new z("Graph.".concat(e,": you cannot merge/update an undirected edge to a directed graph. Use the #.mergeEdge/#.updateEdge or #.addDirectedEdge instead."));if(c)if(d){if("function"!=typeof c)throw new C("Graph.".concat(e,': invalid updater function. Expecting a function but got "').concat(c,'"'))}else if(!s(c))throw new C("Graph.".concat(e,': invalid attributes. Expecting an object but got "').concat(c,'"'));var h;if(o=""+o,a=""+a,d&&(h=c,c=void 0),!t.allowSelfLoops&&o===a)throw new z("Graph.".concat(e,': source & target are the same ("').concat(o,"\"), thus creating a loop explicitly forbidden by this graph 'allowSelfLoops' option set to false."));var p,f,l=t._nodes.get(o),g=t._nodes.get(a);if(!n&&(p=t._edges.get(i))){if(!(p.source.key===o&&p.target.key===a||r&&p.source.key===a&&p.target.key===o))throw new z("Graph.".concat(e,': inconsistency detected when attempting to merge the "').concat(i,'" edge with "').concat(o,'" source & "').concat(a,'" target vs. ("').concat(p.source.key,'", "').concat(p.target.key,'").'));f=p}if(f||t.multi||!l||(f=r?l.undirected[a]:l.out[a]),f){var y=[f.key,!1,!1,!1];if(d?!h:!c)return y;if(d){var w=f.attributes;f.attributes=h(w),t.emit("edgeAttributesUpdated",{type:"replace",key:f.key,attributes:f.attributes})}else u(f.attributes,c),t.emit("edgeAttributesUpdated",{type:"merge",key:f.key,attributes:f.attributes,data:c});return y}c=c||{},d&&h&&(c=h(c));var v={key:null,undirected:r,source:o,target:a,attributes:c};if(n)i=t._edgeKeyGenerator();else if(i=""+i,t._edges.has(i))throw new z("Graph.".concat(e,': the "').concat(i,'" edge already exists in the graph.'));var b=!1,m=!1;l||(l=mt(t,o,{}),b=!0,o===a&&(g=l,m=!0)),g||(g=mt(t,a,{}),m=!0),p=new T(r,i,l,g,c),t._edges.set(i,p);var k=o===a;return r?(l.undirectedDegree++,g.undirectedDegree++,k&&(l.undirectedLoops++,t._undirectedSelfLoopCount++)):(l.outDegree++,g.inDegree++,k&&(l.directedLoops++,t._directedSelfLoopCount++)),t.multi?p.attachMulti():p.attach(),r?t._undirectedSize++:t._directedSize++,v.key=i,t.emit("edgeAdded",v),[i,!0,b,m]}function Gt(t,e){t._edges.delete(e.key);var n=e.source,r=e.target,i=e.attributes,o=e.undirected,a=n===r;o?(n.undirectedDegree--,r.undirectedDegree--,a&&(n.undirectedLoops--,t._undirectedSelfLoopCount--)):(n.outDegree--,r.inDegree--,a&&(n.directedLoops--,t._directedSelfLoopCount--)),t.multi?e.detachMulti():e.detach(),o?t._undirectedSize--:t._directedSize--,t.emit("edgeDropped",{key:e.key,attributes:i,source:n.key,target:r.key,undirected:o})}var xt=function(n){function i(e){var r;if(r=n.call(this)||this,"boolean"!=typeof(e=u({},bt,e)).multi)throw new C("Graph.constructor: invalid 'multi' option. Expecting a boolean but got \"".concat(e.multi,'".'));if(!wt.has(e.type))throw new C('Graph.constructor: invalid \'type\' option. Should be one of "mixed", "directed" or "undirected" but got "'.concat(e.type,'".'));if("boolean"!=typeof e.allowSelfLoops)throw new C("Graph.constructor: invalid 'allowSelfLoops' option. Expecting a boolean but got \"".concat(e.allowSelfLoops,'".'));var i="mixed"===e.type?W:"directed"===e.type?P:K;p(t(r),"NodeDataClass",i);var o="geid_"+yt()+"_",a=0;return p(t(r),"_attributes",{}),p(t(r),"_nodes",new Map),p(t(r),"_edges",new Map),p(t(r),"_directedSize",0),p(t(r),"_undirectedSize",0),p(t(r),"_directedSelfLoopCount",0),p(t(r),"_undirectedSelfLoopCount",0),p(t(r),"_edgeKeyGenerator",(function(){var t;do{t=o+a++}while(r._edges.has(t));return t})),p(t(r),"_options",e),vt.forEach((function(e){return p(t(r),e,r[e])})),f(t(r),"order",(function(){return r._nodes.size})),f(t(r),"size",(function(){return r._edges.size})),f(t(r),"directedSize",(function(){return r._directedSize})),f(t(r),"undirectedSize",(function(){return r._undirectedSize})),f(t(r),"selfLoopCount",(function(){return r._directedSelfLoopCount+r._undirectedSelfLoopCount})),f(t(r),"directedSelfLoopCount",(function(){return r._directedSelfLoopCount})),f(t(r),"undirectedSelfLoopCount",(function(){return r._undirectedSelfLoopCount})),f(t(r),"multi",r._options.multi),f(t(r),"type",r._options.type),f(t(r),"allowSelfLoops",r._options.allowSelfLoops),f(t(r),"implementation",(function(){return"graphology"})),r}r(i,n);var o=i.prototype;return o._resetInstanceCounters=function(){this._directedSize=0,this._undirectedSize=0,this._directedSelfLoopCount=0,this._undirectedSelfLoopCount=0},o.hasNode=function(t){return this._nodes.has(""+t)},o.hasDirectedEdge=function(t,e){if("undirected"===this.type)return!1;if(1===arguments.length){var n=""+t,r=this._edges.get(n);return!!r&&!r.undirected}if(2===arguments.length){t=""+t,e=""+e;var i=this._nodes.get(t);return!!i&&i.out.hasOwnProperty(e)}throw new C("Graph.hasDirectedEdge: invalid arity (".concat(arguments.length,", instead of 1 or 2). You can either ask for an edge id or for the existence of an edge between a source & a target."))},o.hasUndirectedEdge=function(t,e){if("directed"===this.type)return!1;if(1===arguments.length){var n=""+t,r=this._edges.get(n);return!!r&&r.undirected}if(2===arguments.length){t=""+t,e=""+e;var i=this._nodes.get(t);return!!i&&i.undirected.hasOwnProperty(e)}throw new C("Graph.hasDirectedEdge: invalid arity (".concat(arguments.length,", instead of 1 or 2). You can either ask for an edge id or for the existence of an edge between a source & a target."))},o.hasEdge=function(t,e){if(1===arguments.length){var n=""+t;return this._edges.has(n)}if(2===arguments.length){t=""+t,e=""+e;var r=this._nodes.get(t);return!!r&&(void 0!==r.out&&r.out.hasOwnProperty(e)||void 0!==r.undirected&&r.undirected.hasOwnProperty(e))}throw new C("Graph.hasEdge: invalid arity (".concat(arguments.length,", instead of 1 or 2). You can either ask for an edge id or for the existence of an edge between a source & a target."))},o.directedEdge=function(t,e){if("undirected"!==this.type){if(t=""+t,e=""+e,this.multi)throw new z("Graph.directedEdge: this method is irrelevant with multigraphs since there might be multiple edges between source & target. See #.directedEdges instead.");var n=this._nodes.get(t);if(!n)throw new M('Graph.directedEdge: could not find the "'.concat(t,'" source node in the graph.'));if(!this._nodes.has(e))throw new M('Graph.directedEdge: could not find the "'.concat(e,'" target node in the graph.'));var r=n.out&&n.out[e]||void 0;return r?r.key:void 0}},o.undirectedEdge=function(t,e){if("directed"!==this.type){if(t=""+t,e=""+e,this.multi)throw new z("Graph.undirectedEdge: this method is irrelevant with multigraphs since there might be multiple edges between source & target. See #.undirectedEdges instead.");var n=this._nodes.get(t);if(!n)throw new M('Graph.undirectedEdge: could not find the "'.concat(t,'" source node in the graph.'));if(!this._nodes.has(e))throw new M('Graph.undirectedEdge: could not find the "'.concat(e,'" target node in the graph.'));var r=n.undirected&&n.undirected[e]||void 0;return r?r.key:void 0}},o.edge=function(t,e){if(this.multi)throw new z("Graph.edge: this method is irrelevant with multigraphs since there might be multiple edges between source & target. See #.edges instead.");t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.edge: could not find the "'.concat(t,'" source node in the graph.'));if(!this._nodes.has(e))throw new M('Graph.edge: could not find the "'.concat(e,'" target node in the graph.'));var r=n.out&&n.out[e]||n.undirected&&n.undirected[e]||void 0;if(r)return r.key},o.areDirectedNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areDirectedNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"undirected"!==this.type&&(e in n.in||e in n.out)},o.areOutNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areOutNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"undirected"!==this.type&&e in n.out},o.areInNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areInNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"undirected"!==this.type&&e in n.in},o.areUndirectedNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areUndirectedNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"directed"!==this.type&&e in n.undirected},o.areNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"undirected"!==this.type&&(e in n.in||e in n.out)||"directed"!==this.type&&e in n.undirected},o.areInboundNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areInboundNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"undirected"!==this.type&&e in n.in||"directed"!==this.type&&e in n.undirected},o.areOutboundNeighbors=function(t,e){t=""+t,e=""+e;var n=this._nodes.get(t);if(!n)throw new M('Graph.areOutboundNeighbors: could not find the "'.concat(t,'" node in the graph.'));return"undirected"!==this.type&&e in n.out||"directed"!==this.type&&e in n.undirected},o.inDegree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.inDegree: could not find the "'.concat(t,'" node in the graph.'));return"undirected"===this.type?0:e.inDegree},o.outDegree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.outDegree: could not find the "'.concat(t,'" node in the graph.'));return"undirected"===this.type?0:e.outDegree},o.directedDegree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.directedDegree: could not find the "'.concat(t,'" node in the graph.'));return"undirected"===this.type?0:e.inDegree+e.outDegree},o.undirectedDegree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.undirectedDegree: could not find the "'.concat(t,'" node in the graph.'));return"directed"===this.type?0:e.undirectedDegree},o.inboundDegree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.inboundDegree: could not find the "'.concat(t,'" node in the graph.'));var n=0;return"directed"!==this.type&&(n+=e.undirectedDegree),"undirected"!==this.type&&(n+=e.inDegree),n},o.outboundDegree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.outboundDegree: could not find the "'.concat(t,'" node in the graph.'));var n=0;return"directed"!==this.type&&(n+=e.undirectedDegree),"undirected"!==this.type&&(n+=e.outDegree),n},o.degree=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.degree: could not find the "'.concat(t,'" node in the graph.'));var n=0;return"directed"!==this.type&&(n+=e.undirectedDegree),"undirected"!==this.type&&(n+=e.inDegree+e.outDegree),n},o.inDegreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.inDegreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));return"undirected"===this.type?0:e.inDegree-e.directedLoops},o.outDegreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.outDegreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));return"undirected"===this.type?0:e.outDegree-e.directedLoops},o.directedDegreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.directedDegreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));return"undirected"===this.type?0:e.inDegree+e.outDegree-2*e.directedLoops},o.undirectedDegreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.undirectedDegreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));return"directed"===this.type?0:e.undirectedDegree-2*e.undirectedLoops},o.inboundDegreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.inboundDegreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));var n=0,r=0;return"directed"!==this.type&&(n+=e.undirectedDegree,r+=2*e.undirectedLoops),"undirected"!==this.type&&(n+=e.inDegree,r+=e.directedLoops),n-r},o.outboundDegreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.outboundDegreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));var n=0,r=0;return"directed"!==this.type&&(n+=e.undirectedDegree,r+=2*e.undirectedLoops),"undirected"!==this.type&&(n+=e.outDegree,r+=e.directedLoops),n-r},o.degreeWithoutSelfLoops=function(t){t=""+t;var e=this._nodes.get(t);if(!e)throw new M('Graph.degreeWithoutSelfLoops: could not find the "'.concat(t,'" node in the graph.'));var n=0,r=0;return"directed"!==this.type&&(n+=e.undirectedDegree,r+=2*e.undirectedLoops),"undirected"!==this.type&&(n+=e.inDegree+e.outDegree,r+=2*e.directedLoops),n-r},o.source=function(t){t=""+t;var e=this._edges.get(t);if(!e)throw new M('Graph.source: could not find the "'.concat(t,'" edge in the graph.'));return e.source.key},o.target=function(t){t=""+t;var e=this._edges.get(t);if(!e)throw new M('Graph.target: could not find the "'.concat(t,'" edge in the graph.'));return e.target.key},o.extremities=function(t){t=""+t;var e=this._edges.get(t);if(!e)throw new M('Graph.extremities: could not find the "'.concat(t,'" edge in the graph.'));return[e.source.key,e.target.key]},o.opposite=function(t,e){t=""+t,e=""+e;var n=this._edges.get(e);if(!n)throw new M('Graph.opposite: could not find the "'.concat(e,'" edge in the graph.'));var r=n.source.key,i=n.target.key;if(t===r)return i;if(t===i)return r;throw new M('Graph.opposite: the "'.concat(t,'" node is not attached to the "').concat(e,'" edge (').concat(r,", ").concat(i,")."))},o.hasExtremity=function(t,e){t=""+t,e=""+e;var n=this._edges.get(t);if(!n)throw new M('Graph.hasExtremity: could not find the "'.concat(t,'" edge in the graph.'));return n.source.key===e||n.target.key===e},o.isUndirected=function(t){t=""+t;var e=this._edges.get(t);if(!e)throw new M('Graph.isUndirected: could not find the "'.concat(t,'" edge in the graph.'));return e.undirected},o.isDirected=function(t){t=""+t;var e=this._edges.get(t);if(!e)throw new M('Graph.isDirected: could not find the "'.concat(t,'" edge in the graph.'));return!e.undirected},o.isSelfLoop=function(t){t=""+t;var e=this._edges.get(t);if(!e)throw new M('Graph.isSelfLoop: could not find the "'.concat(t,'" edge in the graph.'));return e.source===e.target},o.addNode=function(t,e){var n=function(t,e,n){if(n&&!s(n))throw new C('Graph.addNode: invalid attributes. Expecting an object but got "'.concat(n,'"'));if(e=""+e,n=n||{},t._nodes.has(e))throw new z('Graph.addNode: the "'.concat(e,'" node already exist in the graph.'));var r=new t.NodeDataClass(e,n);return t._nodes.set(e,r),t.emit("nodeAdded",{key:e,attributes:n}),r}(this,t,e);return n.key},o.mergeNode=function(t,e){if(e&&!s(e))throw new C('Graph.mergeNode: invalid attributes. Expecting an object but got "'.concat(e,'"'));t=""+t,e=e||{};var n=this._nodes.get(t);return n?(e&&(u(n.attributes,e),this.emit("nodeAttributesUpdated",{type:"merge",key:t,attributes:n.attributes,data:e})),[t,!1]):(n=new this.NodeDataClass(t,e),this._nodes.set(t,n),this.emit("nodeAdded",{key:t,attributes:e}),[t,!0])},o.updateNode=function(t,e){if(e&&"function"!=typeof e)throw new C('Graph.updateNode: invalid updater function. Expecting a function but got "'.concat(e,'"'));t=""+t;var n=this._nodes.get(t);if(n){if(e){var r=n.attributes;n.attributes=e(r),this.emit("nodeAttributesUpdated",{type:"replace",key:t,attributes:n.attributes})}return[t,!1]}var i=e?e({}):{};return n=new this.NodeDataClass(t,i),this._nodes.set(t,n),this.emit("nodeAdded",{key:t,attributes:i}),[t,!0]},o.dropNode=function(t){t=""+t;var e,n=this._nodes.get(t);if(!n)throw new M('Graph.dropNode: could not find the "'.concat(t,'" node in the graph.'));if("undirected"!==this.type){for(var r in n.out){e=n.out[r];do{Gt(this,e),e=e.next}while(e)}for(var i in n.in){e=n.in[i];do{Gt(this,e),e=e.next}while(e)}}if("directed"!==this.type)for(var o in n.undirected){e=n.undirected[o];do{Gt(this,e),e=e.next}while(e)}this._nodes.delete(t),this.emit("nodeDropped",{key:t,attributes:n.attributes})},o.dropEdge=function(t){var e;if(arguments.length>1){var n=""+arguments[0],r=""+arguments[1];if(!(e=d(this,n,r,this.type)))throw new M('Graph.dropEdge: could not find the "'.concat(n,'" -> "').concat(r,'" edge in the graph.'))}else if(t=""+t,!(e=this._edges.get(t)))throw new M('Graph.dropEdge: could not find the "'.concat(t,'" edge in the graph.'));return Gt(this,e),this},o.dropDirectedEdge=function(t,e){if(arguments.length<2)throw new z("Graph.dropDirectedEdge: it does not make sense to try and drop a directed edge by key. What if the edge with this key is undirected? Use #.dropEdge for this purpose instead.");if(this.multi)throw new z("Graph.dropDirectedEdge: cannot use a {source,target} combo when dropping an edge in a MultiGraph since we cannot infer the one you want to delete as there could be multiple ones.");var n=d(this,t=""+t,e=""+e,"directed");if(!n)throw new M('Graph.dropDirectedEdge: could not find a "'.concat(t,'" -> "').concat(e,'" edge in the graph.'));return Gt(this,n),this},o.dropUndirectedEdge=function(t,e){if(arguments.length<2)throw new z("Graph.dropUndirectedEdge: it does not make sense to drop a directed edge by key. What if the edge with this key is undirected? Use #.dropEdge for this purpose instead.");if(this.multi)throw new z("Graph.dropUndirectedEdge: cannot use a {source,target} combo when dropping an edge in a MultiGraph since we cannot infer the one you want to delete as there could be multiple ones.");var n=d(this,t,e,"undirected");if(!n)throw new M('Graph.dropUndirectedEdge: could not find a "'.concat(t,'" -> "').concat(e,'" edge in the graph.'));return Gt(this,n),this},o.clear=function(){this._edges.clear(),this._nodes.clear(),this._resetInstanceCounters(),this.emit("cleared")},o.clearEdges=function(){for(var t,e=this._nodes.values();!0!==(t=e.next()).done;)t.value.clear();this._edges.clear(),this._resetInstanceCounters(),this.emit("edgesCleared")},o.getAttribute=function(t){return this._attributes[t]},o.getAttributes=function(){return this._attributes},o.hasAttribute=function(t){return this._attributes.hasOwnProperty(t)},o.setAttribute=function(t,e){return this._attributes[t]=e,this.emit("attributesUpdated",{type:"set",attributes:this._attributes,name:t}),this},o.updateAttribute=function(t,e){if("function"!=typeof e)throw new C("Graph.updateAttribute: updater should be a function.");var n=this._attributes[t];return this._attributes[t]=e(n),this.emit("attributesUpdated",{type:"set",attributes:this._attributes,name:t}),this},o.removeAttribute=function(t){return delete this._attributes[t],this.emit("attributesUpdated",{type:"remove",attributes:this._attributes,name:t}),this},o.replaceAttributes=function(t){if(!s(t))throw new C("Graph.replaceAttributes: provided attributes are not a plain object.");return this._attributes=t,this.emit("attributesUpdated",{type:"replace",attributes:this._attributes}),this},o.mergeAttributes=function(t){if(!s(t))throw new C("Graph.mergeAttributes: provided attributes are not a plain object.");return u(this._attributes,t),this.emit("attributesUpdated",{type:"merge",attributes:this._attributes,data:t}),this},o.updateAttributes=function(t){if("function"!=typeof t)throw new C("Graph.updateAttributes: provided updater is not a function.");return this._attributes=t(this._attributes),this.emit("attributesUpdated",{type:"update",attributes:this._attributes}),this},o.updateEachNodeAttributes=function(t,e){if("function"!=typeof t)throw new C("Graph.updateEachNodeAttributes: expecting an updater function.");if(e&&!l(e))throw new C("Graph.updateEachNodeAttributes: invalid hints. Expecting an object having the following shape: {attributes?: [string]}");for(var n,r,i=this._nodes.values();!0!==(n=i.next()).done;)(r=n.value).attributes=t(r.key,r.attributes);this.emit("eachNodeAttributesUpdated",{hints:e||null})},o.updateEachEdgeAttributes=function(t,e){if("function"!=typeof t)throw new C("Graph.updateEachEdgeAttributes: expecting an updater function.");if(e&&!l(e))throw new C("Graph.updateEachEdgeAttributes: invalid hints. Expecting an object having the following shape: {attributes?: [string]}");for(var n,r,i,o,a=this._edges.values();!0!==(n=a.next()).done;)i=(r=n.value).source,o=r.target,r.attributes=t(r.key,r.attributes,i.key,o.key,i.attributes,o.attributes,r.undirected);this.emit("eachEdgeAttributesUpdated",{hints:e||null})},o.forEachAdjacencyEntry=function(t){if("function"!=typeof t)throw new C("Graph.forEachAdjacencyEntry: expecting a callback.");pt(!1,!1,!1,this,t)},o.forEachAdjacencyEntryWithOrphans=function(t){if("function"!=typeof t)throw new C("Graph.forEachAdjacencyEntryWithOrphans: expecting a callback.");pt(!1,!1,!0,this,t)},o.forEachAssymetricAdjacencyEntry=function(t){if("function"!=typeof t)throw new C("Graph.forEachAssymetricAdjacencyEntry: expecting a callback.");pt(!1,!0,!1,this,t)},o.forEachAssymetricAdjacencyEntryWithOrphans=function(t){if("function"!=typeof t)throw new C("Graph.forEachAssymetricAdjacencyEntryWithOrphans: expecting a callback.");pt(!1,!0,!0,this,t)},o.nodes=function(){return Array.from(this._nodes.keys())},o.forEachNode=function(t){if("function"!=typeof t)throw new C("Graph.forEachNode: expecting a callback.");for(var e,n,r=this._nodes.values();!0!==(e=r.next()).done;)t((n=e.value).key,n.attributes)},o.findNode=function(t){if("function"!=typeof t)throw new C("Graph.findNode: expecting a callback.");for(var e,n,r=this._nodes.values();!0!==(e=r.next()).done;)if(t((n=e.value).key,n.attributes))return n.key},o.mapNodes=function(t){if("function"!=typeof t)throw new C("Graph.mapNode: expecting a callback.");for(var e,n,r=this._nodes.values(),i=new Array(this.order),o=0;!0!==(e=r.next()).done;)n=e.value,i[o++]=t(n.key,n.attributes);return i},o.someNode=function(t){if("function"!=typeof t)throw new C("Graph.someNode: expecting a callback.");for(var e,n,r=this._nodes.values();!0!==(e=r.next()).done;)if(t((n=e.value).key,n.attributes))return!0;return!1},o.everyNode=function(t){if("function"!=typeof t)throw new C("Graph.everyNode: expecting a callback.");for(var e,n,r=this._nodes.values();!0!==(e=r.next()).done;)if(!t((n=e.value).key,n.attributes))return!1;return!0},o.filterNodes=function(t){if("function"!=typeof t)throw new C("Graph.filterNodes: expecting a callback.");for(var e,n,r=this._nodes.values(),i=[];!0!==(e=r.next()).done;)t((n=e.value).key,n.attributes)&&i.push(n.key);return i},o.reduceNodes=function(t,e){if("function"!=typeof t)throw new C("Graph.reduceNodes: expecting a callback.");if(arguments.length<2)throw new C("Graph.reduceNodes: missing initial value. You must provide it because the callback takes more than one argument and we cannot infer the initial value from the first iteration, as you could with a simple array.");for(var n,r,i=e,o=this._nodes.values();!0!==(n=o.next()).done;)i=t(i,(r=n.value).key,r.attributes);return i},o.nodeEntries=function(){var t=this._nodes.values();return e(e({},Symbol.iterator,(function(){return this})),"next",(function(){var e=t.next();if(e.done)return e;var n=e.value;return{value:{node:n.key,attributes:n.attributes},done:!1}}))},o.export=function(){var t=this,e=new Array(this._nodes.size),n=0;this._nodes.forEach((function(t,r){e[n++]=function(t,e){var n={key:t};return h(e.attributes)||(n.attributes=u({},e.attributes)),n}(r,t)}));var r=new Array(this._edges.size);return n=0,this._edges.forEach((function(e,i){r[n++]=function(t,e,n){var r={key:e,source:n.source.key,target:n.target.key};return h(n.attributes)||(r.attributes=u({},n.attributes)),"mixed"===t&&n.undirected&&(r.undirected=!0),r}(t.type,i,e)})),{options:{type:this.type,multi:this.multi,allowSelfLoops:this.allowSelfLoops},attributes:this.getAttributes(),nodes:e,edges:r}},o.import=function(t){var e,n,r,o,a,c=this,u=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if(t instanceof i)return t.forEachNode((function(t,e){u?c.mergeNode(t,e):c.addNode(t,e)})),t.forEachEdge((function(t,e,n,r,i,o,a){u?a?c.mergeUndirectedEdgeWithKey(t,n,r,e):c.mergeDirectedEdgeWithKey(t,n,r,e):a?c.addUndirectedEdgeWithKey(t,n,r,e):c.addDirectedEdgeWithKey(t,n,r,e)})),this;if(!s(t))throw new C("Graph.import: invalid argument. Expecting a serialized graph or, alternatively, a Graph instance.");if(t.attributes){if(!s(t.attributes))throw new C("Graph.import: invalid attributes. Expecting a plain object.");u?this.mergeAttributes(t.attributes):this.replaceAttributes(t.attributes)}if(t.nodes){if(r=t.nodes,!Array.isArray(r))throw new C("Graph.import: invalid nodes. Expecting an array.");for(e=0,n=r.length;e<n;e++){ft(o=r[e]);var d=o,h=d.key,p=d.attributes;u?this.mergeNode(h,p):this.addNode(h,p)}}if(t.edges){var f=!1;if("undirected"===this.type&&(f=!0),r=t.edges,!Array.isArray(r))throw new C("Graph.import: invalid edges. Expecting an array.");for(e=0,n=r.length;e<n;e++){lt(a=r[e]);var l=a,g=l.source,y=l.target,w=l.attributes,v=l.undirected,b=void 0===v?f:v;"key"in a?(u?b?this.mergeUndirectedEdgeWithKey:this.mergeDirectedEdgeWithKey:b?this.addUndirectedEdgeWithKey:this.addDirectedEdgeWithKey).call(this,a.key,g,y,w):(u?b?this.mergeUndirectedEdge:this.mergeDirectedEdge:b?this.addUndirectedEdge:this.addDirectedEdge).call(this,g,y,w)}}return this},o.nullCopy=function(t){var e=new i(u({},this._options,t));return e.replaceAttributes(u({},this.getAttributes())),e},o.emptyCopy=function(t){var e=this.nullCopy(t);return this._nodes.forEach((function(t,n){var r=u({},t.attributes);t=new e.NodeDataClass(n,r),e._nodes.set(n,t)})),e},o.copy=function(t){if("string"==typeof(t=t||{}).type&&t.type!==this.type&&"mixed"!==t.type)throw new z('Graph.copy: cannot create an incompatible copy from "'.concat(this.type,'" type to "').concat(t.type,'" because this would mean losing information about the current graph.'));if("boolean"==typeof t.multi&&t.multi!==this.multi&&!0!==t.multi)throw new z("Graph.copy: cannot create an incompatible copy by downgrading a multi graph to a simple one because this would mean losing information about the current graph.");if("boolean"==typeof t.allowSelfLoops&&t.allowSelfLoops!==this.allowSelfLoops&&!0!==t.allowSelfLoops)throw new z("Graph.copy: cannot create an incompatible copy from a graph allowing self loops to one that does not because this would mean losing information about the current graph.");for(var e,n,r=this.emptyCopy(t),i=this._edges.values();!0!==(e=i.next()).done;)kt(r,"copy",!1,(n=e.value).undirected,n.key,n.source.key,n.target.key,u({},n.attributes));return r},o.toJSON=function(){return this.export()},o.toString=function(){return"[object Graph]"},o.inspect=function(){var t=this,e={};this._nodes.forEach((function(t,n){e[n]=t.attributes}));var n={},r={};this._edges.forEach((function(e,i){var o,a=e.undirected?"--":"->",c="",u=e.source.key,d=e.target.key;e.undirected&&u>d&&(o=u,u=d,d=o);var s="(".concat(u,")").concat(a,"(").concat(d,")");i.startsWith("geid_")?t.multi&&(void 0===r[s]?r[s]=0:r[s]++,c+="".concat(r[s],". ")):c+="[".concat(i,"]: "),n[c+=s]=e.attributes}));var i={};for(var o in this)this.hasOwnProperty(o)&&!vt.has(o)&&"function"!=typeof this[o]&&"symbol"!==a(o)&&(i[o]=this[o]);return i.attributes=this._attributes,i.nodes=e,i.edges=n,p(i,"constructor",this.constructor),i},i}(v.exports.EventEmitter);"undefined"!=typeof Symbol&&(xt.prototype[Symbol.for("nodejs.util.inspect.custom")]=xt.prototype.inspect),[{name:function(t){return"".concat(t,"Edge")},generateKey:!0},{name:function(t){return"".concat(t,"DirectedEdge")},generateKey:!0,type:"directed"},{name:function(t){return"".concat(t,"UndirectedEdge")},generateKey:!0,type:"undirected"},{name:function(t){return"".concat(t,"EdgeWithKey")}},{name:function(t){return"".concat(t,"DirectedEdgeWithKey")},type:"directed"},{name:function(t){return"".concat(t,"UndirectedEdgeWithKey")},type:"undirected"}].forEach((function(t){["add","merge","update"].forEach((function(e){var n=t.name(e),r="add"===e?kt:_t;t.generateKey?xt.prototype[n]=function(i,o,a){return r(this,n,!0,"undirected"===(t.type||this.type),null,i,o,a,"update"===e)}:xt.prototype[n]=function(i,o,a,c){return r(this,n,!1,"undirected"===(t.type||this.type),i,o,a,c,"update"===e)}}))})),function(t){Y.forEach((function(e){var n=e.name,r=e.attacher;r(t,n("Node"),I),r(t,n("Source"),R),r(t,n("Target"),2),r(t,n("Opposite"),F)}))}(xt),function(t){J.forEach((function(e){var n=e.name,r=e.attacher;r(t,n("Edge"),"mixed"),r(t,n("DirectedEdge"),"directed"),r(t,n("UndirectedEdge"),"undirected")}))}(xt),function(t){q.forEach((function(e){rt(t,e),function(t,e){var n=e.name,r=e.type,i=e.direction,o="forEach"+n[0].toUpperCase()+n.slice(1,-1);t.prototype[o]=function(t,e,n){if("mixed"===r||"mixed"===this.type||r===this.type){if(1===arguments.length)return tt(!1,this,r,n=t);if(2===arguments.length){t=""+t,n=e;var a=this._nodes.get(t);if(void 0===a)throw new M("Graph.".concat(o,': could not find the "').concat(t,'" node in the graph.'));return et(!1,this.multi,"mixed"===r?this.type:r,i,a,n)}if(3===arguments.length){t=""+t,e=""+e;var c=this._nodes.get(t);if(!c)throw new M("Graph.".concat(o,': could not find the "').concat(t,'" source node in the graph.'));if(!this._nodes.has(e))throw new M("Graph.".concat(o,': could not find the "').concat(e,'" target node in the graph.'));return nt(!1,r,this.multi,i,c,e,n)}throw new C("Graph.".concat(o,": too many arguments (expecting 1, 2 or 3 and got ").concat(arguments.length,")."))}};var a="map"+n[0].toUpperCase()+n.slice(1);t.prototype[a]=function(){var t,e=Array.prototype.slice.call(arguments),n=e.pop();if(0===e.length){var i=0;"directed"!==r&&(i+=this.undirectedSize),"undirected"!==r&&(i+=this.directedSize),t=new Array(i);var a=0;e.push((function(e,r,i,o,c,u,d){t[a++]=n(e,r,i,o,c,u,d)}))}else t=[],e.push((function(e,r,i,o,a,c,u){t.push(n(e,r,i,o,a,c,u))}));return this[o].apply(this,e),t};var c="filter"+n[0].toUpperCase()+n.slice(1);t.prototype[c]=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=[];return t.push((function(t,r,i,o,a,c,u){e(t,r,i,o,a,c,u)&&n.push(t)})),this[o].apply(this,t),n};var u="reduce"+n[0].toUpperCase()+n.slice(1);t.prototype[u]=function(){var t,e,n=Array.prototype.slice.call(arguments);if(n.length<2||n.length>4)throw new C("Graph.".concat(u,": invalid number of arguments (expecting 2, 3 or 4 and got ").concat(n.length,")."));if("function"==typeof n[n.length-1]&&"function"!=typeof n[n.length-2])throw new C("Graph.".concat(u,": missing initial value. You must provide it because the callback takes more than one argument and we cannot infer the initial value from the first iteration, as you could with a simple array."));2===n.length?(t=n[0],e=n[1],n=[]):3===n.length?(t=n[1],e=n[2],n=[n[0]]):4===n.length&&(t=n[2],e=n[3],n=[n[0],n[1]]);var r=e;return n.push((function(e,n,i,o,a,c,u){r=t(r,e,n,i,o,a,c,u)})),this[o].apply(this,n),r}}(t,e),function(t,e){var n=e.name,r=e.type,i=e.direction,o="find"+n[0].toUpperCase()+n.slice(1,-1);t.prototype[o]=function(t,e,n){if("mixed"!==r&&"mixed"!==this.type&&r!==this.type)return!1;if(1===arguments.length)return tt(!0,this,r,n=t);if(2===arguments.length){t=""+t,n=e;var a=this._nodes.get(t);if(void 0===a)throw new M("Graph.".concat(o,': could not find the "').concat(t,'" node in the graph.'));return et(!0,this.multi,"mixed"===r?this.type:r,i,a,n)}if(3===arguments.length){t=""+t,e=""+e;var c=this._nodes.get(t);if(!c)throw new M("Graph.".concat(o,': could not find the "').concat(t,'" source node in the graph.'));if(!this._nodes.has(e))throw new M("Graph.".concat(o,': could not find the "').concat(e,'" target node in the graph.'));return nt(!0,r,this.multi,i,c,e,n)}throw new C("Graph.".concat(o,": too many arguments (expecting 1, 2 or 3 and got ").concat(arguments.length,")."))};var a="some"+n[0].toUpperCase()+n.slice(1,-1);t.prototype[a]=function(){var t=Array.prototype.slice.call(arguments),e=t.pop();return t.push((function(t,n,r,i,o,a,c){return e(t,n,r,i,o,a,c)})),!!this[o].apply(this,t)};var c="every"+n[0].toUpperCase()+n.slice(1,-1);t.prototype[c]=function(){var t=Array.prototype.slice.call(arguments),e=t.pop();return t.push((function(t,n,r,i,o,a,c){return!e(t,n,r,i,o,a,c)})),!this[o].apply(this,t)}}(t,e),it(t,e)}))}(xt),function(t){ot.forEach((function(e){st(t,e),function(t,e){var n=e.name,r=e.type,i=e.direction,o="forEach"+n[0].toUpperCase()+n.slice(1,-1);t.prototype[o]=function(t,e){if("mixed"===r||"mixed"===this.type||r===this.type){t=""+t;var n=this._nodes.get(t);if(void 0===n)throw new M("Graph.".concat(o,': could not find the "').concat(t,'" node in the graph.'));ut(!1,"mixed"===r?this.type:r,i,n,e)}};var a="map"+n[0].toUpperCase()+n.slice(1);t.prototype[a]=function(t,e){var n=[];return this[o](t,(function(t,r){n.push(e(t,r))})),n};var c="filter"+n[0].toUpperCase()+n.slice(1);t.prototype[c]=function(t,e){var n=[];return this[o](t,(function(t,r){e(t,r)&&n.push(t)})),n};var u="reduce"+n[0].toUpperCase()+n.slice(1);t.prototype[u]=function(t,e,n){if(arguments.length<3)throw new C("Graph.".concat(u,": missing initial value. You must provide it because the callback takes more than one argument and we cannot infer the initial value from the first iteration, as you could with a simple array."));var r=n;return this[o](t,(function(t,n){r=e(r,t,n)})),r}}(t,e),function(t,e){var n=e.name,r=e.type,i=e.direction,o=n[0].toUpperCase()+n.slice(1,-1),a="find"+o;t.prototype[a]=function(t,e){if("mixed"===r||"mixed"===this.type||r===this.type){t=""+t;var n=this._nodes.get(t);if(void 0===n)throw new M("Graph.".concat(a,': could not find the "').concat(t,'" node in the graph.'));return ut(!0,"mixed"===r?this.type:r,i,n,e)}};var c="some"+o;t.prototype[c]=function(t,e){return!!this[a](t,e)};var u="every"+o;t.prototype[u]=function(t,e){return!this[a](t,(function(t,n){return!e(t,n)}))}}(t,e),ht(t,e)}))}(xt);var Et=function(t){function e(e){var n=u({type:"directed"},e);if("multi"in n&&!1!==n.multi)throw new C("DirectedGraph.from: inconsistent indication that the graph should be multi in given options!");if("directed"!==n.type)throw new C('DirectedGraph.from: inconsistent "'+n.type+'" type in given options!');return t.call(this,n)||this}return r(e,t),e}(xt),At=function(t){function e(e){var n=u({type:"undirected"},e);if("multi"in n&&!1!==n.multi)throw new C("UndirectedGraph.from: inconsistent indication that the graph should be multi in given options!");if("undirected"!==n.type)throw new C('UndirectedGraph.from: inconsistent "'+n.type+'" type in given options!');return t.call(this,n)||this}return r(e,t),e}(xt),Lt=function(t){function e(e){var n=u({multi:!0},e);if("multi"in n&&!0!==n.multi)throw new C("MultiGraph.from: inconsistent indication that the graph should be simple in given options!");return t.call(this,n)||this}return r(e,t),e}(xt),St=function(t){function e(e){var n=u({type:"directed",multi:!0},e);if("multi"in n&&!0!==n.multi)throw new C("MultiDirectedGraph.from: inconsistent indication that the graph should be simple in given options!");if("directed"!==n.type)throw new C('MultiDirectedGraph.from: inconsistent "'+n.type+'" type in given options!');return t.call(this,n)||this}return r(e,t),e}(xt),Dt=function(t){function e(e){var n=u({type:"undirected",multi:!0},e);if("multi"in n&&!0!==n.multi)throw new C("MultiUndirectedGraph.from: inconsistent indication that the graph should be simple in given options!");if("undirected"!==n.type)throw new C('MultiUndirectedGraph.from: inconsistent "'+n.type+'" type in given options!');return t.call(this,n)||this}return r(e,t),e}(xt);function Ut(t){t.from=function(e,n){var r=u({},e.options,n),i=new t(r);return i.import(e),i}}return Ut(xt),Ut(Et),Ut(At),Ut(Lt),Ut(St),Ut(Dt),xt.Graph=xt,xt.DirectedGraph=Et,xt.UndirectedGraph=At,xt.MultiGraph=Lt,xt.MultiDirectedGraph=St,xt.MultiUndirectedGraph=Dt,xt.InvalidArgumentsGraphError=C,xt.NotFoundGraphError=M,xt.UsageGraphError=z,xt}));
//# sourceMappingURL=graphology.umd.min.js.map
</script><script>(function(we,Ie){typeof exports=="object"&&typeof module<"u"?module.exports=Ie():typeof define=="function"&&define.amd?define(Ie):(we=typeof globalThis<"u"?globalThis:we||self,we.Sigma=Ie())})(this,(function(){"use strict";function we(a){return a.type==="array"}function Ie(a,e){return{type:"number",default:a,variable:e==null?void 0:e.variable}}function Kr(a,e){return{type:"color",default:a,variable:e==null?void 0:e.variable}}function Zr(a,e){return{type:"string",default:a,variable:e==null?void 0:e.variable}}function Qr(a,e){return{type:"boolean",default:a,variable:e==null?void 0:e.variable}}function Jr(a,e){return{type:{enum:a},default:e,variable:!1}}function en(a,e){return{type:"array",items:a,minItems:e==null?void 0:e.minItems}}function tn(a){const e=a.type===WebGL2RenderingContext.FLOAT&&!a.normalized,t=a.type===WebGL2RenderingContext.UNSIGNED_BYTE&&a.size===4&&a.normalized;if(!e&&!t)throw new Error(`Attribute "${a.name}" is invalid: only non-normalized FLOAT and normalized UNSIGNED_BYTE with size 4 are supported.`);return a.normalized?1:a.size}function at(a){let e=0;return a.forEach(t=>e+=tn(t)),e}function Aa(a,e,t){const i=a==="VERTEX"?e.VERTEX_SHADER:e.FRAGMENT_SHADER,r=e.createShader(i);if(r===null)throw new Error("loadShader: error while creating the shader");if(e.shaderSource(r,t),e.compileShader(r),!e.getShaderParameter(r,e.COMPILE_STATUS)){const o=e.getShaderInfoLog(r);throw e.deleteShader(r),new Error(`loadShader: error while compiling the shader:
${o}
${t}`)}return r}function it(a,e){return Aa("VERTEX",a,e)}function rt(a,e){return Aa("FRAGMENT",a,e)}function nt(a,e){const t=a.createProgram();if(t===null)throw new Error("loadProgram: error while creating the program.");let i,r;for(i=0,r=e.length;i<r;i++)a.attachShader(t,e[i]);if(a.linkProgram(t),!a.getProgramParameter(t,a.LINK_STATUS)){const o=a.getProgramInfoLog(t);throw a.deleteProgram(t),new Error(`loadProgram: error while linking the program: ${o}`)}return t}function kt({gl:a,buffer:e,program:t,vertexShader:i,fragmentShader:r}){a.deleteShader(i),a.deleteShader(r),a.deleteProgram(t),a.deleteBuffer(e)}function I(a){return a%1===0?a.toFixed(1):a.toString()}const K=new Map,Gt=new Map;let Da=0;function Wt(a,e){var r,n;const t=(n=(r=a.variables)==null?void 0:r[e.source||e.name.replace(/^a_/,"")])==null?void 0:n.default,i=typeof t=="number"?t:typeof e.defaultValue=="number"?e.defaultValue:0;return I(i)}function ze(a,e,t,i=r=>`v_${r.name.replace(/^a_/,"")}`){const r=[e,t,...a.uniforms.filter(n=>n.type==="float").map(n=>I(n.value??0)),...(a.attributes??[]).map(i)];return`sdf_${a.name}(${r.join(", ")})`}function an(a){let e=a.name;const t=a.uniforms.filter(n=>n.type==="float"&&n.value!==void 0&&n.value!==0).map(n=>`${n.name.replace("u_","")}=${n.value}`).sort(),i=(a.attributes??[]).map(n=>`${n.name}@${n.source??""}=${Wt(a,n)}`),r=[...t,...i];return r.length>0&&(e+="#"+r.join("#")),e}function Ca(a){const e=an(a);if(!K.has(e)){const t={};for(const i of a.uniforms)i.type==="float"&&i.value!==void 0&&(t[i.name]=i.value);K.set(e,{shape:a,uniformValues:t,slug:e}),Gt.set(e,Da++)}return e}function rn(a){return K.get(a)}function nn(a){var e;return(e=K.get(a))==null?void 0:e.shape}function Me(a){return Gt.get(a)??-1}function on(){return Array.from(K.keys())}function sn(a){var e;return((e=K.get(a))==null?void 0:e.shape.glsl)??""}function La(a){const e=[],t=new Set,i=new Set,r=/mat2 rotate2D\(float angle\)\s*\{[^}]+\}/;for(const n of a){if(i.has(n.name))continue;i.add(n.name);let o=n.glsl;r.test(o)&&(t.has("rotate2D")?o=o.replace(r,""):t.add("rotate2D")),e.push(o)}return e.join(`
`)}function Fa(){return La(Array.from(K.values()).map(a=>a.shape))}function Pa(a){const e=Array.from(K.entries());return e.length===0?`
float querySDF(int shapeId, vec2 uv, float size) {
return length(uv) - size;
}
`:`
float querySDF(int shapeId, vec2 uv, float size) {
switch (shapeId) {
${e.map(([i,{shape:r}],n)=>{const o=ze(r,"uv","size",s=>{const l=s.name.replace(/^a_/,"");return a!=null&&a.has(l)?`g_${l}`:Wt(r,s)});return` case ${n}: return ${o}; // ${i}`}).join(`
`)}
default: return length(uv) - size;
}
}
`}function ot(a){return La(a)}function ge(a){return[...new Map(a.flatMap(e=>e.uniforms).map(e=>[e.name,e])).values()]}function wa(a,e){if(a.length===0)return`
void queryNodeSDF(int shapeId, vec2 uv, float size) {
context.sdf = length(uv) - size;
context.inradiusFactor = 1.0;
}
`;const t=l=>ze(l,"uv","size"),i=l=>l.inradiusFactorGLSL??I(l.inradiusFactor??1);if(a.length===1){const l=a[0];return`
void queryNodeSDF(int shapeId, vec2 uv, float size) {
context.sdf = ${t(l)};
context.inradiusFactor = ${i(l)};
}
`}const r=a.map((l,u)=>` case ${u}: // ${l.name}
context.sdf = ${t(l)};
context.inradiusFactor = ${i(l)};
break;`).join(`
`),n=a[0];let o="",s="shapeId";return e&&e.length>1&&(o=`
int globalToLocalShapeId(int globalId) {
switch (globalId) {
${e.map((u,g)=>` case ${u}: return ${g}; // ${a[g].name}`).join(`
`)}
default: return 0;
}
}
`,s="globalToLocalShapeId(shapeId)"),`${o}
void queryNodeSDF(int shapeId, vec2 uv, float size) {
switch (${s}) {
${r}
default:
context.sdf = ${t(n)};
context.inradiusFactor = ${i(n)};
}
}
`}function ln(){K.clear(),Gt.clear(),Da=0}const pe={right:0,left:1,above:2,below:3,over:4},me=5,Bt=3,Ot=`
float matrixScaleX = length(vec2(u_matrix[0][0], u_matrix[1][0]));
float nodeRadiusGraphSpace = nodeSize * u_correctionRatio / u_sizeRatio * 2.0;
float nodeRadiusNDC = nodeRadiusGraphSpace * matrixScaleX;
float nodeRadiusPixels = nodeRadiusNDC * u_resolution.x / 2.0;
`,be=`
mat2 rotate2D(float angle) {
float c = cos(angle);
float s = sin(angle);
return mat2(c, -s, s, c);
}
`,Ut=`
vec2 getLabelDirection(float positionMode) {
if (positionMode < 0.5) return vec2(1.0, 0.0); // Right
if (positionMode < 1.5) return vec2(-1.0, 0.0); // Left
if (positionMode < 2.5) return vec2(0.0, -1.0); // Above
if (positionMode < 3.5) return vec2(0.0, 1.0); // Below
return vec2(0.0); // Over (centered)
}
`,Ia=`
float sdfBox(vec2 p, vec2 halfSize) {
vec2 d = abs(p) - halfSize;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
`,za=`
float sdfRotatedBox(vec2 p, vec2 halfSize, float angle) {
float c = cos(-angle);
float s = sin(-angle);
vec2 rotatedP = mat2(c, -s, s, c) * p;
return sdfBox(rotatedP, halfSize);
}
`,Ma=`
float sdfRoundedBox(vec2 p, vec2 halfSize, float radius) {
vec2 d = abs(p) - halfSize + radius;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
}
`,Na=`
float sdfRoundedRotatedBox(vec2 p, vec2 halfSize, float angle, float radius) {
float c = cos(-angle);
float s = sin(-angle);
vec2 rotatedP = mat2(c, -s, s, c) * p;
return sdfRoundedBox(rotatedP, halfSize, radius);
}
`,$t=`
vec2 labelBoxCenter(float positionMode, float labelStart, vec2 halfSize, float textHalfY) {
if (positionMode < 0.5) return vec2(labelStart + halfSize.x, 0.0); // right
if (positionMode < 1.5) return vec2(-(labelStart + halfSize.x), 0.0); // left
if (positionMode < 2.5) return vec2(0.0, -(labelStart + textHalfY)); // above
if (positionMode < 3.5) return vec2(0.0, labelStart + textHalfY); // below
return vec2(0.0); // over
}
`,st=2,X=`
vec4 readNodeData(sampler2D nodeDataTexture, int nodeDataTextureWidth, int nodeIndex) {
int t = nodeIndex * ${st};
ivec2 coord = ivec2(t % nodeDataTextureWidth, t / nodeDataTextureWidth);
return texelFetch(nodeDataTexture, coord, 0);
}
`,J=`
vec4 readNodeFlags(sampler2D nodeDataTexture, int nodeDataTextureWidth, int nodeIndex) {
int t = nodeIndex * ${st} + 1;
ivec2 coord = ivec2(t % nodeDataTextureWidth, t / nodeDataTextureWidth);
return texelFetch(nodeDataTexture, coord, 0);
}
`,ka=`
vec4 readNodeColor(sampler2D nodeDataTexture, int nodeDataTextureWidth, int nodeIndex) {
int t = nodeIndex * ${st} + 1;
ivec2 coord = ivec2(t % nodeDataTextureWidth, t / nodeDataTextureWidth);
vec4 texel = texelFetch(nodeDataTexture, coord, 0);
float r = floor(texel.b / 65536.0);
float g = floor(mod(texel.b, 65536.0) / 256.0);
float b = mod(texel.b, 256.0);
return vec4(r / 255.0, g / 255.0, b / 255.0, texel.a);
}
`,se=`
vec4 readFrameTexel(sampler2D frameTexture, int frameTextureWidth, int index) {
ivec2 coord = ivec2(index % frameTextureWidth, index / frameTextureWidth);
return texelFetch(frameTexture, coord, 0);
}
`;function Ga(a,e){const t=n=>ze(n,"uv","size");return a.length===1?{code:Wa(t(a[0])),multiShape:!1}:{code:`
float queryShapeSDF(int shapeId, vec2 uv, float size) {
switch (shapeId) {
${a.map((n,o)=>` case ${e?e[o]:o}: return ${t(n)};`).join(`
`)}
default: return ${t(a[0])};
}
}
int g_shapeId;
${Wa("queryShapeSDF(g_shapeId, uv, size)")}
`,multiShape:!0}}function Wa(a){return`
float findEdgeDistance(vec2 direction, float size) {
float lo = 0.0, hi = 2.0;
for (int i = 0; i < 8; i++) {
float mid = (lo + hi) * 0.5;
vec2 uv = direction * mid;
if (${a} < 0.0) lo = mid; else hi = mid;
}
return (lo + hi) * 0.5;
}
`}const Ba=a=>a,Oa=a=>a*a,Ua=a=>a*(2-a),$a=a=>(a*=2)<1?.5*a*a:-.5*(--a*(a-2)-1),Ha=a=>a*a*a,Va=a=>--a*a*a+1,Xa=a=>(a*=2)<1?.5*a*a*a:.5*((a-=2)*a*a+2),qa=a=>a===0?0:Math.pow(2,10*(a-1)),ja=a=>a===1?1:1-Math.pow(2,-10*a),Ya=a=>a===0?0:a===1?1:a<.5?Math.pow(2,10*(2*a-1))/2:(2-Math.pow(2,-10*(2*a-1)))/2,Ht={linear:Ba,quadraticIn:Oa,quadraticOut:Ua,quadraticInOut:$a,cubicIn:Ha,cubicOut:Va,cubicInOut:Xa,exponentialIn:qa,exponentialOut:ja,exponentialInOut:Ya};function lt(a){return a?typeof a=="function"?a:Ht[a]:Ht.linear}const Vt={easing:"quadraticInOut",duration:150};function dn(a,e,t,i){const r=Object.assign({},Vt,t),n=lt(r.easing),o=Date.now(),s={};for(const g in e){const d=e[g];s[g]={};for(const h in d)s[g][h]=a.getNodeAttribute(g,h)}let l=null;const u=()=>{l=null;let g=(Date.now()-o)/r.duration;if(g>=1){for(const d in e){const h=e[d];for(const c in h)a.setNodeAttribute(d,c,h[c])}typeof i=="function"&&i();return}g=n(g);for(const d in e){const h=e[d],c=s[d];for(const f in h)a.setNodeAttribute(d,f,h[f]*g+c[f]*(1-g))}l=requestAnimationFrame(u)};return u(),()=>{l&&cancelAnimationFrame(l)}}function O(){return Float32Array.of(1,0,0,0,1,0,0,0,1)}function Ne(a,e,t){return a[0]=e,a[4]=typeof t=="number"?t:e,a}function Xt(a,e){const t=Math.sin(e),i=Math.cos(e);return a[0]=i,a[1]=t,a[3]=-t,a[4]=i,a}function qt(a,e,t){return a[6]=e,a[7]=t,a}function Z(a,e){const t=a[0],i=a[1],r=a[2],n=a[3],o=a[4],s=a[5],l=a[6],u=a[7],g=a[8],d=e[0],h=e[1],c=e[2],f=e[3],b=e[4],v=e[5],_=e[6],m=e[7],E=e[8];return a[0]=d*t+h*n+c*l,a[1]=d*i+h*o+c*u,a[2]=d*r+h*s+c*g,a[3]=f*t+b*n+v*l,a[4]=f*i+b*o+v*u,a[5]=f*r+b*s+v*g,a[6]=_*t+m*n+E*l,a[7]=_*i+m*o+E*u,a[8]=_*r+m*s+E*g,a}function jt(a,e){const t=Math.cos(e),i=Math.sin(e);return{x:t*a.x+i*a.y,y:-i*a.x+t*a.y}}function xe(a,e,t=1){const i=a[0],r=a[1],n=a[3],o=a[4],s=a[6],l=a[7],u=e.x,g=e.y;return{x:u*i+g*n+s*t,y:u*r+g*o+l*t}}function Ka(a,e){const t=a.height/a.width,i=e.height/e.width;return t<1&&i>1||t>1&&i<1?1:Math.min(Math.max(i,1/i),Math.max(1/t,t))}function ee(a,e,t,i,r){const{angle:n,ratio:o,x:s,y:l}=a,{width:u,height:g}=e,d=O(),h=Math.min(u,g)-2*i,c=Ka(e,t);return r?(Z(d,qt(O(),s,l)),Z(d,Ne(O(),o)),Z(d,Xt(O(),n)),Z(d,Ne(O(),u/h/2/c,g/h/2/c))):(Z(d,Ne(O(),2*(h/u)*c,2*(h/g)*c)),Z(d,Xt(O(),-n)),Z(d,Ne(O(),1/o)),Z(d,qt(O(),-s,-l))),d}function Za(a,e,t){const{x:i,y:r}=xe(a,{x:Math.cos(e.angle),y:Math.sin(e.angle)},0);return 1/Math.sqrt(Math.pow(i,2)+Math.pow(r,2))/t.width}function Yt(a,e,t,i,r){const n=Math.floor(e/r*i),o=Math.floor(a.drawingBufferHeight/r-t/r*i);return[n,o]}const _e={black:"#000000",silver:"#C0C0C0",gray:"#808080",grey:"#808080",white:"#FFFFFF",maroon:"#800000",red:"#FF0000",purple:"#800080",fuchsia:"#FF00FF",green:"#008000",lime:"#00FF00",olive:"#808000",yellow:"#FFFF00",navy:"#000080",blue:"#0000FF",teal:"#008080",aqua:"#00FFFF",darkblue:"#00008B",mediumblue:"#0000CD",darkgreen:"#006400",darkcyan:"#008B8B",deepskyblue:"#00BFFF",darkturquoise:"#00CED1",mediumspringgreen:"#00FA9A",springgreen:"#00FF7F",cyan:"#00FFFF",midnightblue:"#191970",dodgerblue:"#1E90FF",lightseagreen:"#20B2AA",forestgreen:"#228B22",seagreen:"#2E8B57",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",limegreen:"#32CD32",mediumseagreen:"#3CB371",turquoise:"#40E0D0",royalblue:"#4169E1",steelblue:"#4682B4",darkslateblue:"#483D8B",mediumturquoise:"#48D1CC",indigo:"#4B0082",darkolivegreen:"#556B2F",cadetblue:"#5F9EA0",cornflowerblue:"#6495ED",rebeccapurple:"#663399",mediumaquamarine:"#66CDAA",dimgray:"#696969",dimgrey:"#696969",slateblue:"#6A5ACD",olivedrab:"#6B8E23",slategray:"#708090",slategrey:"#708090",lightslategray:"#778899",lightslategrey:"#778899",mediumslateblue:"#7B68EE",lawngreen:"#7CFC00",chartreuse:"#7FFF00",aquamarine:"#7FFFD4",skyblue:"#87CEEB",lightskyblue:"#87CEFA",blueviolet:"#8A2BE2",darkred:"#8B0000",darkmagenta:"#8B008B",saddlebrown:"#8B4513",darkseagreen:"#8FBC8F",lightgreen:"#90EE90",mediumpurple:"#9370DB",darkviolet:"#9400D3",palegreen:"#98FB98",darkorchid:"#9932CC",yellowgreen:"#9ACD32",sienna:"#A0522D",brown:"#A52A2A",darkgray:"#A9A9A9",darkgrey:"#A9A9A9",lightblue:"#ADD8E6",greenyellow:"#ADFF2F",paleturquoise:"#AFEEEE",lightsteelblue:"#B0C4DE",powderblue:"#B0E0E6",firebrick:"#B22222",darkgoldenrod:"#B8860B",mediumorchid:"#BA55D3",rosybrown:"#BC8F8F",darkkhaki:"#BDB76B",mediumvioletred:"#C71585",indianred:"#CD5C5C",peru:"#CD853F",chocolate:"#D2691E",tan:"#D2B48C",lightgray:"#D3D3D3",lightgrey:"#D3D3D3",thistle:"#D8BFD8",orchid:"#DA70D6",goldenrod:"#DAA520",palevioletred:"#DB7093",crimson:"#DC143C",gainsboro:"#DCDCDC",plum:"#DDA0DD",burlywood:"#DEB887",lightcyan:"#E0FFFF",lavender:"#E6E6FA",darksalmon:"#E9967A",violet:"#EE82EE",palegoldenrod:"#EEE8AA",lightcoral:"#F08080",khaki:"#F0E68C",aliceblue:"#F0F8FF",honeydew:"#F0FFF0",azure:"#F0FFFF",sandybrown:"#F4A460",wheat:"#F5DEB3",beige:"#F5F5DC",whitesmoke:"#F5F5F5",mintcream:"#F5FFFA",ghostwhite:"#F8F8FF",salmon:"#FA8072",antiquewhite:"#FAEBD7",linen:"#FAF0E6",lightgoldenrodyellow:"#FAFAD2",oldlace:"#FDF5E6",magenta:"#FF00FF",deeppink:"#FF1493",orangered:"#FF4500",tomato:"#FF6347",hotpink:"#FF69B4",coral:"#FF7F50",darkorange:"#FF8C00",lightsalmon:"#FFA07A",orange:"#FFA500",lightpink:"#FFB6C1",pink:"#FFC0CB",gold:"#FFD700",peachpuff:"#FFDAB9",navajowhite:"#FFDEAD",moccasin:"#FFE4B5",bisque:"#FFE4C4",mistyrose:"#FFE4E1",blanchedalmond:"#FFEBCD",papayawhip:"#FFEFD5",lavenderblush:"#FFF0F5",seashell:"#FFF5EE",cornsilk:"#FFF8DC",lemonchiffon:"#FFFACD",floralwhite:"#FFFAF0",snow:"#FFFAFA",lightyellow:"#FFFFE0",ivory:"#FFFFF0"},Qa=new Int8Array(4),dt=new Int32Array(Qa.buffer,0,1),Ja=new Float32Array(Qa.buffer,0,1),cn=/^\s*rgba?\s*\(/,hn=/^\s*rgba?\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)(?:\s*,\s*(.*)?)?\)\s*$/;function ke(a){let e=0,t=0,i=0,r=1;const n=a.toLowerCase();if(n==="transparent")return{r:0,g:0,b:0,a:0};if(n in _e)return ke(_e[n]);if(a[0]==="#")a.length===4?(e=parseInt(a.charAt(1)+a.charAt(1),16),t=parseInt(a.charAt(2)+a.charAt(2),16),i=parseInt(a.charAt(3)+a.charAt(3),16)):(e=parseInt(a.charAt(1)+a.charAt(2),16),t=parseInt(a.charAt(3)+a.charAt(4),16),i=parseInt(a.charAt(5)+a.charAt(6),16)),a.length===9&&(r=parseInt(a.charAt(7)+a.charAt(8),16)/255);else if(cn.test(a)){const o=a.match(hn);o&&(e=+o[1],t=+o[2],i=+o[3],o[4]&&(r=+o[4]))}return{r:e,g:t,b:i,a:r}}function ei(a,e=!1){const{r:t,g:i,b:r,a:n}=ke(a);return e?[t/255*n,i/255*n,r/255*n,n]:[t/255,i/255,r/255,n]}const Te={};for(const a in _e)Te[a]=H(_e[a]),Te[_e[a]]=Te[a];function ti(a,e,t,i,r){return dt[0]=i<<24|t<<16|e<<8|a,r&&(dt[0]=dt[0]&4278190079),Ja[0]}function H(a){if(a=a.toLowerCase(),typeof Te[a]<"u")return Te[a];const e=ke(a),{r:t,g:i,b:r}=e;let{a:n}=e;n=n*255|0;const o=ti(t,i,r,n,!0);return Te[a]=o,o}function ve(a,e){Ja[0]=H(a);let t=dt[0];e&&(t=t|16777216);const i=t&255,r=t>>8&255,n=t>>16&255,o=t>>24&255;return[i,r,n,o]}function Ge(a){const e=a>>>8&255,t=a>>>16&255,i=a>>>24&255;return((a&255)<<24|i<<16|t<<8|e)>>>0}function Kt(a,e,t,i){return(t<<24|e<<16|a<<8|i)>>>0}function ai(a,e,t,i,r,n){const[o,s]=Yt(a,t,i,r,n),l=new Uint8Array(4);a.bindFramebuffer(a.FRAMEBUFFER,e),a.readPixels(o,s,1,1,a.RGBA,a.UNSIGNED_BYTE,l);const[u,g,d,h]=l;return[u,g,d,h]}function ii(a){const{r:e,g:t,b:i,a:r}=ke(a),n=(e/255).toFixed(6),o=(t/255).toFixed(6),s=(i/255).toFixed(6),l=r.toFixed(6);return`vec4(${n}, ${o}, ${s}, ${l})`}function Zt(a,e){const t=e.size;if(t===0)return;const i=a.length;a.length+=t;let r=0;e.forEach(n=>{a[i+r]=n,r++})}function ri(a,...e){a=a||{};for(let t=0,i=e.length;t<i;t++){const r=e[t];r&&Object.assign(a,r)}return a}function ye(a,e){for(const t in e)if(Object.prototype.hasOwnProperty.call(e,t)&&e[t]!==a[t])return!0;return!1}function ni(a,e){const t=a,i=e;for(const r in t)if(Object.prototype.hasOwnProperty.call(t,r)&&t[r]!==i[r])return!1;for(const r in i)if(Object.prototype.hasOwnProperty.call(i,r)&&i[r]!==t[r])return!1;return!0}function le(a,e,t){t?a.add(e):a.delete(e)}function te(a){return a.labelVisibility==="visible"&&a.visibility!=="hidden"}function Qt(a){return a.backdropVisibility==="visible"&&a.visibility!=="hidden"}function ct(a){return[a.rotationAlignment==="graph"?1:0,a.labelRotationAlignment==="graph"?1:0]}function Jt(a,e,t){const i=a[e];if(i)for(let r=0;r<i.length;r++){const n=i[r];if(!(t<n.offset||t>=n.offset+n.count)){if(n.count===1)i.splice(r,1);else if(t===n.offset)n.offset++,n.count--;else if(t===n.offset+n.count-1)n.count--;else{const o=t+1,s=n.offset+n.count-o;n.count=t-n.offset,i.splice(r+1,0,{offset:o,count:s})}return}}}function ea(a,e,t){if(!a[e]){a[e]=[{offset:t,count:1}];return}const i=a[e];let r=i.length;for(let u=0;u<i.length;u++)if(t<i[u].offset){r=u;break}const n=r>0?i[r-1]:null,o=r<i.length?i[r]:null,s=n&&n.offset+n.count===t,l=o&&t+1===o.offset;s&&l?(n.count+=1+o.count,i.splice(r,1)):s?n.count++:l?(o.offset--,o.count++):i.splice(r,0,{offset:t,count:1})}function un(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var ta,oi;function fn(){return oi||(oi=1,ta=function(e){return e!==null&&typeof e=="object"&&typeof e.addUndirectedEdgeWithKey=="function"&&typeof e.dropNode=="function"&&typeof e.multi=="boolean"}),ta}var gn=fn();const pn=un(gn);function si(a){if(!pn(a))throw new Error("Sigma: invalid graph instance.")}const mn=new Set(["italic","oblique"]),bn=new Set(["bold","bolder","lighter"]);function ht(a){let e="normal",t="normal";const i=a.trim(),r=i.split(/\s+/);let n=0;for(let s=0;s<r.length;s++){const l=r[s].toLowerCase();if(mn.has(l))t=l,n=s+1;else if(bn.has(l)||/^\d{3}$/.test(l))e=l,n=s+1;else if(l==="normal")n=s+1;else break}return{family:r.slice(n).join(" ")||i,weight:e,style:t}}function Se(a,e,t){const i=document.createElement(a);if(e)for(const r in e)i.style[r]=e[r];if(t)for(const r in t)i.setAttribute(r,t[r]);return i}function ut(){return typeof window.devicePixelRatio<"u"?window.devicePixelRatio:1}function ft(a){const{x:[e,t],y:[i,r]}=a;let n=Math.max(t-e,r-i),o=(t+e)/2,s=(r+i)/2;(n===0||Math.abs(n)===1/0||isNaN(n))&&(n=1),isNaN(o)&&(o=0),isNaN(s)&&(s=0);const l=u=>({x:.5+(u.x-o)/n,y:.5+(u.y-s)/n});return l.applyTo=u=>{u.x=.5+(u.x-o)/n,u.y=.5+(u.y-s)/n},l.inverse=u=>({x:o+n*(u.x-.5),y:s+n*(u.y-.5)}),l.ratio=n,l}const xn=2;function li(a,e,t,i=xn){const{width:r,height:n}=e.canvas;let{x:o,y:s,rowHeight:l,maxRowWidth:u}=t;const g={},d=[];for(const h of a){const c=h.width+i,f=h.height+i;if(c>r||f>n||o+c>r&&s+l+f>n){d.push(h);continue}o+c>r&&(u=Math.max(u,o),o=0,s+=l,l=f),h.draw(e,o,s),g[h.key]={x:o,y:s,width:h.width,height:h.height},o+=c,l=Math.max(l,f)}return u=Math.max(u,o),{atlas:g,cursor:{x:o,y:s,rowHeight:l,maxRowWidth:u},remaining:d}}const _n=Object.freeze(Object.defineProperty({__proto__:null,ANIMATE_DEFAULTS:Vt,HTML_COLORS:_e,addPositionToDepthRanges:ea,animateNodes:dn,assign:ri,colorToArray:ve,colorToGLSLString:ii,colorToIndex:Kt,colorToVec4:ei,createElement:Se,createNormalizationFunction:ft,cubicIn:Ha,cubicInOut:Xa,cubicOut:Va,easings:Ht,exponentialIn:qa,exponentialInOut:Ya,exponentialOut:ja,extend:Zt,floatColor:H,getCorrectionRatio:Ka,getMatrixImpact:Za,getPixelColor:ai,getPixelRatio:ut,hasBackdrop:Qt,hasForcedLabel:te,hasNewPartialProps:ye,identity:O,indexToColor:Ge,linear:Ba,matrixFromCamera:ee,multiply:Z,multiplyVec2:xe,nodeRotationFlags:ct,packItemsOnPage:li,parseColor:ke,parseFontString:ht,pickingPixelCoords:Yt,quadraticIn:Oa,quadraticInOut:$a,quadraticOut:Ua,removePositionFromDepthRanges:Jt,resolveEasing:lt,rgbaToFloat:ti,rotate:Xt,rotateVec2:jt,scale:Ne,setMembership:le,shallowEqual:ni,translate:qt,validateGraph:si},Symbol.toStringTag,{value:"Module"})),Tn=1024,vn=1.5,yn=4096,di=-1;class gt{constructor(e,t,i=Tn){this.texture=null,this.dirty=!1,this.dirtyRangeStart=1/0,this.dirtyRangeEnd=-1,this.indexMap=new Map,this.freeIndices=[],this.nextIndex=0,this.gl=e,this.TEXELS_PER_ITEM=t,this.capacity=this.roundUpToPowerOfTwo(i);const r=this.computeTextureDimensions(this.capacity);this.textureWidth=r.width,this.textureHeight=r.height,this.data=new Float32Array(this.textureWidth*this.textureHeight*4),this.createTexture()}computeTextureDimensions(e){const t=e*this.TEXELS_PER_ITEM,i=Math.min(t,yn),r=Math.ceil(t/i);return{width:i,height:r}}roundUpToPowerOfTwo(e){return Math.pow(2,Math.ceil(Math.log2(Math.max(1,e))))}createTexture(){const{gl:e}=this;this.texture=e.createTexture(),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.texture),e.texImage2D(e.TEXTURE_2D,0,e.RGBA32F,this.textureWidth,this.textureHeight,0,e.RGBA,e.FLOAT,this.data),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null)}resize(e){if(e<=this.capacity)return;const t=this.roundUpToPowerOfTwo(Math.ceil(e*vn)),{gl:i}=this,r=this.computeTextureDimensions(t),n=new Float32Array(r.width*r.height*4);n.set(this.data),this.texture&&i.deleteTexture(this.texture),this.data=n,this.capacity=t,this.textureWidth=r.width,this.textureHeight=r.height,this.createTexture(),this.dirty=!0,this.dirtyRangeStart=0,this.dirtyRangeEnd=this.nextIndex}allocate(e){const t=this.indexMap.get(e);if(t!==void 0)return t;let i;return this.freeIndices.length>0?i=this.freeIndices.pop():(i=this.nextIndex++,i>=this.capacity&&this.resize(i+1)),this.indexMap.set(e,i),i}free(e){const t=this.indexMap.get(e);if(t===void 0)return;this.indexMap.delete(e),this.freeIndices.push(t);const i=t*this.TEXELS_PER_ITEM*4;for(let r=0;r<this.TEXELS_PER_ITEM*4;r++)this.data[i+r]=0;this.markDirty(t)}getIndex(e){return this.indexMap.get(e)??-1}has(e){return this.indexMap.has(e)}markDirty(e){this.dirty=!0,this.dirtyRangeStart=Math.min(this.dirtyRangeStart,e),this.dirtyRangeEnd=Math.max(this.dirtyRangeEnd,e+1)}upload(){if(!this.dirty||!this.texture)return;const{gl:e,textureWidth:t}=this;e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.texture);const i=this.dirtyRangeStart*this.TEXELS_PER_ITEM,r=Math.min(this.dirtyRangeEnd*this.TEXELS_PER_ITEM,this.capacity*this.TEXELS_PER_ITEM);if(i<r){const n=Math.floor(i/t),o=Math.floor((r-1)/t);for(let s=n;s<=o;s++){const l=s*t,u=Math.min(l+t,this.capacity*this.TEXELS_PER_ITEM),g=Math.max(i,l),d=Math.min(r,u);if(g<d){const h=g-l,c=d-g,f=this.data.subarray(g*4,d*4);e.texSubImage2D(e.TEXTURE_2D,0,h,s,c,1,e.RGBA,e.FLOAT,f)}}}e.bindTexture(e.TEXTURE_2D,null),this.dirty=!1,this.dirtyRangeStart=1/0,this.dirtyRangeEnd=-1}bind(e){const{gl:t}=this;t.activeTexture(t.TEXTURE0+e),t.bindTexture(t.TEXTURE_2D,this.texture)}getTexture(){return this.texture}getCapacity(){return this.capacity}getTextureWidth(){return this.textureWidth}getTextureHeight(){return this.textureHeight}getTexelsPerItem(){return this.TEXELS_PER_ITEM}getCount(){return this.indexMap.size}getHighWaterMark(){return this.nextIndex}isDirty(){return this.dirty}clear(){this.indexMap.clear(),this.freeIndices=[],this.nextIndex=0,this.data.fill(0),this.dirty=!0,this.dirtyRangeStart=0,this.dirtyRangeEnd=this.capacity}restore(){this.createTexture(),this.dirty=!1,this.dirtyRangeStart=1/0,this.dirtyRangeEnd=-1}kill(){this.texture&&(this.gl.deleteTexture(this.texture),this.texture=null),this.indexMap.clear(),this.freeIndices=[]}}function U(a){const e={},t={};let i=0;for(const r of a)for(const n of r.attributes){const o=n.name.replace(/^a_/,"");o in e||(e[o]=i,t[o]=n,i+=n.size)}return{floatsPerItem:i,texelsPerItem:Math.max(1,Math.ceil(i/4)),offsets:e,specs:t}}class pt extends gt{constructor(e,t,i){super(e,t.texelsPerItem,i),this.floatsPerItem=t.floatsPerItem}updateAllAttributes(e,t){let i=this.indexMap.get(e);i===void 0&&(i=this.allocate(e));const r=i*this.TEXELS_PER_ITEM*4,n=Math.min(t.length,this.floatsPerItem);for(let o=0;o<n;o++)this.data[r+o]=t[o];this.markDirty(i)}updateAllAttributesAtRow(e,t){e>=this.capacity&&this.resize(e+1);const i=e*this.TEXELS_PER_ITEM*4,r=Math.min(t.length,this.floatsPerItem);for(let n=0;n<r;n++)this.data[i+n]=t[n];this.markDirty(e)}}function mt(a,e,t){const i=[],r=new Set;for(let n=0;n<a.length;n++){const o=a[n];for(const s of o.attributes){const l=s.name.replace(/^a_/,"");if(r.has(l))continue;r.add(l);const u=e.offsets[l];if(u===void 0)continue;const g=t==null?void 0:t.get(n);i.push({sourceKey:s.source||l,packedOffset:u,size:s.size,isColor:s.size===4&&!!s.normalized,defaultNum:typeof s.defaultValue=="number"?s.defaultValue:s.defaultValue===!0?1:0,defaultColor:typeof s.defaultValue=="string"?s.defaultValue:"",sourceIndex:n,hasLifecycleHook:!!(g!=null&&g.getAttributeData)})}}return i}function bt(a,e,t,i,r,n){t.fill(0);for(let o=0,s=a.length;o<s;o++){const l=a[o],u=l.packedOffset;let g;if(l.hasLifecycleHook?(g=r.get(l.sourceIndex-n).getAttributeData(e,l.sourceKey),g===null&&(g=e[l.sourceKey])):g=e[l.sourceKey],l.isColor){const d=typeof g=="string"?g:l.defaultColor||i,[h,c,f,b]=ve(d);t[u]=h/255,t[u+1]=c/255,t[u+2]=f/255,t[u+3]=b/255}else if(l.size===1)t[u]=typeof g=="number"?g:typeof g=="boolean"?g?1:0:l.defaultNum;else{const d=Array.isArray(g)?g:null;if(d)for(let h=0;h<l.size;h++)t[u+h]=d[h]??0}}}const xt=["r","g","b","a"];function Ee(a,e){const{offsets:t,specs:i,texelsPerItem:r,floatsPerItem:n}=a,o=Object.keys(t);if(o.length===0||n===0)return{fetchCode:"",varyingAssignments:""};const{varPrefix:s,baseTexelExpr:l,textureWidthUniform:u,textureSamplerUniform:g,outputPrefix:d="v_"}=e,h=[];h.push(` int ${s}BaseTexel = ${l};`),h.push("");for(let f=0;f<r;f++)h.push(` ivec2 ${s}Coord${f} = ivec2((${s}BaseTexel + ${f}) % ${u}, (${s}BaseTexel + ${f}) / ${u});`),h.push(` vec4 ${s}Texel${f} = texelFetch(${g}, ${s}Coord${f}, 0);`);h.push("");const c=[];for(const f of o){const b=i[f],v=t[f],_=Math.floor(v/4),m=v%4,E=`${s}Texel${_}`,p=`${s}Texel${_+1}`,x=`${s}Fetched_${f}`;if(b.size===1)h.push(` float ${x} = ${E}.${xt[m]};`);else if(b.size===4&&m===0)h.push(` vec4 ${x} = ${E};`);else{const T=m+b.size;if(T<=4){const y=`vec${b.size}`,R=xt.slice(m,T).join("");h.push(` ${y} ${x} = ${E}.${R};`)}else{const y=b.size===4?"vec4":`vec${b.size}`,R=xt.slice(m).map(C=>`${E}.${C}`),A=xt.slice(0,T-4).map(C=>`${p}.${C}`),S=[...R,...A].join(", ");h.push(` ${y} ${x} = ${y}(${S});`)}}c.push(` ${d}${f} = ${x};`)}return{fetchCode:h.join(`
`),varyingAssignments:c.join(`
`)}}function aa(a,e,t){if(!(!e||t.type==="sampler2D"))switch(t.type){case"float":a.uniform1f(e,t.value);break;case"int":case"bool":a.uniform1i(e,t.value);break;case"vec2":a.uniform2fv(e,t.value);break;case"vec3":a.uniform3fv(e,t.value);break;case"vec4":a.uniform4fv(e,t.value);break;case"mat3":a.uniformMatrix3fv(e,!1,t.value);break;case"mat4":a.uniformMatrix4fv(e,!1,t.value);break}}const Sn={[WebGL2RenderingContext.BOOL]:1,[WebGL2RenderingContext.BYTE]:1,[WebGL2RenderingContext.UNSIGNED_BYTE]:1,[WebGL2RenderingContext.SHORT]:2,[WebGL2RenderingContext.UNSIGNED_SHORT]:2,[WebGL2RenderingContext.INT]:4,[WebGL2RenderingContext.UNSIGNED_INT]:4,[WebGL2RenderingContext.FLOAT]:4};function _t(a){const e=a.match(/^(#version[^\n]*\n)/);return e?e[1]+`#define PICKING_MODE
`+a.slice(e[1].length):`#define PICKING_MODE
`+a}class ae{constructor(e,t,i){this.floats=new Float32Array,this.ints=new Uint32Array,this.constantArray=new Float32Array,this.capacity=0,this.verticesCount=0,this.bufferGeneration=0,this.uploadedGeneration=new Map,this.constantBufferGeneration=0,this.uploadedConstantGeneration=new Map,this.renderOffset=0,this.renderCount=-1,this.debugStats={drawCalls:0,verticesDrawn:0,bufferUploadBytes:0},this.shadersLogged=!1,this.pickProgram=null;const r=this.getDefinition();if(this.VERTICES=r.VERTICES,this.VERTEX_SHADER_SOURCE=r.VERTEX_SHADER_SOURCE,this.FRAGMENT_SHADER_SOURCE=r.FRAGMENT_SHADER_SOURCE,this.UNIFORMS=r.UNIFORMS,this.ATTRIBUTES=r.ATTRIBUTES,this.METHOD=r.METHOD,this.CONSTANT_ATTRIBUTES="CONSTANT_ATTRIBUTES"in r?r.CONSTANT_ATTRIBUTES:[],this.CONSTANT_DATA="CONSTANT_DATA"in r?r.CONSTANT_DATA:[],this.isInstanced="CONSTANT_ATTRIBUTES"in r,this.ATTRIBUTES_ITEMS_COUNT=at(this.ATTRIBUTES),this.STRIDE=this.VERTICES*this.ATTRIBUTES_ITEMS_COUNT,this.renderer=i,this.normalProgram=this.getProgramInfo("normal",e,r.VERTEX_SHADER_SOURCE,r.FRAGMENT_SHADER_SOURCE,null),this.pickProgram=this.getProgramInfo("pick",e,_t(r.VERTEX_SHADER_SOURCE),_t(r.FRAGMENT_SHADER_SOURCE),null),this.isInstanced){const n=at(this.CONSTANT_ATTRIBUTES);if(this.CONSTANT_DATA.length!==this.VERTICES)throw new Error(`Program: error while getting constant data (expected ${this.VERTICES} items, received ${this.CONSTANT_DATA.length} instead)`);this.constantArray=new Float32Array(this.CONSTANT_DATA.length*n);for(let o=0;o<this.CONSTANT_DATA.length;o++){const s=this.CONSTANT_DATA[o];if(s.length!==n)throw new Error(`Program: error while getting constant data (one vector has ${s.length} items instead of ${n})`);for(let l=0;l<s.length;l++)this.constantArray[o*n+l]=s[l]}this.STRIDE=this.ATTRIBUTES_ITEMS_COUNT}}kill(){kt(this.normalProgram),this.pickProgram&&kt(this.pickProgram)}resetDebugStats(){this.debugStats.drawCalls=0,this.debugStats.verticesDrawn=0,this.debugStats.bufferUploadBytes=0}getProgramInfo(e,t,i,r,n){const o=t.createBuffer();if(o===null)throw new Error("Program: error while creating the WebGL buffer.");const s=it(t,i),l=rt(t,r),u=nt(t,[s,l]),g={};this.UNIFORMS.forEach(c=>{const f=t.getUniformLocation(u,c);f&&(g[c]=f)});const d={};this.ATTRIBUTES.forEach(c=>{d[c.name]=t.getAttribLocation(u,c.name)});let h;if(this.isInstanced&&(this.CONSTANT_ATTRIBUTES.forEach(c=>{d[c.name]=t.getAttribLocation(u,c.name)}),h=t.createBuffer(),h===null))throw new Error("Program: error while creating the WebGL constant buffer.");return{name:e,program:u,gl:t,frameBuffer:n,buffer:o,constantBuffer:h||{},uniformLocations:g,attributeLocations:d,isPicking:e==="pick",vertexShader:s,fragmentShader:l}}bindProgram(e){var n,o,s;let t=0;const{gl:i,buffer:r}=e;this.isInstanced?(i.bindBuffer(i.ARRAY_BUFFER,e.constantBuffer),t=0,this.CONSTANT_ATTRIBUTES.forEach(l=>t+=this.bindAttribute(l,e,t,!1)),this.uploadedConstantGeneration.get(e.constantBuffer)!==this.constantBufferGeneration&&(i.bufferData(i.ARRAY_BUFFER,this.constantArray,i.STATIC_DRAW),this.uploadedConstantGeneration.set(e.constantBuffer,this.constantBufferGeneration),(o=this.renderer)!=null&&o.getSetting("DEBUG_logRenderStats")&&(this.debugStats.bufferUploadBytes+=this.constantArray.byteLength)),i.bindBuffer(i.ARRAY_BUFFER,e.buffer),t=this.renderOffset*this.ATTRIBUTES_ITEMS_COUNT*Float32Array.BYTES_PER_ELEMENT,this.ATTRIBUTES.forEach(l=>t+=this.bindAttribute(l,e,t,!0)),this.uploadedGeneration.get(r)!==this.bufferGeneration&&(i.bufferData(i.ARRAY_BUFFER,this.floats,i.DYNAMIC_DRAW),this.uploadedGeneration.set(r,this.bufferGeneration),(s=this.renderer)!=null&&s.getSetting("DEBUG_logRenderStats")&&(this.debugStats.bufferUploadBytes+=this.floats.byteLength))):(i.bindBuffer(i.ARRAY_BUFFER,r),t=0,this.ATTRIBUTES.forEach(l=>t+=this.bindAttribute(l,e,t)),this.uploadedGeneration.get(r)!==this.bufferGeneration&&(i.bufferData(i.ARRAY_BUFFER,this.floats,i.DYNAMIC_DRAW),this.uploadedGeneration.set(r,this.bufferGeneration),(n=this.renderer)!=null&&n.getSetting("DEBUG_logRenderStats")&&(this.debugStats.bufferUploadBytes+=this.floats.byteLength))),i.bindBuffer(i.ARRAY_BUFFER,null)}unbindProgram(e){this.isInstanced?(this.CONSTANT_ATTRIBUTES.forEach(t=>this.unbindAttribute(t,e,!1)),this.ATTRIBUTES.forEach(t=>this.unbindAttribute(t,e,!0))):this.ATTRIBUTES.forEach(t=>this.unbindAttribute(t,e))}bindAttribute(e,t,i,r){const n=Sn[e.type];if(typeof n!="number")throw new Error(`Program.bind: yet unsupported attribute type "${e.type}"`);const o=t.attributeLocations[e.name],s=t.gl;if(o!==-1){s.enableVertexAttribArray(o);const l=this.isInstanced?(r?this.ATTRIBUTES_ITEMS_COUNT:at(this.CONSTANT_ATTRIBUTES))*Float32Array.BYTES_PER_ELEMENT:this.ATTRIBUTES_ITEMS_COUNT*Float32Array.BYTES_PER_ELEMENT;s.vertexAttribPointer(o,e.size,e.type,e.normalized||!1,l,i),this.isInstanced&&r&&s.vertexAttribDivisor(o,1)}return e.size*n}unbindAttribute(e,t,i){const r=t.attributeLocations[e.name],n=t.gl;r!==-1&&(n.disableVertexAttribArray(r),this.isInstanced&&i&&n.vertexAttribDivisor(r,0))}reallocate(e){e!==this.capacity&&(this.capacity=e,this.verticesCount=this.VERTICES*e,this.floats=new Float32Array(this.isInstanced?this.capacity*this.ATTRIBUTES_ITEMS_COUNT:this.verticesCount*this.ATTRIBUTES_ITEMS_COUNT),this.ints=new Uint32Array(this.floats.buffer),this.invalidateBuffers())}invalidateBuffers(){this.bufferGeneration++,this.constantBufferGeneration++}hasNothingToRender(){return this.verticesCount===0}setTypedUniform(e,t){aa(t.gl,t.uniformLocations[e.name]??null,e)}renderProgram(e,t){const{gl:i,program:r,isPicking:n}=t;n?i.disable(i.BLEND):i.enable(i.BLEND),i.useProgram(r),this.setUniforms(e,t),this.drawWebGL(this.METHOD,t)}render(e,t,i){var n;if(!this.shadersLogged&&((n=this.renderer)!=null&&n.getSetting("DEBUG_logShaders"))&&(this.shadersLogged=!0,console.log(`[sigma] DEBUG_logShaders: ${this.constructor.name}`,{normal:{vertexShaderSource:this.VERTEX_SHADER_SOURCE,fragmentShaderSource:this.FRAGMENT_SHADER_SOURCE},pick:{vertexShaderSource:_t(this.VERTEX_SHADER_SOURCE),fragmentShaderSource:_t(this.FRAGMENT_SHADER_SOURCE)}})),this.hasNothingToRender())return;this.renderOffset=t??0,this.renderCount=i??-1;const r=this.normalProgram.gl;if(r.bindFramebuffer(r.FRAMEBUFFER,null),r.viewport(0,0,e.width*e.pixelRatio,e.height*e.pixelRatio),this.bindProgram(this.normalProgram),this.renderProgram(e,this.normalProgram),this.unbindProgram(this.normalProgram),this.pickProgram&&e.pickingFrameBuffer){const o=Math.ceil(e.width*e.pixelRatio/e.downSizingRatio),s=Math.ceil(e.height*e.pixelRatio/e.downSizingRatio);r.bindFramebuffer(r.FRAMEBUFFER,e.pickingFrameBuffer),r.viewport(0,0,o,s),this.bindProgram(this.pickProgram),this.renderProgram(e,this.pickProgram),this.unbindProgram(this.pickProgram),r.bindFramebuffer(r.FRAMEBUFFER,null),r.viewport(0,0,e.width*e.pixelRatio,e.height*e.pixelRatio)}}drawWebGL(e,{gl:t}){var r;const i=this.renderCount>=0?this.renderCount:this.capacity;(r=this.renderer)!=null&&r.getSetting("DEBUG_logRenderStats")&&(this.debugStats.drawCalls++,this.debugStats.verticesDrawn+=i*this.VERTICES),this.isInstanced?t.drawArraysInstanced(e,0,this.VERTICES,i):t.drawArrays(e,this.renderOffset*this.VERTICES,i*this.VERTICES)}}const Tt=new Map;let ci=!1;function En(a){return new Promise((e,t)=>{const i=new FileReader;i.onload=()=>e(i.result),i.onerror=t,i.readAsDataURL(a)})}function Rn(a){if(!Tt.has(a)){const e=fetch(a).then(t=>t.blob()).then(En).catch(()=>(Tt.delete(a),null));Tt.set(a,e)}return Tt.get(a)}async function An(a){const e=document.createElement("div");e.innerHTML=a;const i=Array.from(e.querySelectorAll("img[src]")).filter(r=>!r.getAttribute("src").startsWith("data:"));return await Promise.all(i.map(async r=>{const n=await Rn(r.getAttribute("src"));n&&r.setAttribute("src",n)})),e.innerHTML}async function hi(a,e=1){const t=a instanceof SVGElement?new XMLSerializer().serializeToString(a):a,n=new DOMParser().parseFromString(t,"image/svg+xml").querySelector("svg");if(!n)return null;let o=parseFloat(n.getAttribute("width")||""),s=parseFloat(n.getAttribute("height")||"");if(!(o>0)||!(s>0)){const h=n.getAttribute("viewBox");if(h){const c=h.trim().split(/[\s,]+/);o=parseFloat(c[2]),s=parseFloat(c[3])}}if(!(o>0)||!(s>0))return console.warn("Sigma: SVG label attachment has no parseable dimensions — skipped."),null;const l=Math.ceil(o*e),u=Math.ceil(s*e),g=new Blob([t],{type:"image/svg+xml"}),d=URL.createObjectURL(g);return new Promise(h=>{const c=new Image;c.onload=()=>{const f=document.createElement("canvas");f.width=l,f.height=u,f.getContext("2d").drawImage(c,0,0,l,u),URL.revokeObjectURL(d);try{f.getContext("2d").getImageData(0,0,1,1)}catch(b){if(b instanceof DOMException&&b.name==="SecurityError"){ci||(ci=!0,console.warn('Sigma: A label attachment was skipped because the rendered canvas is tainted. SVG with <foreignObject> (used by the "html" attachment type) is blocked in Chromium and Safari. Use type: "canvas" with Canvas 2D rendering instead.')),h(null);return}throw b}h(f)},c.onerror=()=>{URL.revokeObjectURL(d),h(null)},c.src=d})}function Dn(a,e){const t=document.createElement("div");t.style.cssText="position:fixed;left:-99999px;top:0;visibility:hidden;width:max-content;height:max-content",t.innerHTML=(e?`<style>${e}</style>`:"")+a,document.body.appendChild(t);const{width:i,height:r}=t.getBoundingClientRect();return document.body.removeChild(t),{width:Math.ceil(i),height:Math.ceil(r)}}async function Cn(a,e,t,i,r=1){const n=a instanceof HTMLElement?a.outerHTML:a,o=await An(n);if(t==null||i==null){const d=Dn(o,e);t=t??d.width,i=i??d.height}const s=Math.ceil(t*r),l=Math.ceil(i*r),u=e?`<style>${e}</style>`:"",g=`<svg xmlns="http://www.w3.org/2000/svg" width="${s}" height="${l}" viewBox="0 0 ${t} ${i}"><foreignObject width="${t}" height="${i}"><body xmlns="http://www.w3.org/1999/xhtml" style="margin:0;padding:0">${u}${o}</body></foreignObject></svg>`;return hi(g,1)}async function Ln(a,e=1){switch(a.type){case"canvas":return a.canvas;case"svg":return hi(a.svg,e);case"html":return Cn(a.html,a.css,a.width,a.height,e)}}const de=2,ia=7,Fn={below:0,above:1,left:2,right:3};function Pn(){return`#version 300 es
precision highp float;
uniform mat3 u_matrix;
uniform vec2 u_resolution;
uniform float u_labelPixelSnapping;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
uniform float u_pixelRatio;
uniform float u_labelMargin;
uniform float u_zoomLabelSizeRatio;
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_nodeFrameTexture;
uniform int u_nodeFrameTextureWidth;
// Atlas size is fixed at 2048×2048 — matches AttachmentManager.ATLAS_SIZE.
uniform sampler2D u_atlasTexture;
const vec2 u_atlasSize = vec2(2048.0);
// Per-instance
in float a_nodeIndex; // node-data texture index
in vec4 a_atlasRect; // x, y, width, height in atlas pixels
in vec2 a_attachmentSize; // attachment dimensions (CSS px)
in float a_positionMode; // label position: 0=right 1=left 2=above 3=below 4=over
in float a_attachmentPlacement; // 0=below 1=above 2=left 3=right (relative to label)
in float a_labelWidth; // label width (CSS px)
in float a_labelHeight; // label height: font line box (CSS px)
in float a_textHeight; // actual glyph height (CSS px)
in float a_labelAngle; // label rotation angle (radians)
// Per-vertex (constant)
in vec2 a_quadCorner; // [-1,-1], [1,-1], [-1,1], [1,1]
out vec2 v_texCoord;
${X}
${J}
${se}
${$t}
${be}
void main() {
int nodeIdx = int(a_nodeIndex);
// Node data: (x, y, size, shapeId).
vec4 nodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
vec2 nodePos = nodeData.xy;
float nodeSize = nodeData.z;
// Normalized edge distance from the shared frame texture (the frame-pass ran
// the SDF search once; we just read the result).
float edgeDist = readFrameTexel(u_nodeFrameTexture, u_nodeFrameTextureWidth, nodeIdx).r;
// Per-node label rotation alignment: 0 = viewport, 1 = label turns with camera.
float labelRotation = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx).g;
vec3 nodeClip = u_matrix * vec3(nodePos, 1.0);
// Node radius in physical pixels (matches label/background).
float matrixScaleX = length(vec2(u_matrix[0][0], u_matrix[1][0]));
float nodeRadiusGraphSpace = nodeSize * u_correctionRatio / u_sizeRatio * 2.0;
float nodeRadiusPixels = nodeRadiusGraphSpace * matrixScaleX * u_resolution.x / 2.0;
// CSS-px inputs -> physical px, scaled by the zoom-dependent label ratio, so
// the attachment stays glued to the label box exactly as the label scales.
float zoomScale = u_zoomLabelSizeRatio;
vec2 labelHalf = vec2(a_labelWidth, a_labelHeight) * 0.5 * zoomScale * u_pixelRatio;
vec2 attachHalf = a_attachmentSize * 0.5 * zoomScale * u_pixelRatio;
float gap = ${de.toFixed(1)} * zoomScale * u_pixelRatio;
float labelMargin = u_labelMargin * zoomScale * u_pixelRatio;
// Graph-aligned labels add the camera angle so the attachment orbits with the box.
mat2 labelRotMat = rotate2D(a_labelAngle - labelRotation * u_cameraAngle);
// Label box center relative to the node center (pre-rotation). The shape-aware
// edge distance comes from the frame texture, so placement matches the label.
vec2 boxCenter = vec2(0.0);
if (a_positionMode < 4.0) {
float labelStart = nodeRadiusPixels * edgeDist + labelMargin;
float textHalf = a_textHeight * 0.5 * zoomScale * u_pixelRatio;
boxCenter = labelBoxCenter(a_positionMode, labelStart, labelHalf, textHalf);
}
// Horizontal anchor of below/above attachments tracks the label position, so
// the attachment hangs from the label edge nearest the node (or is centered
// when the label itself is node-centered): right->left, left->right, else center.
float anchorX = 0.0;
if (a_positionMode < 0.5) anchorX = -labelHalf.x + attachHalf.x; // right label
else if (a_positionMode < 1.5) anchorX = labelHalf.x - attachHalf.x; // left label
// Attachment center relative to the box (pre-rotation, Y-down). This offset
// depends only on the box, the gap and the attachment size — never the shape.
vec2 attachCenter;
if (a_attachmentPlacement < 0.5) {
// below
attachCenter = boxCenter + vec2(anchorX, labelHalf.y + gap + attachHalf.y);
} else if (a_attachmentPlacement < 1.5) {
// above
attachCenter = boxCenter + vec2(anchorX, -(labelHalf.y + gap + attachHalf.y));
} else if (a_attachmentPlacement < 2.5) {
// left, top-aligned
attachCenter = boxCenter + vec2(-(labelHalf.x + gap + attachHalf.x), -labelHalf.y + attachHalf.y);
} else {
// right, top-aligned
attachCenter = boxCenter + vec2(labelHalf.x + gap + attachHalf.x, -labelHalf.y + attachHalf.y);
}
// Rotate the whole assembly by the label angle, around the node center.
vec2 rotatedCenter = labelRotMat * attachCenter;
vec2 cornerOffset = labelRotMat * (a_quadCorner * attachHalf);
// Node center in screen px (Y-down).
vec2 nodeScreen = vec2(
(nodeClip.x + 1.0) * u_resolution.x,
(1.0 - nodeClip.y) * u_resolution.y
) * 0.5;
// Snap node center to the pixel grid so label/backdrop/attachment move as one.
vec2 snapDelta = (round(nodeScreen) - nodeScreen) * u_labelPixelSnapping;
// Snap the quad's top-left to integer pixels so atlas texels map 1:1.
vec2 centerScreen = nodeScreen + rotatedCenter + snapDelta;
vec2 topLeft = centerScreen - attachHalf;
topLeft = mix(topLeft, round(topLeft), u_labelPixelSnapping);
centerScreen = topLeft + attachHalf;
vec2 vertexScreen = centerScreen + cornerOffset;
gl_Position = vec4(
vertexScreen.x * 2.0 / u_resolution.x - 1.0,
1.0 - vertexScreen.y * 2.0 / u_resolution.y,
0.0, 1.0
);
vec2 texOrigin = a_atlasRect.xy / u_atlasSize;
vec2 texSize = a_atlasRect.zw / u_atlasSize;
vec2 uv = (a_quadCorner + 1.0) / 2.0;
v_texCoord = texOrigin + uv * texSize;
}
`}const wn=`#version 300 es
precision highp float;
uniform sampler2D u_atlasTexture;
in vec2 v_texCoord;
layout(location = 0) out vec4 fragColor;
#ifdef PICKING_MODE
layout(location = 1) out vec4 pickColor;
#endif
void main() {
// Canvas textures are premultiplied; output directly for (ONE, 1-SRC_ALPHA) blending
vec4 color = texture(u_atlasTexture, v_texCoord);
if (color.a < 0.01) discard;
fragColor = color;
#ifdef PICKING_MODE
pickColor = vec4(0.0); // Attachments are not pickable
#endif
}
`;function In(a,e,t,i){const{label:r={}}=i,n=r.margin??me,o=r.zoomToLabelSizeRatioFunction??(()=>1),s=Pn(),l=wn,g=class g extends ae{constructor(){super(...arguments),this.totalCount=0,this.bufferCapacity=0}getDefinition(){const{FLOAT:h,TRIANGLE_STRIP:c}=WebGL2RenderingContext;return{VERTICES:4,VERTEX_SHADER_SOURCE:s,FRAGMENT_SHADER_SOURCE:l,METHOD:c,UNIFORMS:["u_matrix","u_resolution","u_labelPixelSnapping","u_sizeRatio","u_correctionRatio","u_cameraAngle","u_pixelRatio","u_labelMargin","u_zoomLabelSizeRatio","u_nodeDataTexture","u_nodeDataTextureWidth","u_nodeFrameTexture","u_nodeFrameTextureWidth","u_atlasTexture"],ATTRIBUTES:[{name:"a_nodeIndex",size:1,type:h},{name:"a_atlasRect",size:4,type:h},{name:"a_attachmentSize",size:2,type:h},{name:"a_positionMode",size:1,type:h},{name:"a_attachmentPlacement",size:1,type:h},{name:"a_labelWidth",size:1,type:h},{name:"a_labelHeight",size:1,type:h},{name:"a_textHeight",size:1,type:h},{name:"a_labelAngle",size:1,type:h}],CONSTANT_ATTRIBUTES:[{name:"a_quadCorner",size:2,type:h}],CONSTANT_DATA:[[-1,-1],[1,-1],[-1,1],[1,1]]}}processAttachment(h,c){const{floats:f}=this;let b=h*this.STRIDE;f[b++]=c.nodeIndex,f[b++]=c.atlasX,f[b++]=c.atlasY,f[b++]=c.atlasW,f[b++]=c.atlasH,f[b++]=c.attachWidth,f[b++]=c.attachHeight,f[b++]=c.positionMode,f[b++]=c.attachmentPlacement,f[b++]=c.labelWidth,f[b++]=c.labelHeight,f[b++]=c.textHeight,f[b++]=c.labelAngle}setUniforms(h,{gl:c,uniformLocations:f}){c.uniformMatrix3fv(f.u_matrix,!1,h.matrix),c.uniform2f(f.u_resolution,h.width*h.pixelRatio,h.height*h.pixelRatio),c.uniform1f(f.u_labelPixelSnapping,h.labelPixelSnapping),c.uniform1f(f.u_sizeRatio,h.sizeRatio),c.uniform1f(f.u_correctionRatio,h.correctionRatio),c.uniform1f(f.u_cameraAngle,h.cameraAngle),c.uniform1f(f.u_pixelRatio,h.pixelRatio),c.uniform1f(f.u_labelMargin,g.labelMargin),c.uniform1f(f.u_zoomLabelSizeRatio,1/o(h.zoomRatio)),c.uniform1i(f.u_nodeDataTexture,h.nodeDataTextureUnit),c.uniform1i(f.u_nodeDataTextureWidth,h.nodeDataTextureWidth),c.uniform1i(f.u_nodeFrameTexture,h.nodeFrameTextureUnit),c.uniform1i(f.u_nodeFrameTextureWidth,h.nodeFrameTextureWidth),c.uniform1i(f.u_atlasTexture,ia)}reallocateAttachments(h){this.totalCount=h,h>this.bufferCapacity&&(this.bufferCapacity=Math.max(h,Math.ceil(this.bufferCapacity*1.5)||10),super.reallocate(this.bufferCapacity))}hasNothingToRender(){return this.totalCount===0}drawWebGL(h,{gl:c}){this.totalCount!==0&&c.drawArraysInstanced(h,0,this.VERTICES,this.totalCount)}};g.labelMargin=n;let u=g;return new u(a,e,t)}const Re=2048;class ui{constructor(e,t,i){this.cache=new Map,this.pending=new Set,this.atlas={},this.glTexture=null,this.dirty=!0,this.gl=e,this.renderers=t,this.scheduleRender=i,this.packCanvas=document.createElement("canvas"),this.packCanvas.width=Re,this.packCanvas.height=Re,this.packCtx=this.packCanvas.getContext("2d")}renderAttachment(e,t,i){const r=`${e}:${t}`;if(this.cache.has(r)||this.pending.has(r))return;const n=this.renderers[t];if(!n)return;const o=n(i);if(!o)return;this.pending.add(r);const{pixelRatio:s}=i;Promise.resolve(o).then(async l=>{if(!this.pending.has(r)||(this.pending.delete(r),!l))return;const u=await Ln(l,s);!u||u.width===0||u.height===0||(this.cache.set(r,{image:u,width:u.width,height:u.height}),this.dirty=!0,this.scheduleRender())})}regenerateAtlas(){if(!this.dirty)return;this.dirty=!1;const e=[];if(this.cache.forEach((s,l)=>{e.push({key:l,width:s.width,height:s.height,draw:(u,g,d)=>{u.drawImage(s.image,g,d)}})}),e.length===0){this.atlas={},this.deleteGLTexture();return}const t={x:0,y:0,rowHeight:0,maxRowWidth:0};this.packCtx.clearRect(0,0,Re,Re);const{atlas:i,remaining:r}=li(e,this.packCtx,t);this.atlas=i,r.length>0&&console.warn(`Sigma: ${r.length} label attachment(s) could not fit in the ${Re}x${Re} atlas and will not be rendered.`),this.deleteGLTexture();const n=this.gl,o=n.createTexture();n.activeTexture(n.TEXTURE0+ia),n.bindTexture(n.TEXTURE_2D,o),n.pixelStorei(n.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),n.texImage2D(n.TEXTURE_2D,0,n.RGBA,n.RGBA,n.UNSIGNED_BYTE,this.packCanvas),n.pixelStorei(n.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MIN_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_MAG_FILTER,n.LINEAR),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_S,n.CLAMP_TO_EDGE),n.texParameteri(n.TEXTURE_2D,n.TEXTURE_WRAP_T,n.CLAMP_TO_EDGE),this.glTexture=o}bindTexture(e){if(!this.glTexture)return;const t=this.gl;t.activeTexture(t.TEXTURE0+e),t.bindTexture(t.TEXTURE_2D,this.glTexture)}getEntry(e,t){const i=`${e}:${t}`;return this.atlas[i]||null}invalidateNode(e){const t=`${e}:`;for(const i of this.cache.keys())i.startsWith(t)&&(this.cache.delete(i),this.dirty=!0);for(const i of this.pending)i.startsWith(t)&&this.pending.delete(i)}restore(){this.glTexture=null,this.dirty=!0}clear(){this.cache.clear(),this.pending.clear(),this.atlas={},this.dirty=!0,this.deleteGLTexture()}kill(){this.clear(),this.packCanvas=null,this.packCtx=null,this.gl=null}deleteGLTexture(){this.glTexture&&(this.gl.deleteTexture(this.glTexture),this.glTexture=null)}}const ie=5;function vt(a,e){return[...e,...a.map(t=>({attributes:t.attributes??[]}))]}function Ae(a,e){const t=U(vt(a,e)),i=new Set(a.flatMap(o=>o.attributes??[]).map(o=>o.name.replace(/^a_/,""))),r={},n={};for(const o of i)r[o]=t.offsets[o],n[o]=t.specs[o];return{...t,offsets:r,specs:n}}function fi(a,e){const t=new Set(["u_matrix","u_sizeRatio","u_correctionRatio","u_cameraAngle","u_pickingPadding","u_nodeDataTexture","u_layerAttributeTexture"]),i=new Set,r=a.flatMap(h=>h.uniforms).filter(h=>t.has(h.name)||i.has(h.name)?!1:(i.add(h.name),!0)).map(h=>`uniform ${h.type} ${h.name};`).join(`
`),n=e.flatMap(h=>h.uniforms).filter(h=>t.has(h.name)||i.has(h.name)?!1:(i.add(h.name),!0)).map(h=>`uniform ${h.type} ${h.name};`).join(`
`),o=vt(a,e),s=new Set,l=o.flatMap(h=>h.attributes).filter(h=>{const c=h.name.replace(/^a_/,"");return s.has(c)?!1:(s.add(c),!0)}).map(h=>{const c=h.name.replace(/^a_/,"");return`out ${h.size===1?"float":`vec${h.size}`} v_${c};`}).join(`
`),{fetchCode:u,varyingAssignments:g}=Ee(U(o),{varPrefix:"layer",baseTexelExpr:"nodeIdx * u_layerAttributeTexelsPerNode",textureWidthUniform:"u_layerAttributeTextureWidth",textureSamplerUniform:"u_layerAttributeTexture"});return`#version 300 es
// Standard node attributes (per instance) - minimal buffer usage
in float a_nodeIndex; // Index into node data texture AND layer attribute texture
in vec4 a_id; // Node ID for picking
in float a_opacity; // Node opacity, applied once to the final fragment
// Constant attributes (per vertex, same for all instances)
in vec2 a_quadCorner; // (-1,-1), (1,-1), (1,1), (-1,1) for quad corners
// Standard uniforms
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
#ifdef PICKING_MODE
uniform float u_pickingPadding;
#endif
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
// Layer attribute texture uniforms
uniform sampler2D u_layerAttributeTexture;
uniform int u_layerAttributeTextureWidth;
uniform int u_layerAttributeTexelsPerNode;
${r}
${n}
// Standard varyings
out vec2 v_uv; // Normalized coordinates [-1, 1]
out vec4 v_id;
out float v_opacity;
out float v_antialiasingWidth; // Width for antialiasing in UV space
out float v_pixelSize; // Node size in pixels (for pixel-mode borders)
out float v_pixelToUV; // Conversion factor: multiply by this to convert screen pixels to UV units
out float v_shapeId; // Shape ID for multi-shape programs
// Layer varyings
${l}
// Node-data fetch helpers (geometry texel + rotation-flags texel)
${X}
${J}
void main() {
// Fetch node geometry: vec4(x, y, size, shapeId).
int nodeIdx = int(a_nodeIndex);
// Hidden nodes are flagged with a negative row by NodeProgram.process(). Push
// the whole quad outside the clip volume: every vertex lands at the same
// out-of-range position, so the primitive is fully clipped and rasterizes
// nothing — neither to the frame buffer nor to the picking buffer.
if (nodeIdx < 0) {
gl_Position = vec4(2.0, 0.0, 0.0, 1.0);
v_id = vec4(0.0);
return;
}
vec4 nodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
vec2 a_position = nodeData.xy;
float a_size = nodeData.z;
v_shapeId = nodeData.w; // Pass shape ID to fragment shader
// Per-node rotation alignment: 0 = viewport (screen-upright), 1 = graph.
float nodeRotation = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx).r;
${u}
// Calculate the actual size in pixels
float size = a_size * u_correctionRatio / u_sizeRatio * 2.0;
// In PICKING_MODE, inflate the quad by nodePickingPadding pixels on each side
#ifdef PICKING_MODE
float paddedSize = size + u_pickingPadding * u_correctionRatio;
vec2 offset = a_quadCorner * paddedSize;
#else
vec2 offset = a_quadCorner * size;
#endif
// Counter-rotate the quad offset so viewport-aligned nodes stay upright as the
// camera turns. Graph-aligned nodes (nodeRotation=1) skip it and turn with the
// camera. The angle scales by (1 - nodeRotation) so both fall out of one path.
{
float ca = u_cameraAngle * (1.0 - nodeRotation);
float c = cos(ca);
float s = sin(ca);
offset = mat2(c, s, -s, c) * offset;
}
vec2 position = a_position + offset;
gl_Position = vec4(
(u_matrix * vec3(position, 1)).xy,
0,
1
);
// In PICKING_MODE, UV is scaled beyond [-1, 1] to match the inflated quad
#ifdef PICKING_MODE
v_uv = a_quadCorner * (paddedSize / size);
#else
v_uv = a_quadCorner;
#endif
// Pass ID to fragment shader
v_id = a_id;
v_opacity = a_opacity;
// Pass pixel size for layers that need pixel-mode calculations
// Multiply by 2 because 'size' is half-width (offset from center), not full diameter
v_pixelSize = size * 2.0;
// Conversion factor from screen pixels to UV units
// Same derivation as v_antialiasingWidth which represents ~1 pixel in UV space
// P pixels in UV space = P * u_correctionRatio / size
v_pixelToUV = u_correctionRatio / size;
// We use an antialiasing width of 1px (so v_pixelToUV)
v_antialiasingWidth = v_pixelToUV;
// Pass layer attributes to fragment shader (fetched from texture)
${g}
}
`}function gi(a,e,t,i=!0){const r=e.map((f,b)=>{const v=`layer_${f.name}`,_=[...f.attributes.map(m=>`v_${m.name.replace(/^a_/,"")}`),...f.uniforms.map(m=>m.name)].join(", ");return` // Layer ${b+1}: ${f.name}
color = blendOver(color, ${v}(${_}));`}).join(`
`),n=new Set(["u_correctionRatio"]),o=new Set,s=a.flatMap(f=>f.uniforms).filter(f=>n.has(f.name)||o.has(f.name)?!1:(o.add(f.name),!0)).map(f=>`uniform ${f.type} ${f.name};`).join(`
`),l=e.flatMap(f=>f.uniforms).filter(f=>n.has(f.name)||o.has(f.name)?!1:(o.add(f.name),!0)).map(f=>`uniform ${f.type} ${f.name};`).join(`
`),u=new Set,g=vt(a,e).flatMap(f=>f.attributes).filter(f=>{const b=f.name.replace(/^a_/,"");return u.has(b)?!1:(u.add(b),!0)}).map(f=>{const b=f.name.replace(/^a_/,"");return`in ${f.size===1?"float":`vec${f.size}`} v_${b};`}).join(`
`),d=ot(a),h=wa(a,t);return`#version 300 es
precision highp float;
// Standard varyings
in vec2 v_uv;
in vec4 v_id;
in float v_opacity;
in float v_antialiasingWidth;
in float v_pixelSize;
in float v_pixelToUV;
in float v_shapeId; // Shape ID for multi-shape programs
// Standard uniforms (needed for some layer calculations like pixel-mode borders)
uniform float u_correctionRatio;
#ifdef PICKING_MODE
uniform float u_pickingPadding;
#endif
// Shape uniforms
${s}
// Layer uniforms
${l}
// Layer varyings
${g}
// Fragment output (single target - picking handled via separate pass)
out vec4 fragColor;
// LayerContext struct - provides rendering context to all layers
struct LayerContext {
float sdf; // Signed distance from shape boundary (negative inside)
vec2 uv; // UV coordinates [-1, 1], center at (0,0)
float shapeSize; // Effective shape size (~diameter) in UV space (1.0 - aaWidth)
float shapeHalfSize; // Effective shape half size (~radius) in UV space
float pixelSize; // Node full size (~diameter) in screen pixels
float aaWidth; // Anti-aliasing width for smooth transitions
float correctionRatio; // Scaling factor for consistent rendering across zoom levels
float pixelToUV; // Conversion factor: multiply screen pixels by this to get UV units
float inradiusFactor; // Ratio of inradius to circumradius (shape depth factor)
};
LayerContext context; // Global instance, populated before layer calls
// Alpha "over" compositing for layer blending
vec4 blendOver(vec4 bg, vec4 fg) {
float a = fg.a;
return vec4(mix(bg.rgb, fg.rgb, a), bg.a + a * (1.0 - bg.a));
}
// SDF shape functions
${d}
// Shape selector function (sets context.sdf and context.inradiusFactor)
${h}
// Layer functions
${e.map(f=>f.glsl).join(`
`)}
void main() {
// 1. Setup LayerContext (available to all layer functions)
context.shapeSize = 1.0 - v_antialiasingWidth;
context.shapeHalfSize = context.shapeSize * 0.5;
context.pixelSize = v_pixelSize;
context.uv = v_uv;
context.aaWidth = v_antialiasingWidth;
context.correctionRatio = u_correctionRatio;
context.pixelToUV = v_pixelToUV;
// Query shape SDF based on shapeId (sets context.sdf and context.inradiusFactor)
queryNodeSDF(int(v_shapeId), v_uv, context.shapeSize);
// 2. Early discard for pixels fully outside the shape (with AA margin)
// In PICKING_MODE, allow extra fragments up to the picking padding distance
#ifdef PICKING_MODE
if (context.sdf > u_pickingPadding * v_pixelToUV + context.aaWidth) discard;
#else
if (context.sdf > context.aaWidth) discard;
#endif
// 3. Apply layers sequentially with "over" compositing
vec4 color = vec4(0.0);
${r}
#ifdef PICKING_MODE
// Picking pass: output node ID for pixels within the picking area.
if (context.sdf > u_pickingPadding * v_pixelToUV) discard;
fragColor = v_id;
#else
${i?` // Visual pass: apply antialiasing at shape boundary, node opacity once
// smoothstep provides smooth transition from opaque to transparent
float alpha = smoothstep(context.aaWidth, -context.aaWidth, context.sdf) * v_opacity;`:` // Visual pass: hard-edged (no anti-aliasing gradient) shape boundary, node opacity applied once
float alpha = (context.sdf < 0.0 ? 1.0 : 0.0) * v_opacity;`}
// Mix with transparent to fade both color AND alpha together (avoids bright halo)
fragColor = mix(vec4(0.0), color, alpha);
#endif
}
`}function pi(a,e){const t=new Set;return t.add("u_matrix"),t.add("u_sizeRatio"),t.add("u_correctionRatio"),t.add("u_cameraAngle"),t.add("u_pickingPadding"),t.add("u_nodeDataTexture"),t.add("u_nodeDataTextureWidth"),t.add("u_layerAttributeTexture"),t.add("u_layerAttributeTextureWidth"),t.add("u_layerAttributeTexelsPerNode"),a.forEach(i=>{i.uniforms.forEach(r=>t.add(r.name))}),e.forEach(i=>{i.uniforms.forEach(r=>t.add(r.name))}),Array.from(t)}function mi(a){const{UNSIGNED_BYTE:e,FLOAT:t}=WebGL2RenderingContext;return[{name:"a_nodeIndex",size:1,type:t},{name:"a_id",size:4,type:e,normalized:!0},{name:"a_opacity",size:1,type:t}]}function ra(a){const{shapes:e,layers:t,shapeGlobalIds:i,antialias:r=!0}=a;return{vertexShader:fi(e,t),fragmentShader:gi(e,t,i,r),uniforms:pi(e,t),attributes:mi()}}function bi(a,e,t){const{specs:i}=Ae(a,e);return Object.keys(i).map(r=>`flat ${t} ${i[r].size===1?"float":`vec${i[r].size}`} v_${r};`).join(`
`)}function xi(a){const{shapes:e,layers:t,shapeGlobalIds:i}=a,r=Ee(Ae(e,t),{varPrefix:"nodeAttr",baseTexelExpr:"nodeIdx * u_layerAttributeTexelsPerNode",textureWidthUniform:"u_layerAttributeTextureWidth",textureSamplerUniform:"u_layerAttributeTexture"}),n=e.length===1?`float inradiusFactor = ${I(e[0].inradiusFactor??1)};`:`float inradiusFactor = ${I(e[0].inradiusFactor??1)};
switch (int(shapeId)) {
${e.map((s,l)=>` case ${i?i[l]:l}: inradiusFactor = ${I(s.inradiusFactor??1)}; break;`).join(`
`)}
default: break;
}`;return`#version 300 es
in float a_nodeIndex;
in float a_labelWidth;
in float a_labelHeight;
in float a_textHeight;
in float a_positionMode;
in float a_labelAngle;
in vec4 a_backdropColor;
in vec4 a_backdropShadowColor;
in float a_backdropShadowBlur;
in float a_backdropPadding;
in vec4 a_backdropBorderColor;
in vec4 a_backdropExtra; // [borderWidth, cornerRadius, labelPadding, area]
in vec2 a_labelBoxOffset;
in vec2 a_quadCorner;
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform float u_labelMargin;
uniform float u_zoomLabelSizeRatio;
uniform float u_labelPixelSnapping;
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_nodeFrameTexture;
uniform int u_nodeFrameTextureWidth;
uniform sampler2D u_layerAttributeTexture;
uniform int u_layerAttributeTextureWidth;
uniform int u_layerAttributeTexelsPerNode;
out vec2 v_uv;
out vec2 v_nodeCenter;
out float v_nodeRadius;
out vec2 v_labelCenter;
out vec2 v_labelHalfSize;
out float v_aaWidth;
out float v_shapeId;
out float v_nodeRotation;
out float v_labelAngle;
out vec4 v_backdropColor;
out vec4 v_backdropShadowColor;
out float v_backdropShadowBlur;
out float v_backdropPadding;
out vec4 v_backdropBorderColor;
out float v_backdropBorderWidth;
out float v_backdropCornerRadius;
out float v_backdropArea;
${bi(e,t,"out")}
${X}
${J}
${se}
void main() {
int nodeIdx = int(a_nodeIndex);
${r.fetchCode}
${r.varyingAssignments}
// Node data: (x, y, size, shapeId). shapeId (global) is forwarded to the
// fragment shader, which keeps the shape SDF for the outline.
vec4 nodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
vec2 nodePosition = nodeData.xy;
float nodeSize = nodeData.z;
float shapeId = nodeData.w;
// Per-node rotation alignment (0 = viewport, 1 = graph). nodeRotation drives
// the fragment's shape outline; labelRotation turns the label box with camera.
vec4 nodeFlags = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
float nodeRotation = nodeFlags.r;
float labelRotation = nodeFlags.g;
v_nodeRotation = nodeRotation;
${Ot}
// CSS pixel attributes are multiplied by u_pixelRatio to match nodeRadiusPixels,
// which is already in physical pixels (nodeRadiusNDC * u_resolution.x / 2.0).
float padding = a_backdropPadding * u_pixelRatio;
float shadowBlur = a_backdropShadowBlur * u_pixelRatio;
// Unpack a_backdropExtra: [borderWidth, cornerRadius, labelPadding, area]
float borderWidth = a_backdropExtra.x * u_pixelRatio;
float cornerRadius = a_backdropExtra.y * u_pixelRatio;
float labelPad = a_backdropExtra.z * u_pixelRatio;
float backdropArea = a_backdropExtra.w;
// Use 2x shadowBlur so the Gaussian fully decays before the quad edge
float totalExpansion = shadowBlur * 2.0 + borderWidth;
float enlargedRadius = nodeRadiusPixels + padding;
// Circumscribed radius for the quad bounds. Non-circular shapes reach past
// their inradius out to their circumradius (= enlargedRadius / inradiusFactor),
// in any direction once rotated. The axis-aligned quad must contain that, or
// the fill/shadow gets clipped (square corners, triangle tip, etc.).
${n}
float boundRadius = enlargedRadius / inradiusFactor;
// Apply zoom-dependent label size scaling
float zoomScale = u_zoomLabelSizeRatio;
float labelW = a_labelWidth * zoomScale * u_pixelRatio;
float labelH = a_labelHeight * zoomScale * u_pixelRatio;
float labelMargin = u_labelMargin * zoomScale * u_pixelRatio;
// Only apply labelPad when a label is actually present
float effectiveLabelPad = labelW > 0.0 ? labelPad : 0.0;
vec2 labelHalfSize = vec2(labelW * 0.5 + effectiveLabelPad, labelH * 0.5 + effectiveLabelPad);
vec2 labelOffset = vec2(0.0);
// Effective label angle: intrinsic angle plus the camera angle when the label
// is graph-aligned, so the box orbits the node in lockstep with the frame-pass.
float labelAngle = a_labelAngle - labelRotation * u_cameraAngle;
float la_c = cos(labelAngle);
float la_s = sin(labelAngle);
mat2 labelRotMat = mat2(la_c, -la_s, la_s, la_c);
vec3 nodeClip = u_matrix * vec3(nodePosition, 1.0);
vec2 snapDelta = vec2(0.0);
if (labelW > 0.0) {
// labelW > 0.0 means this node's label is displayed, so the frame-pass wrote
// its edge distance this frame.
float edgeDistPixels = nodeRadiusPixels * readFrameTexel(u_nodeFrameTexture, u_nodeFrameTextureWidth, nodeIdx).r;
// labelMargin matches the label shader's margin (gap from node edge to text)
float labelStart = edgeDistPixels + labelMargin;
// Above/below center on the actual glyph height, not the font line box, so
// the box stays aligned with the rendered text (matches labelBoxCenter()).
float textHalf = a_textHeight * zoomScale * u_pixelRatio * 0.5;
// Snap node center to pixel grid so label/backdrop/attachment move as a unit
vec2 nodeScreen = vec2(
(nodeClip.x + 1.0) * u_resolution.x,
(1.0 - nodeClip.y) * u_resolution.y
) * 0.5;
snapDelta = (round(nodeScreen) - nodeScreen) * u_labelPixelSnapping;
if (a_positionMode < 0.5) {
// Right: box spans from node center to text end + padding
float boxRightEdge = labelStart + labelW + labelPad;
labelOffset = vec2(boxRightEdge * 0.5, 0.0);
labelHalfSize.x = boxRightEdge * 0.5;
} else if (a_positionMode < 1.5) {
// Left: mirror of right
float boxLeftEdge = labelStart + labelW + labelPad;
labelOffset = vec2(-boxLeftEdge * 0.5, 0.0);
labelHalfSize.x = boxLeftEdge * 0.5;
} else if (a_positionMode < 2.5) {
// Above: text bottom at labelStart, centered horizontally
labelOffset = vec2(0.0, -(labelStart + textHalf));
} else if (a_positionMode < 3.5) {
// Below: text top at labelStart, centered horizontally
labelOffset = vec2(0.0, labelStart + textHalf);
}
// over (>=4): labelOffset stays (0,0) — text centered on the node.
// The attachment-cover shift is in label space (like the attachment itself),
// so rotate it together with the position offset — otherwise the box drifts
// off the attachment as the label angle grows.
labelOffset = labelRotMat * (labelOffset + a_labelBoxOffset * zoomScale * u_pixelRatio);
}
// For node-only mode, zero out label dimensions
if (backdropArea > 0.5 && backdropArea < 1.5) {
labelHalfSize = vec2(0.0);
labelOffset = vec2(0.0);
}
vec2 minBound, maxBound;
bool hasLabelBounds = labelW > 0.0 && (backdropArea > 1.5 || backdropArea < 0.5);
if (hasLabelBounds) {
// Union with label bounds (area=both) or label-only bounds (area=label)
vec2 labelMin, labelMax;
if (a_labelAngle != 0.0) {
vec2 corner1 = labelOffset + labelRotMat * vec2(-labelHalfSize.x, -labelHalfSize.y);
vec2 corner2 = labelOffset + labelRotMat * vec2(labelHalfSize.x, -labelHalfSize.y);
vec2 corner3 = labelOffset + labelRotMat * vec2(labelHalfSize.x, labelHalfSize.y);
vec2 corner4 = labelOffset + labelRotMat * vec2(-labelHalfSize.x, labelHalfSize.y);
labelMin = min(min(corner1, corner2), min(corner3, corner4));
labelMax = max(max(corner1, corner2), max(corner3, corner4));
} else {
labelMin = labelOffset - labelHalfSize;
labelMax = labelOffset + labelHalfSize;
}
if (backdropArea > 1.5) {
// Label-only: bounds from label rect only
minBound = labelMin - totalExpansion;
maxBound = labelMax + totalExpansion;
} else {
// Both: union of node + label
minBound = min(-vec2(boundRadius), labelMin) - totalExpansion;
maxBound = max(vec2(boundRadius), labelMax) + totalExpansion;
}
} else {
// Node-only or no visible label
float totalRadius = boundRadius + totalExpansion;
minBound = -vec2(totalRadius);
maxBound = vec2(totalRadius);
}
vec2 quadSize = maxBound - minBound;
vec2 quadCenter = (minBound + maxBound) * 0.5;
vec2 localPos = quadCenter + a_quadCorner * quadSize * 0.5 + snapDelta;
vec2 ndcOffset = localPos * 2.0 / u_resolution;
ndcOffset.y = -ndcOffset.y;
gl_Position = vec4(nodeClip.xy + ndcOffset, 0.0, 1.0);
v_uv = localPos;
v_nodeCenter = vec2(0.0);
v_nodeRadius = nodeRadiusPixels;
v_labelCenter = labelOffset;
v_labelHalfSize = labelHalfSize;
v_aaWidth = 1.0;
v_shapeId = shapeId;
v_labelAngle = labelAngle;
v_backdropColor = a_backdropColor;
v_backdropShadowColor = a_backdropShadowColor;
v_backdropShadowBlur = a_backdropShadowBlur;
v_backdropPadding = a_backdropPadding;
v_backdropBorderColor = a_backdropBorderColor;
v_backdropBorderWidth = borderWidth;
v_backdropCornerRadius = cornerRadius;
v_backdropArea = backdropArea;
}
`}function _i(a){const{shapes:e,layers:t,shapeGlobalIds:i}=a,r=ot(e),n=ge(e).map(g=>`uniform ${g.type} ${g.name};`).join(`
`),o=g=>ze(g,"nodeUV","1.0");let s;if(e.length===1)s=`float nodeSdfNormalized = ${o(e[0])};`;else{const g=e.map((h,c)=>` case ${i?i[c]:c}: nodeSdfNormalized = ${o(h)}; break;`).join(`
`),d=o(e[0]);s=`float nodeSdfNormalized;
int shapeId = int(v_shapeId);
switch (shapeId) {
${g}
default: nodeSdfNormalized = ${d};
}`}return`#version 300 es
precision highp float;
in vec2 v_uv;
in vec2 v_nodeCenter;
in float v_nodeRadius;
in vec2 v_labelCenter;
in vec2 v_labelHalfSize;
in float v_aaWidth;
in float v_shapeId;
in float v_nodeRotation;
in float v_labelAngle;
in vec4 v_backdropColor;
in vec4 v_backdropShadowColor;
in float v_backdropShadowBlur;
in float v_backdropPadding;
in vec4 v_backdropBorderColor;
in float v_backdropBorderWidth;
in float v_backdropCornerRadius;
in float v_backdropArea;
${bi(e,t,"in")}
uniform float u_cameraAngle;
${n}
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 fragPicking;
${r}
${Ia}
${za}
${Ma}
${Na}
void main() {
vec4 backdropColor = v_backdropColor;
vec4 shadowColor = v_backdropShadowColor;
float shadowBlur = v_backdropShadowBlur;
float padding = v_backdropPadding;
vec4 borderColor = v_backdropBorderColor;
float borderWidth = v_backdropBorderWidth;
float cornerRadius = v_backdropCornerRadius;
float enlargedRadius = v_nodeRadius + padding;
vec2 screenUV = v_uv - v_nodeCenter;
float effectiveRadius = max(enlargedRadius - cornerRadius, 0.01);
float ca = u_cameraAngle * v_nodeRotation;
float ca_c = cos(ca);
float ca_s = sin(ca);
vec2 rotatedScreenUV = mat2(ca_c, -ca_s, ca_s, ca_c) * screenUV;
vec2 nodeUV = vec2(rotatedScreenUV.x, -rotatedScreenUV.y) / effectiveRadius;
// Query the correct shape SDF based on shapeId
${s}
float nodeSdfPixels = nodeSdfNormalized * effectiveRadius - cornerRadius;
// Label SDF with optional corner radius and rotation
float labelSdfPixels;
if (v_labelHalfSize.x > 0.0) {
vec2 labelP = v_uv - v_labelCenter;
labelSdfPixels = sdfRoundedRotatedBox(labelP, v_labelHalfSize, v_labelAngle, cornerRadius);
} else {
labelSdfPixels = 10000.0;
}
// Select area: 0=both, 1=node, 2=label
float combinedSdf;
if (v_backdropArea > 1.5) {
combinedSdf = labelSdfPixels;
} else if (v_backdropArea > 0.5) {
combinedSdf = nodeSdfPixels;
} else {
combinedSdf = min(nodeSdfPixels, labelSdfPixels);
}
// Fill + border composite
float outerEdge = smoothstep(v_aaWidth, -v_aaWidth, combinedSdf);
vec4 background;
if (borderWidth > 0.5) {
float innerEdge = smoothstep(v_aaWidth, -v_aaWidth, combinedSdf + borderWidth);
float fillAlpha = innerEdge * backdropColor.a;
vec4 fill = vec4(backdropColor.rgb * fillAlpha, fillAlpha);
float borderAlpha = (outerEdge - innerEdge) * borderColor.a;
vec4 border = vec4(borderColor.rgb * borderAlpha, borderAlpha);
background = fill + border * (1.0 - fill.a);
} else {
float fillAlpha = outerEdge * backdropColor.a;
background = vec4(backdropColor.rgb * fillAlpha, fillAlpha);
}
// Gaussian-like shadow falloff (mimics canvas shadowBlur)
vec4 shadow = vec4(0.0);
float sigma = shadowBlur / 2.5;
if (sigma > 0.001) {
float shadowDist = max(0.0, combinedSdf);
float shadowAlpha = exp(-(shadowDist * shadowDist) / (2.0 * sigma * sigma)) * shadowColor.a;
shadow = vec4(shadowColor.rgb * shadowAlpha, shadowAlpha);
}
fragColor = background + shadow * (1.0 - background.a);
fragPicking = vec4(0.0);
}
`}function Ti(a){const e=["u_matrix","u_sizeRatio","u_correctionRatio","u_cameraAngle","u_resolution","u_pixelRatio","u_labelMargin","u_zoomLabelSizeRatio","u_labelPixelSnapping","u_nodeDataTexture","u_nodeDataTextureWidth","u_nodeFrameTexture","u_nodeFrameTextureWidth","u_layerAttributeTexture","u_layerAttributeTextureWidth","u_layerAttributeTexelsPerNode"];for(const t of ge(a))e.includes(t.name)||e.push(t.name);return e}function vi(a){return{vertexShader:xi(a),fragmentShader:_i(a),uniforms:Ti(a.shapes)}}function yi(a,e,t,i){const{label:r={},shapes:n,layers:o,getAttributeTexture:s,shapeGlobalIds:l}=i;if(n.length===0)throw new Error("createBackdropProgram: at least one shape must be provided in 'shapes'");const u=r.margin??5,g=r.zoomToLabelSizeRatioFunction??(()=>1),h=vi({shapes:n,layers:o,shapeGlobalIds:l}),f=class f extends ae{constructor(){super(...arguments),this.totalBackdropCount=0,this.bufferCapacity=0}getDefinition(){const{FLOAT:v,TRIANGLE_STRIP:_}=WebGL2RenderingContext;return{VERTICES:4,VERTEX_SHADER_SOURCE:h.vertexShader,FRAGMENT_SHADER_SOURCE:h.fragmentShader,METHOD:_,UNIFORMS:h.uniforms,ATTRIBUTES:[{name:"a_nodeIndex",size:1,type:v},{name:"a_labelWidth",size:1,type:v},{name:"a_labelHeight",size:1,type:v},{name:"a_textHeight",size:1,type:v},{name:"a_positionMode",size:1,type:v},{name:"a_labelAngle",size:1,type:v},{name:"a_backdropColor",size:4,type:v},{name:"a_backdropShadowColor",size:4,type:v},{name:"a_backdropShadowBlur",size:1,type:v},{name:"a_backdropPadding",size:1,type:v},{name:"a_backdropBorderColor",size:4,type:v},{name:"a_backdropExtra",size:4,type:v},{name:"a_labelBoxOffset",size:2,type:v}],CONSTANT_ATTRIBUTES:[{name:"a_quadCorner",size:2,type:v}],CONSTANT_DATA:[[-1,-1],[1,-1],[-1,1],[1,1]]}}processBackdrop(v,_){const{floats:m,STRIDE:E}=this;let p=v*E;m[p++]=_.nodeIndex,m[p++]=_.labelWidth,m[p++]=_.labelHeight,m[p++]=_.textHeight,m[p++]=pe[_.position],m[p++]=_.labelAngle,m[p++]=_.backdropColor[0],m[p++]=_.backdropColor[1],m[p++]=_.backdropColor[2],m[p++]=_.backdropColor[3],m[p++]=_.backdropShadowColor[0],m[p++]=_.backdropShadowColor[1],m[p++]=_.backdropShadowColor[2],m[p++]=_.backdropShadowColor[3],m[p++]=_.backdropShadowBlur,m[p++]=_.backdropPadding,m[p++]=_.backdropBorderColor[0],m[p++]=_.backdropBorderColor[1],m[p++]=_.backdropBorderColor[2],m[p++]=_.backdropBorderColor[3],m[p++]=_.backdropBorderWidth,m[p++]=_.backdropCornerRadius,m[p++]=_.backdropLabelPadding,m[p++]=_.backdropArea,m[p++]=_.labelBoxOffset[0],m[p++]=_.labelBoxOffset[1]}setUniforms(v,_){const{gl:m,uniformLocations:E}=_;m.uniformMatrix3fv(E.u_matrix,!1,v.matrix),m.uniform1f(E.u_sizeRatio,v.sizeRatio),m.uniform1f(E.u_correctionRatio,v.correctionRatio),m.uniform1f(E.u_cameraAngle,v.cameraAngle),m.uniform2f(E.u_resolution,v.width*v.pixelRatio,v.height*v.pixelRatio),m.uniform1f(E.u_pixelRatio,v.pixelRatio),m.uniform1f(E.u_labelMargin,f.labelMargin),m.uniform1f(E.u_zoomLabelSizeRatio,1/g(v.zoomRatio)),m.uniform1f(E.u_labelPixelSnapping,v.labelPixelSnapping),m.uniform1i(E.u_nodeDataTexture,v.nodeDataTextureUnit),m.uniform1i(E.u_nodeDataTextureWidth,v.nodeDataTextureWidth),m.uniform1i(E.u_nodeFrameTexture,v.nodeFrameTextureUnit),m.uniform1i(E.u_nodeFrameTextureWidth,v.nodeFrameTextureWidth);const p=s();E.u_layerAttributeTexture&&p&&(p.bind(ie),m.uniform1i(E.u_layerAttributeTexture,ie),m.uniform1i(E.u_layerAttributeTextureWidth,p.getTextureWidth()),m.uniform1i(E.u_layerAttributeTexelsPerNode,p.getTexelsPerItem()));for(const x of ge(n))this.setTypedUniform(x,_)}hasNothingToRender(){return this.totalBackdropCount===0}drawWebGL(v,{gl:_}){this.totalBackdropCount!==0&&(this.isInstanced?_.drawArraysInstanced(v,0,this.VERTICES,this.totalBackdropCount):_.drawArrays(v,0,this.totalBackdropCount*this.VERTICES))}reallocate(v){this.totalBackdropCount=v,v>this.bufferCapacity&&(this.bufferCapacity=Math.max(v,Math.ceil(this.bufferCapacity*1.5)||10),super.reallocate(this.bufferCapacity))}};f.labelMargin=u;let c=f;return new c(a,e,t)}class na extends ae{constructor(){super(...arguments),this.totalCharacterCount=0,this.bufferCapacity=0}processLabel(e,t,i){if(i.hidden||!i.text)return 0;const r=i.text,n=r.length;for(let o=0;o<n;o++){const s=r[o];this.processCharacter(t+o,i,s,o)}return n}hasNothingToRender(){return this.totalCharacterCount===0}drawWebGL(e,{gl:t}){this.totalCharacterCount!==0&&(this.isInstanced?t.drawArraysInstanced(e,0,this.VERTICES,this.totalCharacterCount):t.drawArrays(e,0,this.totalCharacterCount*this.VERTICES))}reallocate(e){this.totalCharacterCount=e,e>this.bufferCapacity&&(this.bufferCapacity=Math.max(e,Math.ceil(this.bufferCapacity*1.5)||1e3),super.reallocate(this.bufferCapacity))}}function zn(){return`#version 300 es
in float a_nodeIndex;
in vec4 a_id;
in vec4 a_color;
in float a_labelWidth;
in float a_labelHeight;
in float a_textHeight;
in float a_positionMode;
in float a_labelAngle;
in float a_padding;
in vec2 a_quadCorner;
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform float u_labelMargin;
uniform float u_zoomLabelSizeRatio;
uniform float u_labelPixelSnapping;
uniform float u_pickingPadding;
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_nodeFrameTexture;
uniform int u_nodeFrameTextureWidth;
out vec4 v_id;
out vec4 v_color;
${X}
${J}
${se}
${$t}
void main() {
int nodeIdx = int(a_nodeIndex);
// Node data: (x, y, size, shapeId).
vec4 nodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
vec2 nodePosition = nodeData.xy;
float nodeSize = nodeData.z;
// Shape-aware edge distance, read once from the shared frame texture.
float edgeDist = readFrameTexel(u_nodeFrameTexture, u_nodeFrameTextureWidth, nodeIdx).r;
// Per-node label rotation alignment: 0 = viewport, 1 = label turns with camera.
float labelRotation = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx).g;
${Ot}
float zoomScale = u_zoomLabelSizeRatio;
float labelW = a_labelWidth * zoomScale * u_pixelRatio;
float labelH = a_labelHeight * zoomScale * u_pixelRatio;
float labelMargin = u_labelMargin * zoomScale * u_pixelRatio;
#ifdef PICKING_MODE
float padding = u_pickingPadding * u_pixelRatio;
#else
float padding = a_padding * u_pixelRatio;
#endif
if (labelW <= 0.0) {
gl_Position = vec4(2.0, 0.0, 0.0, 1.0);
v_id = vec4(0.0);
v_color = vec4(0.0);
return;
}
vec2 labelHalfSize = vec2(labelW * 0.5 + padding, labelH * 0.5 + padding);
vec2 labelOffset = vec2(0.0);
// Graph-aligned labels add the camera angle so the rect orbits with the text.
float labelAngle = a_labelAngle - labelRotation * u_cameraAngle;
float la_c = cos(labelAngle);
float la_s = sin(labelAngle);
mat2 labelRotMat = mat2(la_c, -la_s, la_s, la_c);
vec3 nodeClip = u_matrix * vec3(nodePosition, 1.0);
vec2 nodeScreen = vec2(
(nodeClip.x + 1.0) * u_resolution.x,
(1.0 - nodeClip.y) * u_resolution.y
) * 0.5;
vec2 snapDelta = (round(nodeScreen) - nodeScreen) * u_labelPixelSnapping;
if (a_positionMode < 4.0) {
float labelStart = nodeRadiusPixels * edgeDist + labelMargin;
float textHalf = a_textHeight * zoomScale * u_pixelRatio * 0.5;
// Box center uses the text half-size; the padding expands the quad below.
labelOffset = labelRotMat * labelBoxCenter(a_positionMode, labelStart, vec2(labelW * 0.5, labelH * 0.5), textHalf);
}
// Rotate the rect with the label so it stays aligned with the (rotated) text.
vec2 localPos = labelOffset + labelRotMat * (a_quadCorner * labelHalfSize);
vec2 ndcOffset = (localPos + snapDelta) * 2.0 / u_resolution;
ndcOffset.y = -ndcOffset.y;
gl_Position = vec4(nodeClip.xy + ndcOffset, 0.0, 1.0);
v_id = a_id;
v_color = a_color;
}
`}const Mn=`#version 300 es
precision highp float;
in vec4 v_id;
in vec4 v_color;
out vec4 fragColor;
void main() {
#ifdef PICKING_MODE
fragColor = v_id;
#else
if (v_color.a <= 0.0) discard;
// v_color is non-premultiplied RGBA (0-1); convert to premultiplied for blending
fragColor = vec4(v_color.rgb * v_color.a, v_color.a);
#endif
}
`;function Nn(a,e,t,i){const{label:r={}}=i,n=r.margin??me,o=r.zoomToLabelSizeRatioFunction??(()=>1),s=zn(),u=class u extends ae{constructor(){super(...arguments),this.totalCount=0,this.bufferCapacity=0}getDefinition(){const{FLOAT:d,UNSIGNED_BYTE:h,TRIANGLE_STRIP:c}=WebGL2RenderingContext;return{VERTICES:4,VERTEX_SHADER_SOURCE:s,FRAGMENT_SHADER_SOURCE:Mn,METHOD:c,UNIFORMS:["u_matrix","u_sizeRatio","u_correctionRatio","u_cameraAngle","u_resolution","u_pixelRatio","u_labelMargin","u_zoomLabelSizeRatio","u_labelPixelSnapping","u_pickingPadding","u_nodeDataTexture","u_nodeDataTextureWidth","u_nodeFrameTexture","u_nodeFrameTextureWidth"],ATTRIBUTES:[{name:"a_nodeIndex",size:1,type:d},{name:"a_id",size:4,type:h,normalized:!0},{name:"a_color",size:4,type:h,normalized:!0},{name:"a_labelWidth",size:1,type:d},{name:"a_labelHeight",size:1,type:d},{name:"a_textHeight",size:1,type:d},{name:"a_positionMode",size:1,type:d},{name:"a_labelAngle",size:1,type:d},{name:"a_padding",size:1,type:d}],CONSTANT_ATTRIBUTES:[{name:"a_quadCorner",size:2,type:d}],CONSTANT_DATA:[[-1,-1],[1,-1],[-1,1],[1,1]]}}processLabelBackground(d,h){const{floats:c,ints:f}=this;let b=d*this.STRIDE;c[b++]=h.nodeIndex,f[b++]=h.id,c[b++]=h.color,c[b++]=h.labelWidth,c[b++]=h.labelHeight,c[b++]=h.textHeight,c[b++]=h.positionMode,c[b++]=h.labelAngle,c[b++]=h.padding}setUniforms(d,{gl:h,uniformLocations:c}){h.uniformMatrix3fv(c.u_matrix,!1,d.matrix),h.uniform1f(c.u_sizeRatio,d.sizeRatio),h.uniform1f(c.u_correctionRatio,d.correctionRatio),h.uniform1f(c.u_cameraAngle,d.cameraAngle),h.uniform2f(c.u_resolution,d.width*d.pixelRatio,d.height*d.pixelRatio),h.uniform1f(c.u_pixelRatio,d.pixelRatio),h.uniform1f(c.u_labelMargin,u.labelMargin),h.uniform1f(c.u_zoomLabelSizeRatio,1/o(d.zoomRatio)),h.uniform1f(c.u_labelPixelSnapping,d.labelPixelSnapping),h.uniform1f(c.u_pickingPadding,d.labelPickingPadding),h.uniform1i(c.u_nodeDataTexture,d.nodeDataTextureUnit),h.uniform1i(c.u_nodeDataTextureWidth,d.nodeDataTextureWidth),h.uniform1i(c.u_nodeFrameTexture,d.nodeFrameTextureUnit),h.uniform1i(c.u_nodeFrameTextureWidth,d.nodeFrameTextureWidth)}hasNothingToRender(){return this.totalCount===0}drawWebGL(d,{gl:h}){this.totalCount!==0&&h.drawArraysInstanced(h.TRIANGLE_STRIP,0,this.VERTICES,this.totalCount)}reallocate(d){this.totalCount=d,d>this.bufferCapacity&&(this.bufferCapacity=Math.max(d,Math.ceil(this.bufferCapacity*1.5)||10),super.reallocate(this.bufferCapacity))}};u.labelMargin=n;let l=u;return new l(a,e,t)}var yt={exports:{}},Si;function kn(){if(Si)return yt.exports;Si=1;var a=typeof Reflect=="object"?Reflect:null,e=a&&typeof a.apply=="function"?a.apply:function(x,T,y){return Function.prototype.apply.call(x,T,y)},t;a&&typeof a.ownKeys=="function"?t=a.ownKeys:Object.getOwnPropertySymbols?t=function(x){return Object.getOwnPropertyNames(x).concat(Object.getOwnPropertySymbols(x))}:t=function(x){return Object.getOwnPropertyNames(x)};function i(p){console&&console.warn&&console.warn(p)}var r=Number.isNaN||function(x){return x!==x};function n(){n.init.call(this)}yt.exports=n,yt.exports.once=_,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._eventsCount=0,n.prototype._maxListeners=void 0;var o=10;function s(p){if(typeof p!="function")throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof p)}Object.defineProperty(n,"defaultMaxListeners",{enumerable:!0,get:function(){return o},set:function(p){if(typeof p!="number"||p<0||r(p))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+p+".");o=p}}),n.init=function(){(this._events===void 0||this._events===Object.getPrototypeOf(this)._events)&&(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},n.prototype.setMaxListeners=function(x){if(typeof x!="number"||x<0||r(x))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+x+".");return this._maxListeners=x,this};function l(p){return p._maxListeners===void 0?n.defaultMaxListeners:p._maxListeners}n.prototype.getMaxListeners=function(){return l(this)},n.prototype.emit=function(x){for(var T=[],y=1;y<arguments.length;y++)T.push(arguments[y]);var R=x==="error",A=this._events;if(A!==void 0)R=R&&A.error===void 0;else if(!R)return!1;if(R){var S;if(T.length>0&&(S=T[0]),S instanceof Error)throw S;var C=new Error("Unhandled error."+(S?" ("+S.message+")":""));throw C.context=S,C}var L=A[x];if(L===void 0)return!1;if(typeof L=="function")e(L,this,T);else for(var D=L.length,F=f(L,D),y=0;y<D;++y)e(F[y],this,T);return!0};function u(p,x,T,y){var R,A,S;if(s(T),A=p._events,A===void 0?(A=p._events=Object.create(null),p._eventsCount=0):(A.newListener!==void 0&&(p.emit("newListener",x,T.listener?T.listener:T),A=p._events),S=A[x]),S===void 0)S=A[x]=T,++p._eventsCount;else if(typeof S=="function"?S=A[x]=y?[T,S]:[S,T]:y?S.unshift(T):S.push(T),R=l(p),R>0&&S.length>R&&!S.warned){S.warned=!0;var C=new Error("Possible EventEmitter memory leak detected. "+S.length+" "+String(x)+" listeners added. Use emitter.setMaxListeners() to increase limit");C.name="MaxListenersExceededWarning",C.emitter=p,C.type=x,C.count=S.length,i(C)}return p}n.prototype.addListener=function(x,T){return u(this,x,T,!1)},n.prototype.on=n.prototype.addListener,n.prototype.prependListener=function(x,T){return u(this,x,T,!0)};function g(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length===0?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function d(p,x,T){var y={fired:!1,wrapFn:void 0,target:p,type:x,listener:T},R=g.bind(y);return R.listener=T,y.wrapFn=R,R}n.prototype.once=function(x,T){return s(T),this.on(x,d(this,x,T)),this},n.prototype.prependOnceListener=function(x,T){return s(T),this.prependListener(x,d(this,x,T)),this},n.prototype.removeListener=function(x,T){var y,R,A,S,C;if(s(T),R=this._events,R===void 0)return this;if(y=R[x],y===void 0)return this;if(y===T||y.listener===T)--this._eventsCount===0?this._events=Object.create(null):(delete R[x],R.removeListener&&this.emit("removeListener",x,y.listener||T));else if(typeof y!="function"){for(A=-1,S=y.length-1;S>=0;S--)if(y[S]===T||y[S].listener===T){C=y[S].listener,A=S;break}if(A<0)return this;A===0?y.shift():b(y,A),y.length===1&&(R[x]=y[0]),R.removeListener!==void 0&&this.emit("removeListener",x,C||T)}return this},n.prototype.off=n.prototype.removeListener,n.prototype.removeAllListeners=function(x){var T,y,R;if(y=this._events,y===void 0)return this;if(y.removeListener===void 0)return arguments.length===0?(this._events=Object.create(null),this._eventsCount=0):y[x]!==void 0&&(--this._eventsCount===0?this._events=Object.create(null):delete y[x]),this;if(arguments.length===0){var A=Object.keys(y),S;for(R=0;R<A.length;++R)S=A[R],S!=="removeListener"&&this.removeAllListeners(S);return this.removeAllListeners("removeListener"),this._events=Object.create(null),this._eventsCount=0,this}if(T=y[x],typeof T=="function")this.removeListener(x,T);else if(T!==void 0)for(R=T.length-1;R>=0;R--)this.removeListener(x,T[R]);return this};function h(p,x,T){var y=p._events;if(y===void 0)return[];var R=y[x];return R===void 0?[]:typeof R=="function"?T?[R.listener||R]:[R]:T?v(R):f(R,R.length)}n.prototype.listeners=function(x){return h(this,x,!0)},n.prototype.rawListeners=function(x){return h(this,x,!1)},n.listenerCount=function(p,x){return typeof p.listenerCount=="function"?p.listenerCount(x):c.call(p,x)},n.prototype.listenerCount=c;function c(p){var x=this._events;if(x!==void 0){var T=x[p];if(typeof T=="function")return 1;if(T!==void 0)return T.length}return 0}n.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]};function f(p,x){for(var T=new Array(x),y=0;y<x;++y)T[y]=p[y];return T}function b(p,x){for(;x+1<p.length;x++)p[x]=p[x+1];p.pop()}function v(p){for(var x=new Array(p.length),T=0;T<x.length;++T)x[T]=p[T].listener||p[T];return x}function _(p,x){return new Promise(function(T,y){function R(S){p.removeListener(x,A),y(S)}function A(){typeof p.removeListener=="function"&&p.removeListener("error",R),T([].slice.call(arguments))}E(p,x,A,{once:!0}),x!=="error"&&m(p,R,{once:!0})})}function m(p,x,T){typeof p.on=="function"&&E(p,"error",x,T)}function E(p,x,T,y){if(typeof p.on=="function")y.once?p.once(x,T):p.on(x,T);else if(typeof p.addEventListener=="function")p.addEventListener(x,function R(A){y.once&&p.removeEventListener(x,R),T(A)});else throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type '+typeof p)}return yt.exports}var Ei=kn();const q={fontSize:64,buffer:8,radius:24,cutoff:.25,maxTextureSize:2048,debounceTimeout:100},We=2,Be=1e20;function Gn(a,e,t,i,r){const n=a+r*4,o=document.createElement("canvas");o.width=n,o.height=n;const s=o.getContext("2d",{willReadFrequently:!0});return s.font=`${i} ${t} ${a}px ${e}`,s.textBaseline="alphabetic",s.textAlign="left",s.fillStyle="black",{ctx:s,canvasSize:n,gridOuter:new Float64Array(n*n),gridInner:new Float64Array(n*n),f:new Float64Array(n),z:new Float64Array(n+1),v:new Uint16Array(n)}}function Wn(a,e,t,i,r){const{ctx:n,canvasSize:o}=a,s=n.measureText(e),l=s.width,{actualBoundingBoxAscent:u,actualBoundingBoxDescent:g,actualBoundingBoxLeft:d,actualBoundingBoxRight:h}=s,c=Math.ceil(u),f=Math.ceil(d),b=Math.max(0,Math.min(o-t,Math.ceil(d)+Math.ceil(h))),v=Math.min(o-t,c+Math.ceil(g)),_=b+2*t,m=v+2*t,E=Math.max(_*m,0),p=new Uint8ClampedArray(E),x={data:p,width:_,height:m,glyphWidth:b,glyphHeight:v,glyphTop:c,glyphLeft:f,glyphAdvance:l};if(b===0||v===0)return x;const{gridInner:T,gridOuter:y}=a;n.clearRect(t,t,b,v),n.fillText(e,t+f,t+c);const R=n.getImageData(t,t,b,v);y.fill(Be,0,E),T.fill(0,0,E);for(let A=0;A<v;A++)for(let S=0;S<b;S++){const C=R.data[4*(A*b+S)+3]/255;if(C===0)continue;const L=(A+t)*_+S+t;if(C===1)y[L]=0,T[L]=Be;else{const D=.5-C;y[L]=D>0?D*D:0,T[L]=D<0?D*D:0}}Ri(y,0,0,_,m,_,a.f,a.v,a.z),Ri(T,t,t,b,v,_,a.f,a.v,a.z);for(let A=0;A<E;A++){const S=Math.sqrt(y[A])-Math.sqrt(T[A]);p[A]=Math.round(255-255*(S/i+r))}return x}function Ri(a,e,t,i,r,n,o,s,l){for(let u=e;u<e+i;u++)Ai(a,t*n+u,n,r,o,s,l);for(let u=t;u<t+r;u++)Ai(a,u*n+e,1,i,o,s,l)}function Ai(a,e,t,i,r,n,o){n[0]=0,o[0]=-Be,o[1]=Be,r[0]=a[e];for(let s=1,l=0,u=0;s<i;s++){r[s]=a[e+s*t];const g=s*s;do{const d=n[l];u=(r[s]-r[d]+g-d*d)/(s-d)/2}while(u<=o[l]&&--l>-1);l++,n[l]=s,o[l]=u,o[l+1]=Be}for(let s=0,l=0;s<i;s++){for(;o[l+1]<s;)l++;const u=n[l],g=s-u;a[e+s*t]=r[u]+g*g}}const Nt=class Nt extends Ei.EventEmitter{constructor(e={}){super(),this.fonts=new Map,this.textures=[],this.cursor={x:0,y:0,rowHeight:0,atlasIndex:0},this.pendingGlyphs=[],this.debounceTimer=null,this.options={...q,...e},this.canvas=document.createElement("canvas"),this.canvas.width=this.options.maxTextureSize,this.canvas.height=this.options.maxTextureSize,this.ctx=this.canvas.getContext("2d",{willReadFrequently:!0}),this.measureCanvas=document.createElement("canvas"),this.measureCtx=this.measureCanvas.getContext("2d"),this.textures.push(this.ctx.getImageData(0,0,1,1))}getFontKey(e){return`${e.family}-${e.weight}-${e.style}`}registerFont(e){const t=this.getFontKey(e);if(this.fonts.has(t))return t;const i=Gn(this.options.fontSize,e.family,e.weight,e.style,this.options.buffer);return this.fonts.set(t,{descriptor:e,generator:i,glyphs:new Map}),t}ensureGlyphs(e,t){const i=this.fonts.get(t);if(!i)throw new Error(`Font "${t}" is not registered. Call registerFont() first.`);let r=!1;for(const n of e){const o=n.codePointAt(0);o!==void 0&&(i.glyphs.has(o)||(this.pendingGlyphs.push({fontKey:t,charCode:o}),r=!0))}r&&this.scheduleTextureGeneration()}measureText(e,t){const i=this.fonts.get(t);if(!i)throw new Error(`Font "${t}" is not registered.`);const{family:r,weight:n,style:o}=i.descriptor;return this.measureCtx.font=`${o} ${n} ${this.options.fontSize}px ${r}`,this.measureCtx.measureText(e).width}getGlyph(e,t){const i=this.fonts.get(t);if(i)return i.glyphs.get(e)}getTextures(){return this.textures}getFontCount(){return this.fonts.size}getGlyphCount(){let e=0;for(const t of this.fonts.values())e+=t.glyphs.size;return e}hasPendingGlyphs(){return this.pendingGlyphs.length>0}flush(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.generateTextures()}destroy(){this.debounceTimer&&clearTimeout(this.debounceTimer),this.fonts.clear(),this.textures=[],this.pendingGlyphs=[],this.removeAllListeners()}scheduleTextureGeneration(){this.debounceTimer===null&&(this.options.debounceTimeout===null?this.generateTextures():this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.generateTextures()},this.options.debounceTimeout))}generateTextures(){if(this.pendingGlyphs.length===0)return;const{maxTextureSize:e,buffer:t,radius:i,cutoff:r}=this.options;for(const{fontKey:n,charCode:o}of this.pendingGlyphs){const s=this.fonts.get(n);if(!s||s.glyphs.has(o))continue;const l=String.fromCodePoint(o),u=Wn(s.generator,l,t,i,r),g=u.width,d=u.height;this.cursor.x+g+We>e&&(this.cursor.x=0,this.cursor.y+=this.cursor.rowHeight+We,this.cursor.rowHeight=0),this.cursor.y+d+We>e&&(this.finalizeCurrentTexture(),this.cursor={x:0,y:0,rowHeight:0,atlasIndex:this.cursor.atlasIndex+1},this.ctx.clearRect(0,0,e,e));const h=u.data,c=new Uint8ClampedArray(g*d*4);for(let v=0;v<h.length;v++){const _=v*4;c[_]=255,c[_+1]=255,c[_+2]=255,c[_+3]=h[v]}const f=new ImageData(c,g,d);this.ctx.putImageData(f,this.cursor.x,this.cursor.y);const b={charCode:o,width:u.glyphWidth,height:u.glyphHeight,bearingX:-u.glyphLeft-t,bearingY:u.glyphTop+t,advance:u.glyphAdvance,atlasX:this.cursor.x,atlasY:this.cursor.y,atlasWidth:g,atlasHeight:d,atlasIndex:this.cursor.atlasIndex};s.glyphs.set(o,b),this.cursor.x+=g+We,this.cursor.rowHeight=Math.max(this.cursor.rowHeight,d)}this.finalizeCurrentTexture(),this.pendingGlyphs=[],this.emit(Nt.ATLAS_UPDATED_EVENT,{textures:this.textures,glyphCount:this.getGlyphCount()})}finalizeCurrentTexture(){const{maxTextureSize:e}=this.options,t=this.cursor.y>0||this.cursor.rowHeight>0,i=Math.min(e,Math.max(this.cursor.x,t?e:1)),r=Math.min(e,this.cursor.y+this.cursor.rowHeight+We),n=this.ctx.getImageData(0,0,i,r);this.cursor.atlasIndex>=this.textures.length?this.textures.push(n):this.textures[this.cursor.atlasIndex]=n}};Nt.ATLAS_UPDATED_EVENT="atlasUpdated";let re=Nt;const Bn=q.fontSize;function Di(){return`#version 300 es
// ============================================================================
// Attributes
// ============================================================================
// Per-character (instanced)
in float a_nodeIndex; // Index into node data texture
in vec2 a_charOffset; // Character offset from label origin (pixels)
in vec2 a_charSize; // Character dimensions (pixels)
in vec4 a_texCoords; // Atlas coords: (x, y, width, height) in pixels
in vec4 a_color; // Text color (RGBA)
in float a_margin; // Gap between node edge and label (pixels)
in float a_positionMode; // Position: 0=right, 1=left, 2=above, 3=below, 4=over
in float a_labelWidth; // Total label width (pixels)
in float a_labelHeight; // Label height (pixels)
in float a_verticalCenter; // Vertical center offset from baseline (pixels)
in float a_textHeight; // Actual text height: maxAscent + maxDescent (pixels)
in float a_labelAngle; // Label rotation angle (radians)
// Per-vertex (constant quad corners)
in vec2 a_quadCorner; // Quad corner: [-1,-1], [1,-1], [-1,1], [1,1]
// ============================================================================
// Uniforms
// ============================================================================
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
uniform vec2 u_resolution;
uniform vec2 u_atlasSize;
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_nodeFrameTexture;
uniform int u_nodeFrameTextureWidth;
uniform float u_zoomLabelSizeRatio;
uniform float u_labelPixelSnapping;
uniform float u_pixelRatio;
// ============================================================================
// Varyings
// ============================================================================
out vec2 v_texCoord;
out vec4 v_color;
out float v_fontScale;
// ============================================================================
// Constants
// ============================================================================
const float bias = 255.0 / 254.0;
const float ATLAS_FONT_SIZE = ${I(Bn)};
// ============================================================================
// Helper Functions
// ============================================================================
${X}
${J}
${se}
${Ut}
// ============================================================================
// Main
// ============================================================================
void main() {
// -------------------------------------------------------------------------
// Step 0: Fetch node data + shared edge distance from textures
// -------------------------------------------------------------------------
// Node-data texture format: vec4(x, y, size, shapeId)
// 2D texture layout: texCoord = (index % width, index / width)
int nodeIdx = int(a_nodeIndex);
vec4 nodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
vec2 a_anchorPosition = nodeData.xy;
float a_nodeSize = nodeData.z;
// Normalized edge distance from the shared frame texture (the frame-pass ran
// the SDF search once; the label just reads the result).
float edgeDist = readFrameTexel(u_nodeFrameTexture, u_nodeFrameTextureWidth, nodeIdx).r;
// Per-node label rotation alignment: 0 = viewport, 1 = label turns with camera.
float labelRotation = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx).g;
// Apply zoom-dependent label size scaling
// Positional values are in CSS pixels; multiply by u_pixelRatio to convert to
// physical pixels, which is what the NDC conversion (/ u_resolution) expects.
float zoomScale = u_zoomLabelSizeRatio;
float margin = a_margin * zoomScale * u_pixelRatio;
vec2 charOffset = a_charOffset * zoomScale * u_pixelRatio;
vec2 charSize = a_charSize * zoomScale * u_pixelRatio;
float labelWidth = a_labelWidth * zoomScale * u_pixelRatio;
float labelHeight = a_labelHeight * zoomScale;
// Font scale: ratio of CSS label size to the base atlas font size.
// Divided by u_pixelRatio because the atlas is generated at ATLAS_FONT_SIZE * pixelRatio,
// which cancels out the u_pixelRatio in the fragment shader's gamma formula and keeps
// the anti-aliasing band width consistent across pixel densities.
v_fontScale = a_labelHeight * zoomScale / (ATLAS_FONT_SIZE * u_pixelRatio);
// -------------------------------------------------------------------------
// Step 1: Transform node position to clip space
// -------------------------------------------------------------------------
vec3 anchorClip = u_matrix * vec3(a_anchorPosition, 1.0);
// -------------------------------------------------------------------------
// Step 2: Convert node size to screen pixels
// -------------------------------------------------------------------------
float matrixScaleX = length(vec2(u_matrix[0][0], u_matrix[1][0]));
float nodeRadiusGraphSpace = a_nodeSize * u_correctionRatio / u_sizeRatio * 2.0;
float nodeRadiusNDC = nodeRadiusGraphSpace * matrixScaleX;
float nodeRadiusPixels = nodeRadiusNDC * u_resolution.x / 2.0;
// -------------------------------------------------------------------------
// Step 3: Calculate position offset from the shared edge distance
// -------------------------------------------------------------------------
vec2 positionOffset = vec2(0.0);
if (a_positionMode < 4.0) {
vec2 screenDir = getLabelDirection(a_positionMode);
float boundaryDistPixels = nodeRadiusPixels * edgeDist;
positionOffset = screenDir * (boundaryDistPixels + margin);
}
// -------------------------------------------------------------------------
// Step 4: Calculate final vertex position
// -------------------------------------------------------------------------
vec2 cornerOffset = (a_quadCorner + 1.0) * 0.5;
vec2 charPixelPos = positionOffset + charOffset + cornerOffset * charSize;
// Apply text alignment based on position mode
float verticalCenter = a_verticalCenter * zoomScale * u_pixelRatio;
float textHeight = a_textHeight * zoomScale * u_pixelRatio;
float baselineToDescent = textHeight / 2.0 - verticalCenter;
float baselineToAscent = textHeight / 2.0 + verticalCenter;
if (a_positionMode < 0.5) {
// Right: vertically center
charPixelPos.y += verticalCenter;
} else if (a_positionMode < 1.5) {
// Left: right-align and vertically center
charPixelPos.x -= labelWidth;
charPixelPos.y += verticalCenter;
} else if (a_positionMode < 2.5) {
// Above: center horizontally, bottom of text at anchor
charPixelPos.x -= labelWidth * 0.5;
charPixelPos.y -= baselineToDescent;
} else if (a_positionMode < 3.5) {
// Below: center horizontally, top of text at anchor
charPixelPos.x -= labelWidth * 0.5;
charPixelPos.y += baselineToAscent;
} else {
// Over: center both
charPixelPos.x -= labelWidth * 0.5;
charPixelPos.y += verticalCenter;
}
// Apply label angle rotation. Graph-aligned labels add the camera angle so the
// whole label orbits the node in lockstep with the frame-pass edge distance.
float labelAngle = a_labelAngle - labelRotation * u_cameraAngle;
float la_c = cos(labelAngle);
float la_s = sin(labelAngle);
mat2 labelRotMat = mat2(la_c, -la_s, la_s, la_c);
charPixelPos = labelRotMat * charPixelPos;
// Snap node center to pixel grid so label/backdrop/attachment move as a unit
vec2 nodeScreen = vec2(
(anchorClip.x + 1.0) * u_resolution.x,
(1.0 - anchorClip.y) * u_resolution.y
) * 0.5;
charPixelPos += (round(nodeScreen) - nodeScreen) * u_labelPixelSnapping;
// Convert to NDC (flip Y: screen Y-down -> clip Y-up)
vec2 ndcOffset = vec2(charPixelPos.x, -charPixelPos.y) * 2.0 / u_resolution;
gl_Position = vec4(anchorClip.xy + ndcOffset, 0.0, 1.0);
// -------------------------------------------------------------------------
// Step 5: Texture coordinates
// -------------------------------------------------------------------------
v_texCoord = (a_texCoords.xy + cornerOffset * a_texCoords.zw) / u_atlasSize;
// -------------------------------------------------------------------------
// Step 6: Pass color
// -------------------------------------------------------------------------
v_color = a_color;
v_color.a *= bias;
}
`}function Ci(){return`#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
in float v_fontScale;
uniform sampler2D u_atlas;
uniform float u_gamma;
uniform float u_sdfBuffer;
uniform float u_pixelRatio;
// Fragment output (single target - picking handled via separate pass)
out vec4 fragColor;
void main() {
#ifdef PICKING_MODE
// Labels are not pickable - discard all fragments in picking mode
fragColor = vec4(0.0);
discard;
#else
// Sample SDF value from atlas (high = inside glyph, low = outside)
float sdfValue = texture(u_atlas, v_texCoord).a;
// Edge threshold: 1.0 - cutoff = 0.75 for default cutoff=0.25
// This is where the glyph edge is located in the SDF
float edgeThreshold = 1.0 - u_sdfBuffer;
// Gamma controls the anti-aliasing band width.
// Scale inversely with font scale so small labels get a wider AA band
// (smoother) and large labels get a tighter band (sharper).
float gamma = u_gamma / (u_pixelRatio * v_fontScale);
// Pure gamma-based anti-aliasing using smoothstep
// The AA band extends from (threshold - gamma) to (threshold + gamma)
float alpha = smoothstep(edgeThreshold - gamma, edgeThreshold + gamma, sdfValue);
// Premultiplied alpha output for correct blending
float finalAlpha = v_color.a * alpha;
fragColor = vec4(v_color.rgb * finalAlpha, finalAlpha);
#endif
}
`}function Li(){return["u_matrix","u_sizeRatio","u_correctionRatio","u_cameraAngle","u_resolution","u_atlasSize","u_atlas","u_gamma","u_sdfBuffer","u_pixelRatio","u_nodeDataTexture","u_nodeDataTextureWidth","u_nodeFrameTexture","u_nodeFrameTextureWidth","u_zoomLabelSizeRatio","u_labelPixelSnapping"]}function Fi(){return{vertexShader:Di(),fragmentShader:Ci(),uniforms:Li()}}function Pi(a,e,t,i){const{label:r={}}=i,n=r.margin??me,o=r.zoomToLabelSizeRatioFunction??(()=>1),s=Fi(),u=class u extends na{constructor(d,h,c){var b,v,_;if(super(d,h,c),this.atlasTexture=null,this.atlasNeedsUpdate=!1,this.labelGlyphCache=new Map,this.atlasFontSize=q.fontSize*ut(),this.atlasManager=new re({fontSize:this.atlasFontSize}),this.gamma=.025,this.sdfBuffer=q.cutoff,this.atlasTexture=d.createTexture(),!this.atlasTexture)throw new Error("NodeLabelProgram: failed to create atlas texture");d.bindTexture(d.TEXTURE_2D,this.atlasTexture),d.texParameteri(d.TEXTURE_2D,d.TEXTURE_WRAP_S,d.CLAMP_TO_EDGE),d.texParameteri(d.TEXTURE_2D,d.TEXTURE_WRAP_T,d.CLAMP_TO_EDGE),d.texParameteri(d.TEXTURE_2D,d.TEXTURE_MIN_FILTER,d.LINEAR),d.texParameteri(d.TEXTURE_2D,d.TEXTURE_MAG_FILTER,d.LINEAR),d.bindTexture(d.TEXTURE_2D,null),this.atlasManager.on(re.ATLAS_UPDATED_EVENT,()=>{this.atlasNeedsUpdate=!0});const f={family:((b=r.font)==null?void 0:b.family)||"sans-serif",weight:((v=r.font)==null?void 0:v.weight)||"normal",style:((_=r.font)==null?void 0:_.style)||"normal"};this.defaultFontKey=this.atlasManager.registerFont(f)}getDefinition(){const{FLOAT:d,UNSIGNED_BYTE:h,TRIANGLE_STRIP:c}=WebGL2RenderingContext;return{VERTICES:4,VERTEX_SHADER_SOURCE:s.vertexShader,FRAGMENT_SHADER_SOURCE:s.fragmentShader,METHOD:c,UNIFORMS:s.uniforms,ATTRIBUTES:[{name:"a_nodeIndex",size:1,type:d},{name:"a_charOffset",size:2,type:d},{name:"a_charSize",size:2,type:d},{name:"a_texCoords",size:4,type:d},{name:"a_color",size:4,type:h,normalized:!0},{name:"a_margin",size:1,type:d},{name:"a_positionMode",size:1,type:d},{name:"a_labelWidth",size:1,type:d},{name:"a_labelHeight",size:1,type:d},{name:"a_verticalCenter",size:1,type:d},{name:"a_textHeight",size:1,type:d},{name:"a_labelAngle",size:1,type:d}],CONSTANT_ATTRIBUTES:[{name:"a_quadCorner",size:2,type:d}],CONSTANT_DATA:[[-1,-1],[1,-1],[-1,1],[1,1]]}}prepareLabelGlyphs(d,h){if(h.hidden||!h.text){this.labelGlyphCache.delete(d);return}const c=h.text,f=h.fontKey||this.defaultFontKey;this.atlasManager.ensureGlyphs(c,f);const b=[],v=[];let _=0,m=0,E=0;for(const p of c){const x=p.codePointAt(0);if(x===void 0){b.push(void 0),v.push(_);continue}const T=this.atlasManager.getGlyph(x,f);b.push(T),v.push(_),T&&(_+=T.advance,m=Math.max(m,T.bearingY),E=Math.max(E,T.atlasHeight-T.bearingY))}this.labelGlyphCache.set(d,{glyphs:b,xOffsets:v,totalWidth:_,totalHeight:m+E,verticalCenterOffset:(m-E)/2})}processCharacter(d,h,c,f){const{floats:b,STRIDE:v}=this,_=d*v,m=this.labelGlyphCache.get(h.parentKey);if(!m||!m.glyphs[f]){for(let R=0;R<v;R++)b[_+R]=0;return}const E=m.glyphs[f],p=m.xOffsets[f],x=h.size/this.atlasFontSize,T=H(h.color);let y=_;b[y++]=h.nodeIndex,b[y++]=(p+E.bearingX)*x,b[y++]=-E.bearingY*x,b[y++]=E.atlasWidth*x,b[y++]=E.atlasHeight*x,b[y++]=E.atlasX,b[y++]=E.atlasY,b[y++]=E.atlasWidth,b[y++]=E.atlasHeight,b[y++]=T,b[y++]=h.margin,b[y++]=pe[h.position],b[y++]=m.totalWidth*x,b[y++]=h.size,b[y++]=m.verticalCenterOffset*x,b[y++]=m.totalHeight*x,b[y++]=h.labelAngle}processLabel(d,h,c){return this.prepareLabelGlyphs(d,c),super.processLabel(d,h,c)}updateAtlasTexture(){if(!this.atlasNeedsUpdate)return;const d=this.normalProgram.gl,h=this.atlasManager.getTextures();if(h.length===0)return;const c=h[0];d.bindTexture(d.TEXTURE_2D,this.atlasTexture),d.texImage2D(d.TEXTURE_2D,0,d.RGBA,c.width,c.height,0,d.RGBA,d.UNSIGNED_BYTE,c.data),d.bindTexture(d.TEXTURE_2D,null),this.atlasNeedsUpdate=!1}setUniforms(d,h){const{gl:c,uniformLocations:f}=h;c.uniformMatrix3fv(f.u_matrix,!1,d.matrix),c.uniform1f(f.u_sizeRatio,d.sizeRatio),c.uniform1f(f.u_correctionRatio,d.correctionRatio),c.uniform1f(f.u_cameraAngle,d.cameraAngle),c.uniform2f(f.u_resolution,d.width*d.pixelRatio,d.height*d.pixelRatio);const b=this.atlasManager.getTextures();b.length>0?c.uniform2f(f.u_atlasSize,b[0].width,b[0].height):c.uniform2f(f.u_atlasSize,1,1),c.activeTexture(c.TEXTURE0),c.bindTexture(c.TEXTURE_2D,this.atlasTexture),c.uniform1i(f.u_atlas,0),f.u_nodeDataTexture!==void 0&&c.uniform1i(f.u_nodeDataTexture,d.nodeDataTextureUnit),f.u_nodeDataTextureWidth!==void 0&&c.uniform1i(f.u_nodeDataTextureWidth,d.nodeDataTextureWidth),c.uniform1i(f.u_nodeFrameTexture,d.nodeFrameTextureUnit),c.uniform1i(f.u_nodeFrameTextureWidth,d.nodeFrameTextureWidth),c.uniform1f(f.u_gamma,this.gamma),c.uniform1f(f.u_sdfBuffer,this.sdfBuffer),c.uniform1f(f.u_pixelRatio,d.pixelRatio),c.uniform1f(f.u_zoomLabelSizeRatio,1/u.zoomToLabelSizeRatioFunction(d.zoomRatio)),c.uniform1f(f.u_labelPixelSnapping,d.labelPixelSnapping)}renderProgram(d,h){this.updateAtlasTexture(),this.atlasManager.hasPendingGlyphs()&&(this.atlasManager.flush(),this.updateAtlasTexture()),super.renderProgram(d,h)}registerFont(d,h="normal",c="normal"){return this.atlasManager.registerFont({family:d,weight:h,style:c})}getAtlasManager(){return this.atlasManager}measureLabel(d,h,c){const f=c||this.defaultFontKey;this.atlasManager.ensureGlyphs(d,f),this.atlasManager.hasPendingGlyphs()&&this.atlasManager.flush();let b=0,v=0,_=0;for(const E of d){const p=E.codePointAt(0);if(p===void 0)continue;const x=this.atlasManager.getGlyph(p,f);x&&(b+=x.advance,v=Math.max(v,x.bearingY),_=Math.max(_,x.atlasHeight-x.bearingY))}const m=h/this.atlasFontSize;return{width:b*m,height:h,textHeight:(v+_)*m}}ensureGlyphsReady(d,h){const c=h||this.defaultFontKey;for(const f of d)this.atlasManager.ensureGlyphs(f,c);this.atlasManager.flush()}kill(){const d=this.normalProgram.gl;this.atlasTexture&&(d.deleteTexture(this.atlasTexture),this.atlasTexture=null),this.atlasManager.destroy(),this.labelGlyphCache.clear(),super.kill()}};u.labelMargin=n,u.zoomToLabelSizeRatioFunction=o;let l=u;return new l(a,e,t)}const On=`#version 300 es
precision highp float;
in float v_edgeDist;
// R32F target: only the .r channel is stored.
out vec4 fragColor;
void main() {
fragColor = vec4(v_edgeDist, 0.0, 0.0, 0.0);
}
`;function Un(a,e,t){const i=ot(a),r=ge(a).map(h=>`uniform ${h.type} ${h.name};`).join(`
`),{code:n,multiShape:o}=Ga(a,t),s=Ae(a,e),l=Object.keys(s.specs).map(h=>`float v_${h};`).join(`
`),{fetchCode:u,varyingAssignments:g}=Ee(s,{varPrefix:"nodeAttr",baseTexelExpr:"nodeIdx * u_layerAttributeTexelsPerNode",textureWidthUniform:"u_layerAttributeTextureWidth",textureSamplerUniform:"u_layerAttributeTexture"});return`#version 300 es
precision highp float;
uniform float u_cameraAngle;
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform float u_frameTextureWidth;
uniform float u_frameTextureHeight;
uniform sampler2D u_layerAttributeTexture;
uniform int u_layerAttributeTextureWidth;
uniform int u_layerAttributeTexelsPerNode;
${r}
in float a_nodeIndex; // node-data texture index (also the target frame texel)
in float a_positionMode; // 0=right 1=left 2=above 3=below 4=over
in float a_labelAngle; // intrinsic label angle (radians)
out float v_edgeDist;
// Shape attributes (plain globals: this pass is vertex-only)
${l}
${X}
${J}
${i}
${n}
${Ut}
void main() {
int nodeIdx = int(a_nodeIndex);
${u}
${g}
${o?`// Multi-shape: the shape id lives in the node-data texture's .w channel:
g_shapeId = int(readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx).w);`:"// Single-shape mode, shape id not needed"}
// Per-node rotation alignment (0 = viewport, 1 = graph).
vec4 nodeFlags = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, nodeIdx);
float nodeRotation = nodeFlags.r;
float labelRotation = nodeFlags.g;
// Effective angle: intrinsic (style-given) plus the camera angle when the label
// is graph-aligned. The companions place the box at this same angle.
float effectiveAngle = a_labelAngle - labelRotation * u_cameraAngle;
// The "over" mode (4) sits on the node center, so it has no edge distance.
float edgeDist = 0.0;
if (a_positionMode < 4.0) {
vec2 screenDir = getLabelDirection(a_positionMode);
// Rotate by the effective label angle. Inlined rather than via rotate2D()
// because the shape SDFs (getShapeGLSLForShapes) already define that helper
// for multi-shape programs, and redefining it would be a GLSL error.
float ea_c = cos(effectiveAngle);
float ea_s = sin(effectiveAngle);
vec2 rotatedScreenDir = mat2(ea_c, -ea_s, ea_s, ea_c) * screenDir;
// Screen (Y-down) -> SDF (Y-up).
vec2 sdfDir = vec2(rotatedScreenDir.x, -rotatedScreenDir.y);
// Counter-rotate into the shape's local frame for graph-aligned nodes, so the
// boundary is queried against the shape's actual on-screen orientation.
float nodeCa = -u_cameraAngle * nodeRotation;
float nc = cos(nodeCa), ns = sin(nodeCa);
sdfDir = mat2(nc, -ns, ns, nc) * sdfDir;
edgeDist = findEdgeDistance(sdfDir, 1.0);
}
v_edgeDist = edgeDist;
// Scatter to this node's texel center in the frame texture.
float x = mod(a_nodeIndex, u_frameTextureWidth);
float y = floor(a_nodeIndex / u_frameTextureWidth);
vec2 ndc = (vec2(x, y) + 0.5) / vec2(u_frameTextureWidth, u_frameTextureHeight) * 2.0 - 1.0;
gl_Position = vec4(ndc, 0.0, 1.0);
gl_PointSize = 1.0;
}
`}const et=class et{constructor(e,t){this.uniformLocations={};const{shapes:i,layers:r,shapeGlobalIds:n}=t;if(i.length===0)throw new Error("NodeLabelFramePass: at least one shape must be provided");this.gl=e,this.shapeUniforms=ge(i),this.hasAttributeData=Object.keys(Ae(i,r).offsets).length>0,this.vertexShader=it(e,Un(i,r,n)),this.fragmentShader=rt(e,On),this.program=nt(e,[this.vertexShader,this.fragmentShader]);const o=["u_cameraAngle","u_nodeDataTexture","u_nodeDataTextureWidth","u_frameTextureWidth","u_frameTextureHeight","u_layerAttributeTexture","u_layerAttributeTextureWidth","u_layerAttributeTexelsPerNode",...this.shapeUniforms.map(l=>l.name)];for(const l of o)this.uniformLocations[l]=e.getUniformLocation(this.program,l);const s=et.FLOATS_PER_POINT*4;this.vao=e.createVertexArray(),this.buffer=e.createBuffer(),e.bindVertexArray(this.vao),e.bindBuffer(e.ARRAY_BUFFER,this.buffer);for(const[l,u]of[["a_nodeIndex",0],["a_positionMode",4],["a_labelAngle",8]]){const g=e.getAttribLocation(this.program,l);g>=0&&(e.enableVertexAttribArray(g),e.vertexAttribPointer(g,1,e.FLOAT,!1,s,u))}e.bindVertexArray(null)}run(e,t,i,r,n){if(t===0)return;const{gl:o}=this;o.useProgram(this.program),o.bindVertexArray(this.vao),o.bindBuffer(o.ARRAY_BUFFER,this.buffer),o.bufferData(o.ARRAY_BUFFER,e.subarray(0,t*et.FLOATS_PER_POINT),o.DYNAMIC_DRAW);const s=this.uniformLocations;o.uniform1f(s.u_cameraAngle,r.cameraAngle),o.uniform1i(s.u_nodeDataTexture,r.nodeDataTextureUnit),o.uniform1i(s.u_nodeDataTextureWidth,r.nodeDataTextureWidth),o.uniform1f(s.u_frameTextureWidth,i.getTextureWidth()),o.uniform1f(s.u_frameTextureHeight,i.getTextureHeight()),this.hasAttributeData&&n&&(n.bind(ie),s.u_layerAttributeTexture&&o.uniform1i(s.u_layerAttributeTexture,ie),s.u_layerAttributeTextureWidth&&o.uniform1i(s.u_layerAttributeTextureWidth,n.getTextureWidth()),s.u_layerAttributeTexelsPerNode&&o.uniform1i(s.u_layerAttributeTexelsPerNode,n.getTexelsPerItem()));for(const l of this.shapeUniforms)aa(o,this.uniformLocations[l.name],l);i.bindAsRenderTarget(),o.disable(o.BLEND),o.disable(o.DEPTH_TEST),o.drawArrays(o.POINTS,0,t),o.bindFramebuffer(o.FRAMEBUFFER,null),o.bindVertexArray(null)}kill(){const{gl:e}=this;e.deleteProgram(this.program),e.deleteShader(this.vertexShader),e.deleteShader(this.fragmentShader),e.deleteBuffer(this.buffer),e.deleteVertexArray(this.vao)}};et.FLOATS_PER_POINT=3;let St=et;function wi(a,e,t,i,r){var x;const{label:n={},shapes:o}=i;if(o.length===0)throw new Error("createNodeProgram: at least one shape must be provided in 'shapes'");const s={},l=[];let u;o.forEach((T,y)=>{const R=Ca(T);y===0&&(u=R),s[T.name]=y,l[y]=Me(R)});let g=[...i.layers],d=ra({shapes:o,layers:g,shapeGlobalIds:o.length>1?l:void 0,antialias:r});const h=Pi(a,null,t,{label:n}),c=yi(a,null,t,{shapes:o,layers:g,getAttributeTexture:()=>p.getAttributeTexture(),label:n,shapeGlobalIds:o.length>1?l:void 0}),f=Nn(a,e,t,{label:n}),b=i.labelAttachments&&Object.keys(i.labelAttachments).length>0?In(a,null,t,{label:n}):null,v=new St(a,{shapes:o,layers:g,shapeGlobalIds:o.length>1?l:void 0}),_=vt(o,g),m=U(_),E=(x=class extends ae{constructor(y,R,A){super(y,R,A),this.layerLifecycles=new Map,this.layersNeedingRegeneration=new Set,this.attrDescriptors=[],this._pickingBuffer=R;let S=E.layerTextures.get(y);S||(S=new pt(y,m),E.layerTextures.set(y,S)),this.layerAttributeTexture=S;const C=E.textureRefCounts.get(y)||0;E.textureRefCounts.set(y,C+1),this.packedAttributeData=new Float32Array(m.floatsPerItem),g.forEach((D,F)=>{if(D.lifecycle){const P={gl:y,renderer:{refresh:()=>A.refresh()},getUniformLocation:M=>y.getUniformLocation(this.normalProgram.program,M),requestShaderRegeneration:()=>{this.layersNeedingRegeneration.add(F)},requestRefresh:()=>{A.refresh()}},w=D.lifecycle(P);this.layerLifecycles.set(F,w)}}),this.layerLifecycles.forEach(D=>{var F;(F=D.init)==null||F.call(D)});const L=new Map;this.layerLifecycles.forEach((D,F)=>{D.getAttributeData&&L.set(F,D)}),this.attrDescriptors=mt(_,m,L)}getDefinition(){const{FLOAT:y,TRIANGLE_STRIP:R}=WebGL2RenderingContext;return{VERTICES:4,VERTEX_SHADER_SOURCE:d.vertexShader,FRAGMENT_SHADER_SOURCE:d.fragmentShader,METHOD:R,UNIFORMS:d.uniforms,ATTRIBUTES:d.attributes,CONSTANT_ATTRIBUTES:[{name:"a_quadCorner",size:2,type:y}],CONSTANT_DATA:[[-1,-1],[1,-1],[-1,1],[1,1]]}}maybeRegenerateShaders(){if(this.layersNeedingRegeneration.size===0)return;g=g.map((L,D)=>{if(this.layersNeedingRegeneration.has(D)){const F=this.layerLifecycles.get(D);if(F!=null&&F.regenerate)return{...F.regenerate(),lifecycle:L.lifecycle}}return L}),this.layersNeedingRegeneration.clear(),d=ra({shapes:o,layers:g,shapeGlobalIds:o.length>1?l:void 0,antialias:this.renderer.getSetting("antialiasNodes")});const y=this.normalProgram.gl,{program:R,buffer:A,vertexShader:S,fragmentShader:C}=this.normalProgram;y.deleteProgram(R),y.deleteBuffer(A),y.deleteShader(S),y.deleteShader(C),this.normalProgram=this.getProgramInfo("normal",y,d.vertexShader,d.fragmentShader,this._pickingBuffer)}allocateNode(y){this.layerAttributeTexture.allocate(y)}freeNode(y){this.layerAttributeTexture.free(y)}getAttributeTexture(){return this.layerAttributeTexture}uploadLayerTexture(){this.layerAttributeTexture.upload()}process(y,R,A,S,C){let L=R*this.STRIDE;if(A.visibility==="hidden"){for(let D=L+this.STRIDE;L<D;L++)this.floats[L]=0;this.floats[R*this.STRIDE]=di;return}this.processVisibleItem(Ge(y),L,A,S,C)}processVisibleItem(y,R,A,S,C){const{floats:L,ints:D}=this;if(L[R++]=S,D[R++]=y,L[R++]=A.opacity??1,m.floatsPerItem===0)return;const F=this.packedAttributeData;bt(this.attrDescriptors,A,F,A.color,this.layerLifecycles,0),this.layerAttributeTexture.updateAllAttributes(C,F)}setUniforms(y,R){const{gl:A,uniformLocations:S}=R;S.u_matrix&&A.uniformMatrix3fv(S.u_matrix,!1,y.matrix),S.u_sizeRatio&&A.uniform1f(S.u_sizeRatio,y.sizeRatio),S.u_correctionRatio&&A.uniform1f(S.u_correctionRatio,y.correctionRatio),S.u_pickingPadding&&A.uniform1f(S.u_pickingPadding,y.nodePickingPadding),S.u_cameraAngle&&A.uniform1f(S.u_cameraAngle,y.cameraAngle),S.u_nodeDataTexture&&A.uniform1i(S.u_nodeDataTexture,y.nodeDataTextureUnit),S.u_nodeDataTextureWidth&&A.uniform1i(S.u_nodeDataTextureWidth,y.nodeDataTextureWidth),S.u_layerAttributeTexture&&(this.layerAttributeTexture.bind(ie),A.uniform1i(S.u_layerAttributeTexture,ie)),S.u_layerAttributeTextureWidth&&A.uniform1i(S.u_layerAttributeTextureWidth,this.layerAttributeTexture.getTextureWidth()),S.u_layerAttributeTexelsPerNode&&A.uniform1i(S.u_layerAttributeTexelsPerNode,this.layerAttributeTexture.getTexelsPerItem()),o.forEach(C=>{C.uniforms.forEach(L=>{this.setTypedUniform(L,R)})}),g.forEach(C=>{C.uniforms.forEach(L=>{this.setTypedUniform(L,R)})})}render(y,R,A){this.maybeRegenerateShaders(),super.render(y,R,A)}renderProgram(y,R){const{gl:A,program:S}=R;A.useProgram(S),R===this.normalProgram&&this.layerLifecycles.forEach(C=>{var L;(L=C.beforeRender)==null||L.call(C)}),super.renderProgram(y,R)}kill(){this.layerLifecycles.forEach(A=>{var S;(S=A.kill)==null||S.call(A)}),this.layerLifecycles.clear();const y=this.normalProgram.gl,R=(E.textureRefCounts.get(y)||1)-1;R<=0?(this.layerAttributeTexture.kill(),E.layerTextures.delete(y),E.textureRefCounts.delete(y)):E.textureRefCounts.set(y,R),super.kill()}},x.layerTextures=new WeakMap,x.textureRefCounts=new WeakMap,x),p=new E(a,null,t);return{nodeProgram:p,labelProgram:h,backdropProgram:c,labelBackgroundProgram:f,attachmentProgram:b,framePass:v,shapeSlug:u,shapeNameToIndex:o.length>1?s:void 0,shapeGlobalIds:o.length>1?l:void 0}}function j(a){return typeof a=="object"&&a!==null&&"attribute"in a}function Ii(){return{name:"circle",glsl:`
float sdf_circle(vec2 uv, float size) {
return length(uv) - size;
}
`,uniforms:[]}}function zi(a,e){return typeof a=="number"?a:(typeof a=="object"&&a!==null&&"attribute"in a,e)}function $n(a){const{cornerRadius:e,rotation:t}=a??{},i=zi(e,0),r=zi(t,0);return{name:"square",glsl:`
${be}
float sdf_square(vec2 uv, float size, float cornerRadius, float rotation) {
// Apply rotation if needed
vec2 p = uv;
if (rotation != 0.0) {
p = rotate2D(rotation) * p;
}
// Distance to box with given corner radius
// Based on Inigo Quilez's box SDF: https://iquilezles.org/articles/distfunctions2d/
vec2 d = abs(p) - vec2(size - cornerRadius);
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - cornerRadius;
}
`,uniforms:[{name:"u_cornerRadius",type:"float",value:i},{name:"u_rotation",type:"float",value:r}],inradiusFactor:Math.SQRT1_2}}function Mi(a,e){return typeof a=="number"?a:(typeof a=="object"&&a!==null&&"attribute"in a,e)}function Hn(a){const{cornerRadius:e,rotation:t}=a??{},i=Mi(e,0),r=Mi(t,0);return{name:"triangle",glsl:`
${be}
float sdf_triangle(vec2 uv, float size, float cornerRadius, float rotation) {
// Apply rotation if needed
vec2 p = uv;
if (rotation != 0.0) {
p = rotate2D(rotation) * p;
}
// Equilateral triangle SDF
// Based on Inigo Quilez's triangle SDF: https://iquilezles.org/articles/distfunctions2d/
//
// The IQ formula uses parameter 'r' where the triangle has width = 2r.
// For circumradius R (distance from centroid to vertex):
// R = r * 2 / sqrt(3), so r = R * sqrt(3) / 2
const float k = sqrt(3.0);
float r = size * k / 2.0;
p.x = abs(p.x) - r;
p.y = p.y + r / k;
if (p.x + k * p.y > 0.0) {
p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;
}
p.x -= clamp(p.x, -2.0 * r, 0.0);
float dist = -length(p) * sign(p.y);
// Apply corner radius if specified
if (cornerRadius > 0.0) {
dist = dist + cornerRadius;
}
return dist;
}
`,uniforms:[{name:"u_cornerRadius",type:"float",value:i},{name:"u_rotation",type:"float",value:r}],inradiusFactor:.5}}function Ni(a,e){return typeof a=="number"?a:(typeof a=="object"&&a!==null&&"attribute"in a,e)}function Vn(a){const{cornerRadius:e,rotation:t}=a??{},i=Ni(e,0),r=Ni(t,0);return{name:"diamond",glsl:`
${be}
float sdf_diamond(vec2 uv, float size, float cornerRadius, float rotation) {
// Apply rotation if needed
vec2 p = uv;
if (rotation != 0.0) {
p = rotate2D(rotation) * p;
}
// Diamond SDF - using rhombus formula from Inigo Quilez
// https://iquilezles.org/articles/distfunctions2d/
// For a diamond (square rotated 45°), b = (size, size)
vec2 b = vec2(size, -size);
p = abs(p);
float h = clamp((dot(b, p) + b.y * b.y) / dot(b, b), 0.0, 1.0);
p -= b * vec2(h, h - 1.0);
float d = length(p) * sign(p.x);
// Apply corner radius if specified
if (cornerRadius > 0.0) {
d = d + cornerRadius;
}
return d;
}
`,uniforms:[{name:"u_cornerRadius",type:"float",value:i},{name:"u_rotation",type:"float",value:r}],inradiusFactor:Math.SQRT1_2}}function Xn(a){const{aspectRatio:e={attribute:"aspectRatio"},cornerRadius:t,rotation:i}=a??{},r=`
${be}
float sdf_rectangle(vec2 uv, float size, float cornerRadius, float rotation, float aspectRatio) {
// Apply rotation if needed
vec2 p = uv;
if (rotation != 0.0) {
p = rotate2D(rotation) * p;
}
vec2 halfExtent = aspectRatio >= 1.0 ? vec2(size, size / aspectRatio) : vec2(size * aspectRatio, size);
// Keeps thin rectangles from growing past their half extent
float radius = min(cornerRadius, min(halfExtent.x, halfExtent.y));
// Based on Inigo Quilez's box SDF: https://iquilezles.org/articles/distfunctions2d/
vec2 d = abs(p) - (halfExtent - radius);
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
}
`,n=[{name:"u_cornerRadius",type:"float",value:typeof t=="number"?t:0},{name:"u_rotation",type:"float",value:typeof i=="number"?i:0}],o=u=>`0.70710678 / max(${u}, 1.0 / ${u})`;if(!j(e))return{name:"rectangle",glsl:r,uniforms:[...n,{name:"u_aspectRatio",type:"float",value:e}],inradiusFactor:Math.SQRT1_2,inradiusFactorGLSL:o(I(e))};const{attribute:s,default:l=1}=e;return{name:"rectangle",glsl:r,uniforms:n,attributes:[{name:"aspectRatio",size:1,type:WebGL2RenderingContext.FLOAT,source:s}],variables:{[s]:{type:"number",default:l}},inradiusFactor:Math.SQRT1_2,inradiusFactorGLSL:o("v_aspectRatio")}}function ki(a){const{UNSIGNED_BYTE:e}=WebGL2RenderingContext,t=(a==null?void 0:a.color)??{attribute:"color"};if(!j(t)){const o=`
vec4 layer_fill() {
return ${ii(t)};
}
`;return{name:"fill",uniforms:[],attributes:[],glsl:o}}const i=t.attribute;return{name:"fill",uniforms:[],attributes:[{name:"fillColor",size:4,type:e,normalized:!0,source:i}],glsl:`
vec4 layer_fill(vec4 v_fillColor) {
return v_fillColor;
}
`}}const ne=6;function Oe(a){const{offsets:e,specs:t,floatsPerItem:i}=a,r=Object.keys(e);if(r.length===0||i===0)return{uniformDeclarations:"",uniformNames:[],vertexVaryingDeclarations:"",fragmentVaryingDeclarations:"",fetchCode:"",varyingAssignments:""};const n=`
uniform sampler2D u_edgeAttributeTexture;
uniform int u_edgeAttributeTextureWidth;
uniform int u_edgeAttributeTexelsPerEdge;`,o=["u_edgeAttributeTexture","u_edgeAttributeTextureWidth","u_edgeAttributeTexelsPerEdge"],s=r.map(h=>`${t[h].size===1?"float":`vec${t[h].size}`} v_${h};`),l=s.map(h=>`out ${h}`).join(`
`),u=s.map(h=>`in ${h}`).join(`
`),{fetchCode:g,varyingAssignments:d}=Ee(a,{varPrefix:"attr",baseTexelExpr:"edgeIdx * u_edgeAttributeTexelsPerEdge",textureWidthUniform:"u_edgeAttributeTextureWidth",textureSamplerUniform:"u_edgeAttributeTexture"});return{uniformDeclarations:n,uniformNames:o,vertexVaryingDeclarations:l,fragmentVaryingDeclarations:u,fetchCode:g,varyingAssignments:d}}function qn(a){return`
float findSourceClampT_${a}(vec2 source, float sourceSize, int sourceShapeId, float sourceRotateAlign, vec2 target, float margin) {
float lo = 0.0, hi = 0.5;
float nodeExtent = sourceSize * u_correctionRatio / u_sizeRatio * 2.0;
float effectiveSize = 1.0 - u_correctionRatio / nodeExtent;
// Counter-rotate the query point so viewport-aligned nodes (rotateAlign=0) are
// clamped against their on-screen orientation; graph-aligned nodes skip it.
float ca = u_cameraAngle * (1.0 - sourceRotateAlign);
float rc = cos(ca), rs = sin(ca);
mat2 rot = mat2(rc, -rs, rs, rc);
for (int i = 0; i < 12; i++) {
float mid = (lo + hi) * 0.5;
vec2 pos = path_${a}_position(mid, source, target);
vec2 localPos = rot * ((pos - source) / nodeExtent);
float sdf = querySDF(sourceShapeId, localPos, effectiveSize);
if (sdf < 0.0) lo = mid;
else hi = mid;
}
float pathLen = path_${a}_length(source, target);
float marginT = (margin * u_correctionRatio / u_sizeRatio) / pathLen;
return (lo + hi) * 0.5 + marginT;
}
`}function jn(a){return`
float findTargetClampT_${a}(vec2 source, vec2 target, float targetSize, int targetShapeId, float targetRotateAlign, float margin) {
float lo = 0.5, hi = 1.0;
float nodeExtent = targetSize * u_correctionRatio / u_sizeRatio * 2.0;
float effectiveSize = 1.0 - u_correctionRatio / nodeExtent;
// See findSourceClampT_ for the rotation rationale.
float ca = u_cameraAngle * (1.0 - targetRotateAlign);
float rc = cos(ca), rs = sin(ca);
mat2 rot = mat2(rc, -rs, rs, rc);
for (int i = 0; i < 12; i++) {
float mid = (lo + hi) * 0.5;
vec2 pos = path_${a}_position(mid, source, target);
vec2 localPos = rot * ((pos - target) / nodeExtent);
float sdf = querySDF(targetShapeId, localPos, effectiveSize);
if (sdf < 0.0) hi = mid;
else lo = mid;
}
float pathLen = path_${a}_length(source, target);
float marginT = (margin * u_correctionRatio / u_sizeRatio) / pathLen;
return (lo + hi) * 0.5 - marginT;
}
`}function Gi(a){return`
// Auto-generated numerical tangent (from position via finite differences)
vec2 path_${a}_tangent(float t, vec2 source, vec2 target) {
float epsilon = 0.001;
float t1 = max(0.0, t - epsilon);
float t2 = min(1.0, t + epsilon);
vec2 p1 = path_${a}_position(t1, source, target);
vec2 p2 = path_${a}_position(t2, source, target);
return normalize(p2 - p1);
}
// Auto-generated normal (perpendicular to tangent)
vec2 path_${a}_normal(float t, vec2 source, vec2 target) {
vec2 tangent = path_${a}_tangent(t, source, target);
return vec2(-tangent.y, tangent.x);
}
`}function Wi(a){return`
// Rotate a 2D vector by angle (counter-clockwise)
vec2 ${a}_rotate(vec2 v, float angle) {
float c = cos(angle);
float s = sin(angle);
return vec2(c * v.x - s * v.y, s * v.x + c * v.y);
}
`}function Yn(a){return`
// Auto-generated path length (samples position 16 times)
float path_${a}_length(vec2 source, vec2 target) {
float len = 0.0;
vec2 prev = path_${a}_position(0.0, source, target);
for (int i = 1; i <= 16; i++) {
float t = float(i) / 16.0;
vec2 curr = path_${a}_position(t, source, target);
len += length(curr - prev);
prev = curr;
}
return len;
}
`}function Kn(a){return`
// Auto-generated closest_t (coarse sample + ternary search)
float path_${a}_closest_t(vec2 p, vec2 source, vec2 target) {
// Coarse search: find best among 10 samples
float bestT = 0.0;
float bestDist = 1e10;
for (int i = 0; i <= 10; i++) {
float t = float(i) / 10.0;
vec2 pos = path_${a}_position(t, source, target);
float d = length(p - pos);
if (d < bestDist) {
bestDist = d;
bestT = t;
}
}
// Refine with ternary search
float lo = max(0.0, bestT - 0.1);
float hi = min(1.0, bestT + 0.1);
for (int i = 0; i < 10; i++) {
float mid1 = lo + (hi - lo) / 3.0;
float mid2 = hi - (hi - lo) / 3.0;
float d1 = length(p - path_${a}_position(mid1, source, target));
float d2 = length(p - path_${a}_position(mid2, source, target));
if (d1 < d2) {
hi = mid2;
} else {
lo = mid1;
}
}
return (lo + hi) * 0.5;
}
`}function Zn(a){return`
// Auto-generated signed distance (via closest_t + normal)
float path_${a}_distance(vec2 p, vec2 source, vec2 target) {
float closestT = path_${a}_closest_t(p, source, target);
vec2 closest = path_${a}_position(closestT, source, target);
vec2 diff = p - closest;
float dist = length(diff);
if (dist < 0.0001) return 0.0;
// Get normal at closest point
vec2 normal = path_${a}_normal(closestT, source, target);
return dist * sign(dot(diff, normal));
}
`}function Qn(a){return`
// Auto-generated t_at_distance (binary search)
float path_${a}_t_at_distance(float targetDist, vec2 source, vec2 target) {
if (targetDist <= 0.0) return 0.0;
float totalLen = path_${a}_length(source, target);
if (targetDist >= totalLen) return 1.0;
// Binary search for t
float lo = 0.0, hi = 1.0;
for (int i = 0; i < 12; i++) {
float mid = (lo + hi) * 0.5;
// Compute arc length from 0 to mid
float arcLen = 0.0;
vec2 prev = path_${a}_position(0.0, source, target);
for (int j = 1; j <= 8; j++) {
float t = mid * float(j) / 8.0;
vec2 curr = path_${a}_position(t, source, target);
arcLen += length(curr - prev);
prev = curr;
}
if (arcLen < targetDist) {
lo = mid;
} else {
hi = mid;
}
}
return (lo + hi) * 0.5;
}
`}function Et(a,e){return new RegExp(`\\b(float|vec[234]|void|int|bool)\\s+${e}\\s*\\(`).test(a)}function Bi(a,e){const t=[];return Et(e,`path_${a}_length`)||t.push(Yn(a)),Et(e,`path_${a}_closest_t`)||t.push(Kn(a)),Et(e,`path_${a}_distance`)||t.push(Zn(a)),Et(e,`path_${a}_t_at_distance`)||t.push(Qn(a)),t.length>0?`
// ============================================================================
// Auto-generated fallback functions (path only provided position)
// ============================================================================
${t.join(`
`)}`:""}function Oi(a){const{paths:e,layers:t}=a,i=a.extremities??[];if(e.length===0)throw new Error("At least one path is required in 'paths'");if(t.length===0)throw new Error("At least one layer is required in 'layers'");const r=a.defaultHead??"none",n=a.defaultTail??"none";return{paths:e,extremities:i,layers:t,path:e[0],layer:t[0],defaultHead:r,defaultTail:n}}const{FLOAT:Ue,UNSIGNED_BYTE:Ui}=WebGL2RenderingContext;function Jn(a,e,t){const i=new Set(["u_matrix","u_sizeRatio","u_correctionRatio","u_zoomRatio","u_pixelRatio","u_cameraAngle","u_minEdgeThickness","u_pickingPadding","u_nodeDataTexture","u_nodeDataTextureWidth","u_edgeDataTexture","u_edgeDataTextureWidth","u_edgeFrameTexture","u_edgeFrameTextureWidth","u_edgeAttributeTexture","u_edgeAttributeTextureWidth","u_edgeAttributeTexelsPerEdge"]),r=new Set(i);return a.forEach(n=>n.uniforms.forEach(o=>r.add(o.name))),e.forEach(n=>n.uniforms.forEach(o=>r.add(o.name))),t.forEach(n=>n.uniforms.forEach(o=>r.add(o.name))),Array.from(r)}function oa(a){const e=new Set,t=[];for(const i of a)e.has(i.name)||(e.add(i.name),t.push(`// Path: ${i.name}`),t.push(i.glsl),t.push(Gi(i.name)),t.push(Bi(i.name,i.glsl)));return t.join(`
`)}function eo(a){const e=[];for(const t of a)e.push(`// Extremity: ${t.name}`),e.push(t.glsl);return e.join(`
`)}const to=[{queryName:"queryPathPosition",pathFunc:"position",returnType:"vec2",params:"float t, vec2 source, vec2 target",args:"t, source, target"},{queryName:"queryPathTangent",pathFunc:"tangent",returnType:"vec2",params:"float t, vec2 source, vec2 target",args:"t, source, target"},{queryName:"queryPathNormal",pathFunc:"normal",returnType:"vec2",params:"float t, vec2 source, vec2 target",args:"t, source, target"},{queryName:"queryPathLength",pathFunc:"length",returnType:"float",params:"vec2 source, vec2 target",args:"source, target"},{queryName:"queryPathClosestT",pathFunc:"closest_t",returnType:"float",params:"vec2 p, vec2 source, vec2 target",args:"p, source, target"}];function ao(a,e){const{queryName:t,pathFunc:i,returnType:r,params:n,args:o}=e;if(a.length===1)return`${r} ${t}(int pathId, ${n}) {
return path_${a[0].name}_${i}(${o});
}`;const s=a.map((l,u)=>` case ${u}: return path_${l.name}_${i}(${o});`).join(`
`);return`${r} ${t}(int pathId, ${n}) {
switch (pathId) {
${s}
default: return path_${a[0].name}_${i}(${o});
}
}`}function sa(a){return to.map(e=>ao(a,e)).join(`
`)}function io(a){return a.length===1?`float queryExtremitySDF(int extremityId, vec2 uv, float lengthRatio, float widthRatio) {
return extremity_${a[0].name}(uv, lengthRatio, widthRatio);
}`:`float queryExtremitySDF(int extremityId, vec2 uv, float lengthRatio, float widthRatio) {
switch (extremityId) {
${a.map((t,i)=>` case ${i}: return extremity_${t.name}(uv, lengthRatio, widthRatio);`).join(`
`)}
default: return extremity_${a[0].name}(uv, lengthRatio, widthRatio);
}
}`}function ro(a){const e=[];for(const t of a)e.push(qn(t.name)),e.push(jn(t.name));if(a.length===1)e.push(`
float queryFindSourceClampT(int pathId, vec2 source, float sourceSize, int sourceShapeId, float sourceRotateAlign, vec2 target, float margin) {
return findSourceClampT_${a[0].name}(source, sourceSize, sourceShapeId, sourceRotateAlign, target, margin);
}
float queryFindTargetClampT(int pathId, vec2 source, vec2 target, float targetSize, int targetShapeId, float targetRotateAlign, float margin) {
return findTargetClampT_${a[0].name}(source, target, targetSize, targetShapeId, targetRotateAlign, margin);
}`);else{const t=a.map((r,n)=>` case ${n}: return findSourceClampT_${r.name}(source, sourceSize, sourceShapeId, sourceRotateAlign, target, margin);`).join(`
`),i=a.map((r,n)=>` case ${n}: return findTargetClampT_${r.name}(source, target, targetSize, targetShapeId, targetRotateAlign, margin);`).join(`
`);e.push(`
float queryFindSourceClampT(int pathId, vec2 source, float sourceSize, int sourceShapeId, float sourceRotateAlign, vec2 target, float margin) {
switch (pathId) {
${t}
default: return findSourceClampT_${a[0].name}(source, sourceSize, sourceShapeId, sourceRotateAlign, target, margin);
}
}
float queryFindTargetClampT(int pathId, vec2 source, vec2 target, float targetSize, int targetShapeId, float targetRotateAlign, float margin) {
switch (pathId) {
${i}
default: return findTargetClampT_${a[0].name}(source, target, targetSize, targetShapeId, targetRotateAlign, margin);
}
}`)}return e.join(`
`)}function no(a,e,t,i,r){const n=U([...a,...t]),o=Oe(n),s=Ae(i,r),l=Object.keys(s.offsets),u=T=>{const{size:y}=s.specs[T];return y===1?"float":`vec${y}`},g=l.map(T=>`${u(T)} v_source_${T};
${u(T)} v_target_${T};`).join(`
`),d=Ee(s,{varPrefix:"srcNodeAttr",baseTexelExpr:"srcIdx * u_layerAttributeTexelsPerNode",textureWidthUniform:"u_layerAttributeTextureWidth",textureSamplerUniform:"u_layerAttributeTexture",outputPrefix:"v_source_"}),h=Ee(s,{varPrefix:"tgtNodeAttr",baseTexelExpr:"tgtIdx * u_layerAttributeTexelsPerNode",textureWidthUniform:"u_layerAttributeTextureWidth",textureSamplerUniform:"u_layerAttributeTexture",outputPrefix:"v_target_"}),c=l.map(T=>`${u(T)} g_${T};`).join(`
`),f=l.map(T=>` g_${T} = v_source_${T};`).join(`
`),b=l.map(T=>` g_${T} = v_target_${T};`).join(`
`),v=new Set,_=[],m=T=>{v.has(T.name)||(v.add(T.name),_.push(`uniform ${T.type} ${T.name};`))};a.forEach(T=>T.uniforms.forEach(m)),e.forEach(T=>T.uniforms.forEach(m));const E=e.map(T=>I(T.widthFactor)).join(", "),p=Math.max(...a.map(T=>T.minBodyLengthRatio||0));return`#version 300 es
// Node and edge data textures
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_edgeDataTexture;
uniform int u_edgeDataTextureWidth;
// Edge attribute texture (for path-specific attributes like curvature)
${o.uniformDeclarations}
// Node attribute texture (for node shape attributes)
uniform sampler2D u_layerAttributeTexture;
uniform int u_layerAttributeTextureWidth;
uniform int u_layerAttributeTexelsPerNode;
// Render params needed for clamping
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
uniform float u_minEdgeThickness;
// Edge-frame texture dimensions, for scattering each point to its texel
uniform float u_frameTextureWidth;
uniform float u_frameTextureHeight;
// Custom path/extremity uniforms
${_.join(`
`)}
// Path attribute varyings — assigned so path functions can read them as globals
${o.vertexVaryingDeclarations}
// Node size varyings — read by path functions like loop
out float v_sourceNodeSize;
out float v_targetNodeSize;
// Node shape attributes per endpoint, and the ones querySDF reads
${g}
${c}
// Scattered output written to the edge-frame texel: [tStart, tEnd, straightenFactor, pathLength]
out vec4 v_clamp;
// Extremity width factor array (needed for extremityScale computation)
const float EXTREMITY_WIDTH_FACTORS[${e.length}] = float[](${E});
// Node-data fetch helpers (geometry texel + rotation-flags texel)
${X}
${J}
// Shape SDFs for node boundary clamping
${Fa()}
${Pa(new Set(l))}
// Path functions
${oa(a)}
${sa(a)}
${ro(a)}
void main() {
// One point per edge-data row; the row index is the edge-frame texel target.
int edgeIdx = gl_VertexID;
int texel0Idx = edgeIdx * 2;
int texel1Idx = edgeIdx * 2 + 1;
ivec2 edgeTexCoord0 = ivec2(texel0Idx % u_edgeDataTextureWidth, texel0Idx / u_edgeDataTextureWidth);
ivec2 edgeTexCoord1 = ivec2(texel1Idx % u_edgeDataTextureWidth, texel1Idx / u_edgeDataTextureWidth);
vec4 edgeData0 = texelFetch(u_edgeDataTexture, edgeTexCoord0, 0);
vec4 edgeData1 = texelFetch(u_edgeDataTexture, edgeTexCoord1, 0);
int srcIdx = int(edgeData0.x);
int tgtIdx = int(edgeData0.y);
float a_thickness = edgeData0.z;
float a_headLengthRatio = edgeData1.x;
float a_tailLengthRatio = edgeData1.y;
int pathId = int(edgeData1.z);
int extremityPacked = int(edgeData1.w);
int headId = extremityPacked >> 4;
int tailId = extremityPacked & 15;
// Fetch path/layer attributes and assign to path-function globals
${o.fetchCode}
${o.varyingAssignments}
// Fetch node shape attributes for both endpoints
${d.fetchCode}
${d.varyingAssignments}
${h.fetchCode}
${h.varyingAssignments}
// Fetch node geometry (texel 0) and rotation flags (texel 1).
vec4 srcNodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, srcIdx);
vec4 tgtNodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, tgtIdx);
vec2 a_source = srcNodeData.xy;
vec2 a_target = tgtNodeData.xy;
float a_sourceSize = srcNodeData.z;
float a_targetSize = tgtNodeData.z;
float a_sourceShapeId = srcNodeData.w;
float a_targetShapeId = tgtNodeData.w;
// Per-node rotation alignment (0 = viewport, 1 = graph), for boundary clamping.
float a_sourceRotateAlign = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, srcIdx).r;
float a_targetRotateAlign = readNodeFlags(u_nodeDataTexture, u_nodeDataTextureWidth, tgtIdx).r;
v_sourceNodeSize = a_sourceSize;
v_targetNodeSize = a_targetSize;
float headLengthRatio = a_headLengthRatio;
float tailLengthRatio = a_tailLengthRatio;
float headWidthFactor = EXTREMITY_WIDTH_FACTORS[headId];
float tailWidthFactor = EXTREMITY_WIDTH_FACTORS[tailId];
float minBodyLengthRatio = ${I(p)};
// Thickness in WebGL units (needed to compute clamping margin and extremity lengths)
float pixelsThickness = max(a_thickness, u_minEdgeThickness * u_sizeRatio);
float webGLThickness = pixelsThickness * u_correctionRatio / u_sizeRatio;
// SDF clamping: find where the edge body meets the node boundaries. Always
// searched (ungated) so labels get a true boundary clamp; the body re-applies
// its extremity gating in-shader.
${f}
float tStart = queryFindSourceClampT(pathId, a_source, a_sourceSize, int(a_sourceShapeId), a_sourceRotateAlign, a_target, 0.0);
${b}
float tEnd = queryFindTargetClampT(pathId, a_source, a_target, a_targetSize, int(a_targetShapeId), a_targetRotateAlign, 0.0);
// straightenFactor (frame .z) is consumed only by the body, which runs to the
// node center when an extremity is absent. Derive the straightening math from
// these gated clamps — not the ungated boundary ones above — so it matches the
// geometry it drives. The ungated tStart/tEnd remain what labels read.
float bodyTStart = tailLengthRatio > 0.0 ? tStart : 0.0;
float bodyTEnd = headLengthRatio > 0.0 ? tEnd : 1.0;
// Path length and zone boundaries (needed for straightening check)
float pathLength = queryPathLength(pathId, a_source, a_target);
float visibleLength = pathLength * (bodyTEnd - bodyTStart);
float headLength = headLengthRatio * webGLThickness;
float tailLength = tailLengthRatio * webGLThickness;
float minBodyLength = minBodyLengthRatio * webGLThickness;
float totalNeededLength = headLength + tailLength + minBodyLength;
float extremityScale = 1.0;
if (totalNeededLength > visibleLength && totalNeededLength > 0.0001) {
extremityScale = visibleLength / totalNeededLength;
headLength *= extremityScale;
tailLength *= extremityScale;
}
float headLengthT = pathLength > 0.0001 ? headLength / pathLength : 0.0;
float tailLengthT = pathLength > 0.0001 ? tailLength / pathLength : 0.0;
float tTailEnd = bodyTStart + tailLengthT;
float tHeadStart = bodyTEnd - headLengthT;
if (tTailEnd > tHeadStart) {
float mid = (bodyTStart + bodyTEnd) * 0.5;
tTailEnd = mid;
tHeadStart = mid;
}
// Straighten factor: blend toward straight line when path twists in extremity zones
float straightenFactor = 0.0;
{
float maxDeviation = 0.0;
if (tailLengthT > 0.0001) {
vec2 tailTang = queryPathTangent(pathId, tTailEnd, a_source, a_target);
vec2 tailChord = queryPathPosition(pathId, bodyTStart, a_source, a_target)
- queryPathPosition(pathId, tTailEnd, a_source, a_target);
float tailChordLen = length(tailChord);
if (tailChordLen > 0.0001) {
maxDeviation = max(maxDeviation, 1.0 - dot(-tailTang, tailChord / tailChordLen));
}
}
if (headLengthT > 0.0001) {
vec2 headTang = queryPathTangent(pathId, tHeadStart, a_source, a_target);
vec2 headChord = queryPathPosition(pathId, bodyTEnd, a_source, a_target)
- queryPathPosition(pathId, tHeadStart, a_source, a_target);
float headChordLen = length(headChord);
if (headChordLen > 0.0001) {
maxDeviation = max(maxDeviation, 1.0 - dot(headTang, headChord / headChordLen));
}
}
straightenFactor = smoothstep(0.035, 0.5, maxDeviation);
}
// When straightening, blend the ungated tStart/tEnd (the label-facing clamps)
// toward straight-line clamp positions.
if (straightenFactor > 0.001) {
if (tailLengthRatio > 0.0) {
${f}
float srcExtent = a_sourceSize * u_correctionRatio / u_sizeRatio * 2.0;
float srcEffective = 1.0 - u_correctionRatio / srcExtent;
float srcCa = u_cameraAngle * (1.0 - a_sourceRotateAlign);
mat2 srcRot = mat2(cos(srcCa), -sin(srcCa), sin(srcCa), cos(srcCa));
float lo = 0.0, hi = 0.5;
for (int i = 0; i < 12; i++) {
float mid = (lo + hi) * 0.5;
vec2 pos = mix(a_source, a_target, mid);
vec2 localPos = srcRot * ((pos - a_source) / srcExtent);
float sdf = querySDF(int(a_sourceShapeId), localPos, srcEffective);
if (sdf < 0.0) lo = mid; else hi = mid;
}
tStart = mix(tStart, (lo + hi) * 0.5, straightenFactor);
}
if (headLengthRatio > 0.0) {
${b}
float tgtExtent = a_targetSize * u_correctionRatio / u_sizeRatio * 2.0;
float tgtEffective = 1.0 - u_correctionRatio / tgtExtent;
float tgtCa = u_cameraAngle * (1.0 - a_targetRotateAlign);
mat2 tgtRot = mat2(cos(tgtCa), -sin(tgtCa), sin(tgtCa), cos(tgtCa));
float lo = 0.5, hi = 1.0;
for (int i = 0; i < 12; i++) {
float mid = (lo + hi) * 0.5;
vec2 pos = mix(a_source, a_target, mid);
vec2 localPos = tgtRot * ((pos - a_target) / tgtExtent);
float sdf = querySDF(int(a_targetShapeId), localPos, tgtEffective);
if (sdf < 0.0) hi = mid; else lo = mid;
}
tEnd = mix(tEnd, (lo + hi) * 0.5, straightenFactor);
}
}
v_clamp = vec4(tStart, tEnd, straightenFactor, pathLength);
// Scatter this point to its edge's texel center in the frame texture.
float x = mod(float(edgeIdx), u_frameTextureWidth);
float y = floor(float(edgeIdx) / u_frameTextureWidth);
vec2 ndc = (vec2(x, y) + 0.5) / vec2(u_frameTextureWidth, u_frameTextureHeight) * 2.0 - 1.0;
gl_Position = vec4(ndc, 0.0, 1.0);
gl_PointSize = 1.0;
}
`}const Rt=0,$i=1,At=2;function oo(a,e,t){const i=[];t&&(i.push([Rt,0,-1],[Rt,0,1]),i.push([Rt,1,-1],[Rt,1,1]));for(let r=0;r<=a;r++){const n=r/a;i.push([$i,n,-1],[$i,n,1])}return e&&(i.push([At,0,-1],[At,0,1]),i.push([At,1,-1],[At,1,1])),{data:i,attributes:[{name:"a_zone",size:1,type:Ue},{name:"a_zoneT",size:1,type:Ue},{name:"a_side",size:1,type:Ue}],verticesPerEdge:i.length}}function so(a,e,t,i){const r=U([...a,...t]),n=Oe(r),o=new Set(["u_matrix","u_sizeRatio","u_correctionRatio","u_zoomRatio","u_pixelRatio","u_cameraAngle","u_minEdgeThickness","u_pickingPadding","u_nodeDataTexture"]),s=new Set,l=[],u=v=>{!o.has(v.name)&&!s.has(v.name)&&(s.add(v.name),l.push(`uniform ${v.type} ${v.name};`))};a.forEach(v=>v.uniforms.forEach(u)),e.forEach(v=>v.uniforms.forEach(u)),t.forEach(v=>v.uniforms.forEach(u));const g=i.map(v=>`in ${v.size===1?"float":`vec${v.size}`} ${v.name};`).join(`
`),d=e.map(v=>I(v.widthFactor)).join(", "),h=Math.max(...a.map(v=>v.minBodyLengthRatio||0)),c=t.some(v=>v.needsNodeColors),f=a.some(v=>v.needsNodeSize);return`#version 300 es
// Constant attributes (per vertex)
${g}
// Per-edge attributes
// Edge data (source/target indices, thickness, extremity ratios, path/extremity IDs)
// is fetched from edge data texture via edge index
in float a_edgeIndex; // Index into edge data texture
in vec4 a_color; // Edge color
in vec4 a_id; // Edge ID for picking
in float a_opacity; // Edge opacity
// Standard uniforms
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_zoomRatio;
uniform float u_pixelRatio;
uniform float u_cameraAngle;
uniform float u_minEdgeThickness;
#ifdef PICKING_MODE
uniform float u_pickingPadding;
#endif
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_edgeDataTexture;
uniform int u_edgeDataTextureWidth;
uniform sampler2D u_edgeFrameTexture;
uniform int u_edgeFrameTextureWidth;
// Edge path attribute texture uniforms
${n.uniformDeclarations}
// Custom uniforms
${l.join(`
`)}
// Standard varyings
out vec4 v_color;
out float v_opacity;
out vec4 v_id;
out float v_thickness; // Edge body thickness (in consistent units)
out float v_t;
out float v_tStart;
out float v_tEnd;
out float v_side;
out float v_antialiasingWidth; // Anti-aliasing width (normalized: u_correctionRatio / thickness)
out vec2 v_source;
out vec2 v_target;
out float v_edgeLength;
${f?`
out float v_sourceNodeSize; // Source node size (mirrored in labels/generator.ts as plain float)
out float v_targetNodeSize; // Target node size (mirrored in labels/generator.ts as plain float)`:""}
${c?`
out vec4 v_sourceColor;
out vec4 v_targetColor;`:""}
// Zone varyings
out float v_zone; // 0=tail, 1=body, 2=head
out float v_zoneT; // Position within zone [0,1]
out float v_headLengthRatio; // Head length as ratio of thickness
out float v_tailLengthRatio; // Tail length as ratio of thickness
out float v_headWidthRatio; // Head width factor
out float v_tailWidthRatio; // Tail width factor
// Multi-path/extremity varyings
flat out int v_pathId;
flat out int v_headId;
flat out int v_tailId;
// Path/layer attribute varyings (fetched from edge attribute texture)
${n.vertexVaryingDeclarations}
const float bias = 255.0 / 254.0;
// Width factor array for extremities (shared pool for head/tail)
const float EXTREMITY_WIDTH_FACTORS[${e.length}] = float[](${d});
// All path functions
${oa(a)}
// Path selector functions
${sa(a)}
// Node-data fetch helper (geometry texel of the two-texel node stride)
${X}
${c?ka:""}
// Per-edge clamp from the frame-pass (tStart, tEnd, straightenFactor, pathLength)
${se}
void main() {
// Fetch edge data from edge texture (2 texels per edge)
// Texel 0: sourceNodeIndex, targetNodeIndex, thickness, reserved
// Texel 1: headLengthRatio, tailLengthRatio, pathId, (headId << 4) | tailId
int edgeIdx = int(a_edgeIndex);
// Hidden edges are flagged with a negative row by EdgeProgram.process(). Push
// the whole primitive outside the clip volume: every vertex lands at the same
// out-of-range position, so it is fully clipped and rasterizes nothing —
// neither to the frame buffer nor to the picking buffer.
if (edgeIdx < 0) {
gl_Position = vec4(2.0, 0.0, 0.0, 1.0);
v_color = vec4(0.0);
v_id = vec4(0.0);
return;
}
int texel0Idx = edgeIdx * 2;
int texel1Idx = edgeIdx * 2 + 1;
ivec2 edgeTexCoord0 = ivec2(texel0Idx % u_edgeDataTextureWidth, texel0Idx / u_edgeDataTextureWidth);
ivec2 edgeTexCoord1 = ivec2(texel1Idx % u_edgeDataTextureWidth, texel1Idx / u_edgeDataTextureWidth);
vec4 edgeData0 = texelFetch(u_edgeDataTexture, edgeTexCoord0, 0);
vec4 edgeData1 = texelFetch(u_edgeDataTexture, edgeTexCoord1, 0);
// Unpack edge data
int srcIdx = int(edgeData0.x);
int tgtIdx = int(edgeData0.y);
float a_thickness = edgeData0.z;
// edgeData0.w is now reserved (curvature moved to path attribute texture)
float a_headLengthRatio = edgeData1.x;
float a_tailLengthRatio = edgeData1.y;
int pathId = int(edgeData1.z);
int extremityPacked = int(edgeData1.w);
int headId = extremityPacked >> 4;
int tailId = extremityPacked & 15;
// Fetch path/layer attributes from edge attribute texture
${n.fetchCode}
// Assign path/layer attribute varyings
${n.varyingAssignments}
// Fetch source and target node geometry (texel 0 of the two-texel node stride)
vec4 srcNodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, srcIdx);
vec4 tgtNodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, tgtIdx);
vec2 a_source = srcNodeData.xy;
vec2 a_target = tgtNodeData.xy;
${f?`
// Assign node size varyings early (path functions like loops need them during clamping)
v_sourceNodeSize = srcNodeData.z;
v_targetNodeSize = tgtNodeData.z;`:""}
${c?`
v_sourceColor = readNodeColor(u_nodeDataTexture, u_nodeDataTextureWidth, srcIdx);
v_targetColor = readNodeColor(u_nodeDataTexture, u_nodeDataTextureWidth, tgtIdx);`:""}
// Convert thickness to WebGL units
float minThickness = u_minEdgeThickness;
float pixelsThickness = max(a_thickness, minThickness * u_sizeRatio);
float webGLThickness = pixelsThickness * u_correctionRatio / u_sizeRatio;
// Extremity parameters from ID lookups (shared pool)
float headLengthRatio = a_headLengthRatio;
float tailLengthRatio = a_tailLengthRatio;
float headWidthFactor = EXTREMITY_WIDTH_FACTORS[headId];
float tailWidthFactor = EXTREMITY_WIDTH_FACTORS[tailId];
float minBodyLengthRatio = ${I(h)};
// Per-edge values from the frame-pass texture (read by edge index).
// tStart/tEnd are the ungated boundary clamps: re-apply the body's extremity
// gating here (no extremity → body runs to the node center, 0/1).
vec4 frameClamp = readFrameTexel(u_edgeFrameTexture, u_edgeFrameTextureWidth, edgeIdx);
float tStart = a_tailLengthRatio > 0.0 ? frameClamp.x : 0.0;
float tEnd = a_headLengthRatio > 0.0 ? frameClamp.y : 1.0;
float straightenFactor = frameClamp.z;
float pathLength = frameClamp.w;
// Anti-aliasing width (~1 pixel, normalized by thickness)
float antialiasingWidth = u_correctionRatio / webGLThickness;
float visibleLength = pathLength * (tEnd - tStart);
// Compute extremity lengths in world units
float headLength = headLengthRatio * webGLThickness;
float tailLength = tailLengthRatio * webGLThickness;
float minBodyLength = minBodyLengthRatio * webGLThickness;
// Handle short edges: scale down extremities if needed
float totalNeededLength = headLength + tailLength + minBodyLength;
float extremityScale = 1.0;
if (totalNeededLength > visibleLength && totalNeededLength > 0.0001) {
extremityScale = visibleLength / totalNeededLength;
headLength *= extremityScale;
tailLength *= extremityScale;
}
// Convert lengths to t-values
float headLengthT = pathLength > 0.0001 ? headLength / pathLength : 0.0;
float tailLengthT = pathLength > 0.0001 ? tailLength / pathLength : 0.0;
// Zone boundaries in t-space
float tTailEnd = tStart + tailLengthT;
float tHeadStart = tEnd - headLengthT;
// Ensure body has non-negative length
if (tTailEnd > tHeadStart) {
float mid = (tStart + tEnd) * 0.5;
tTailEnd = mid;
tHeadStart = mid;
}
// Convert to webGL units for geometry expansion
float aaWidthWebGL = antialiasingWidth * webGLThickness;
// Extra geometry width for picking padding (0 in visual mode)
#ifdef PICKING_MODE
float pickingPaddingWebGL = u_pickingPadding * u_correctionRatio;
#else
float pickingPaddingWebGL = 0.0;
#endif
// Straight-line direction and normal (for blending when straightenFactor > 0)
vec2 straightDir = length(a_target - a_source) > 0.0001
? normalize(a_target - a_source) : vec2(1.0, 0.0);
vec2 straightNormal = vec2(-straightDir.y, straightDir.x);
// Zone-based vertex processing using path selectors
vec2 position;
vec2 normal;
float t;
float zone = a_zone;
float zoneT = a_zoneT;
float side = a_side;
// Scaled extremity width factors (geometry must be at least as wide as body)
float scaledTailWidth = max(tailWidthFactor * extremityScale, 1.0);
float scaledHeadWidth = max(headWidthFactor * extremityScale, 1.0);
if (zone < 0.5) {
// TAIL ZONE: rectangular quad with scaled width
vec2 tang = queryPathTangent(pathId, tTailEnd, a_source, a_target);
normal = vec2(-tang.y, tang.x);
vec2 centerPos = mix(queryPathPosition(pathId, tStart, a_source, a_target),
queryPathPosition(pathId, tTailEnd, a_source, a_target), zoneT);
float halfWidth = webGLThickness * scaledTailWidth * 0.5 + aaWidthWebGL + pickingPaddingWebGL;
position = centerPos + normal * side * halfWidth;
t = mix(tStart, tTailEnd, zoneT);
} else if (zone < 1.5) {
// BODY ZONE: follows path curvature with width = 1.0
t = mix(tTailEnd, tHeadStart, zoneT);
normal = queryPathNormal(pathId, t, a_source, a_target);
float halfWidth = webGLThickness * 0.5 + aaWidthWebGL + pickingPaddingWebGL;
position = queryPathPosition(pathId, t, a_source, a_target) + normal * side * halfWidth;
} else {
// HEAD ZONE: rectangular quad with scaled width
vec2 tang = queryPathTangent(pathId, tHeadStart, a_source, a_target);
normal = vec2(-tang.y, tang.x);
vec2 centerPos = mix(queryPathPosition(pathId, tHeadStart, a_source, a_target),
queryPathPosition(pathId, tEnd, a_source, a_target), zoneT);
float halfWidth = webGLThickness * scaledHeadWidth * 0.5 + aaWidthWebGL + pickingPaddingWebGL;
position = centerPos + normal * side * halfWidth;
t = mix(tHeadStart, tEnd, zoneT);
}
// Blend toward straight line based on path twist in extremity zones
if (straightenFactor > 0.001) {
float zoneWidth = zone < 0.5 ? webGLThickness * scaledTailWidth * 0.5 + aaWidthWebGL + pickingPaddingWebGL :
zone < 1.5 ? webGLThickness * 0.5 + aaWidthWebGL + pickingPaddingWebGL :
webGLThickness * scaledHeadWidth * 0.5 + aaWidthWebGL + pickingPaddingWebGL;
vec2 straightPos = mix(a_source, a_target, t) + straightNormal * side * zoneWidth;
position = mix(position, straightPos, straightenFactor);
}
gl_Position = vec4((u_matrix * vec3(position, 1.0)).xy, 0.0, 1.0);
// Pass varyings to fragment shader
v_color = a_color;
v_color.a *= bias;
v_opacity = a_opacity;
v_id = a_id;
v_thickness = webGLThickness;
v_t = t;
v_tStart = tStart;
v_tEnd = tEnd;
v_side = side;
v_antialiasingWidth = antialiasingWidth;
v_source = a_source;
v_target = a_target;
v_edgeLength = pathLength;
// Zone varyings
v_zone = zone;
v_zoneT = zoneT;
v_headLengthRatio = headLengthRatio * extremityScale;
v_tailLengthRatio = tailLengthRatio * extremityScale;
// Scale extremity width proportionally with length when crushed
v_headWidthRatio = headWidthFactor * extremityScale;
v_tailWidthRatio = tailWidthFactor * extremityScale;
// Multi-path varyings
v_pathId = pathId;
v_headId = headId;
v_tailId = tailId;
}
`}function lo(a,e,t,i){const r=U([...a,...t]),n=Oe(r),o=new Set(["u_matrix","u_sizeRatio","u_correctionRatio","u_zoomRatio","u_pixelRatio","u_cameraAngle","u_minEdgeThickness","u_pickingPadding"]),s=new Set,l=[],u=_=>{!o.has(_.name)&&!s.has(_.name)&&(s.add(_.name),l.push(`uniform ${_.type} ${_.name};`))};a.forEach(_=>_.uniforms.forEach(u)),e.forEach(_=>_.uniforms.forEach(u)),t.forEach(_=>_.uniforms.forEach(u));const g=e.map(_=>I(_.baseRatio??.5)).join(", "),d=_=>{var m;return e.length>1?`EXTREMITY_BASE_RATIOS[${_}]`:I(((m=e[0])==null?void 0:m.baseRatio)??.5)},h=e.some(_=>j(_.length)?!0:_.length>0),c=t.some(_=>_.needsNodeColors),f=a.some(_=>_.needsNodeSize),b=t.map((_,m)=>` // Layer ${m+1}: ${_.name}
color = blendOver(color, layer_${_.name}(context));`).join(`
`);return`#version 300 es
precision highp float;
// Standard varyings
in vec4 v_color;
in float v_opacity;
in vec4 v_id;
in float v_thickness; // Edge body thickness
in float v_t;
in float v_tStart;
in float v_tEnd;
in float v_side;
in float v_antialiasingWidth; // Anti-aliasing width (normalized: u_correctionRatio / thickness)
in vec2 v_source;
in vec2 v_target;
in float v_edgeLength;
${f?`
in float v_sourceNodeSize; // Source node size (mirrored in labels/generator.ts as plain float)
in float v_targetNodeSize; // Target node size (mirrored in labels/generator.ts as plain float)`:""}
${c?`
in vec4 v_sourceColor;
in vec4 v_targetColor;`:""}
// Zone varyings
in float v_zone; // 0=tail, 1=body, 2=head
in float v_zoneT; // Position within zone [0,1]
in float v_headLengthRatio; // Head length as ratio of thickness (scaled for short edges)
in float v_tailLengthRatio; // Tail length as ratio of thickness (scaled for short edges)
in float v_headWidthRatio; // Head width factor
in float v_tailWidthRatio; // Tail width factor
// Multi-path/extremity varyings
flat in int v_pathId;
flat in int v_headId;
flat in int v_tailId;
// Path/layer attribute varyings (from vertex shader texture fetch)
${n.fragmentVaryingDeclarations}
// Standard uniforms (needed by some path types)
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_cameraAngle;
#ifdef PICKING_MODE
uniform float u_pickingPadding;
#endif
// Custom uniforms
${l.join(`
`)}
// Fragment output (single target - picking handled via separate pass)
out vec4 fragColor;
${e.length>1?`// Base ratio array for extremities (shared pool for head/tail)
const float EXTREMITY_BASE_RATIOS[${e.length}] = float[](${g});`:""}
// EdgeContext struct
struct EdgeContext {
float t; // Position along path [0, 1]
float sdf; // Signed distance from centerline
vec2 position; // World position
vec2 tangent; // Path tangent
vec2 normal; // Path normal
float thickness; // Edge thickness
float aaWidth; // Anti-aliasing width
float edgeLength; // Total path length
float tStart; // Clamped start t
float tEnd; // Clamped end t
float distanceFromSource; // Arc distance from source
float distanceToTarget; // Arc distance to target
};
EdgeContext context;
// Alpha "over" compositing for layer blending
vec4 blendOver(vec4 bg, vec4 fg) {
float a = fg.a;
return vec4(mix(bg.rgb, fg.rgb, a), bg.a + a * (1.0 - bg.a));
}
// All path functions
${oa(a)}
// Path selector functions
${sa(a)}
// All extremity functions
${eo(e)}
// Extremity SDF selector (shared pool for head/tail)
${io(e)}
// Layer functions
${t.map(_=>_.glsl).join(`
`)}
// Helper: Compute arc length using path selector
float computeArcLengthMulti(int pathId, float t0, float t1, vec2 source, vec2 target, int samples) {
float arcLen = 0.0;
vec2 prev = queryPathPosition(pathId, t0, source, target);
for (int i = 1; i <= samples; i++) {
float t = t0 + (t1 - t0) * float(i) / float(samples);
vec2 curr = queryPathPosition(pathId, t, source, target);
arcLen += length(curr - prev);
prev = curr;
}
return arcLen;
}
void main() {
// Compute normalized t within visible edge (0 = start, 1 = end)
float tNorm = (v_t - v_tStart) / max(v_tEnd - v_tStart, 0.0001);
// Edge body half-thickness
float halfThickness = v_thickness * 0.5;
// Convert normalized AA width to webGL units (~1 pixel)
float aaWidthWebGL = v_antialiasingWidth * v_thickness;
// Distance from centerline based on v_side interpolation
float zoneWidthFactor = v_zone < 0.5 ? v_tailWidthRatio :
v_zone < 1.5 ? 1.0 :
v_headWidthRatio;
// In PICKING_MODE, inflate halfGeometryWidth to match the inflated vertex geometry
#ifdef PICKING_MODE
float halfGeometryWidth = halfThickness * zoneWidthFactor + aaWidthWebGL + u_pickingPadding * aaWidthWebGL;
#else
float halfGeometryWidth = halfThickness * zoneWidthFactor + aaWidthWebGL;
#endif
float distFromCenter = abs(v_side) * halfGeometryWidth;
// Populate EdgeContext (for layer functions)
context.t = tNorm;
context.sdf = distFromCenter - halfThickness;
context.position = queryPathPosition(v_pathId, v_t, v_source, v_target);
context.tangent = queryPathTangent(v_pathId, v_t, v_source, v_target);
context.normal = vec2(-context.tangent.y, context.tangent.x);
context.thickness = v_thickness;
context.aaWidth = aaWidthWebGL;
context.edgeLength = v_edgeLength;
context.tStart = v_tStart;
context.tEnd = v_tEnd;
// Compute arc distances
float visibleLength = v_edgeLength * (v_tEnd - v_tStart);
float pathT = v_t;
float pathTNorm = tNorm;
${a.every(_=>_.linearParameterization)?` // All paths have linear parameterization: t maps directly to arc distance
context.distanceFromSource = pathTNorm * visibleLength;
context.distanceToTarget = (1.0 - pathTNorm) * visibleLength;`:a.every(_=>!_.linearParameterization)?` // No paths have linear parameterization: use numerical integration
context.distanceFromSource = computeArcLengthMulti(v_pathId, v_tStart, pathT, v_source, v_target, 16);
context.distanceToTarget = computeArcLengthMulti(v_pathId, pathT, v_tEnd, v_source, v_target, 16);`:` // Mixed parameterization: use analytical for linear paths, numerical for others
if (${a.filter(_=>_.linearParameterization).map(_=>`v_pathId == ${a.indexOf(_)}`).join(" || ")}) {
context.distanceFromSource = pathTNorm * visibleLength;
context.distanceToTarget = (1.0 - pathTNorm) * visibleLength;
} else {
context.distanceFromSource = computeArcLengthMulti(v_pathId, v_tStart, pathT, v_source, v_target, 16);
context.distanceToTarget = computeArcLengthMulti(v_pathId, pathT, v_tEnd, v_source, v_target, 16);
}`}
// Compute SDF based on zone using extremity selector (shared pool)
float bodySDF = distFromCenter - halfThickness;
float finalSDF;
${h?` // Base ratios, from baseRatioLookup
float headBaseRatio = ${d("v_headId")};
float tailBaseRatio = ${d("v_tailId")};
if (v_zone < 0.5) {
// TAIL ZONE: v_zoneT goes 0 (tip) to 1 (base)
vec2 uv = vec2((1.0 - v_zoneT) * v_tailLengthRatio, v_side * v_tailWidthRatio * 0.5);
float tailSDF = queryExtremitySDF(v_tailId, uv, v_tailLengthRatio, v_tailWidthRatio) * v_thickness;
// Apply union only near base (v_zoneT > 1 - baseRatio)
if (v_zoneT > 1.0 - tailBaseRatio) {
finalSDF = min(tailSDF, bodySDF);
} else {
finalSDF = tailSDF;
}
} else if (v_zone < 1.5) {
// BODY ZONE: distance from centerline
finalSDF = bodySDF;
} else {
// HEAD ZONE: v_zoneT goes 0 (base) to 1 (tip)
vec2 uv = vec2(v_zoneT * v_headLengthRatio, v_side * v_headWidthRatio * 0.5);
float headSDF = queryExtremitySDF(v_headId, uv, v_headLengthRatio, v_headWidthRatio) * v_thickness;
// Apply union only near base (v_zoneT < baseRatio)
if (v_zoneT < headBaseRatio) {
finalSDF = min(headSDF, bodySDF);
} else {
finalSDF = headSDF;
}
}`:` // No extremity draws tail/head geometry, so every vertex is body zone.
finalSDF = bodySDF;`}
#ifdef PICKING_MODE
// Picking pass: output edge ID for pixels within the picking area
if (finalSDF > u_pickingPadding * aaWidthWebGL) discard;
fragColor = v_id;
#else
${i?` // Visual pass: anti-aliased edge with layers, edge opacity applied once
float alpha = smoothstep(aaWidthWebGL, -aaWidthWebGL, finalSDF) * v_opacity;`:` // Visual pass: hard-edged (no anti-aliasing gradient) edge with layers, edge opacity applied once
float alpha = (finalSDF < 0.0 ? 1.0 : 0.0) * v_opacity;`}
if (alpha < 0.01) discard;
// Apply layers sequentially with "over" compositing
vec4 color = vec4(0.0);
${b}
// Mix with transparent to fade both color AND alpha (pre-multiplied alpha for correct blending)
fragColor = mix(vec4(0.0), color, alpha);
#endif
}
`}function co(a,e,t){const i=j(e.length)?!0:e.length>0,r=j(t.length)?!0:t.length>0;if(a.generateConstantData){const n=a.generateConstantData();return{data:n.data,attributes:n.attributes,verticesPerEdge:n.verticesPerEdge}}return oo(a.segments,i,r)}function la(a){const{paths:e,extremities:t,layers:i}=Oi(a),r=a.antialias??!0,n=new Map,o=new Map,s=new Map;for(const _ of e)for(const m of t)for(const E of t){const p=`${_.name}:${m.name}:${E.name}`,x=co(_,m,E);n.set(p,x.verticesPerEdge),o.set(p,x.data);for(const T of x.attributes)s.has(T.name)||s.set(T.name,T)}const l=Array.from(s.values()),u={};l.forEach((_,m)=>{u[_.name]=m});let g=0,d="";for(const[_,m]of n)m>g&&(g=m,d=_);const h=o.get(d)||[],[c]=d.split(":"),f=e.find(_=>_.name===c);let b=[];f!=null&&f.generateConstantData?b=f.generateConstantData().attributes:b=[{name:"a_zone"},{name:"a_zoneT"},{name:"a_side"}];const v=h.map(_=>{const m=new Array(l.length).fill(0);return b.forEach((E,p)=>{const x=u[E.name];x!==void 0&&p<_.length&&(m[x]=_[p])}),m});return{vertexShader:so(e,t,i,l),fragmentShader:lo(e,t,i,r),uniforms:Jn(e,t,i),attributes:ho(),verticesPerEdge:g,constantData:v,constantAttributes:l,vertexCountsPerCombination:n,constantDataPerCombination:o}}function ho(a,e,t){return[{name:"a_edgeIndex",size:1,type:Ue},{name:"a_color",size:4,type:Ui,normalized:!0},{name:"a_id",size:4,type:Ui,normalized:!0},{name:"a_opacity",size:1,type:Ue}]}const uo=`#version 300 es
precision highp float;
in vec4 v_clamp;
// RGBA32F target: stores (tStart, tEnd, straightenFactor, pathLength).
out vec4 fragColor;
void main() {
fragColor = v_clamp;
}
`;class Hi{constructor(e,t){this.uniformLocations={};const{paths:i,extremities:r,layers:n,nodeShapes:o,nodeLayers:s}=t;this.gl=e,this.hasAttributeData=U([...i,...n]).floatsPerItem>0,this.hasNodeAttributeData=Object.keys(Ae(o,s).offsets).length>0,this.vertexShader=it(e,no(i,r,n,o,s)),this.fragmentShader=rt(e,uo),this.program=nt(e,[this.vertexShader,this.fragmentShader]);const l=new Set;this.customUniforms=[];for(const g of[...i,...r])for(const d of g.uniforms)l.has(d.name)||(l.add(d.name),this.customUniforms.push(d));const u=["u_sizeRatio","u_correctionRatio","u_cameraAngle","u_minEdgeThickness","u_nodeDataTexture","u_nodeDataTextureWidth","u_edgeDataTexture","u_edgeDataTextureWidth","u_edgeAttributeTexture","u_edgeAttributeTextureWidth","u_edgeAttributeTexelsPerEdge","u_layerAttributeTexture","u_layerAttributeTextureWidth","u_layerAttributeTexelsPerNode","u_frameTextureWidth","u_frameTextureHeight",...this.customUniforms.map(g=>g.name)];for(const g of u)this.uniformLocations[g]=e.getUniformLocation(this.program,g);this.vao=e.createVertexArray()}run(e,t,i,r,n){if(i===0)return;const{gl:o}=this,s=this.uniformLocations;o.useProgram(this.program),o.bindVertexArray(this.vao),s.u_sizeRatio&&o.uniform1f(s.u_sizeRatio,e.sizeRatio),s.u_correctionRatio&&o.uniform1f(s.u_correctionRatio,e.correctionRatio),s.u_cameraAngle&&o.uniform1f(s.u_cameraAngle,e.cameraAngle),s.u_minEdgeThickness&&o.uniform1f(s.u_minEdgeThickness,e.minEdgeThickness),s.u_nodeDataTexture&&o.uniform1i(s.u_nodeDataTexture,e.nodeDataTextureUnit),s.u_nodeDataTextureWidth&&o.uniform1i(s.u_nodeDataTextureWidth,e.nodeDataTextureWidth),s.u_edgeDataTexture&&o.uniform1i(s.u_edgeDataTexture,e.edgeDataTextureUnit),s.u_edgeDataTextureWidth&&o.uniform1i(s.u_edgeDataTextureWidth,e.edgeDataTextureWidth),s.u_frameTextureWidth&&o.uniform1f(s.u_frameTextureWidth,t.getTextureWidth()),s.u_frameTextureHeight&&o.uniform1f(s.u_frameTextureHeight,t.getTextureHeight()),this.hasAttributeData&&r&&(r.bind(ne),s.u_edgeAttributeTexture&&o.uniform1i(s.u_edgeAttributeTexture,ne),s.u_edgeAttributeTextureWidth&&o.uniform1i(s.u_edgeAttributeTextureWidth,r.getTextureWidth()),s.u_edgeAttributeTexelsPerEdge&&o.uniform1i(s.u_edgeAttributeTexelsPerEdge,r.getTexelsPerItem())),this.hasNodeAttributeData&&n&&(n.bind(ie),s.u_layerAttributeTexture&&o.uniform1i(s.u_layerAttributeTexture,ie),s.u_layerAttributeTextureWidth&&o.uniform1i(s.u_layerAttributeTextureWidth,n.getTextureWidth()),s.u_layerAttributeTexelsPerNode&&o.uniform1i(s.u_layerAttributeTexelsPerNode,n.getTexelsPerItem()));for(const l of this.customUniforms)aa(o,this.uniformLocations[l.name],l);t.bindAsRenderTarget(),o.disable(o.BLEND),o.disable(o.DEPTH_TEST),o.drawArrays(o.POINTS,0,i),o.bindFramebuffer(o.FRAMEBUFFER,null),o.bindVertexArray(null)}kill(){const{gl:e}=this;e.deleteProgram(this.program),e.deleteShader(this.vertexShader),e.deleteShader(this.fragmentShader),e.deleteVertexArray(this.vao)}}function $e(a,e){if(typeof a=="string"){const[i,r,n,o]=ei(a).map(s=>s.toFixed(6));return{glsl:`vec4(${i}, ${r}, ${n}, ${o})`,attributes:[],needsNodeColors:!1}}if("node"in a&&a.node)return{glsl:a.node==="source"?"v_sourceColor":"v_targetColor",attributes:[],needsNodeColors:!0};const t=a;return{glsl:`v_${e}`,attributes:[{name:`a_${e}`,size:4,type:WebGL2RenderingContext.UNSIGNED_BYTE,normalized:!0,source:t.attribute,defaultValue:t.default}],needsNodeColors:!1}}const fo="pixels",go="butt";function po(a){return a===void 0||a===!1?{tail:!1,head:!1}:a===!0?{tail:!0,head:!0}:a==="head"?{tail:!1,head:!0}:{tail:!0,head:!1}}function mo(a){return a===void 0?{tail:0,head:0}:typeof a=="number"?{tail:a,head:a}:{tail:a.tail??0,head:a.head??0}}function bo(a){const e=a??{},t=e.dashColor??{attribute:"color"},i=e.gapColor??0,r={dashSize:e.dashSize??{value:10,mode:"pixels"},gapSize:e.gapSize??{value:10,mode:"pixels"},dashOffset:e.dashOffset??{value:0,mode:"pixels"}},n=e.align??.5,o=e.cap??go,s=po(e.solidExtremities),l=mo(e.solidMargin),u=$e(t,"dashColor"),d=[{name:"u_sizeMode",type:"vec3",value:[r.dashSize,r.gapSize,r.dashOffset].map(p=>(p.mode??fo)==="relative"?1:0)},{name:"u_align",type:"float",value:n},{name:"u_solidExtremities",type:"vec2",value:[s.tail?1:0,s.head?1:0]},{name:"u_solidMargin",type:"vec2",value:[l.tail,l.head]}],h=[...u.attributes];let c=u.needsNodeColors,f;if(typeof i=="number")f=`vec4(dashColor.rgb, dashColor.a * ${I(i)})`;else{const p=$e(i,"gapColor");f=p.glsl,h.push(...p.attributes),c=c||p.needsNodeColors}const b=!("value"in r.dashSize),v=!("value"in r.gapSize),_=!("value"in r.dashOffset);["dashSize","gapSize","dashOffset"].forEach(p=>{"value"in r[p]?d.push({name:`u_${p}`,type:"float",value:r[p].value}):(h.push({name:`a_${p}`,size:1,type:WebGL2RenderingContext.FLOAT,source:r[p].attribute}),d.push({name:`u_${p}`,type:"float",value:r[p].default??0}))});const m=(p,x)=>x?`(v_${p} > 0.0 ? v_${p} : u_${p})`:`u_${p}`;return{name:"dashed",glsl:`
// Dashed pattern layer with antialiased boundaries
// Uniforms:
// u_dashSize: size of each dash (or default when using attribute)
// u_gapSize: size of gaps between dashes (or default when using attribute)
// u_dashOffset: offset to shift the pattern (or default when using attribute)
// u_sizeMode: vec3 indicating if values are thickness-relative (x=dash, y=gap, z=offset)
// u_align: pattern alignment (0=start, 0.5=center, 1=end)
// u_solidExtremities: vec2(tail, head) - 1.0 means solid, 0.0 means dashed
// u_solidMargin: vec2(tail, head) - extra solid margin in pixels
${b?"// Varying: v_dashSize for per-edge dash size":""}
${v?"// Varying: v_gapSize for per-edge gap size":""}
${_?"// Varying: v_dashOffset for per-edge dash offset":""}
vec4 layer_dashed(EdgeContext ctx) {
// Dash color (straight alpha, matching v_color and blendOver)
vec4 dashColor = ${u.glsl};
// Check for solid zones first (extremities and margins)
// v_zone: 0=tail extremity, 1=body, 2=head extremity
// v_tailLengthRatio and v_headLengthRatio give extremity lengths as ratio of thickness
// Tail solid zone check
if (u_solidExtremities.x > 0.5 && v_zone < 0.5) {
// In tail extremity zone and solidExtremities.tail is enabled
return dashColor;
}
// Head solid zone check
if (u_solidExtremities.y > 0.5 && v_zone > 1.5) {
// In head extremity zone and solidExtremities.head is enabled
return dashColor;
}
// Compute extremity lengths in world units for margin calculation
float tailExtremityLength = v_tailLengthRatio * ctx.thickness;
float headExtremityLength = v_headLengthRatio * ctx.thickness;
// Convert pixel margins to world units (same formula as thickness conversion)
float tailMarginWorld = u_solidMargin.x * u_correctionRatio / u_sizeRatio;
float headMarginWorld = u_solidMargin.y * u_correctionRatio / u_sizeRatio;
// Tail margin check (margin starts after extremity zone)
float tailSolidZone = (u_solidExtremities.x > 0.5 ? tailExtremityLength : 0.0) + tailMarginWorld;
if (ctx.distanceFromSource < tailSolidZone) {
return dashColor;
}
// Head margin check (margin starts before extremity zone)
float headSolidZone = (u_solidExtremities.y > 0.5 ? headExtremityLength : 0.0) + headMarginWorld;
if (ctx.distanceToTarget < headSolidZone) {
return dashColor;
}
// Get dash size values (from attribute if available, otherwise uniform)
float dashSizeValue = ${m("dashSize",b)};
float gapSizeValue = ${m("gapSize",v)};
float dashOffsetValue = ${m("dashOffset",_)};
// Compute actual sizes (either in pixels converted to world units, or relative to thickness)
float pixelToWorld = u_correctionRatio / u_sizeRatio;
float dashSize = dashSizeValue * (u_sizeMode.x > 0.5 ? ctx.thickness : pixelToWorld);
float gapSize = gapSizeValue * (u_sizeMode.y > 0.5 ? ctx.thickness : pixelToWorld);
float dashOffset = dashOffsetValue * (u_sizeMode.z > 0.5 ? ctx.thickness : pixelToWorld);
// Early return when no visible dash pattern:
// - dashSize ≈ 0: no dashes to show, return transparent (let plain layer show through)
// - gapSize ≈ 0: all dash/no gap, effectively solid, return transparent (let plain layer handle it)
// Threshold scales with pixelToWorld so it stays ~0.1px regardless of zoom
float dashThreshold = 0.1 * pixelToWorld;
if (dashSize < dashThreshold || gapSize < dashThreshold) {
return vec4(0.0);
}
// Pattern length is dash + gap
float patternLength = dashSize + gapSize;
// Adjust distances for solid zones (pattern starts after solid zones)
float adjustedDistFromSource = ctx.distanceFromSource - tailSolidZone;
float adjustedDistToTarget = ctx.distanceToTarget - headSolidZone;
// Compute alignment anchor point
// - align: 0 → anchor at start, pattern begins with a dash
// - align: 1 → anchor at end, pattern ends with a dash
// - align: 0.5 → anchor at center, pattern is symmetric
float dashedLength = adjustedDistFromSource + adjustedDistToTarget;
float anchorDist = u_align * dashedLength;
// Position within the repeating pattern
// By subtracting anchorDist, we ensure the anchor point maps to position 0 in the pattern
// This avoids the unstable mod(dashedLength, patternLength) operation
float posInPattern = mod(adjustedDistFromSource - anchorDist + dashOffset, patternLength);
// Signed longitudinal distance to the nearest dash center, wrapping across
// pattern repetitions so both dash edges antialias correctly
float longFromCenter =
mod(posInPattern - dashSize * 0.5 + patternLength * 0.5, patternLength) - patternLength * 0.5;
// Compute signed distance field for the dash
// Positive inside dash, negative inside gap
${o==="round"?` // Capsule SDF: round caps carved inside the dash length, so a dash never
// exceeds dashSize. Dashes shorter than the thickness degenerate to circles
// (dots) of diameter dashSize.
// ctx.sdf is 0 at the stroke boundary, so shift it back to centerline distance
float transverse = ctx.sdf + ctx.thickness * 0.5;
float capRadius = min(ctx.thickness, dashSize) * 0.5;
float halfLength = dashSize * 0.5 - capRadius;
vec2 q = vec2(max(abs(longFromCenter) - halfLength, 0.0), transverse);
float sdf = capRadius - length(q);`:" float sdf = dashSize * 0.5 - abs(longFromCenter);"}
// Apply antialiasing using smoothstep
// aaWidth is in world units, same as our distance
float dashAlpha = smoothstep(-ctx.aaWidth, ctx.aaWidth, sdf);
// Gap color (straight alpha, like the dash color)
vec4 gapColor = ${f};
// Blend between gap and dash colors
return mix(gapColor, dashColor, dashAlpha);
}
`,uniforms:d,attributes:h,needsNodeColors:c}}function xo(a){const e=a.map(i=>i.offset);e[0]===void 0&&(e[0]=0),e[a.length-1]===void 0&&(e[a.length-1]=1);let t=0;for(let i=0;i<e.length;i++){const r=e[i];r!==void 0&&(t=Math.max(t,Math.min(Math.max(r,0),1)),e[i]=t)}for(let i=1;i<e.length;i++){if(e[i]!==void 0)continue;let r=i+1;for(;e[r]===void 0;)r++;const n=e[i-1],o=e[r];for(let s=i;s<r;s++)e[s]=n+(o-n)*(s-i+1)/(r-i+1);i=r}return e}function _o(a){if(a.stops.length<2)throw new Error("layerGradient: at least two stops are required");const e=a.stops.map(l=>typeof l=="string"?{color:l,offset:void 0}:{color:"color"in l?l.color:l,offset:l.offset}),t=xo(e),i=e.map((l,u)=>$e(l.color,`gradientStop${u}`)),r=a.enabled,n=i.flatMap(l=>l.attributes);r&&n.push({name:"a_gradient",size:1,type:WebGL2RenderingContext.FLOAT,source:r.attribute,defaultValue:r.default??!0});const o=t.map((l,u)=>{if(u===0)return"";const g=t[u-1].toFixed(6),d=Math.max(l-t[u-1],1e-6).toFixed(6);return`
vec4 c${u} = ${i[u].glsl};
color = mix(color, vec4(c${u}.rgb * c${u}.a, c${u}.a), clamp((ctx.t - ${g}) / ${d}, 0.0, 1.0));`}).join("");return{name:"gradient",glsl:`
// Gradient layer: interpolates the color stops over the visible span of the
// edge (ctx.t is 0 where the edge leaves the source node, 1 where it reaches
// the target node). Interpolation happens in premultiplied space so fading to
// a (semi-)transparent stop keeps the hue instead of darkening through black;
// the returned color is straight-alpha, matching the blendOver convention.
vec4 layer_gradient(EdgeContext ctx) {${r?`
// Per-edge toggle: fall through to the layers below when disabled
if (v_gradient < 0.5) return vec4(0.0);
`:""}
vec4 c0 = ${i[0].glsl};
vec4 color = vec4(c0.rgb * c0.a, c0.a);${o}
return color.a > 0.0 ? vec4(color.rgb / color.a, color.a) : vec4(0.0);
}
`,uniforms:[],attributes:n,needsNodeColors:i.some(l=>l.needsNodeColors)}}function De(a){const e=a==null?void 0:a.color;if(e===void 0)return{name:"plain",glsl:`
// Plain solid color layer
vec4 layer_plain(EdgeContext ctx) {
return v_color;
}
`,uniforms:[],attributes:[]};const t=$e(e,"plainColor");return{name:"plain",glsl:`
// Plain solid color layer
vec4 layer_plain(EdgeContext ctx) {
return ${t.glsl};
}
`,uniforms:[],attributes:t.attributes,needsNodeColors:t.needsNodeColors}}function He(a,e,t,i,r,n){if(a.length===1)return`${i} ${e}(int pathId, ${r}) {
return path_${a[0].name}_${t}(${n});
}`;const o=a.map((s,l)=>` case ${l}: return path_${s.name}_${t}(${n});`).join(`
`);return`${i} ${e}(int pathId, ${r}) {
switch (pathId) {
${o}
default: return path_${a[0].name}_${t}(${n});
}
}`}const To=`
vec3 computeEdgeLabelBodyBounds(
float tStart, float tEnd, float pathLength,
float webGLThickness, float headLengthRatio, float tailLengthRatio
) {
float visibleLength = pathLength * (tEnd - tStart);
float headLength = headLengthRatio * webGLThickness;
float tailLength = tailLengthRatio * webGLThickness;
float totalNeededLength = headLength + tailLength;
if (totalNeededLength > visibleLength && totalNeededLength > 0.0001) {
float extremityScale = visibleLength / totalNeededLength;
headLength *= extremityScale;
tailLength *= extremityScale;
}
float bodyStartDist = tStart * pathLength + tailLength;
float bodyEndDist = tEnd * pathLength - headLength;
return vec3(bodyStartDist, bodyEndDist, max(bodyEndDist - bodyStartDist, 0.0));
}
`;function vo(a,e){const t=I(a),i=I(e);return`
float computeEdgeLabelAlpha(float bodyLength, float textWidthWebGL) {
float ratio = textWidthWebGL > 0.0001 ? min(bodyLength / textWidthWebGL, 1.0) : 1.0;
if (ratio < ${t}) return 0.0;
if (ratio < ${i}) return (ratio - ${t}) / (${i} - ${t});
return 1.0;
}
`}const yo=`
float computeEdgeLabelScreenDir(int pathId, float t, vec2 source, vec2 target, mat3 matrix) {
vec2 tangent = queryPathTangent(pathId, t, source, target);
return (matrix * vec3(tangent, 0.0)).x < 0.0 ? -1.0 : 1.0;
}
`,So=`
float computeEdgeLabelPerpOffset(
float positionMode,
float halfThickness, float marginWebGL, float halfTextHeight,
float readingSign, float screenDir
) {
float magnitude = halfThickness + marginWebGL + halfTextHeight;
if (positionMode == 1.0) return magnitude * readingSign;
if (positionMode == 2.0) return -magnitude * readingSign;
if (positionMode == 3.0) return magnitude * screenDir;
return 0.0;
}
`;function Vi(a){const{paths:e,minVisibilityThreshold:t,fullVisibilityThreshold:i}=a,r=e.some(s=>s.hasSharpCorners),n=e.map(s=>`// --- Path: ${s.name} ---
${s.glsl}
// Tangent/normal functions: analytical if provided, otherwise numerical
${s.analyticalTangentGlsl||Gi(s.name)}
// Auto-generated fallbacks for any missing path functions
${Bi(s.name,s.glsl)}
// Corner skip helpers (for paths with sharp corners like step/taxi)
${s.cornerSkipGlsl||""}
`).join(`
`),o=r?`// Corner function selectors (only some paths have sharp corners)
vec2 queryGetCornerTs(int pathId, vec2 source, vec2 target) {
switch (pathId) {
${e.map((s,l)=>s.hasSharpCorners?` case ${l}: return path_${s.name}_getCornerTs(source, target);`:` case ${l}: return vec2(-1.0, -1.0); // No corners for ${s.name}`).join(`
`)}
default: return vec2(-1.0, -1.0);
}
}
vec2 queryGetCornerConcavity(int pathId, vec2 source, vec2 target, float perpOffset) {
switch (pathId) {
${e.map((s,l)=>s.hasSharpCorners?` case ${l}: return path_${s.name}_getCornerConcavity(source, target, perpOffset);`:` case ${l}: return vec2(0.0, 0.0); // No corners for ${s.name}`).join(`
`)}
default: return vec2(0.0, 0.0);
}
}`:"";return`
// ============================================================================
// Node data fetch (geometry texel) and per-edge clamp fetch (edge-frame texture)
// ============================================================================
${X}
${se}
// ============================================================================
// Path Functions (one block per path)
// ============================================================================
${n}
// ============================================================================
// Path Query Selectors (dispatch by pathId)
// ============================================================================
${He(e,"queryPathPosition","position","vec2","float t, vec2 source, vec2 target","t, source, target")}
${He(e,"queryPathTangent","tangent","vec2","float t, vec2 source, vec2 target","t, source, target")}
${He(e,"queryPathNormal","normal","vec2","float t, vec2 source, vec2 target","t, source, target")}
${He(e,"queryPathLength","length","float","vec2 source, vec2 target","source, target")}
${He(e,"queryPathTAtDistance","t_at_distance","float","float dist, vec2 source, vec2 target","dist, source, target")}
${o}
// ============================================================================
// Shared helpers (body bounds, alpha ramp, screen direction, perpendicular offset)
// ============================================================================
${To}
${vo(t,i)}
${yo}
${So}
`}const Eo=q.fontSize,Ro=new Map,Xi=24,qi=(Xi+1)*2;function Ao(a){const{paths:e,fontSizeMode:t,headLengthRatio:i,tailLengthRatio:r,minVisibilityThreshold:n,fullVisibilityThreshold:o,keepUpright:s}=a,l=t==="scaled",u=De(),g=U([...e,u]),d=Oe(g);return`#version 300 es
// Per-instance attributes
in float a_edgeIndex;
in float a_edgeAttrIndex;
in float a_baseFontSize;
in float a_totalTextWidth;
in float a_positionMode;
in float a_margin;
in float a_padding;
in vec4 a_color;
in vec4 a_id;
// Per-vertex (constant) attribute: strip vertex index
in float a_vertexIndex;
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_pixelRatio;
uniform float u_cameraAngle;
uniform vec2 u_resolution;
uniform sampler2D u_nodeDataTexture;
uniform int u_nodeDataTextureWidth;
uniform sampler2D u_edgeDataTexture;
uniform int u_edgeDataTextureWidth;
uniform sampler2D u_edgeFrameTexture; // Per-edge clamp (tStart, tEnd, straightenFactor, pathLength)
uniform int u_edgeFrameTextureWidth;
${l?"uniform float u_zoomSizeRatio;":""}
${d.uniformDeclarations}
out vec4 v_color;
out vec4 v_id;
out float v_alphaModifier;
const float ATLAS_FONT_SIZE = ${I(Eo)};
const float HEAD_RATIO = ${I(i)};
const float TAIL_RATIO = ${I(r)};
const int RIBBON_SEGMENTS = ${Xi};
// Path attribute varyings (declared as plain locals since this shader has no FS inputs for them)
${d.vertexVaryingDeclarations.replace(/out /g,"")}
// Node size variables used by some path functions (self-loops, etc.)
float v_sourceNodeSize;
float v_targetNodeSize;
// Shared preamble: shape SDFs, path functions + selectors, clamp, helpers.
${Vi({paths:e,minVisibilityThreshold:n,fullVisibilityThreshold:o})}
void main() {
int vIdx = int(a_vertexIndex);
int pairIdx = vIdx / 2;
int side = vIdx - pairIdx * 2; // 0 = left/bottom, 1 = right/top
// --- Fetch edge data (2 texels per edge) ---
int edgeIdx = int(a_edgeIndex);
int texel0Idx = edgeIdx * 2;
int texel1Idx = edgeIdx * 2 + 1;
ivec2 e0 = ivec2(texel0Idx % u_edgeDataTextureWidth, texel0Idx / u_edgeDataTextureWidth);
ivec2 e1 = ivec2(texel1Idx % u_edgeDataTextureWidth, texel1Idx / u_edgeDataTextureWidth);
vec4 edgeData0 = texelFetch(u_edgeDataTexture, e0, 0);
vec4 edgeData1 = texelFetch(u_edgeDataTexture, e1, 0);
int srcIdx = int(edgeData0.x);
int tgtIdx = int(edgeData0.y);
float thickness = edgeData0.z;
int pathId = int(edgeData1.z);
// --- Fetch path attributes (curvature, etc.) ---
{
int edgeIdx = int(a_edgeAttrIndex);
${d.fetchCode}
${d.varyingAssignments}
}
// --- Fetch node data: geometry (texel 0) + rotation flags (texel 1) ---
vec4 srcN = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, srcIdx);
vec4 tgtN = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, tgtIdx);
vec2 source = srcN.xy;
vec2 target = tgtN.xy;
float sourceSize = srcN.z;
float targetSize = tgtN.z;
v_sourceNodeSize = sourceSize;
v_targetNodeSize = targetSize;
// --- Pixel-to-graph conversion (fixed font mode) ---
float matrixScaleX = length(vec2(u_matrix[0][0], u_matrix[1][0]));
float pixelToGraph = 2.0 / (matrixScaleX * u_resolution.x);
float webGLThickness = thickness * u_correctionRatio / u_sizeRatio;
// --- Body bounds (shared with edge label shader) ---
vec4 edgeClamp = readFrameTexel(u_edgeFrameTexture, u_edgeFrameTextureWidth, edgeIdx);
vec3 bodyBounds = computeEdgeLabelBodyBounds(
edgeClamp.x, edgeClamp.y, edgeClamp.w,
webGLThickness, HEAD_RATIO, TAIL_RATIO
);
float bodyStartDist = bodyBounds.x;
float bodyEndDist = bodyBounds.y;
float bodyLength = bodyBounds.z;
// --- Text dimensions ---
float baseFontSize = a_baseFontSize;
${l?`float fontScale = baseFontSize / ATLAS_FONT_SIZE * u_zoomSizeRatio;
float textWidthWebGL = a_totalTextWidth * fontScale * u_correctionRatio / u_sizeRatio;
float halfTextHeight = baseFontSize * 0.35 * u_zoomSizeRatio * u_correctionRatio / u_sizeRatio;
float marginWebGL = a_margin * u_zoomSizeRatio * u_correctionRatio / u_sizeRatio;`:`float fontScale = baseFontSize / ATLAS_FONT_SIZE;
float textWidthWebGL = a_totalTextWidth * fontScale * pixelToGraph;
float halfTextHeight = baseFontSize * 0.35 * pixelToGraph;
float marginWebGL = a_margin * pixelToGraph;`}
float paddingWebGL = a_padding * pixelToGraph;
// --- Alpha modifier (shared with edge label shader) ---
float alphaModifier = computeEdgeLabelAlpha(bodyLength, textWidthWebGL);
if (alphaModifier <= 0.0 || textWidthWebGL <= 0.0) {
gl_Position = vec4(2.0, 0.0, 0.0, 1.0);
v_color = vec4(0.0);
v_id = vec4(0.0);
v_alphaModifier = 0.0;
return;
}
// --- Perpendicular offset (shared with edge label shader) ---
float bodyCenterDist = (bodyStartDist + bodyEndDist) * 0.5;
float centerT = queryPathTAtDistance(pathId, bodyCenterDist, source, target);
float screenDir = computeEdgeLabelScreenDir(pathId, centerT, source, target, u_matrix);
float readingSign = ${s?"screenDir":"1.0"};
float halfThickness = webGLThickness * 0.5;
float perpOffset = computeEdgeLabelPerpOffset(
a_positionMode, halfThickness, marginWebGL, halfTextHeight, readingSign, screenDir
);
// --- Ribbon span (clipped to body) ---
float halfTextWebGL = textWidthWebGL * 0.5;
float labelStartDist = max(bodyCenterDist - halfTextWebGL, bodyStartDist);
float labelEndDist = min(bodyCenterDist + halfTextWebGL, bodyEndDist);
// Sample centerline at this pair, then apply perpendicular offset.
// The ribbon follows the centerline path (simple & robust); for curved edges
// this closely matches the offset path the characters sit on.
float u = float(pairIdx) / float(RIBBON_SEGMENTS);
float arcDist = mix(labelStartDist, labelEndDist, u);
float t = queryPathTAtDistance(pathId, arcDist, source, target);
vec2 pos = queryPathPosition(pathId, t, source, target);
vec2 tan = queryPathTangent(pathId, t, source, target);
vec2 perp = vec2(-tan.y, tan.x);
vec2 centerPos = pos + perp * perpOffset;
float halfRibbon = halfTextHeight + paddingWebGL;
float sideSign = side == 0 ? -1.0 : 1.0;
vec2 ribbonPos = centerPos + perp * (sideSign * halfRibbon);
vec3 clipPos = u_matrix * vec3(ribbonPos, 1.0);
gl_Position = vec4(clipPos.xy, 0.0, 1.0);
v_color = a_color;
v_id = a_id;
v_alphaModifier = alphaModifier;
}
`}const Do=`#version 300 es
precision highp float;
in vec4 v_color;
in vec4 v_id;
in float v_alphaModifier;
out vec4 fragColor;
void main() {
#ifdef PICKING_MODE
if (v_alphaModifier <= 0.0) discard;
fragColor = v_id;
#else
float alpha = v_color.a * v_alphaModifier;
if (alpha <= 0.0) discard;
fragColor = vec4(v_color.rgb * alpha, alpha);
#endif
}
`;function ji(a,e,t,i){const{shaderConfig:r}=i,{paths:n,fontSizeMode:o}=r;if(n.length===0)throw new Error("createEdgeLabelBackgroundProgram: shaderConfig must declare at least one path");const s=De(),l=U([...n,s]),u=mt([...n,s],l),g=Ao(r);class d extends ae{constructor(c,f,b){super(c,f,b),this.totalCount=0,this.bufferCapacity=0,this.edgeAttributeTexture=null,this.edgeAttributeTexture=new pt(c,l),this.packedAttributeData=new Float32Array(l.floatsPerItem)}getDefinition(){const{FLOAT:c,UNSIGNED_BYTE:f,TRIANGLE_STRIP:b}=WebGL2RenderingContext,v=[];for(let m=0;m<qi;m++)v.push([m]);const _=["u_matrix","u_sizeRatio","u_correctionRatio","u_pixelRatio","u_cameraAngle","u_resolution","u_nodeDataTexture","u_nodeDataTextureWidth","u_edgeDataTexture","u_edgeDataTextureWidth","u_edgeFrameTexture","u_edgeFrameTextureWidth"];return o==="scaled"&&_.push("u_zoomSizeRatio"),l.floatsPerItem>0&&_.push("u_edgeAttributeTexture","u_edgeAttributeTextureWidth","u_edgeAttributeTexelsPerEdge"),{VERTICES:qi,VERTEX_SHADER_SOURCE:g,FRAGMENT_SHADER_SOURCE:Do,METHOD:b,UNIFORMS:_,ATTRIBUTES:[{name:"a_edgeIndex",size:1,type:c},{name:"a_edgeAttrIndex",size:1,type:c},{name:"a_baseFontSize",size:1,type:c},{name:"a_totalTextWidth",size:1,type:c},{name:"a_positionMode",size:1,type:c},{name:"a_margin",size:1,type:c},{name:"a_padding",size:1,type:c},{name:"a_color",size:4,type:f,normalized:!0},{name:"a_id",size:4,type:f,normalized:!0}],CONSTANT_ATTRIBUTES:[{name:"a_vertexIndex",size:1,type:c}],CONSTANT_DATA:v}}processEdgeLabelBackground(c,f,b){let v=0;if(this.edgeAttributeTexture&&l.floatsPerItem>0){v=this.edgeAttributeTexture.allocate(f);const p=this.packedAttributeData;bt(u,b.edgeAttributes,p,"",Ro,0),this.edgeAttributeTexture.updateAllAttributes(f,p)}const{floats:_,ints:m}=this;let E=c*this.STRIDE;_[E++]=b.edgeIndex,_[E++]=v,_[E++]=b.baseFontSize,_[E++]=b.totalTextWidth,_[E++]=b.positionMode,_[E++]=b.margin,_[E++]=b.padding,_[E++]=b.color,m[E++]=b.id}setUniforms(c,{gl:f,uniformLocations:b}){if(f.uniformMatrix3fv(b.u_matrix,!1,c.matrix),f.uniform1f(b.u_sizeRatio,c.sizeRatio),f.uniform1f(b.u_correctionRatio,c.correctionRatio),f.uniform1f(b.u_pixelRatio,c.pixelRatio),f.uniform1f(b.u_cameraAngle,c.cameraAngle),f.uniform2f(b.u_resolution,c.width,c.height),b.u_nodeDataTexture!==void 0&&f.uniform1i(b.u_nodeDataTexture,c.nodeDataTextureUnit),b.u_nodeDataTextureWidth!==void 0&&f.uniform1i(b.u_nodeDataTextureWidth,c.nodeDataTextureWidth),b.u_edgeDataTexture!==void 0&&f.uniform1i(b.u_edgeDataTexture,c.edgeDataTextureUnit),b.u_edgeDataTextureWidth!==void 0&&f.uniform1i(b.u_edgeDataTextureWidth,c.edgeDataTextureWidth),b.u_edgeFrameTexture!==void 0&&f.uniform1i(b.u_edgeFrameTexture,c.edgeFrameTextureUnit),b.u_edgeFrameTextureWidth!==void 0&&f.uniform1i(b.u_edgeFrameTextureWidth,c.edgeFrameTextureWidth),o==="scaled"&&b.u_zoomSizeRatio!==void 0){const v=this.renderer.getSetting("zoomToSizeRatioFunction");f.uniform1f(b.u_zoomSizeRatio,1/v(c.zoomRatio))}this.edgeAttributeTexture&&l.floatsPerItem>0&&b.u_edgeAttributeTexture!==void 0&&(this.edgeAttributeTexture.bind(ne),f.uniform1i(b.u_edgeAttributeTexture,ne),f.uniform1i(b.u_edgeAttributeTextureWidth,this.edgeAttributeTexture.getTextureWidth()),f.uniform1i(b.u_edgeAttributeTexelsPerEdge,this.edgeAttributeTexture.getTexelsPerItem()))}renderProgram(c,f){this.edgeAttributeTexture&&l.floatsPerItem>0&&this.edgeAttributeTexture.upload(),super.renderProgram(c,f)}kill(){this.edgeAttributeTexture&&(this.edgeAttributeTexture.kill(),this.edgeAttributeTexture=null),super.kill()}drawWebGL(c,{gl:f}){this.totalCount!==0&&f.drawArraysInstanced(f.TRIANGLE_STRIP,0,this.VERTICES,this.totalCount)}reallocate(c){this.totalCount=c,c>this.bufferCapacity&&(this.bufferCapacity=Math.max(c,Math.ceil(this.bufferCapacity*1.5)||10),super.reallocate(this.bufferCapacity))}}return new d(a,e,t)}function Yi(a){return{paths:a.paths,headLengthRatio:a.headLengthRatio??0,tailLengthRatio:a.tailLengthRatio??0,fontSizeMode:a.fontSizeMode??"fixed",minVisibilityThreshold:a.minVisibilityThreshold??.7,fullVisibilityThreshold:a.fullVisibilityThreshold??.8,keepUpright:a.keepUpright??!0}}const Co=q.fontSize,Lo=17/64;function Fo(a){const{paths:e,hasBorder:t=!1,fontSizeMode:i="fixed",minVisibilityThreshold:r=.5,fullVisibilityThreshold:n=.6,keepUpright:o=!0}=a,s=i==="scaled",l=e.some(c=>c.hasSharpCorners),u=De(),g=U([...e,u]),d=Oe(g);return`#version 300 es
// ============================================================================
// Attributes - Per Character (Instanced)
// ============================================================================
// Edge geometry: indices for texture lookup
// Edge data (source/target node indices, thickness, head/tail ratios) is fetched from edge data texture
// Edge path attributes (curvature, etc.) are fetched from edge attribute texture
in float a_edgeIndex; // Index into edge data texture
in float a_edgeAttrIndex; // Index into edge attribute texture (for curvature, etc.)
in float a_baseFontSize; // Base font size in pixels (per-label)
// Character metrics (in glyph units = atlas font size pixels)
in vec4 a_charMetrics; // (charTextOffset, charAdvance, totalTextWidth, positionMode)
in vec4 a_charDims; // (charSize.x, charSize.y, charOffset.x, charOffset.y)
// Atlas texture coordinates
in vec4 a_texCoords; // (x, y, width, height) in atlas pixels
// Label parameters
in vec2 a_labelParams; // (margin, unused)
// Appearance
in vec4 a_color; // Character color (RGBA, normalized)
${t?"in vec4 a_borderColor; // Border color (RGBA, normalized)":""}
// ============================================================================
// Attributes - Per Vertex (Constant)
// ============================================================================
in vec2 a_quadCorner; // Quad corner: (0,0), (1,0), (0,1), (1,1)
// ============================================================================
// Uniforms
// ============================================================================
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
uniform float u_pixelRatio;
uniform float u_cameraAngle; // Required by node shape SDFs
// u_sdfBufferPixels kept for ABI compatibility but unused in shader
uniform float u_sdfBufferPixels;
uniform vec2 u_resolution;
uniform vec2 u_atlasSize;
uniform sampler2D u_nodeDataTexture; // Shared texture with node position/size/shape data
uniform int u_nodeDataTextureWidth; // Width of 2D node data texture for coordinate calculation
uniform sampler2D u_edgeDataTexture; // Shared texture with edge data
uniform int u_edgeDataTextureWidth; // Width of 2D edge data texture for coordinate calculation
uniform sampler2D u_edgeFrameTexture; // Per-edge clamp (tStart, tEnd, straightenFactor, pathLength)
uniform int u_edgeFrameTextureWidth;
${s?"uniform float u_zoomSizeRatio; // Zoom-based size ratio from zoomToSizeRatioFunction":""}
// Edge path attribute texture uniforms (for curvature and other path attributes)
${d.uniformDeclarations}
// ============================================================================
// Varyings
// ============================================================================
out vec2 v_texCoord;
out vec4 v_color;
${t?"out vec4 v_borderColor;":""}
out float v_edgeFade; // 0 = fully visible, 1 = fully faded (outside body)
out float v_alphaModifier; // 0-1 based on label visibility ratio
out float v_fontScale; // Ratio of rendered font size to atlas font size
${t?"out float v_positionMode; // Position mode for conditional border (0=over needs border)":""}
// ============================================================================
// Constants
// ============================================================================
const float bias = 255.0 / 254.0;
const float FADE_WIDTH_PIXELS = 15.0; // Width of fade gradient in pixels
const float ATLAS_FONT_SIZE = ${I(Co)}; // Base font size used in SDF atlas
const float VERTICAL_CENTER_RATIO = ${I(Lo)}; // Baseline to visual center ratio
// ============================================================================
// Path Attribute Variables (set in main, used by path functions)
// ============================================================================
// Path attributes are fetched from the edge attribute texture and stored in
// variables with v_ prefix (e.g., v_curvature) for path functions to access.
${d.vertexVaryingDeclarations.replace(/out /g,"")}
// Node size variables (set in main, used by some path functions like loops).
// These mirror the v_sourceNodeSize / v_targetNodeSize varyings in generator.ts,
// but are plain floats here since the label shader is vertex-only.
float v_sourceNodeSize;
float v_targetNodeSize;
// Shared preamble: shape SDFs, path functions + selectors, clamp, helpers.
${Vi({paths:e,minVisibilityThreshold:r,fullVisibilityThreshold:n})}
// ============================================================================
// Main
// ============================================================================
void main() {
// -------------------------------------------------------------------------
// Fetch edge data from edge texture (2 texels per edge)
// -------------------------------------------------------------------------
// Texel 0: sourceNodeIndex, targetNodeIndex, thickness, reserved
// Texel 1: headLengthRatio, tailLengthRatio, pathId, extremityIds
int edgeIdx = int(a_edgeIndex);
int texel0Idx = edgeIdx * 2;
int texel1Idx = edgeIdx * 2 + 1;
ivec2 edgeTexCoord0 = ivec2(texel0Idx % u_edgeDataTextureWidth, texel0Idx / u_edgeDataTextureWidth);
ivec2 edgeTexCoord1 = ivec2(texel1Idx % u_edgeDataTextureWidth, texel1Idx / u_edgeDataTextureWidth);
vec4 edgeData0 = texelFetch(u_edgeDataTexture, edgeTexCoord0, 0);
vec4 edgeData1 = texelFetch(u_edgeDataTexture, edgeTexCoord1, 0);
// Unpack edge data
int srcIdx = int(edgeData0.x);
int tgtIdx = int(edgeData0.y);
float thickness = edgeData0.z;
// edgeData0.w is reserved
float headLengthRatio = edgeData1.x;
float tailLengthRatio = edgeData1.y;
int pathId = int(edgeData1.z); // Path type for multi-path support
float baseFontSize = a_baseFontSize;
// -------------------------------------------------------------------------
// Fetch path attributes from edge attribute texture
// -------------------------------------------------------------------------
// Note: The fetch code uses 'edgeIdx' variable, so we set it to the attribute texture index
{
int edgeIdx = int(a_edgeAttrIndex); // Use attribute texture index for path attributes
${d.fetchCode}
${d.varyingAssignments}
}
// -------------------------------------------------------------------------
// Fetch node data from node texture (geometry texel + rotation-flags texel)
// -------------------------------------------------------------------------
vec4 srcNodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, srcIdx);
vec4 tgtNodeData = readNodeData(u_nodeDataTexture, u_nodeDataTextureWidth, tgtIdx);
vec2 source = srcNodeData.xy;
vec2 target = tgtNodeData.xy;
float sourceSize = srcNodeData.z;
float targetSize = tgtNodeData.z;
v_sourceNodeSize = sourceSize;
v_targetNodeSize = targetSize;
float charTextOffset = a_charMetrics.x;
float charAdvance = a_charMetrics.y;
float totalTextWidth = a_charMetrics.z;
float positionMode = a_charMetrics.w;
vec2 charSize = a_charDims.xy;
vec2 charOffset = a_charDims.zw;
float margin = a_labelParams.x;
// -------------------------------------------------------------------------
// Compute pixel-to-graph conversion (for fixed font size mode)
// -------------------------------------------------------------------------
// This converts screen pixels to graph units such that N pixels on screen
// becomes N pixels regardless of zoom level.
// matrixScaleX is how much the matrix scales graph units to clip space
float matrixScaleX = length(vec2(u_matrix[0][0], u_matrix[1][0]));
float pixelToGraph = 2.0 / (matrixScaleX * u_resolution.x);
// -------------------------------------------------------------------------
// Step 1: Convert thickness to WebGL units
// -------------------------------------------------------------------------
float webGLThickness = thickness * u_correctionRatio / u_sizeRatio;
// -------------------------------------------------------------------------
// Step 2: Compute body bounds (truncated at node boundaries + extremities)
// -------------------------------------------------------------------------
vec4 edgeClamp = readFrameTexel(u_edgeFrameTexture, u_edgeFrameTextureWidth, edgeIdx);
vec3 bodyBounds = computeEdgeLabelBodyBounds(
edgeClamp.x, edgeClamp.y, edgeClamp.w,
webGLThickness, headLengthRatio, tailLengthRatio
);
float bodyStartDist = bodyBounds.x;
float bodyEndDist = bodyBounds.y;
float bodyLength = bodyBounds.z;
// -------------------------------------------------------------------------
// Step 3: Compute font scale and text dimensions
// -------------------------------------------------------------------------
// Font size modes:
// - "fixed": Constant pixel size regardless of zoom, using pixelToGraph conversion
// - "scaled": Scales with zoom using zoomToSizeRatioFunction
${s?`// Scaled mode: font scales with zoom
float fontScale = baseFontSize / ATLAS_FONT_SIZE * u_zoomSizeRatio;
// Convert glyph-unit metrics to WebGL units (scales with zoom)
float textWidthWebGL = totalTextWidth * fontScale * u_correctionRatio / u_sizeRatio;
float charOffsetWebGL = charTextOffset * fontScale * u_correctionRatio / u_sizeRatio;
float charAdvanceWebGL = charAdvance * fontScale * u_correctionRatio / u_sizeRatio;`:`// Fixed mode: font stays constant in screen pixels
float fontScale = baseFontSize / ATLAS_FONT_SIZE;
// Convert glyph-unit metrics to graph units using pixelToGraph (zoom-independent)
float textWidthWebGL = totalTextWidth * fontScale * pixelToGraph;
float charOffsetWebGL = charTextOffset * fontScale * pixelToGraph;
float charAdvanceWebGL = charAdvance * fontScale * pixelToGraph;`}
// Font scale varying for fragment shader gamma scaling.
// Ratio of rendered font size to atlas font size — used to tighten the
// anti-aliasing band for large labels so they stay sharp.
v_fontScale = fontScale;
// -------------------------------------------------------------------------
// Step 4: Alpha modifier from how much of the label fits in the body
// -------------------------------------------------------------------------
float alphaModifier = computeEdgeLabelAlpha(bodyLength, textWidthWebGL);
// -------------------------------------------------------------------------
// Step 5: Compute character center offset (truncation check moved to after curvature adjustment)
// -------------------------------------------------------------------------
// Body center in arc distance
float bodyCenterDist = (bodyStartDist + bodyEndDist) * 0.5;
float centerT = queryPathTAtDistance(pathId, bodyCenterDist, source, target);
float screenDir = computeEdgeLabelScreenDir(pathId, centerT, source, target, u_matrix);
// Reversed labels lay their characters out from target to source
float readingSign = ${o?"screenDir":"1.0"};
// Character center position relative to label center (on centerline, before curvature adjustment)
float charCenterOffset = readingSign * (charOffsetWebGL + charAdvanceWebGL * 0.5 - textWidthWebGL * 0.5);
// -------------------------------------------------------------------------
// Step 6: Compute perpendicular offset based on position mode
// -------------------------------------------------------------------------
// Position modes: 0=over, 1=above, 2=below, 3=auto
// Needed early for curvature-adaptive character spacing in Step 7
float halfThickness = webGLThickness * 0.5;
${s?`// Scaled mode: margin and text height scale with zoom (same factor as font)
float marginWebGL = margin * u_zoomSizeRatio * u_correctionRatio / u_sizeRatio;
float halfTextHeight = baseFontSize * 0.35 * u_zoomSizeRatio * u_correctionRatio / u_sizeRatio;`:`// Fixed mode: margin and text height stay constant in screen pixels
float marginWebGL = margin * pixelToGraph;
float halfTextHeight = baseFontSize * 0.35 * pixelToGraph;`}
float perpOffset = computeEdgeLabelPerpOffset(
positionMode, halfThickness, marginWebGL, halfTextHeight, readingSign, screenDir
);
// -------------------------------------------------------------------------
// Step 7: Position character on path using offset path traversal
// -------------------------------------------------------------------------
// For "over" mode (perpOffset = 0), use simple centerline placement
// For above/below modes, walk along the offset path to find correct position
float charT;
if (perpOffset == 0.0) {
// Simple case: place on centerline
float charArcDist = bodyCenterDist + charCenterOffset;
charT = queryPathTAtDistance(pathId, charArcDist, source, target);
} else {
// Offset path traversal: walk along the offset curve to find character position
// This ensures even character spacing regardless of curvature
${l?`// -----------------------------------------------------------------------
// Corner skip setup for step/taxi edges with above/below labels
// -----------------------------------------------------------------------
// At concave corners (inner side of the bend), characters would bunch up
// because the offset path has near-zero arc length. We detect corner
// crossings during the offset path traversal and add skip distance.
// Get corner t values and concavity
vec2 cornerTs = queryGetCornerTs(pathId, source, target);
vec2 concavity = queryGetCornerConcavity(pathId, source, target, perpOffset);
// Skip distance in graph units, proportional to on-screen font size
// For fixed mode: use pixelToGraph so gap stays constant regardless of zoom
// For scaled mode: use the same conversion as text width
${s?"float skipDistGraph = STEP_INNER_CORNER_SKIP_FACTOR * baseFontSize * u_zoomSizeRatio * u_correctionRatio / u_sizeRatio;":"float skipDistGraph = STEP_INNER_CORNER_SKIP_FACTOR * baseFontSize * pixelToGraph;"}
// Corner t values for detecting crossings during traversal
float corner1T = cornerTs.x;
float corner2T = cornerTs.y;
bool corner1IsConcave = concavity.x > 0.5;
bool corner2IsConcave = concavity.y > 0.5;`:""}
// Target distance along offset path from center
float targetOffsetDist = abs(charCenterOffset);
// Handle center character (charCenterOffset ≈ 0) - no search needed
if (targetOffsetDist < 0.0001) {
charT = centerT;
} else {
vec2 centerPos = queryPathPosition(pathId, centerT, source, target);
vec2 centerNormal = queryPathNormal(pathId, centerT, source, target);
vec2 offsetCenter = centerPos + centerNormal * perpOffset;
float searchDir = charCenterOffset > 0.0 ? 1.0 : -1.0;
// Search bounds (t values for body start and end)
float tBodyStart = queryPathTAtDistance(pathId, bodyStartDist, source, target);
float tBodyEnd = queryPathTAtDistance(pathId, bodyEndDist, source, target);
// Walk along offset path to find character position
float accumDist = 0.0;
vec2 prevOffsetPos = offsetCenter;
float prevT = centerT;
float foundT = centerT;
// Search range depends on direction
float tSearchEnd = searchDir > 0.0 ? tBodyEnd : tBodyStart;
${l?`// Track which concave corners we've crossed to add skip distance
bool crossedCorner1 = false;
bool crossedCorner2 = false;
float effectiveTargetDist = targetOffsetDist;`:""}
const int STEPS = 32;
for (int i = 1; i <= STEPS; i++) {
// Step along centerline t, from center toward target
float stepT = centerT + searchDir * float(i) * abs(tSearchEnd - centerT) / float(STEPS);
${l?`// Check for concave corner crossings and add skip distance
// Corner 1 crossing check
if (corner1IsConcave && !crossedCorner1) {
bool crossingCorner1 = (searchDir > 0.0)
? (prevT < corner1T && stepT >= corner1T)
: (prevT > corner1T && stepT <= corner1T);
if (crossingCorner1) {
crossedCorner1 = true;
effectiveTargetDist += skipDistGraph;
}
}
// Corner 2 crossing check
if (corner2IsConcave && !crossedCorner2) {
bool crossingCorner2 = (searchDir > 0.0)
? (prevT < corner2T && stepT >= corner2T)
: (prevT > corner2T && stepT <= corner2T);
if (crossingCorner2) {
crossedCorner2 = true;
effectiveTargetDist += skipDistGraph;
}
}`:""}
// Compute offset position at this t
vec2 stepPos = queryPathPosition(pathId, stepT, source, target);
vec2 stepNormal = queryPathNormal(pathId, stepT, source, target);
vec2 offsetPos = stepPos + stepNormal * perpOffset;
// Distance along offset path
float segDist = length(offsetPos - prevOffsetPos);
${l?`if (accumDist + segDist >= effectiveTargetDist) {
// Interpolate within segment to find exact t
float remaining = effectiveTargetDist - accumDist;
float segT = remaining / max(segDist, 0.0001);
foundT = mix(prevT, stepT, segT);
break;
}`:`if (accumDist + segDist >= targetOffsetDist) {
// Interpolate within segment to find exact t
float remaining = targetOffsetDist - accumDist;
float segT = remaining / max(segDist, 0.0001);
foundT = mix(prevT, stepT, segT);
break;
}`}
accumDist += segDist;
prevOffsetPos = offsetPos;
prevT = stepT;
// Update foundT to last valid position in case loop exhausts without finding target
foundT = stepT;
}
charT = foundT;
}
}
// Get position and tangent at final character position
vec2 pathPos = queryPathPosition(pathId, charT, source, target);
vec2 tangent = queryPathTangent(pathId, charT, source, target);
// Compute perpendicular direction (90 degrees from tangent)
vec2 perpDir = vec2(-tangent.y, tangent.x);
// Apply perpendicular offset to path position
vec2 offsetPathPos = pathPos + perpDir * perpOffset;
// -------------------------------------------------------------------------
// Step 8: Build character quad
// -------------------------------------------------------------------------
// Character size in screen pixels
vec2 charSizePixels = charSize * fontScale;
// Character offset from origin to the atlas region's top-left corner.
// bearingX/bearingY already include the SDF buffer.
vec2 charOffsetPixels = charOffset * fontScale;
// The character's local X offset from pathPos (which is at character center)
float charLocalX = -charAdvance * 0.5 * fontScale;
// Build quad position:
// - Start at character origin (charLocalX on X axis, 0 on Y axis = baseline)
// - Add bearing offset to get to atlas region corner
// - Add quad corner * size to get vertex position
vec2 quadPos;
quadPos.x = charLocalX + charOffsetPixels.x + a_quadCorner.x * charSizePixels.x;
// charOffset.y = -bearingY (negated), so -charOffsetPixels.y = bearingY * fontScale
// (distance from baseline to atlas region top, positive = upward)
// Quad corner (0,0) = bottom-left, (1,1) = top-right
quadPos.y = -charOffsetPixels.y - charSizePixels.y * (1.0 - a_quadCorner.y);
// Center vertically on the path by offsetting by half the visual text height
// VERTICAL_CENTER_RATIO is the distance from baseline to visual center as a ratio of atlas font size
float verticalCenterOffset = VERTICAL_CENTER_RATIO * ATLAS_FONT_SIZE * fontScale;
quadPos.y -= verticalCenterOffset;
// -------------------------------------------------------------------------
// Step 9: Rotate quad to align with tangent
// -------------------------------------------------------------------------
// Rotation matrix from the reading direction
// readingDir = (cos(angle), sin(angle)), so we can build rotation directly
vec2 readingDir = tangent * readingSign;
mat2 rotation = mat2(readingDir.x, readingDir.y, -readingDir.y, readingDir.x);
// Convert pixel offset to WebGL units for rotation
${s?"vec2 quadPosWebGL = quadPos * u_correctionRatio / u_sizeRatio; // Scaled mode":"vec2 quadPosWebGL = quadPos * pixelToGraph; // Fixed mode: use pixelToGraph for zoom-independent size"}
// Rotate around character center on path
vec2 rotatedOffset = rotation * quadPosWebGL;
// Final position in graph space (using offset path position for above/below modes)
vec2 worldPos = offsetPathPos + rotatedOffset;
// -------------------------------------------------------------------------
// Step 10: Transform to clip space
// -------------------------------------------------------------------------
vec3 clipPos = u_matrix * vec3(worldPos, 1.0);
gl_Position = vec4(clipPos.xy, 0.0, 1.0);
// -------------------------------------------------------------------------
// Step 11: Texture coordinates
// -------------------------------------------------------------------------
// Flip Y for texture coordinates (texture Y goes down, quad Y goes up)
vec2 texCorner = vec2(a_quadCorner.x, 1.0 - a_quadCorner.y);
v_texCoord = (a_texCoords.xy + texCorner * a_texCoords.zw) / u_atlasSize;
// -------------------------------------------------------------------------
// Step 12: Pass color, border color, and alpha modifier
// -------------------------------------------------------------------------
v_color = a_color;
v_color.a *= bias;
${t?` v_borderColor = a_borderColor;
v_borderColor.a *= bias;
v_positionMode = positionMode;`:""}
v_alphaModifier = alphaModifier;
// -------------------------------------------------------------------------
// Step 13: Compute edge fade for soft truncation
// -------------------------------------------------------------------------
// Convert fade width from pixels to WebGL units
${s?"float fadeWidthWebGL = FADE_WIDTH_PIXELS * u_correctionRatio / u_sizeRatio;":"float fadeWidthWebGL = FADE_WIDTH_PIXELS * pixelToGraph;"}
// Compute the arc position of THIS VERTEX (not just character center)
// The quad extends from charCenter - advance/2 to charCenter + advance/2
// a_quadCorner.x is 0 for left edge, 1 for right edge
float vertexLocalOffset = readingSign * (a_quadCorner.x - 0.5) * charAdvanceWebGL;
float vertexArcOffset = charCenterOffset + vertexLocalOffset;
// Compute distance from body edges (positive = inside body, negative = outside)
float halfBody = bodyLength * 0.5;
float distFromStart = vertexArcOffset + halfBody; // Distance from body start edge
float distFromEnd = halfBody - vertexArcOffset; // Distance from body end edge
float distFromEdge = min(distFromStart, distFromEnd);
// Compute fade: 0 = fully visible (deep inside body), 1 = fully faded (at body edge)
// Fade goes from 0 (at 2*fadeWidth inside) to 1 (at body edge)
// This ensures text is fully transparent before reaching extremities
v_edgeFade = 1.0 - smoothstep(0.0, fadeWidthWebGL * 2.0, distFromEdge);
}
`}function Po(a={}){const{hasBorder:e=!1}=a;return`#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
${e?"in vec4 v_borderColor;":""}
in float v_edgeFade; // 0 = fully visible, 1 = fully faded
in float v_alphaModifier; // 0-1 based on label visibility ratio
in float v_fontScale; // Ratio of rendered font size to atlas font size
${e?"in float v_positionMode; // Position mode (0=over, 1=above, 2=below, 3=auto)":""}
uniform sampler2D u_atlas;
uniform float u_gamma;
uniform float u_sdfBuffer;
uniform float u_pixelRatio;
${e?"uniform float u_borderWidth; // Border width in SDF units (normalized)":""}
// Fragment output (single target - picking handled via separate pass)
out vec4 fragColor;
void main() {
#ifdef PICKING_MODE
// Edge labels are not pickable - discard all fragments in picking mode
fragColor = vec4(0.0);
discard;
#else
// SDF stores normalized distance: 0.5 = on edge, >0.5 = inside glyph
float sdfValue = texture(u_atlas, v_texCoord).a;
// Edge threshold accounting for SDF buffer padding
float edge = 1.0 - u_sdfBuffer;
// Scale gamma inversely with font scale so small labels get a wider AA band
// (smoother) and large labels get a tighter band (sharper).
float aaWidth = u_gamma / (u_pixelRatio * v_fontScale);
// Apply edge fade for soft truncation at body boundaries
// Also apply visibility-based alpha modifier for short edge labels
float edgeAlpha = (1.0 - v_edgeFade) * v_alphaModifier;
${e?` // Fill alpha: fully opaque inside the glyph
float fillAlpha = smoothstep(edge - aaWidth, edge + aaWidth, sdfValue);
// Only apply border for "over" position mode (v_positionMode == 0.0)
// Labels positioned above/below/auto don't overlap the edge line and don't need borders
if (v_positionMode < 0.5) {
// Border rendering: compute alpha for both fill and border regions
// Border extends from (edge - borderWidth) to edge
float borderEdge = edge - u_borderWidth;
// Border alpha: opaque in the border region (between borderEdge and edge)
float borderAlpha = smoothstep(borderEdge - aaWidth, borderEdge + aaWidth, sdfValue);
// Composite: fill on top of border
// Border is visible where borderAlpha > 0 but fillAlpha < 1
vec3 borderColorPremult = v_borderColor.rgb * v_borderColor.a * borderAlpha * edgeAlpha;
vec3 fillColorPremult = v_color.rgb * v_color.a * fillAlpha * edgeAlpha;
// Blend fill over border (fill replaces border where fill is opaque)
float finalBorderAlpha = borderAlpha * (1.0 - fillAlpha);
vec3 finalColor = fillColorPremult + v_borderColor.rgb * v_borderColor.a * finalBorderAlpha * edgeAlpha;
float finalAlpha = (v_color.a * fillAlpha + v_borderColor.a * finalBorderAlpha) * edgeAlpha;
fragColor = vec4(finalColor, finalAlpha);
} else {
// No border for above/below/auto positions - simple text rendering
float finalAlpha = v_color.a * fillAlpha * edgeAlpha;
fragColor = vec4(v_color.rgb * finalAlpha, finalAlpha);
}`:` // Smooth transition from transparent to opaque at glyph edge
float alpha = smoothstep(edge - aaWidth, edge + aaWidth, sdfValue);
// Premultiplied alpha output
float finalAlpha = v_color.a * alpha * edgeAlpha;
fragColor = vec4(v_color.rgb * finalAlpha, finalAlpha);`}
#endif
}
`}function wo(a,e=!1,t="fixed"){const i=["u_matrix","u_sizeRatio","u_correctionRatio","u_pixelRatio","u_cameraAngle","u_sdfBufferPixels","u_resolution","u_atlasSize","u_atlas","u_gamma","u_sdfBuffer","u_nodeDataTexture","u_nodeDataTextureWidth","u_edgeDataTexture","u_edgeDataTextureWidth","u_edgeFrameTexture","u_edgeFrameTextureWidth","u_edgeAttributeTexture","u_edgeAttributeTextureWidth","u_edgeAttributeTexelsPerEdge"];t==="scaled"&&i.push("u_zoomSizeRatio"),e&&i.push("u_borderWidth");for(const r of a)for(const n of r.uniforms)i.includes(n.name)||i.push(n.name);return i}function Ki(a){const{hasBorder:e=!1,fontSizeMode:t="fixed"}=a;return{vertexShader:Fo(a),fragmentShader:Po({hasBorder:e}),uniforms:wo(a.paths,e,t)}}const Io=new Map;function zo(a){switch(a){case"over":return 0;case"above":return 1;case"below":return 2;case"auto":return 3;default:return 0}}function Zi(a,e,t,i){const{color:r,margin:n,textBorder:o}=i,s=Yi(i),{paths:l,fontSizeMode:u,minVisibilityThreshold:g,fullVisibilityThreshold:d,keepUpright:h}=s,c=!!o,f=Ki({paths:l,hasBorder:c,fontSizeMode:u,minVisibilityThreshold:g,fullVisibilityThreshold:d,keepUpright:h}),v=class v extends na{constructor(m,E,p){super(m,E,p),this.atlasTexture=null,this.atlasNeedsUpdate=!1,this.refreshTimeout=null,this.labelGlyphCache=new Map,this.edgeAttributeTexture=null;const x=De();if(this.attributeLayout=U([...l,x]),this.attrDescriptors=mt([...l,x],this.attributeLayout),this.edgeAttributeTexture=new pt(m,this.attributeLayout),this.packedAttributeData=new Float32Array(this.attributeLayout.floatsPerItem),this.atlasManager=new re,this.gamma=.025,this.sdfBuffer=q.cutoff,this.atlasTexture=m.createTexture(),!this.atlasTexture)throw new Error("EdgeLabelProgram: failed to create atlas texture");m.bindTexture(m.TEXTURE_2D,this.atlasTexture),m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_S,m.CLAMP_TO_EDGE),m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_T,m.CLAMP_TO_EDGE),m.texParameteri(m.TEXTURE_2D,m.TEXTURE_MIN_FILTER,m.LINEAR),m.texParameteri(m.TEXTURE_2D,m.TEXTURE_MAG_FILTER,m.LINEAR),m.bindTexture(m.TEXTURE_2D,null),this.atlasManager.on(re.ATLAS_UPDATED_EVENT,()=>{this.atlasNeedsUpdate=!0,this.refreshTimeout!==null&&clearTimeout(this.refreshTimeout),this.refreshTimeout=setTimeout(()=>{this.refreshTimeout=null,this.renderer.refresh()},0)});const T={family:"sans-serif",weight:"normal",style:"normal"};this.defaultFontKey=this.atlasManager.registerFont(T)}getDefinition(){const{FLOAT:m,UNSIGNED_BYTE:E,TRIANGLE_STRIP:p}=WebGL2RenderingContext,x=new Set,T=[];for(const y of l)for(const R of y.attributes){const A=R.name.startsWith("a_")?R.name:`a_${R.name}`;x.has(A)||(x.add(A),T.push({name:A,size:R.size,type:R.type}))}return{VERTICES:4,VERTEX_SHADER_SOURCE:f.vertexShader,FRAGMENT_SHADER_SOURCE:f.fragmentShader,METHOD:p,UNIFORMS:f.uniforms,ATTRIBUTES:[{name:"a_edgeIndex",size:1,type:m},{name:"a_edgeAttrIndex",size:1,type:m},{name:"a_baseFontSize",size:1,type:m},{name:"a_charMetrics",size:4,type:m},{name:"a_charDims",size:4,type:m},{name:"a_texCoords",size:4,type:m},{name:"a_labelParams",size:2,type:m},{name:"a_color",size:4,type:E,normalized:!0},...c?[{name:"a_borderColor",size:4,type:E,normalized:!0}]:[],...T.filter(y=>!["a_curvature","curvature"].includes(y.name))],CONSTANT_ATTRIBUTES:[{name:"a_quadCorner",size:2,type:m}],CONSTANT_DATA:[[0,0],[1,0],[0,1],[1,1]]}}prepareLabelGlyphs(m,E){if(E.hidden||!E.text){this.labelGlyphCache.delete(m);return}const p=E.text,x=E.fontKey||this.defaultFontKey;this.atlasManager.ensureGlyphs(p,x);const T=[],y=[];let R=0;for(const A of p){const S=A.codePointAt(0);if(S===void 0){T.push(void 0),y.push(R);continue}const C=this.atlasManager.getGlyph(S,x);T.push(C),y.push(R),C&&(R+=C.advance)}this.labelGlyphCache.set(m,{glyphs:T,xOffsets:y,totalWidth:R})}processCharacter(m,E,p,x){var N;const{floats:T}=this,y=this.STRIDE,R=m*y,A=this.labelGlyphCache.get(E.parentKey);if(!A||!A.glyphs[x]){for(let z=0;z<y;z++)T[R+z]=0;return}const S=A.glyphs[x],C=A.xOffsets[x],L=v.labelColor,D=L&&typeof L=="object"&&"color"in L&&L.color?L.color:E.color,F=H(D);let P=R;T[P++]=E.edgeIndex,T[P++]=((N=this.edgeAttributeTexture)==null?void 0:N.getIndex(E.parentKey))??0,T[P++]=E.size,T[P++]=C,T[P++]=S.advance,T[P++]=A.totalWidth,T[P++]=zo(E.position),T[P++]=S.atlasWidth,T[P++]=S.atlasHeight,T[P++]=S.bearingX,T[P++]=-S.bearingY,T[P++]=S.atlasX,T[P++]=S.atlasY,T[P++]=S.atlasWidth,T[P++]=S.atlasHeight;const w=v.labelMargin??E.margin;if(T[P++]=w,T[P++]=0,T[P++]=F,c&&o){let z;typeof o.color=="string"?z=o.color:z=o.color.color||"#ffffff",T[P++]=H(z)}const M=new Set;for(const z of l)for(const B of z.attributes){const G=B.name.startsWith("a_")?B.name.slice(2):B.name;if(G!=="curvature"&&!M.has(G)){M.add(G);for(let tt=0;tt<B.size;tt++)T[P++]=0}}}processEdgeLabel(m,E,p){if(this.prepareLabelGlyphs(m,p),this.edgeAttributeTexture&&!p.hidden&&p.text){this.edgeAttributeTexture.allocate(m);const x=this.packedAttributeData;bt(this.attrDescriptors,p.edgeAttributes,x,"",Io,0),this.edgeAttributeTexture.updateAllAttributes(m,x)}return super.processLabel(m,E,p)}updateAtlasTexture(){if(!this.atlasNeedsUpdate)return;const m=this.normalProgram.gl,E=this.atlasManager.getTextures();if(E.length===0)return;const p=E[0];m.bindTexture(m.TEXTURE_2D,this.atlasTexture),m.texImage2D(m.TEXTURE_2D,0,m.RGBA,p.width,p.height,0,m.RGBA,m.UNSIGNED_BYTE,p.data),m.bindTexture(m.TEXTURE_2D,null),this.atlasNeedsUpdate=!1}setUniforms(m,E){const{gl:p,uniformLocations:x}=E,T=this.atlasManager.getTextures(),y=T.length>0?[T[0].width,T[0].height]:[1,1];if(p.uniformMatrix3fv(x.u_matrix,!1,m.matrix),p.uniform1f(x.u_sizeRatio,m.sizeRatio),p.uniform1f(x.u_correctionRatio,m.correctionRatio),p.uniform1f(x.u_pixelRatio,m.pixelRatio),p.uniform1f(x.u_cameraAngle,m.cameraAngle),p.uniform1f(x.u_sdfBufferPixels,q.buffer),p.uniform2f(x.u_resolution,m.width,m.height),p.uniform2f(x.u_atlasSize,y[0],y[1]),p.uniform1f(x.u_gamma,this.gamma),p.uniform1f(x.u_sdfBuffer,this.sdfBuffer),p.activeTexture(p.TEXTURE0),p.bindTexture(p.TEXTURE_2D,this.atlasTexture),p.uniform1i(x.u_atlas,0),x.u_nodeDataTexture!==void 0&&p.uniform1i(x.u_nodeDataTexture,m.nodeDataTextureUnit),x.u_nodeDataTextureWidth!==void 0&&p.uniform1i(x.u_nodeDataTextureWidth,m.nodeDataTextureWidth),x.u_edgeDataTexture!==void 0&&p.uniform1i(x.u_edgeDataTexture,m.edgeDataTextureUnit),x.u_edgeDataTextureWidth!==void 0&&p.uniform1i(x.u_edgeDataTextureWidth,m.edgeDataTextureWidth),x.u_edgeFrameTexture!==void 0&&p.uniform1i(x.u_edgeFrameTexture,m.edgeFrameTextureUnit),x.u_edgeFrameTextureWidth!==void 0&&p.uniform1i(x.u_edgeFrameTextureWidth,m.edgeFrameTextureWidth),c&&o&&x.u_borderWidth!==void 0){const R=o.width/q.buffer*this.sdfBuffer;p.uniform1f(x.u_borderWidth,R)}if(this.edgeAttributeTexture&&x.u_edgeAttributeTexture!==void 0&&(this.edgeAttributeTexture.bind(ne),p.uniform1i(x.u_edgeAttributeTexture,ne),p.uniform1i(x.u_edgeAttributeTextureWidth,this.edgeAttributeTexture.getTextureWidth()),p.uniform1i(x.u_edgeAttributeTexelsPerEdge,this.edgeAttributeTexture.getTexelsPerItem())),u==="scaled"&&x.u_zoomSizeRatio!==void 0){const A=1/this.renderer.getSetting("zoomToSizeRatioFunction")(m.zoomRatio);p.uniform1f(x.u_zoomSizeRatio,A)}}renderProgram(m,E){this.updateAtlasTexture(),this.atlasManager.hasPendingGlyphs()&&(this.atlasManager.flush(),this.updateAtlasTexture()),this.edgeAttributeTexture&&this.edgeAttributeTexture.upload(),super.renderProgram(m,E)}registerFont(m,E="normal",p="normal"){return this.atlasManager.registerFont({family:m,weight:E,style:p})}getAtlasManager(){return this.atlasManager}ensureGlyphsReady(m,E){const p=E||this.defaultFontKey;for(const x of m)this.atlasManager.ensureGlyphs(x,p);this.atlasManager.flush()}measureLabelAtlasWidth(m,E){const p=E||this.defaultFontKey;this.atlasManager.ensureGlyphs(m,p),this.atlasManager.hasPendingGlyphs()&&this.atlasManager.flush();let x=0;for(const T of m){const y=T.codePointAt(0);if(y===void 0)continue;const R=this.atlasManager.getGlyph(y,p);R&&(x+=R.advance)}return x}measureLabel(m,E,p){const x=this.measureLabelAtlasWidth(m,p),T=E/q.fontSize;return{width:x*T,height:E,textHeight:E}}kill(){const m=this.normalProgram.gl;this.refreshTimeout!==null&&(clearTimeout(this.refreshTimeout),this.refreshTimeout=null),this.atlasTexture&&(m.deleteTexture(this.atlasTexture),this.atlasTexture=null),this.edgeAttributeTexture&&(this.edgeAttributeTexture.kill(),this.edgeAttributeTexture=null),this.atlasManager.destroy(),this.labelGlyphCache.clear(),super.kill()}};v.labelColor=r,v.labelMargin=n;let b=v;return new b(a,e,t)}function Mo(){return{name:"none",glsl:`
// No extremity - always returns positive (outside)
float extremity_none(vec2 uv, float lengthRatio, float widthRatio) {
return 1.0;
}
`,length:0,widthFactor:1,margin:0,uniforms:[],attributes:[]}}function Qi(a,e,t,i,r,n,o){const s=Oi(i),{paths:l,layers:u,defaultHead:g,defaultTail:d}=s,h=[Mo(),...s.extremities],c={},f={};l.forEach((S,C)=>c[S.name]=C),h.forEach((S,C)=>f[S.name]=C);const b=f[g]??0,v=f[d]??0;let _=null;const m=U([...l,...u]),E=class extends ae{constructor(S,C,L){_||(_=la({paths:l,extremities:h,layers:u,antialias:r})),super(S,C,L),this.layerLifecycles=new Map,this.needsShaderRegeneration=!1,this.edgeAttributeTexture=null,this.layout=m,this.attrDescriptors=[],this.lifecycleIndexOffset=l.length,this._pickingBuffer=C,this.edgeAttributeTexture=new pt(S,this.layout),this.packedAttributeData=new Float32Array(this.layout.floatsPerItem),u.forEach((F,P)=>{if(F.lifecycle){const w={gl:S,renderer:{refresh:()=>L.refresh()},getUniformLocation:M=>S.getUniformLocation(this.normalProgram.program,M),requestShaderRegeneration:()=>{this.needsShaderRegeneration=!0},requestRefresh:()=>{L.refresh()}};this.layerLifecycles.set(P,F.lifecycle(w))}}),this.layerLifecycles.forEach(F=>{var P;return(P=F.init)==null?void 0:P.call(F)});const D=new Map;this.layerLifecycles.forEach((F,P)=>{F.getAttributeData&&D.set(l.length+P,F)}),this.attrDescriptors=mt([...l,...u],this.layout,D)}getAttributeTexture(){return this.edgeAttributeTexture}resolveEdgeIds(S,C,L){const D=S;let F=0,P=b,w=v;const M=C?D.selfLoopPath:L&&D.parallelPath?D.parallelPath:D.path;M&&c[M]!==void 0&&(F=c[M]),D.head&&D.head!=="none"&&f[D.head]!==void 0&&(P=f[D.head]),D.tail&&D.tail!=="none"&&f[D.tail]!==void 0&&(w=f[D.tail]);const N=h[P],z=h[w];return{pathId:F,headId:P,tailId:w,headLengthRatio:j(N.length)?0:N.length,tailLengthRatio:j(z.length)?0:z.length}}getDefinition(){const{TRIANGLE_STRIP:S}=WebGL2RenderingContext,C=S,L=_;return{VERTICES:L.verticesPerEdge,VERTEX_SHADER_SOURCE:L.vertexShader,FRAGMENT_SHADER_SOURCE:L.fragmentShader,METHOD:C,UNIFORMS:L.uniforms,ATTRIBUTES:L.attributes,CONSTANT_ATTRIBUTES:L.constantAttributes,CONSTANT_DATA:L.constantData}}maybeRegenerateShaders(){if(!this.needsShaderRegeneration)return;this.needsShaderRegeneration=!1;const S=u.map((w,M)=>{const N=this.layerLifecycles.get(M);return N!=null&&N.regenerate?N.regenerate():w});_=la({paths:l,extremities:h,layers:S,antialias:this.renderer.getSetting("antialiasEdges")});const C=this.normalProgram.gl,{program:L,buffer:D,vertexShader:F,fragmentShader:P}=this.normalProgram;C.deleteProgram(L),C.deleteBuffer(D),C.deleteShader(F),C.deleteShader(P),this.normalProgram=this.getProgramInfo("normal",C,_.vertexShader,_.fragmentShader,this._pickingBuffer)}process(S,C,L,D,F,P){let w=C*this.STRIDE;if(F.visibility==="hidden"||L.visibility==="hidden"||D.visibility==="hidden"){for(let M=w+this.STRIDE;w<M;w++)this.floats[w]=0;this.floats[C*this.STRIDE]=di;return}this.processVisibleItem(Ge(S),w,L,D,F,P)}processVisibleItem(S,C,L,D,F,P){const{floats:w,ints:M}=this;w[C++]=P,w[C++]=H(F.color),M[C++]=S,w[C++]=F.opacity??1;const N=this.packedAttributeData;bt(this.attrDescriptors,F,N,F.color,this.layerLifecycles,this.lifecycleIndexOffset),this.edgeAttributeTexture.updateAllAttributesAtRow(P,N)}setUniforms(S,C){const{gl:L,uniformLocations:D}=C;D.u_matrix&&L.uniformMatrix3fv(D.u_matrix,!1,S.matrix),D.u_sizeRatio&&L.uniform1f(D.u_sizeRatio,S.sizeRatio),D.u_correctionRatio&&L.uniform1f(D.u_correctionRatio,S.correctionRatio),D.u_zoomRatio&&L.uniform1f(D.u_zoomRatio,S.zoomRatio),D.u_pixelRatio&&L.uniform1f(D.u_pixelRatio,S.pixelRatio),D.u_cameraAngle&&L.uniform1f(D.u_cameraAngle,S.cameraAngle),D.u_feather&&L.uniform1f(D.u_feather,S.antiAliasingFeather),D.u_minEdgeThickness&&L.uniform1f(D.u_minEdgeThickness,S.minEdgeThickness),D.u_pickingPadding&&L.uniform1f(D.u_pickingPadding,S.edgePickingPadding),D.u_nodeDataTexture&&L.uniform1i(D.u_nodeDataTexture,S.nodeDataTextureUnit),D.u_nodeDataTextureWidth&&L.uniform1i(D.u_nodeDataTextureWidth,S.nodeDataTextureWidth),D.u_edgeDataTexture&&L.uniform1i(D.u_edgeDataTexture,S.edgeDataTextureUnit),D.u_edgeDataTextureWidth&&L.uniform1i(D.u_edgeDataTextureWidth,S.edgeDataTextureWidth),D.u_edgeFrameTexture&&L.uniform1i(D.u_edgeFrameTexture,S.edgeFrameTextureUnit),D.u_edgeFrameTextureWidth&&L.uniform1i(D.u_edgeFrameTextureWidth,S.edgeFrameTextureWidth),this.edgeAttributeTexture&&this.layout.floatsPerItem>0&&(this.edgeAttributeTexture.bind(ne),D.u_edgeAttributeTexture&&L.uniform1i(D.u_edgeAttributeTexture,ne),D.u_edgeAttributeTextureWidth&&L.uniform1i(D.u_edgeAttributeTextureWidth,this.edgeAttributeTexture.getTextureWidth()),D.u_edgeAttributeTexelsPerEdge&&L.uniform1i(D.u_edgeAttributeTexelsPerEdge,this.edgeAttributeTexture.getTexelsPerItem()));const F=new Set;l.forEach(P=>{P.uniforms.forEach(w=>{F.has(w.name)||(F.add(w.name),this.setTypedUniform(w,C))})}),h.forEach(P=>{P.uniforms.forEach(w=>{F.has(w.name)||(F.add(w.name),this.setTypedUniform(w,C))})}),u.forEach(P=>{P.uniforms.forEach(w=>{F.has(w.name)||(F.add(w.name),this.setTypedUniform(w,C))})})}render(S,C,L){this.maybeRegenerateShaders(),super.render(S,C,L)}renderProgram(S,C){this.layerLifecycles.forEach(L=>{var D;return(D=L.beforeRender)==null?void 0:D.call(L)}),super.renderProgram(S,C)}uploadAttributeTexture(){this.edgeAttributeTexture&&this.layout.floatsPerItem>0&&this.edgeAttributeTexture.upload()}kill(){this.layerLifecycles.forEach(S=>{var C;return(C=S.kill)==null?void 0:C.call(S)}),this.edgeAttributeTexture&&(this.edgeAttributeTexture.kill(),this.edgeAttributeTexture=null),super.kill()}},p=h[b],x=h[v],T={paths:l,headLengthRatio:j(p.length)?0:p.length,tailLengthRatio:j(x.length)?0:x.length,...i.label},y=Yi(T),R=()=>({labelProgram:Zi(a,null,t,T),labelBackgroundProgram:ji(a,e,t,{shaderConfig:y})}),A=new Hi(a,{paths:l,extremities:h,layers:u,nodeShapes:n,nodeLayers:o});return{edgeProgram:new E(a,null,t),createLabelPrograms:R,framePass:A}}function Ji(){return{name:"straight",segments:1,minBodyLengthRatio:0,linearParameterization:!0,glsl:`
// Position at parameter t ∈ [0, 1]
vec2 path_straight_position(float t, vec2 source, vec2 target) {
return mix(source, target, t);
}
// Total length of the path (analytical - more efficient than sampling)
float path_straight_length(vec2 source, vec2 target) {
return length(target - source);
}
`,uniforms:[],attributes:[]}}function No(a){const{segments:e=16}=a??{};return{name:"curved",segments:e,glsl:`
// Compute control point from curvature
// Control point is placed perpendicular to the midpoint of source-target line
vec2 computeControlPoint(vec2 source, vec2 target, float curvature) {
vec2 midpoint = 0.5 * (source + target);
vec2 delta = target - source;
// Perpendicular direction (rotated 90 degrees)
vec2 perp = vec2(-delta.y, delta.x);
float len = length(perp);
if (len < 0.0001) return midpoint;
perp = perp / len;
// Offset by curvature * edge length
return midpoint + perp * curvature * len;
}
// Position at parameter t ∈ [0, 1]
vec2 path_curved_position(float t, vec2 source, vec2 target) {
float curvature = v_curvature;
vec2 control = computeControlPoint(source, target, curvature);
float u = 1.0 - t;
return u * u * source + 2.0 * u * t * control + t * t * target;
}
// Derivative of quadratic Bezier (for efficient arc length computation)
vec2 path_curved_derivative(float t, vec2 source, vec2 target) {
float curvature = v_curvature;
vec2 control = computeControlPoint(source, target, curvature);
// B'(t) = 2(1-t)(P1-P0) + 2t(P2-P1)
return 2.0 * (1.0 - t) * (control - source) + 2.0 * t * (target - control);
}
// Approximate arc length using 5-point Gauss-Legendre quadrature
float path_curved_length(vec2 source, vec2 target) {
// Gauss-Legendre 5-point weights and abscissae
const float x1 = 0.9061798459, x2 = 0.5384693101;
const float w1 = 0.2369268850, w2 = 0.4786286705, w3 = 0.5688888889;
// Transform from [-1,1] to [0,1]: t = 0.5 * (x + 1)
float t1a = 0.5 * (-x1 + 1.0), t1b = 0.5 * (x1 + 1.0);
float t2a = 0.5 * (-x2 + 1.0), t2b = 0.5 * (x2 + 1.0);
// Evaluate derivative magnitudes at sample points
float d1a = length(path_curved_derivative(t1a, source, target));
float d1b = length(path_curved_derivative(t1b, source, target));
float d2a = length(path_curved_derivative(t2a, source, target));
float d2b = length(path_curved_derivative(t2b, source, target));
float d3 = length(path_curved_derivative(0.5, source, target));
// Sum weighted samples (factor of 0.5 for interval transformation)
return 0.5 * (w1 * (d1a + d1b) + w2 * (d2a + d2b) + w3 * d3);
}
`,uniforms:[],attributes:[{name:"curvature",size:1,type:WebGL2RenderingContext.FLOAT}],variables:{curvature:{type:"number",default:0}},spread:{variable:"curvature",compute:(i,r,n)=>(i-(r-1)/2)*n}}}function ko(a){const{segments:e=16,orientation:t="automatic",rotateWithCamera:i=!1,curveOffset:r=.5,curvePosition:n=.5}=a??{};let o,s=0;typeof t=="number"?(o=3,s=t):t==="horizontal"?o=1:t==="vertical"?o=2:o=0;const l=`
// S-curve path constants (baked from options)
const int CURVEDS_ORIENTATION = ${o};
const float CURVEDS_FIXED_ANGLE = ${I(s)};
const bool CURVEDS_ROTATE_WITH_CAMERA = ${i?"true":"false"};
const float CURVEDS_OFFSET = ${I(r)};
const float CURVEDS_POSITION = ${I(n)};
// ============================================================================
// HELPER: Rotate a 2D vector by angle (counter-clockwise)
// ============================================================================
vec2 curvedS_rotate(vec2 v, float angle) {
float c = cos(angle);
float s = sin(angle);
return vec2(c * v.x - s * v.y, s * v.x + c * v.y);
}
// ============================================================================
// HELPER: Get control point direction based on orientation
// ============================================================================
vec2 getCurvedSControlDirection(vec2 source, vec2 target) {
vec2 delta = target - source;
if (CURVEDS_ORIENTATION == 1) {
// Horizontal: control points extend horizontally
return vec2(sign(delta.x), 0.0);
} else if (CURVEDS_ORIENTATION == 2) {
// Vertical: control points extend vertically
return vec2(0.0, sign(delta.y));
} else if (CURVEDS_ORIENTATION == 3) {
// Fixed angle
return vec2(cos(CURVEDS_FIXED_ANGLE), sin(CURVEDS_FIXED_ANGLE));
} else {
// Automatic: choose based on which delta is larger
if (abs(delta.x) >= abs(delta.y)) {
return vec2(sign(delta.x), 0.0);
} else {
return vec2(0.0, sign(delta.y));
}
}
}
// ============================================================================
// HELPER: Get cubic Bézier control points
// ============================================================================
void getCurvedSControlPoints(vec2 source, vec2 target, out vec2 c1, out vec2 c2) {
vec2 dir = getCurvedSControlDirection(source, target);
float dist = length(target - source);
// Control point distances based on curveOffset
float offset1 = dist * CURVEDS_OFFSET * CURVEDS_POSITION * 2.0;
float offset2 = dist * CURVEDS_OFFSET * (1.0 - CURVEDS_POSITION) * 2.0;
c1 = source + dir * offset1;
c2 = target - dir * offset2;
}
// ============================================================================
// HELPER: Evaluate cubic Bézier: B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
// ============================================================================
vec2 curvedS_cubicBezier(float t, vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
float t2 = t * t;
float t3 = t2 * t;
float mt = 1.0 - t;
float mt2 = mt * mt;
float mt3 = mt2 * mt;
return mt3 * p0 + 3.0 * mt2 * t * p1 + 3.0 * mt * t2 * p2 + t3 * p3;
}
// ============================================================================
// HELPER: Evaluate cubic Bézier derivative: B'(t)
// ============================================================================
vec2 curvedS_cubicBezierDerivative(float t, vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
float t2 = t * t;
float mt = 1.0 - t;
float mt2 = mt * mt;
// B'(t) = 3(1-t)²(P₁-P₀) + 6(1-t)t(P₂-P₁) + 3t²(P₃-P₂)
return 3.0 * mt2 * (p1 - p0) + 6.0 * mt * t * (p2 - p1) + 3.0 * t2 * (p3 - p2);
}
// ============================================================================
// POSITION - Core function for vertex placement
// ============================================================================
vec2 path_curvedS_position(float t, vec2 source, vec2 target) {
// Apply camera rotation if not rotating with camera
vec2 src = source;
vec2 tgt = target;
if (!CURVEDS_ROTATE_WITH_CAMERA) {
src = curvedS_rotate(source, -u_cameraAngle);
tgt = curvedS_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
// Handle degenerate case (very close nodes) -> straight line
if (length(delta) < 0.0001) {
vec2 result = mix(src, tgt, t);
if (!CURVEDS_ROTATE_WITH_CAMERA) {
result = curvedS_rotate(result, u_cameraAngle);
}
return result;
}
// Get control points
vec2 c1, c2;
getCurvedSControlPoints(src, tgt, c1, c2);
// Evaluate Bézier
vec2 result = curvedS_cubicBezier(t, src, c1, c2, tgt);
// Rotate back to world space if needed
if (!CURVEDS_ROTATE_WITH_CAMERA) {
result = curvedS_rotate(result, u_cameraAngle);
}
return result;
}
`;return{name:"curvedS",segments:e,minBodyLengthRatio:0,glsl:l,uniforms:[],attributes:[]}}function Go(a){const{orientation:e="automatic",rotateWithCamera:t=!1,offset:i=.5,innerCornerSkipFactor:r=1}=a??{};let n,o=0;typeof e=="number"?(n=3,o=e):e==="horizontal"?n=1:e==="vertical"?n=2:n=0;const s=`
// Step path constants (baked from options)
const float STEP_OFFSET = ${I(i)};
const int STEP_ORIENTATION = ${n};
const float STEP_FIXED_ANGLE = ${I(o)};
const bool STEP_ROTATE_WITH_CAMERA = ${t?"true":"false"};
${Wi("step")}
// ============================================================================
// HELPER: Get step segment points (source, corner1, corner2, target)
// ============================================================================
void getStepSegmentPoints(vec2 source, vec2 target, out vec2 c1, out vec2 c2) {
vec2 delta = target - source;
// Determine orientation
bool horizontalFirst;
if (STEP_ORIENTATION == 1) {
horizontalFirst = true;
} else if (STEP_ORIENTATION == 2) {
horizontalFirst = false;
} else if (STEP_ORIENTATION == 3) {
// Fixed angle mode: first segment goes in fixed direction
vec2 dir = vec2(cos(STEP_FIXED_ANGLE), sin(STEP_FIXED_ANGLE));
float projLen = dot(delta, dir) * STEP_OFFSET;
c1 = source + dir * projLen;
// Last segment goes in same fixed direction
c2 = target - dir * (dot(delta, dir) * (1.0 - STEP_OFFSET));
return;
} else {
// Automatic: choose based on which delta is larger
horizontalFirst = abs(delta.x) >= abs(delta.y);
}
if (horizontalFirst) {
// H→V→H pattern
float midX = source.x + delta.x * STEP_OFFSET;
c1 = vec2(midX, source.y);
c2 = vec2(midX, target.y);
} else {
// V→H→V pattern
float midY = source.y + delta.y * STEP_OFFSET;
c1 = vec2(source.x, midY);
c2 = vec2(target.x, midY);
}
}
// ============================================================================
// POSITION - Core function for vertex placement
// ============================================================================
vec2 path_step_position(float t, vec2 source, vec2 target) {
// Apply camera rotation if not rotating with camera
vec2 src = source;
vec2 tgt = target;
if (!STEP_ROTATE_WITH_CAMERA) {
src = step_rotate(source, -u_cameraAngle);
tgt = step_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
// Handle degenerate case (aligned nodes) -> straight line
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
vec2 result = mix(src, tgt, t);
if (!STEP_ROTATE_WITH_CAMERA) {
result = step_rotate(result, u_cameraAngle);
}
return result;
}
// Get segment points
vec2 c1, c2;
getStepSegmentPoints(src, tgt, c1, c2);
// Compute segment lengths
float L1 = length(c1 - src);
float L2 = length(c2 - c1);
float L3 = length(tgt - c2);
float totalLen = L1 + L2 + L3;
// Find position along path
float dist = t * totalLen;
vec2 result;
if (dist <= L1) {
float localT = dist / max(L1, 0.0001);
result = mix(src, c1, localT);
} else if (dist <= L1 + L2) {
float localT = (dist - L1) / max(L2, 0.0001);
result = mix(c1, c2, localT);
} else {
float localT = (dist - L1 - L2) / max(L3, 0.0001);
result = mix(c2, tgt, localT);
}
// Rotate back to world space if needed
if (!STEP_ROTATE_WITH_CAMERA) {
result = step_rotate(result, u_cameraAngle);
}
return result;
}
// ============================================================================
// LENGTH - Total path length
// ============================================================================
float path_step_length(vec2 source, vec2 target) {
// Apply camera rotation if not rotating with camera
vec2 src = source;
vec2 tgt = target;
if (!STEP_ROTATE_WITH_CAMERA) {
src = step_rotate(source, -u_cameraAngle);
tgt = step_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
// Handle degenerate case
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
return length(delta);
}
vec2 c1, c2;
getStepSegmentPoints(src, tgt, c1, c2);
return length(c1 - src) + length(c2 - c1) + length(tgt - c2);
}
`,l=`
// ============================================================================
// ANALYTICAL TANGENT - Exact segment direction with narrow blend at corners
// ============================================================================
// This provides precise tangent computation for edge labels, avoiding the
// 45-degree rotation artifacts that numerical differentiation causes at corners.
vec2 path_step_tangent(float t, vec2 source, vec2 target) {
// Apply camera rotation if not rotating with camera
vec2 src = source;
vec2 tgt = target;
if (!STEP_ROTATE_WITH_CAMERA) {
src = step_rotate(source, -u_cameraAngle);
tgt = step_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
// Handle degenerate case (aligned nodes) -> straight line
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
vec2 dir = length(delta) > 0.0001 ? normalize(delta) : vec2(1.0, 0.0);
if (!STEP_ROTATE_WITH_CAMERA) {
dir = step_rotate(dir, u_cameraAngle);
}
return dir;
}
// Get segment points
vec2 c1, c2;
getStepSegmentPoints(src, tgt, c1, c2);
// Compute segment lengths and corner t values
float L1 = length(c1 - src);
float L2 = length(c2 - c1);
float L3 = length(tgt - c2);
float totalLen = L1 + L2 + L3;
float tCorner1 = L1 / totalLen;
float tCorner2 = (L1 + L2) / totalLen;
// Segment directions
vec2 dir1 = normalize(c1 - src);
vec2 dir2 = normalize(c2 - c1);
vec2 dir3 = normalize(tgt - c2);
// Narrow blend zone at corners (2% of total t range)
// This creates a smooth transition over a few characters at corners
float blendZone = 0.02;
vec2 tangent;
if (t < tCorner1 - blendZone) {
tangent = dir1;
} else if (t < tCorner1 + blendZone) {
float blend = smoothstep(tCorner1 - blendZone, tCorner1 + blendZone, t);
tangent = normalize(mix(dir1, dir2, blend));
} else if (t < tCorner2 - blendZone) {
tangent = dir2;
} else if (t < tCorner2 + blendZone) {
float blend = smoothstep(tCorner2 - blendZone, tCorner2 + blendZone, t);
tangent = normalize(mix(dir2, dir3, blend));
} else {
tangent = dir3;
}
// Rotate back to world space if needed
if (!STEP_ROTATE_WITH_CAMERA) {
tangent = step_rotate(tangent, u_cameraAngle);
}
return tangent;
}
// Normal is perpendicular to tangent
vec2 path_step_normal(float t, vec2 source, vec2 target) {
vec2 tangent = path_step_tangent(t, source, target);
return vec2(-tangent.y, tangent.x);
}
`,u=`
// ============================================================================
// CORNER SKIP HELPERS - For above/below label positioning on step edges
// ============================================================================
// When labels are positioned above or below a step edge, characters can
// overlap at "concave" corners (inner side of the bend). These helpers
// detect concave corners and compute skip distances to create gaps.
// Constant for inner corner skip factor (baked from options)
const float STEP_INNER_CORNER_SKIP_FACTOR = ${I(r)};
// Returns the t values for the two corners of the step path.
// x = t at corner 1 (between segment A and B)
// y = t at corner 2 (between segment B and C)
vec2 path_step_getCornerTs(vec2 source, vec2 target) {
vec2 src = source;
vec2 tgt = target;
if (!STEP_ROTATE_WITH_CAMERA) {
src = step_rotate(source, -u_cameraAngle);
tgt = step_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
// Degenerate case: no real corners
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
return vec2(0.5, 0.5);
}
vec2 c1, c2;
getStepSegmentPoints(src, tgt, c1, c2);
float L1 = length(c1 - src);
float L2 = length(c2 - c1);
float L3 = length(tgt - c2);
float totalLen = L1 + L2 + L3;
return vec2(L1 / totalLen, (L1 + L2) / totalLen);
}
// Determines which corners are concave relative to the label position.
// A corner is "concave" for a label if the label is on the inner side of the bend.
//
// perpOffset > 0 means "above" (left side of path direction in screen coords)
// perpOffset < 0 means "below" (right side of path direction)
//
// Returns: x = 1.0 if corner 1 is concave, 0.0 otherwise
// y = 1.0 if corner 2 is concave, 0.0 otherwise
vec2 path_step_getCornerConcavity(vec2 source, vec2 target, float perpOffset) {
if (abs(perpOffset) < 0.0001) {
return vec2(0.0); // Centerline mode - no corners to skip
}
vec2 src = source;
vec2 tgt = target;
if (!STEP_ROTATE_WITH_CAMERA) {
src = step_rotate(source, -u_cameraAngle);
tgt = step_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
// Degenerate case: no corners
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
return vec2(0.0);
}
vec2 c1, c2;
getStepSegmentPoints(src, tgt, c1, c2);
// Segment directions (in rotated space)
vec2 dir1 = normalize(c1 - src);
vec2 dir2 = normalize(c2 - c1);
vec2 dir3 = normalize(tgt - c2);
// Cross product gives bend direction:
// Positive = counter-clockwise turn (left turn)
// Negative = clockwise turn (right turn)
// cross(a, b) = a.x * b.y - a.y * b.x
float bend1 = dir1.x * dir2.y - dir1.y * dir2.x;
float bend2 = dir2.x * dir3.y - dir2.y * dir3.x;
// A corner is concave for the label if:
// - The bend goes one way (CW or CCW)
// - The label is on the same side as the bend (inside the bend)
//
// When bend is positive (CCW/left turn) and perpOffset > 0 (above/left side),
// the label is on the INNER side (concave) - skip needed.
//
// When bend is positive (CCW/left turn) and perpOffset < 0 (below/right side),
// the label is on the OUTER side (convex) - no skip needed.
//
// Formula: concave if (bend * perpOffset) > 0
float concave1 = (bend1 * perpOffset > 0.0) ? 1.0 : 0.0;
float concave2 = (bend2 * perpOffset > 0.0) ? 1.0 : 0.0;
return vec2(concave1, concave2);
}
`;return{name:"step",segments:64,minBodyLengthRatio:2,linearParameterization:!0,glsl:s,analyticalTangentGlsl:l,cornerSkipGlsl:u,hasSharpCorners:!0,innerCornerSkipFactor:r,uniforms:[],attributes:[]}}function Wo(a){const{orientation:e="automatic",rotateWithCamera:t=!1,offset:i=.5,cornerRadius:r=.4}=a??{};let n,o=0;return typeof e=="number"?(n=3,o=e):e==="horizontal"?n=1:e==="vertical"?n=2:n=0,{name:"stepCurved",segments:32,glsl:`
// Step curved path constants (baked from options)
const float STEPC_OFFSET = ${I(i)};
const int STEPC_ORIENTATION = ${n};
const float STEPC_FIXED_ANGLE = ${I(o)};
const bool STEPC_ROTATE_WITH_CAMERA = ${t?"true":"false"};
const float STEPC_CORNER_RATIO = ${I(r)};
${Wi("stepC")}
// ============================================================================
// HELPER: Get taxi segment points with corner radius info
// ============================================================================
void getStepCSegmentPoints(vec2 source, vec2 target, out vec2 c1, out vec2 c2, out float cornerRadius) {
vec2 delta = target - source;
// Determine orientation
bool horizontalFirst;
if (STEPC_ORIENTATION == 1) {
horizontalFirst = true;
} else if (STEPC_ORIENTATION == 2) {
horizontalFirst = false;
} else if (STEPC_ORIENTATION == 3) {
vec2 dir = vec2(cos(STEPC_FIXED_ANGLE), sin(STEPC_FIXED_ANGLE));
float projLen = dot(delta, dir) * STEPC_OFFSET;
c1 = source + dir * projLen;
c2 = target - dir * (dot(delta, dir) * (1.0 - STEPC_OFFSET));
float L1 = length(c1 - source);
float L2 = length(c2 - c1);
float L3 = length(target - c2);
cornerRadius = min(min(L1, L2 * 0.5), L3) * STEPC_CORNER_RATIO;
return;
} else {
horizontalFirst = abs(delta.x) >= abs(delta.y);
}
if (horizontalFirst) {
float midX = source.x + delta.x * STEPC_OFFSET;
c1 = vec2(midX, source.y);
c2 = vec2(midX, target.y);
} else {
float midY = source.y + delta.y * STEPC_OFFSET;
c1 = vec2(source.x, midY);
c2 = vec2(target.x, midY);
}
// Compute corner radius based on shortest segment
float L1 = length(c1 - source);
float L2 = length(c2 - c1);
float L3 = length(target - c2);
cornerRadius = min(min(L1, L2 * 0.5), L3) * STEPC_CORNER_RATIO;
}
// ============================================================================
// HELPER: Quadratic bezier evaluation
// ============================================================================
vec2 stepC_bezier(vec2 p0, vec2 p1, vec2 p2, float t) {
float mt = 1.0 - t;
return mt * mt * p0 + 2.0 * mt * t * p1 + t * t * p2;
}
vec2 stepC_bezierTangent(vec2 p0, vec2 p1, vec2 p2, float t) {
return normalize(2.0 * (1.0 - t) * (p1 - p0) + 2.0 * t * (p2 - p1));
}
float stepC_bezierLength(vec2 p0, vec2 p1, vec2 p2) {
// Approximate length using 8 samples
float len = 0.0;
vec2 prev = p0;
for (int i = 1; i <= 8; i++) {
float t = float(i) / 8.0;
vec2 curr = stepC_bezier(p0, p1, p2, t);
len += length(curr - prev);
prev = curr;
}
return len;
}
// ============================================================================
// PATH LENGTH - Total path length with rounded corners
// ============================================================================
float path_stepCurved_length(vec2 source, vec2 target) {
vec2 src = source;
vec2 tgt = target;
if (!STEPC_ROTATE_WITH_CAMERA) {
src = stepC_rotate(source, -u_cameraAngle);
tgt = stepC_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
return length(delta);
}
vec2 c1, c2;
float r;
getStepCSegmentPoints(src, tgt, c1, c2, r);
// Segment lengths (shortened by corner radius on each end)
float L1 = max(length(c1 - src) - r, 0.0);
float L2 = max(length(c2 - c1) - 2.0 * r, 0.0);
float L3 = max(length(tgt - c2) - r, 0.0);
// Corner arc lengths
vec2 dir1 = normalize(c1 - src);
vec2 dir2 = normalize(c2 - c1);
vec2 dir3 = normalize(tgt - c2);
vec2 corner1_start = c1 - dir1 * r;
vec2 corner1_end = c1 + dir2 * r;
vec2 corner2_start = c2 - dir2 * r;
vec2 corner2_end = c2 + dir3 * r;
float arc1 = stepC_bezierLength(corner1_start, c1, corner1_end);
float arc2 = stepC_bezierLength(corner2_start, c2, corner2_end);
return L1 + arc1 + L2 + arc2 + L3;
}
// ============================================================================
// POSITION - Position along rounded path
// ============================================================================
vec2 path_stepCurved_position(float t, vec2 source, vec2 target) {
vec2 src = source;
vec2 tgt = target;
if (!STEPC_ROTATE_WITH_CAMERA) {
src = stepC_rotate(source, -u_cameraAngle);
tgt = stepC_rotate(target, -u_cameraAngle);
}
vec2 delta = tgt - src;
if (abs(delta.x) < 0.0001 || abs(delta.y) < 0.0001) {
vec2 result = mix(src, tgt, t);
if (!STEPC_ROTATE_WITH_CAMERA) {
result = stepC_rotate(result, u_cameraAngle);
}
return result;
}
vec2 c1, c2;
float r;
getStepCSegmentPoints(src, tgt, c1, c2, r);
vec2 dir1 = normalize(c1 - src);
vec2 dir2 = normalize(c2 - c1);
vec2 dir3 = normalize(tgt - c2);
// Key points
vec2 seg1_end = c1 - dir1 * r;
vec2 corner1_end = c1 + dir2 * r;
vec2 seg2_end = c2 - dir2 * r;
vec2 corner2_end = c2 + dir3 * r;
// Segment lengths
float L1 = length(seg1_end - src);
float arc1 = stepC_bezierLength(seg1_end, c1, corner1_end);
float L2 = length(seg2_end - corner1_end);
float arc2 = stepC_bezierLength(seg2_end, c2, corner2_end);
float L3 = length(tgt - corner2_end);
float totalLen = L1 + arc1 + L2 + arc2 + L3;
float dist = t * totalLen;
vec2 result;
if (dist <= L1) {
float localT = dist / max(L1, 0.0001);
result = mix(src, seg1_end, localT);
} else if (dist <= L1 + arc1) {
float localDist = dist - L1;
float localT = localDist / max(arc1, 0.0001);
result = stepC_bezier(seg1_end, c1, corner1_end, localT);
} else if (dist <= L1 + arc1 + L2) {
float localDist = dist - L1 - arc1;
float localT = localDist / max(L2, 0.0001);
result = mix(corner1_end, seg2_end, localT);
} else if (dist <= L1 + arc1 + L2 + arc2) {
float localDist = dist - L1 - arc1 - L2;
float localT = localDist / max(arc2, 0.0001);
result = stepC_bezier(seg2_end, c2, corner2_end, localT);
} else {
float localDist = dist - L1 - arc1 - L2 - arc2;
float localT = localDist / max(L3, 0.0001);
result = mix(corner2_end, tgt, localT);
}
if (!STEPC_ROTATE_WITH_CAMERA) {
result = stepC_rotate(result, u_cameraAngle);
}
return result;
}
`,uniforms:[],attributes:[]}}function er(a){const{segments:e=32}=a??{};return{name:"loop",segments:e,glsl:`
const float LOOP_PI = 3.141592653589793;
float loopWorldRadius() {
float nodeWorldRadius = v_sourceNodeSize * u_correctionRatio / u_sizeRatio;
return max(v_loopRadius * nodeWorldRadius, 0.001);
}
// Cubic Bézier with P0 = P3 = source.
// Control points extend outward orthogonal to the node surface at exit/entry angles.
vec2 path_loop_position(float t, vec2 source, vec2 target) {
float R = loopWorldRadius();
float angle = v_loopAngle + (v_loopFixedOrientation > 0.5 ? u_cameraAngle : 0.0);
float halfSpread = v_loopSpread * 0.5;
float exitAngle = angle - halfSpread;
float entryAngle = angle + halfSpread;
// Control point distance: at t=0.5 the Bézier reaches 0.75 * cpDist * cos(halfSpread)
// from source. Solve for cpDist so the loop tip reaches exactly R.
float cpDist = R / (0.75 * cos(halfSpread));
vec2 cp1 = source + cpDist * vec2(cos(exitAngle), sin(exitAngle));
vec2 cp2 = source + cpDist * vec2(cos(entryAngle), sin(entryAngle));
float u = 1.0 - t;
vec2 d1 = cp1 - source;
vec2 d2 = cp2 - source;
return source + 3.0 * t * u * (u * d1 + t * d2);
}
// Approximate arc length via chord sampling
float path_loop_length(vec2 source, vec2 target) {
float len = 0.0;
vec2 prev = path_loop_position(0.0, source, target);
const int STEPS = 16;
for (int i = 1; i <= STEPS; i++) {
float t = float(i) / float(STEPS);
vec2 cur = path_loop_position(t, source, target);
len += length(cur - prev);
prev = cur;
}
return len;
}
`,needsNodeSize:!0,uniforms:[],attributes:[{name:"loopRadius",size:1,type:WebGL2RenderingContext.FLOAT},{name:"loopAngle",size:1,type:WebGL2RenderingContext.FLOAT},{name:"loopSpread",size:1,type:WebGL2RenderingContext.FLOAT},{name:"loopFixedOrientation",size:1,type:WebGL2RenderingContext.FLOAT}],variables:{loopRadius:{type:"number",default:4},loopAngle:{type:"number",default:Math.PI/4},loopSpread:{type:"number",default:80*Math.PI/180},loopFixedOrientation:{type:"number",default:0}},spread:{variable:"loopRadius",compute:i=>i+4}}}function Bo(a){const{lengthRatio:e=5,widthRatio:t=4,margin:i=0}=a??{};return{name:"arrow",glsl:`
// Arrow SDF: triangle with base at x=0, tip at x=lengthRatio
// uv.x: 0 (base) to lengthRatio (tip), uv.y: [-halfW, +halfW]
// Returns signed distance (negative inside, positive outside)
float extremity_arrow(vec2 uv, float lengthRatio, float widthRatio) {
float x = uv.x;
float y = abs(uv.y);
float halfW = widthRatio * 0.5;
// Past the tip: euclidean distance to tip point
if (x > lengthRatio) {
return length(vec2(x - lengthRatio, y));
}
// Back edge: signed distance to x=0 line
float backDist = -x;
// Side edge: signed distance to sloped triangle edge
float clampedX = max(0.0, x);
float maxY = halfW * (1.0 - clampedX / lengthRatio);
float sideDist = y - maxY;
// Convex shape SDF = max of half-plane distances
return max(backDist, sideDist);
}
`,length:e,widthFactor:t,margin:i,uniforms:[],attributes:[]}}function Oo(a){const{lengthRatio:e=.75,widthRatio:t=4,margin:i=0}=a??{};return{name:"bar",glsl:`
// Box SDF (same geometry as square, different defaults)
float extremity_bar(vec2 uv, float lengthRatio, float widthRatio) {
float halfW = widthRatio * 0.5;
// Box SDF: distance to rectangle [0, lengthRatio] × [-halfW, halfW]
vec2 center = vec2(lengthRatio * 0.5, 0.0);
vec2 halfSize = vec2(lengthRatio * 0.5, halfW);
vec2 d = abs(uv - center) - halfSize;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
`,length:e,widthFactor:t,margin:i,baseRatio:1,uniforms:[],attributes:[]}}function Uo(a){const{lengthRatio:e=4,widthRatio:t=e+1,margin:i=0}=a??{};return{name:"circle",glsl:`
// Circle SDF: circle centered at (lengthRatio/2, 0) with radius = lengthRatio/2
// uv.x: 0 (base) to lengthRatio (far edge), uv.y: [-halfW, +halfW]
float extremity_circle(vec2 uv, float lengthRatio, float widthRatio) {
float radius = lengthRatio * 0.5;
vec2 center = vec2(radius, 0.0);
return length(uv - center) - radius;
}
`,length:e,widthFactor:t,margin:i,uniforms:[],attributes:[]}}function $o(a){const{lengthRatio:e=5,widthRatio:t=4,margin:i=0}=a??{};return{name:"diamond",glsl:`
// Diamond SDF: rhombus with vertices at (0,0), (L/2, W/2), (L, 0), (L/2, -W/2)
float extremity_diamond(vec2 uv, float lengthRatio, float widthRatio) {
float halfL = lengthRatio * 0.5;
float halfW = widthRatio * 0.5;
// Center the diamond at (halfL, 0)
vec2 p = abs(uv - vec2(halfL, 0.0));
// Diamond is the set |x/halfL| + |y/halfW| <= 1
// Signed distance: (p.x/halfL + p.y/halfW - 1) * normalization
float d = p.x / halfL + p.y / halfW - 1.0;
// Scale by the distance from center to edge along the gradient direction
float norm = length(vec2(1.0 / halfL, 1.0 / halfW));
return d / norm;
}
`,length:e,widthFactor:t,margin:i,uniforms:[],attributes:[]}}function Ho(a){const{lengthRatio:e=4,widthRatio:t=4,margin:i=0}=a??{};return{name:"square",glsl:`
// Box SDF (same geometry as bar, different defaults)
float extremity_square(vec2 uv, float lengthRatio, float widthRatio) {
float halfW = widthRatio * 0.5;
vec2 center = vec2(lengthRatio * 0.5, 0.0);
vec2 halfSize = vec2(lengthRatio * 0.5, halfW);
vec2 d = abs(uv - center) - halfSize;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
`,length:e,widthFactor:t,margin:i,baseRatio:1,uniforms:[],attributes:[]}}class da{constructor(e){this.buckets=new Map,this.keyDepth=new Map;for(const t of e)this.buckets.set(t,new Set)}has(e){return this.keyDepth.has(e)}getBucket(e){return this.buckets.get(e)}set(e,t){var n;const i=this.keyDepth.get(e);if(i===t)return;const r=this.buckets.get(t);if(!r)throw new Error(`Sigma: "${t}" is not a declared depth layer`);i!==void 0&&((n=this.buckets.get(i))==null||n.delete(e)),r.add(e),this.keyDepth.set(e,t)}remove(e){const t=this.keyDepth.get(e);t!==void 0&&(this.buckets.get(t).delete(e),this.keyDepth.delete(e))}clearAll(){for(const e of this.buckets.values())e.clear();this.keyDepth.clear()}getSorted(e,t){const i=this.buckets.get(e);return!i||i.size===0?[]:[...i].sort((r,n)=>t(r)-t(n))}}const tr=8;class Vo{constructor(e){this.program=null,this.vao=null,this.drawFramebuffer=null,this.readFramebuffer=null,this.viewport=new Int32Array(4),this.blend=!1,this.blendSrcRGB=WebGL2RenderingContext.ONE,this.blendDstRGB=WebGL2RenderingContext.ZERO,this.blendSrcAlpha=WebGL2RenderingContext.ONE,this.blendDstAlpha=WebGL2RenderingContext.ZERO,this.blendEquationRGB=WebGL2RenderingContext.FUNC_ADD,this.blendEquationAlpha=WebGL2RenderingContext.FUNC_ADD,this.colorMask=[!0,!0,!0,!0],this.activeTexture=WebGL2RenderingContext.TEXTURE0,this.textures=[],this.gl=e}save(){const{gl:e}=this;this.program=e.getParameter(e.CURRENT_PROGRAM),this.vao=e.getParameter(e.VERTEX_ARRAY_BINDING),this.drawFramebuffer=e.getParameter(e.DRAW_FRAMEBUFFER_BINDING),this.readFramebuffer=e.getParameter(e.READ_FRAMEBUFFER_BINDING),this.viewport=e.getParameter(e.VIEWPORT),this.blend=e.isEnabled(e.BLEND),this.blendSrcRGB=e.getParameter(e.BLEND_SRC_RGB),this.blendDstRGB=e.getParameter(e.BLEND_DST_RGB),this.blendSrcAlpha=e.getParameter(e.BLEND_SRC_ALPHA),this.blendDstAlpha=e.getParameter(e.BLEND_DST_ALPHA),this.blendEquationRGB=e.getParameter(e.BLEND_EQUATION_RGB),this.blendEquationAlpha=e.getParameter(e.BLEND_EQUATION_ALPHA),this.colorMask=e.getParameter(e.COLOR_WRITEMASK),this.activeTexture=e.getParameter(e.ACTIVE_TEXTURE);for(let t=0;t<tr;t++)e.activeTexture(e.TEXTURE0+t),this.textures[t]=e.getParameter(e.TEXTURE_BINDING_2D);e.activeTexture(this.activeTexture)}restore(){const{gl:e}=this;for(let t=0;t<tr;t++)e.activeTexture(e.TEXTURE0+t),e.bindTexture(e.TEXTURE_2D,this.textures[t]);e.activeTexture(this.activeTexture),e.useProgram(this.program),e.bindVertexArray(this.vao),e.bindFramebuffer(e.DRAW_FRAMEBUFFER,this.drawFramebuffer),e.bindFramebuffer(e.READ_FRAMEBUFFER,this.readFramebuffer),e.viewport(this.viewport[0],this.viewport[1],this.viewport[2],this.viewport[3]),this.blend?e.enable(e.BLEND):e.disable(e.BLEND),e.blendFuncSeparate(this.blendSrcRGB,this.blendDstRGB,this.blendSrcAlpha,this.blendDstAlpha),e.blendEquationSeparate(this.blendEquationRGB,this.blendEquationAlpha),e.colorMask(this.colorMask[0],this.colorMask[1],this.colorMask[2],this.colorMask[3]),e.bindBuffer(e.PIXEL_PACK_BUFFER,null)}}class ar extends gt{constructor(e,t){super(e,2,t)}updateNode(e,t,i,r,n,o,s,l){const u=this.indexMap.get(e);if(u===void 0)throw new Error(`Node "${e}" not allocated in NodeDataTexture`);const[g,d,h,c]=ve(l),f=u*2*4;this.data[f]=t,this.data[f+1]=i,this.data[f+2]=r,this.data[f+3]=n,this.data[f+4]=o,this.data[f+5]=s,this.data[f+6]=g*65536+d*256+h,this.data[f+7]=c/255,this.markDirty(u)}}const Xo=1024,qo=1.5,jo=4096;class ca{constructor(e,t){if(this.texture=null,this.framebuffer=null,!e.getExtension("EXT_color_buffer_float"))throw new Error("sigma: EXT_color_buffer_float is required for label/edge placement but is unavailable in this WebGL2 context.");this.gl=e,this.channels=t.channels,this.capacity=this.roundUpToPowerOfTwo(t.initialCapacity??Xo);const i=this.computeDimensions(this.capacity);this.textureWidth=i.width,this.textureHeight=i.height,this.create()}roundUpToPowerOfTwo(e){return Math.pow(2,Math.ceil(Math.log2(Math.max(1,e))))}computeDimensions(e){const t=Math.min(e,jo),i=Math.ceil(e/t);return{width:t,height:i}}create(){const{gl:e}=this,t=this.channels===1?e.R32F:e.RGBA32F,i=this.channels===1?e.RED:e.RGBA;if(this.texture=e.createTexture(),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.texture),e.texImage2D(e.TEXTURE_2D,0,t,this.textureWidth,this.textureHeight,0,i,e.FLOAT,null),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),this.framebuffer=e.createFramebuffer(),e.bindFramebuffer(e.FRAMEBUFFER,this.framebuffer),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,this.texture,0),e.checkFramebufferStatus(e.FRAMEBUFFER)!==e.FRAMEBUFFER_COMPLETE)throw new Error("sigma: float framebuffer for frame-pass placement is incomplete.");e.bindFramebuffer(e.FRAMEBUFFER,null),e.bindTexture(e.TEXTURE_2D,null)}ensureCapacity(e){if(e<=this.capacity)return;const{gl:t}=this;this.capacity=this.roundUpToPowerOfTwo(Math.ceil(e*qo));const i=this.computeDimensions(this.capacity);this.textureWidth=i.width,this.textureHeight=i.height,this.texture&&t.deleteTexture(this.texture),this.framebuffer&&t.deleteFramebuffer(this.framebuffer),this.create()}bindAsRenderTarget(){const{gl:e}=this;e.bindFramebuffer(e.FRAMEBUFFER,this.framebuffer),e.viewport(0,0,this.textureWidth,this.textureHeight)}bind(e){const{gl:t}=this;t.activeTexture(t.TEXTURE0+e),t.bindTexture(t.TEXTURE_2D,this.texture)}getTexture(){return this.texture}getTextureWidth(){return this.textureWidth}getTextureHeight(){return this.textureHeight}restore(){this.gl.getExtension("EXT_color_buffer_float"),this.create()}kill(){const{gl:e}=this;this.texture&&(e.deleteTexture(this.texture),this.texture=null),this.framebuffer&&(e.deleteFramebuffer(this.framebuffer),this.framebuffer=null)}}class ir extends gt{constructor(e,t){super(e,2,t)}updateEdge(e,t,i,r,n,o,s=0,l=0,u=0){const g=this.indexMap.get(e);if(g===void 0)throw new Error(`Edge "${e}" not allocated in EdgeDataTexture`);const d=g*this.TEXELS_PER_ITEM*4;this.data[d+0]=t,this.data[d+1]=i,this.data[d+2]=r,this.data[d+3]=0,this.data[d+4]=n,this.data[d+5]=o,this.data[d+6]=s,this.data[d+7]=(l&15)<<4|u&15,this.markDirty(g)}}const Yo=Object.freeze(Object.defineProperty({__proto__:null,AttachmentManager:ui,DEFAULT_LABEL_BACKGROUND_PADDING:Bt,DEFAULT_LABEL_MARGIN:me,DataTexture:gt,DepthBucketCollection:da,EdgeDataTexture:ir,EdgeFramePass:Hi,FrameTexture:ca,GLSL_GET_LABEL_DIRECTION:Ut,GLSL_LABEL_BOX_CENTER:$t,GLSL_NODE_SIZE_TO_PIXELS:Ot,GLSL_READ_FRAME_TEXEL:se,GLSL_READ_NODE_COLOR:ka,GLSL_READ_NODE_DATA:X,GLSL_READ_NODE_FLAGS:J,GLSL_ROTATE_2D:be,GLSL_SDF_BOX:Ia,GLSL_SDF_ROTATED_BOX:za,GLSL_SDF_ROUNDED_BOX:Ma,GLSL_SDF_ROUNDED_ROTATED_BOX:Na,GLStateGuard:Vo,LabelProgram:na,NODE_DATA_TEXELS_PER_NODE:st,NodeDataTexture:ar,NodeLabelFramePass:St,POSITION_MODE_MAP:pe,Program:ae,clearShapeInstanceRegistry:ln,collectAttributes:mi,collectBackdropUniforms:Ti,collectLabelUniforms:Li,collectUniforms:pi,createBackdropProgram:yi,createEdgeLabelBackgroundProgram:ji,createEdgeLabelProgram:Zi,createEdgeProgram:Qi,createLabelProgram:Pi,createNodeProgram:wi,dedupeShapeUniforms:ge,extremityArrow:Bo,extremityBar:Oo,extremityCircle:Uo,extremityDiamond:$o,extremitySquare:Ho,generateBackdropFragmentShader:_i,generateBackdropShaders:vi,generateBackdropVertexShader:xi,generateEdgeLabelShaders:Ki,generateEdgeShaders:la,generateFindEdgeDistanceForShapes:Ga,generateFragmentShader:gi,generateLabelFragmentShader:Ci,generateLabelShaders:Fi,generateLabelVertexShader:Di,generateNodeShapeSelectorGLSL:wa,generateSDFCall:ze,generateShaders:ra,generateShapeSelectorGLSL:Pa,generateVertexShader:fi,getAllShapeGLSL:Fa,getAttributesItemsCount:at,getRegisteredShapeInstance:rn,getRegisteredShapeSlugs:on,getShapeFromSlug:nn,getShapeGLSL:sn,getShapeGLSLForShapes:ot,getShapeId:Me,getStaticAttributeDefault:Wt,isAttributeSource:j,killProgram:kt,layerDashed:bo,layerFill:ki,layerGradient:_o,layerPlain:De,loadFragmentShader:rt,loadProgram:nt,loadVertexShader:it,numberToGLSLFloat:I,pathCurved:No,pathCurvedS:ko,pathLine:Ji,pathLoop:er,pathStep:Go,pathStepCurved:Wo,registerShapeInstance:Ca,resolveEdgeColorValue:$e,sdfCircle:Ii,sdfDiamond:Vn,sdfRectangle:Xn,sdfSquare:$n,sdfTriangle:Hn},Symbol.toStringTag,{value:"Module"}));function rr(a){return typeof a=="object"&&"glsl"in a&&!("attributes"in a)}function nr(a){return typeof a=="object"&&"glsl"in a&&!("attributes"in a)}const Ve={shapes:[Ii()],variables:{},layers:[ki()],label:{},backdrop:{},labelAttachments:{}},Ce={paths:[Ji(),er()],extremities:[],variables:{},layers:[De()],defaultHead:"none",defaultTail:"none",label:{}},Dt=["nodes","topNodes"],Ct=["edges","topEdges"],Lt=[...Ct,...Dt],or={nodes:Ve,edges:Ce,depthLayers:[...Lt]};function Ko(a){return typeof a=="object"&&"uniforms"in a&&Array.isArray(a.uniforms)}function Zo(a){return typeof a=="object"&&"uniforms"in a&&"attributes"in a&&"glsl"in a}function Qo(a){return typeof a=="object"&&"uniforms"in a&&"attributes"in a&&"segments"in a}function Jo(a){return typeof a=="object"&&"uniforms"in a&&"attributes"in a&&"length"in a}function es(a){return typeof a=="object"&&"uniforms"in a&&"attributes"in a&&"glsl"in a}function ts(a){return typeof a=="object"&&"glsl"in a&&"name"in a&&!("uniforms"in a)}function as(a){return typeof a=="object"&&"glsl"in a&&"name"in a&&!("uniforms"in a)}function is(a){return typeof a=="object"&&"glsl"in a&&"name"in a&&!("uniforms"in a)}function sr(a){if(Ko(a))return a;if(ts(a))return{name:a.name,glsl:a.glsl,inradiusFactor:a.inradiusFactor,uniforms:[]};throw new Error(`Invalid node shape specification: ${JSON.stringify(a)}`)}function lr(a){if(Zo(a))return a;if(rr(a))return{name:a.name,glsl:a.glsl,uniforms:[],attributes:[]};throw new Error(`Invalid node layer specification: ${JSON.stringify(a)}`)}function dr(a){if(Qo(a))return a;if(as(a))return{name:a.name,glsl:a.glsl,segments:a.segments,uniforms:[],attributes:[]};throw new Error(`Invalid edge path specification: ${JSON.stringify(a)}`)}function cr(a){if(es(a))return a;if(nr(a))return{name:a.name,glsl:a.glsl,uniforms:[],attributes:[]};throw new Error(`Invalid edge layer specification: ${JSON.stringify(a)}`)}function hr(a){if(Jo(a))return a;if(is(a))return{name:a.name,glsl:a.glsl,length:a.length,widthFactor:a.widthFactor,margin:0,uniforms:[],attributes:[]};throw new Error(`Invalid edge extremity specification: ${JSON.stringify(a)}`)}function ha(a){const e=(a==null?void 0:a.shapes)??Ve.shapes,t=(a==null?void 0:a.layers)??Ve.layers,i=e.map(sr),r=t.map(lr);if(i.length===0)throw new Error("At least one node shape must be specified.");if(r.length===0)throw new Error("At least one node layer must be specified.");return{shapes:i,layers:r}}function ur(a){const e=(a==null?void 0:a.paths)??Ce.paths,t=(a==null?void 0:a.extremities)??Ce.extremities,i=(a==null?void 0:a.layers)??Ce.layers,r=e.map(dr),n=t.map(hr).filter(s=>s!==null),o=i.map(cr);if(r.length===0)throw new Error("At least one edge path must be specified.");if(o.length===0)throw new Error("At least one edge layer must be specified.");return{paths:r,extremities:n,layers:o}}function fr(a,e){const t={};for(const i of a)i.variables&&Object.assign(t,i.variables);return Object.assign(t,e||{}),t}function gr(a,e,t,i,r){const{shapes:n,layers:o}=ha(i),s=fr(n,i==null?void 0:i.variables);return{...wi(a,e,t,{shapes:n,layers:o,label:i==null?void 0:i.label,backdrop:i==null?void 0:i.backdrop,labelAttachments:i==null?void 0:i.labelAttachments},r),variables:s}}function pr(a,e,t,i,r,n){const{paths:o,extremities:s,layers:l}=ur(i),{shapes:u,layers:g}=ha(n),d=fr(o,i==null?void 0:i.variables);return{...Qi(a,e,t,{paths:o,extremities:s,layers:l,defaultHead:i==null?void 0:i.defaultHead,defaultTail:i==null?void 0:i.defaultTail,label:i==null?void 0:i.label},r,u,g),variables:d,paths:o}}const rs=Object.freeze(Object.defineProperty({__proto__:null,DEFAULT_DEPTH_LAYERS:Lt,DEFAULT_EDGE_DEPTH_LAYERS:Ct,DEFAULT_EDGE_PRIMITIVES:Ce,DEFAULT_NODE_DEPTH_LAYERS:Dt,DEFAULT_NODE_PRIMITIVES:Ve,DEFAULT_PRIMITIVES:or,arrayProp:en,booleanProp:Qr,colorProp:Kr,enumProp:Jr,generateEdgeProgram:pr,generateNodeProgram:gr,isArrayPropertySchema:we,isCustomEdgeLayer:nr,isCustomNodeLayer:rr,numberProp:Ie,parseEdgeExtremity:hr,parseEdgeLayer:cr,parseEdgePath:dr,parseEdgePrimitives:ur,parseNodeLayer:lr,parseNodePrimitives:ha,parseNodeShape:sr,stringProp:Zr},Symbol.toStringTag,{value:"Module"})),Ft={hideEdgesOnMove:!1,hideLabelsOnMove:!1,renderLabels:!0,renderEdgeLabels:!1,edgeLabelAnchors:"nodeLabels",enableEdgeEvents:!1,nodeLabelEvents:!1,edgeLabelEvents:!1,pickingDownSizingRatio:2,nodePickingPadding:0,edgePickingPadding:4,labelPickingPadding:10,stagePadding:30,minEdgeThickness:1.7,antiAliasingFeather:1,antialiasEdges:!0,antialiasNodes:!0,dragTimeout:100,draggedEventsTolerance:3,inertiaDuration:200,inertiaRatio:3,zoomDuration:250,zoomingRatio:1.7,doubleClickTimeout:300,doubleClickZoomingRatio:2.2,doubleClickZoomingDuration:200,tapMoveTolerance:10,zoomToSizeRatioFunction:Math.sqrt,itemSizesReference:"positions",autoRescale:!0,autoRescaleContent:"positions",enableNodeDrag:!1,getDraggedNodes:a=>[a],dragPositionToAttributes:null,labelDensity:1,labelGridCellSize:100,labelRenderedSizeThreshold:6,labelPixelSnapping:!0,minCameraRatio:null,maxCameraRatio:null,enableCameraZooming:!0,enableCameraPanning:!0,enableCameraRotation:!0,enableCameraMouseRotation:!0,cameraPanBoundaries:null,gestureTarget:"graph",sharedGestureWheelMessage:"Use Ctrl + scroll to zoom the graph",sharedGestureAppleWheelMessage:"Use ⌘ + scroll to zoom the graph",sharedGestureTouchMessage:"Use two fingers to move the graph",allowInvalidContainer:!1,DEBUG_displayPickingLayer:!1,DEBUG_logShaders:!1,DEBUG_logRenderStats:!1,DEBUG_gpuTimerQueries:!1};function Pt(a){if(typeof a.labelDensity!="number"||a.labelDensity<0)throw new Error("Settings: invalid `labelDensity`. Expecting a positive number.");const{minCameraRatio:e,maxCameraRatio:t}=a;if(typeof e=="number"&&typeof t=="number"&&t<e)throw new Error("Settings: invalid camera ratio boundaries. Expecting `maxCameraRatio` to be greater than `minCameraRatio`.")}function mr(a){return ri({},Ft,a)}const ns=Object.freeze(Object.defineProperty({__proto__:null,DEFAULT_SETTINGS:Ft,resolveSettings:mr,validateSettings:Pt},Symbol.toStringTag,{value:"Module"}));function ua(a){return typeof a=="object"&&a!==null&&"attribute"in a}function br(a){return typeof a=="function"}function fa(a){return typeof a=="object"&&a!==null&&"when"in a&&typeof a.when=="function"&&"then"in a}function ga(a){return typeof a=="object"&&a!==null&&"whenState"in a&&"then"in a}function pa(a){return typeof a=="object"&&a!==null&&"whenData"in a&&"then"in a}const os={isHovered:!1,isLabelHovered:!1,isHidden:!1,isHighlighted:!1,isDragged:!1},ss={isHovered:!1,isLabelHovered:!1,isHidden:!1,isHighlighted:!1,parallelIndex:0,parallelCount:1},ls={isIdle:!0,isPanning:!1,isZooming:!1,isDragging:!1,hasHovered:!1,hasHighlighted:!1};function ds(a){return{...os,...a}}function cs(a){return{...ss,...a}}function xr(a){return{...ls,...a}}const _r={x:{attribute:"x"},y:{attribute:"y"},size:{whenState:"isHovered",then:{attribute:"size",defaultValue:12},else:{attribute:"size",defaultValue:10}},color:{attribute:"color",defaultValue:"#666"},label:{attribute:"label"},visibility:{whenState:"isHidden",then:"hidden",else:"visible"},labelVisibility:{whenState:"isHovered",then:"visible",else:"auto"},backdropVisibility:{whenState:"isHovered",then:"visible",else:"hidden"},backdropColor:"#ffffff",backdropShadowColor:"rgba(0, 0, 0, 0.5)",backdropShadowBlur:12,backdropPadding:6},Tr={size:{attribute:"size",defaultValue:1},color:{attribute:"color",defaultValue:"#ccc"},label:{attribute:"label"},visibility:{whenState:"isHidden",then:"hidden",else:"visible"}},vr={nodes:_r,edges:Tr},Xe={nodes:{..._r,depth:{whenState:"isHovered",then:"topNodes",else:"nodes"}},edges:{...Tr,depth:{whenState:["isHighlighted","isHovered"],then:"topEdges",else:"edges"}}};function hs(a){return a}function us(a){return"min"in a||"max"in a||"minValue"in a||"maxValue"in a||"easing"in a}function fs(a){return"dict"in a}function qe(a,e){return typeof a=="string"?e[a]===!0:Array.isArray(a)?a.every(t=>e[t]===!0):typeof a=="object"&&a!==null?Object.entries(a).every(([t,i])=>e[t]===i):!1}function gs(a,e){const t=e[a.attribute];return t===void 0?a.defaultValue:t}function ps(a,e,t){const i=e[a.attribute];if(i===void 0)return a.defaultValue;const r=Number(i);if(isNaN(r))return a.defaultValue;if(a.min===void 0&&a.max===void 0)return r;const n=a.minValue??r,o=a.maxValue??r;if(o===n)return a.min??r;let s=(r-n)/(o-n);s=Math.max(0,Math.min(1,s)),s=lt(a.easing)(s);const u=a.min??0,g=a.max??1;return u+s*(g-u)}function ms(a,e){const t=e[a.attribute];if(t===void 0)return a.defaultValue;const i=String(t);return i in a.dict?a.dict[i]:a.defaultValue}function bs(a,e,t){return fs(a)?ms(a,e):us(a)?ps(a,e):gs(a,e)}function ma(a,e){return typeof a=="string"?!!e[a]:Array.isArray(a)?a.every(t=>!!e[t]):typeof a=="object"&&a!==null?Object.entries(a).every(([t,i])=>e[t]===i):!1}function je(a,e,t,i,r,n){if(a==null)return n;if(typeof a!="object"&&typeof a!="function")return a;if(fa(a)){const o=a.when(e,t,i,r)?a.then:a.else;return o===void 0?n:je(o,e,t,i,r,n)}if(ga(a)){const o=qe(a.whenState,t)?a.then:a.else;return o===void 0?n:je(o,e,t,i,r,n)}if(pa(a)){const o=ma(a.whenData,e)?a.then:a.else;return o===void 0?n:je(o,e,t,i,r,n)}return br(a)?a(e,t,i,r)??n:ua(a)?bs(a,e)??n:a}function V(a){if(a==null)return"static";if(fa(a))return"graph-state";if(ga(a)){const e=V(a.then),t=a.else!==void 0?V(a.else):"static";return $("item-state",$(e,t))}if(pa(a)){const e=V(a.then),t=a.else!==void 0?V(a.else):"static";return $(e,t)}return br(a)?"graph-state":(ua(a),"static")}function $(a,e){return a==="graph-state"||e==="graph-state"?"graph-state":a==="item-state"||e==="item-state"?"item-state":"static"}function ba(a){if(!a)return{dependency:"static",xAttribute:null,yAttribute:null};const e=Array.isArray(a)?a:[a];let t="static",i=null,r=null;for(const n of e)if("matchData"in n&&"cases"in n){const o=n.cases;for(const s of Object.values(o))for(const l of Object.values(s))t=$(t,V(l))}else if("matchState"in n&&"cases"in n){t=$(t,"item-state");const o=n.cases;for(const s of Object.values(o))for(const l of Object.values(s))t=$(t,V(l))}else if("when"in n&&"then"in n)t="graph-state";else if("whenState"in n){t=$(t,"item-state");const o=n.then;if(o&&typeof o=="object")for(const l of Object.values(o))t=$(t,V(l));const s=n.else;if(s&&typeof s=="object")for(const l of Object.values(s))t=$(t,V(l))}else if("whenData"in n){const o=n.then;if(o&&typeof o=="object")for(const l of Object.values(o))t=$(t,V(l));const s=n.else;if(s&&typeof s=="object")for(const l of Object.values(s))t=$(t,V(l))}else for(const[o,s]of Object.entries(n))if(!_a.has(o)&&(t=$(t,V(s)),(o==="x"||o==="y")&&ua(s))){const l=s.attribute;o==="x"&&!i&&(i=l),o==="y"&&!r&&(r=l)}return{dependency:t,xAttribute:i,yAttribute:r}}const oe={size:10,color:"#666",opacity:1,shape:"circle",rotationAlignment:"viewport",labelRotationAlignment:"viewport",visibility:"visible",depth:"nodes",zIndex:0,label:"",labelColor:"#000",labelSize:12,labelFont:"sans-serif",labelVisibility:"auto",labelPosition:"right",labelAngle:0,labelDepth:"nodes",backdropVisibility:"hidden",backdropColor:"transparent",backdropShadowColor:"transparent",backdropShadowBlur:0,backdropPadding:0,backdropBorderColor:"transparent",backdropBorderWidth:0,backdropCornerRadius:0,backdropLabelPadding:-1,backdropArea:"both",labelAttachment:null,labelAttachmentPlacement:"below"},xa={size:1,color:"#ccc",opacity:1,path:"straight",selfLoopPath:"loop",parallelSpread:.25,tail:"none",head:"none",visibility:"visible",depth:"edges",zIndex:0,label:"",labelColor:"#666",labelVisibility:"auto",labelDepth:"edges"},_a=new Set(["when","whenState","whenData","then","else"]),yr=Object.keys(oe),xs=Object.values(oe),Sr=Object.keys(xa),_s=Object.values(xa);function Le(a,e,t,i,r,n,o){for(const s in e){if(_a.has(s))continue;const l=a[s]??o[s];a[s]=je(e[s],t,i,r,n,l)}"depth"in e&&!("labelDepth"in e)&&(a.labelDepth=a.depth)}function Er(a,e,t,i,r,n,o){for(const s of e)if("matchData"in s&&"cases"in s){const l=String(t[s.matchData]??""),u=s.cases;l in u&&Le(a,u[l],t,i,r,n,o)}else if("matchState"in s&&"cases"in s){const l=String(i[s.matchState]??""),u=s.cases;l in u&&Le(a,u[l],t,i,r,n,o)}else if("when"in s&&"then"in s){const l=s.when;if(!l(t,i,r,n))continue;Le(a,s.then,t,i,r,n,o)}else if("whenState"in s&&"then"in s){if(!qe(s.whenState,i))continue;Le(a,s.then,t,i,r,n,o)}else if("whenData"in s&&"then"in s){if(!ma(s.whenData,t))continue;Le(a,s.then,t,i,r,n,o)}else Le(a,s,t,i,r,n,o)}function Ta(a,e,t,i,r,n){const o=n||{},s=o;for(let u=0,g=yr.length;u<g;u++)s[yr[u]]=xs[u];if(s.x=void 0,s.y=void 0,s.labelBackgroundColor=void 0,s.labelBackgroundPadding=void 0,s.labelCursor=void 0,!a)return s.x=e.x??0,s.y=e.y??0,o.size=e.size??10,o.color=e.color??"#666",o.label=e.label??"",o;const l=Array.isArray(a)?a:[a];return Er(s,l,e,t,i,r,oe),o}function va(a,e,t,i,r,n){const o=n||{},s=o;for(let u=0,g=Sr.length;u<g;u++)s[Sr[u]]=_s[u];if(!a)return o.size=e.size??1,o.color=e.color??"#ccc",o.label=e.label??"",o;const l=Array.isArray(a)?a:[a];return Er(s,l,e,t,i,r,xa),typeof s.labelPosition=="number"&&(s.labelPosition=void 0),o}function Ts(a,e,t){if(a==null)return t;if(typeof a=="function")return a(e)??t;if(typeof a=="object"&&"when"in a){const i=a,n=i.when(e)?i.then:i.else;return n===void 0?t:typeof n=="function"?n(e)??t:n??t}if(typeof a=="object"&&"whenState"in a){const i=a,n=qe(i.whenState,e)?i.then:i.else;return n===void 0?t:typeof n=="function"?n(e)??t:n??t}return a??t}function Rr(a,e){const t={};if(!a)return t;const i=Array.isArray(a)?a:[a];for(const r of i)if("when"in r&&"then"in r){const o=r.when(e)?r.then:r.else;o&&typeof o=="object"&&ya(t,o,e)}else if("whenState"in r&&"then"in r){const o=qe(r.whenState,e)?r.then:r.else;o&&typeof o=="object"&&ya(t,o,e)}else ya(t,r,e);return t}function ya(a,e,t){for(const[i,r]of Object.entries(e))_a.has(i)||(a[i]=Ts(r,t))}class wt extends Ei.EventEmitter{constructor(){super(),this.rawEmitter=this}}const vs=Xe,ys=vr,Ar=or,Ss=Object.freeze(Object.defineProperty({__proto__:null,DEFAULT_EDGE_PRIMITIVES:Ce,DEFAULT_NODE_PRIMITIVES:Ve,DEFAULT_PRIMITIVES:Ar,DEFAULT_STYLES:vs,DEPTHLESS_STYLES:ys,TypedEventEmitter:wt,analyzeStyleDeclaration:ba,defineSigmaOptions:hs,evaluateDataPredicate:ma,evaluateEdgeStyle:va,evaluateNodeStyle:Ta,evaluateStatePredicate:qe,isInlineDataConditional:pa,isInlineFunctionConditional:fa,isInlineStateConditional:ga,resolveGraphicValue:je},Symbol.toStringTag,{value:"Module"})),Dr=1.5,It={x:.5,y:.5,angle:0,ratio:1};class Ye extends wt{constructor(){super(...arguments),this.minRatio=null,this.maxRatio=null,this.enabled=!0,this.enabledZooming=!0,this.enabledPanning=!0,this.enabledRotation=!0,this.constrainState=null,this.state={...It},this.previousState={...It},this.nextFrame=null,this.currentAnimationFrom=null,this.currentAnimationTo=null,this.animationCallback=null}get x(){return this.state.x}get y(){return this.state.y}get angle(){return this.state.angle}get ratio(){return this.state.ratio}static from(e){return new Ye().setState(e)}getState(){return{...this.state}}getPreviousState(){return{...this.previousState}}getBoundedRatio(e){let t=e;return typeof this.minRatio=="number"&&(t=Math.max(t,this.minRatio)),typeof this.maxRatio=="number"&&(t=Math.min(t,this.maxRatio)),t}validateState(e){const t=this.getState();return this.enabledPanning&&typeof e.x=="number"&&(t.x=e.x),this.enabledPanning&&typeof e.y=="number"&&(t.y=e.y),this.enabledZooming&&typeof e.ratio=="number"&&(t.ratio=this.getBoundedRatio(e.ratio)),this.enabledRotation&&typeof e.angle=="number"&&(t.angle=e.angle),this.constrainState?this.constrainState(t):t}isAnimating(){return this.nextFrame!==null}setState(e){if(!this.enabled)return this;const t=this.validateState(e);return ni(this.state,t)?this:(this.previousState=this.state,this.state=t,this.emit("updated",this.getState()),this)}updateState(e){return this.setState(e(this.getState())),this}animate(e,t){return this.enabled?new Promise(i=>this.runAnimation(e,{...Vt,...t},i)):Promise.resolve()}cancelAnimation(){return this.nextFrame!==null&&(cancelAnimationFrame(this.nextFrame),this.nextFrame=null),this.resolveAnimation(),this.emitAnimationEnd(!1),this}zoomIn({factor:e=Dr,...t}={}){return this.animate({ratio:this.ratio/e},t)}zoomOut({factor:e=Dr,...t}={}){return this.animate({ratio:this.ratio*e},t)}reset(e){return this.animate(It,e)}runAnimation(e,t,i){const r=lt(t.easing),n=Date.now(),o=this.getState(),s=this.validateState(e),l=()=>{if(!this.enabled){this.cancelAnimation();return}const u=t.duration>0?(Date.now()-n)/t.duration:1;if(u>=1){this.nextFrame=null,this.setState(s),this.resolveAnimation(),this.emitAnimationEnd(!0);return}const g=r(u);this.setState({x:o.x+(s.x-o.x)*g,y:o.y+(s.y-o.y)*g,angle:o.angle+(s.angle-o.angle)*g,ratio:o.ratio+(s.ratio-o.ratio)*g}),this.nextFrame=requestAnimationFrame(l)};this.cancelAnimation(),this.currentAnimationFrom=o,this.currentAnimationTo=s,this.animationCallback=i,this.emit("animationStart",{from:o,to:s}),l()}resolveAnimation(){const e=this.animationCallback;this.animationCallback=null,e&&e()}emitAnimationEnd(e){const t=this.currentAnimationFrom,i=this.currentAnimationTo;!t||!i||(this.currentAnimationFrom=null,this.currentAnimationTo=null,this.emit("animationEnd",{from:t,to:i,completed:e}))}}function W(a,e){const t=e.getBoundingClientRect();return{x:a.clientX-t.left,y:a.clientY-t.top}}function Y(a,e){const t={...W(a,e),sigmaDefaultPrevented:!1,preventSigmaDefault(){t.sigmaDefaultPrevented=!0},original:a};return t}function ce(a){const e="x"in a?a:{...a.touches[0]||a.previousTouches[0],original:a.original,sigmaDefaultPrevented:a.sigmaDefaultPrevented,preventSigmaDefault:()=>{a.sigmaDefaultPrevented=!0,e.sigmaDefaultPrevented=!0}};return e}function Es(a,e){const t=Y(a,e);return t.delta=Cr(a),t}const Rs=2;function zt(a){const e=[];for(let t=0,i=Math.min(a.length,Rs);t<i;t++)e.push(a[t]);return e}function Ke(a,e,t){const i={touches:zt(a.touches).map(r=>W(r,t)),previousTouches:e.map(r=>W(r,t)),sigmaDefaultPrevented:!1,preventSigmaDefault(){i.sigmaDefaultPrevented=!0},original:a};return i}function Cr(a){if(typeof a.deltaY<"u")return a.deltaY*-3/360;if(typeof a.detail<"u")return a.detail/-9;throw new Error("Captor: could not extract delta from event.")}class Lr extends wt{constructor(e,t){super(),this.container=e,this.renderer=t}}const As=["doubleClickTimeout","doubleClickZoomingDuration","doubleClickZoomingRatio","dragTimeout","draggedEventsTolerance","enableCameraMouseRotation","gestureTarget","inertiaDuration","inertiaRatio","zoomDuration","zoomingRatio"].reduce((a,e)=>({...a,[e]:Ft[e]}),{});function Ze(a){return a instanceof PointerEvent&&a.pointerType==="touch"}class Fr extends Lr{constructor(e,t){super(e,t),this.enabled=!0,this.draggedEvents=0,this.downStartTime=null,this.lastMouseX=null,this.lastMouseY=null,this.isMouseDown=!1,this.isMoving=!1,this.isPanningStage=!1,this.movingTimeout=null,this.startCameraState=null,this.clicks=0,this.doubleClickTimeout=null,this.isRightMouseDown=!1,this.startRotationAngle=null,this.startCameraAngle=null,this.currentWheelDirection=0,this.lastWheelAnimationId=0,this.settings=As,this.handleRightClick=this.handleRightClick.bind(this),this.handleDown=this.handleDown.bind(this),this.handleUp=this.handleUp.bind(this),this.handleMove=this.handleMove.bind(this),this.handleWheel=this.handleWheel.bind(this),this.handleLeave=this.handleLeave.bind(this),this.handleEnter=this.handleEnter.bind(this),e.addEventListener("contextmenu",this.handleRightClick,{capture:!1}),e.addEventListener("pointerdown",this.handleDown,{capture:!1}),e.addEventListener("wheel",this.handleWheel,{capture:!1,passive:!1}),e.addEventListener("pointerleave",this.handleLeave,{capture:!1}),e.addEventListener("pointerenter",this.handleEnter,{capture:!1}),document.addEventListener("pointermove",this.handleMove,{capture:!1}),document.addEventListener("pointerup",this.handleUp,{capture:!1})}kill(){const e=this.container;e.removeEventListener("contextmenu",this.handleRightClick),e.removeEventListener("pointerdown",this.handleDown),e.removeEventListener("wheel",this.handleWheel),e.removeEventListener("pointerleave",this.handleLeave),e.removeEventListener("pointerenter",this.handleEnter),document.removeEventListener("pointermove",this.handleMove),document.removeEventListener("pointerup",this.handleUp)}handleClick(e){if(this.enabled){if(this.clicks++,this.clicks===2)return this.clicks=0,typeof this.doubleClickTimeout=="number"&&(clearTimeout(this.doubleClickTimeout),this.doubleClickTimeout=null),this.handleDoubleClick(e);setTimeout(()=>{this.clicks=0,this.doubleClickTimeout=null},this.settings.doubleClickTimeout),this.draggedEvents<this.settings.draggedEventsTolerance&&this.emit("click",Y(e,this.container))}}handleRightClick(e){this.enabled&&(this.settings.enableCameraMouseRotation&&e.preventDefault(),this.emit("rightClick",Y(e,this.container)))}handleDoubleClick(e){if(!this.enabled)return;e.preventDefault(),e.stopPropagation();const t=Y(e,this.container);if(this.emit("doubleClick",t),t.sigmaDefaultPrevented)return;const i=this.renderer.getCamera(),r=i.getBoundedRatio(i.getState().ratio/this.settings.doubleClickZoomingRatio);i.animate(this.renderer.getViewportZoomedState(W(e,this.container),r),{easing:"quadraticInOut",duration:this.settings.doubleClickZoomingDuration})}handleDown(e){if(!(!this.enabled||Ze(e))){if(e.button===0){this.startCameraState=this.renderer.getCamera().getState();const{x:t,y:i}=W(e,this.container);this.lastMouseX=t,this.lastMouseY=i,this.draggedEvents=0,this.downStartTime=Date.now(),this.isMouseDown=!0}if(e.button===2&&this.settings.enableCameraMouseRotation){const{x:t,y:i}=W(e,this.container),r=this.container.offsetWidth/2,n=this.container.offsetHeight/2;this.startRotationAngle=Math.atan2(i-n,t-r),this.startCameraAngle=this.renderer.getCamera().getState().angle,this.isRightMouseDown=!0}this.emit("mousedown",Y(e,this.container))}}handleUp(e){if(!this.enabled||Ze(e)||!this.isMouseDown&&!this.isRightMouseDown)return;if(this.isRightMouseDown){this.isRightMouseDown=!1,this.startRotationAngle=null,this.startCameraAngle=null,this.emit("mouseup",Y(e,this.container));return}const t=this.renderer.getCamera();this.isMouseDown=!1,this.isPanningStage&&(this.isPanningStage=!1,this.renderer._setPanning(!1)),typeof this.movingTimeout=="number"&&(clearTimeout(this.movingTimeout),this.movingTimeout=null);const{x:i,y:r}=W(e,this.container),n=t.getState(),o=t.getPreviousState();this.isMoving?t.animate({x:n.x+this.settings.inertiaRatio*(n.x-o.x),y:n.y+this.settings.inertiaRatio*(n.y-o.y)},{duration:this.settings.inertiaDuration,easing:"quadraticOut"}):(this.lastMouseX!==i||this.lastMouseY!==r)&&t.setState({x:n.x,y:n.y}),this.isMoving=!1,setTimeout(()=>{const s=this.draggedEvents>0;this.draggedEvents=0;const l=this.renderer.getSetting("hideEdgesOnMove")||this.renderer.getSetting("hideLabelsOnMove");s&&l&&this.renderer.refresh()},0),this.emit("mouseup",Y(e,this.container)),(e.target===this.container||e.composedPath()[0]===this.container)&&this.handleClick(e)}handleMove(e){if(!this.enabled||Ze(e))return;const t=Y(e,this.container);if(this.emit("mousemovebody",t),(e.target===this.container||e.composedPath()[0]===this.container)&&this.emit("mousemove",t),this.isMouseDown&&this.draggedEvents++,!t.sigmaDefaultPrevented){if(this.isMouseDown){this.isMoving=!0,typeof this.movingTimeout=="number"&&clearTimeout(this.movingTimeout),this.movingTimeout=window.setTimeout(()=>{this.movingTimeout=null,this.isMoving=!1},this.settings.dragTimeout);const i=this.renderer.getCamera(),{x:r,y:n}=W(e,this.container),o=this.renderer.viewportToFramedGraph({x:this.lastMouseX,y:this.lastMouseY}),s=this.renderer.viewportToFramedGraph({x:r,y:n}),l=o.x-s.x,u=o.y-s.y,g=i.getState(),d=g.x+l,h=g.y+u;this.isPanningStage||(this.isPanningStage=!0,this.renderer._setPanning(!0)),i.setState({x:d,y:h}),this.lastMouseX=r,this.lastMouseY=n,e.preventDefault(),e.stopPropagation()}if(this.isRightMouseDown){const{x:i,y:r}=W(e,this.container),n=this.container.offsetWidth/2,o=this.container.offsetHeight/2,l=Math.atan2(r-o,i-n)-this.startRotationAngle;this.renderer.getCamera().setState({angle:this.startCameraAngle+l}),e.preventDefault(),e.stopPropagation()}}}handleLeave(e){Ze(e)||this.emit("mouseleave",Y(e,this.container))}handleEnter(e){Ze(e)||this.emit("mouseenter",Y(e,this.container))}handleWheel(e){const t=this.renderer.getCamera();if(!this.enabled||!t.enabledZooming)return;const i=Es(e,this.container);if(this.emit("wheel",i),i.sigmaDefaultPrevented)return;const{gestureTarget:r}=this.settings;if(r==="page")return;if(r==="shared"&&!e.ctrlKey&&!e.metaKey){this.renderer._showGestureHint("wheel");return}const n=Cr(e);if(!n)return;e.preventDefault(),e.stopPropagation();const o=t.getState().ratio,s=n>0?1/this.settings.zoomingRatio:this.settings.zoomingRatio,l=t.getBoundedRatio(o*s),u=n>0?1:-1,g=Date.now();if(o===l||this.currentWheelDirection===u&&this.lastWheelTriggerTime&&g-this.lastWheelTriggerTime<this.settings.zoomDuration/5)return;const d=++this.lastWheelAnimationId;t.animate(this.renderer.getViewportZoomedState(W(e,this.container),l),{easing:"quadraticOut",duration:this.settings.zoomDuration}).then(()=>{this.lastWheelAnimationId===d&&(this.currentWheelDirection=0)}),this.currentWheelDirection=u,this.lastWheelTriggerTime=g}setSettings(e){this.settings=e}}const Ds=["dragTimeout","gestureTarget","inertiaDuration","inertiaRatio","doubleClickTimeout","doubleClickZoomingRatio","doubleClickZoomingDuration","tapMoveTolerance"].reduce((a,e)=>({...a,[e]:Ft[e]}),{});class Pr extends Lr{constructor(e,t){super(e,t),this.enabled=!0,this.isMoving=!1,this.hasMoved=!1,this.isPanningStage=!1,this.isZoomingStage=!1,this.touchMode=0,this.startTouchesPositions=[],this.lastTouches=[],this.lastTap=null,this.settings=Ds,this.handleStart=this.handleStart.bind(this),this.handleLeave=this.handleLeave.bind(this),this.handleMove=this.handleMove.bind(this),e.addEventListener("touchstart",this.handleStart,{capture:!1}),e.addEventListener("touchcancel",this.handleLeave,{capture:!1}),document.addEventListener("touchend",this.handleLeave,{capture:!1,passive:!1}),document.addEventListener("touchmove",this.handleMove,{capture:!1,passive:!1})}kill(){const e=this.container;e.removeEventListener("touchstart",this.handleStart),e.removeEventListener("touchcancel",this.handleLeave),document.removeEventListener("touchend",this.handleLeave),document.removeEventListener("touchmove",this.handleMove)}getDimensions(){return{width:this.container.offsetWidth,height:this.container.offsetHeight}}doesCapture(e){const{gestureTarget:t}=this.settings;return t==="graph"?!0:t==="page"?!1:e>=2}syncStageFlags(){const e=this.touchMode===1&&this.hasMoved,t=this.touchMode===2;e!==this.isPanningStage&&(this.isPanningStage=e,this.renderer._setPanning(e)),t!==this.isZoomingStage&&(this.isZoomingStage=t,this.renderer._setZooming(t))}handleStart(e){if(!this.enabled)return;const t=zt(e.touches);if(this.touchMode=t.length,this.startCameraState=this.renderer.getCamera().getState(),this.startTouchesPositions=t.map(i=>W(i,this.container)),this.touchMode===2){const[{x:i,y:r},{x:n,y:o}]=this.startTouchesPositions;this.startTouchesAngle=Math.atan2(o-r,n-i),this.startTouchesDistance=Math.sqrt(Math.pow(n-i,2)+Math.pow(o-r,2))}this.syncStageFlags(),this.emit("touchdown",Ke(e,this.lastTouches,this.container)),this.lastTouches=t,this.lastTouchesPositions=this.startTouchesPositions,e.cancelable&&(this.doesCapture(e.touches.length)||this.renderer._hasNodeDrag())&&e.preventDefault()}handleLeave(e){if(!(!this.enabled||!this.startTouchesPositions.length)){switch(e.cancelable&&this.doesCapture(this.touchMode)&&e.preventDefault(),this.movingTimeout&&(this.isMoving=!1,clearTimeout(this.movingTimeout)),this.touchMode){case 2:if(e.touches.length===1){this.handleStart(e);break}case 1:if(this.isMoving){const t=this.renderer.getCamera(),i=t.getState(),r=t.getPreviousState();t.animate({x:i.x+this.settings.inertiaRatio*(i.x-r.x),y:i.y+this.settings.inertiaRatio*(i.y-r.y)},{duration:this.settings.inertiaDuration,easing:"quadraticOut"})}this.hasMoved=!1,this.isMoving=!1,this.touchMode=0;break}if(this.syncStageFlags(),this.emit("touchup",Ke(e,this.lastTouches,this.container)),!e.touches.length){const t=W(this.lastTouches[0],this.container),i=this.startTouchesPositions[0],r=(t.x-i.x)**2+(t.y-i.y)**2;if(!e.touches.length&&r<this.settings.tapMoveTolerance**2)if(this.lastTap&&Date.now()-this.lastTap.time<this.settings.doubleClickTimeout){const n=Ke(e,this.lastTouches,this.container);if(this.emit("doubletap",n),this.lastTap=null,!n.sigmaDefaultPrevented&&this.settings.gestureTarget!=="page"){const o=this.renderer.getCamera(),s=o.getBoundedRatio(o.getState().ratio/this.settings.doubleClickZoomingRatio);o.animate(this.renderer.getViewportZoomedState(t,s),{easing:"quadraticInOut",duration:this.settings.doubleClickZoomingDuration})}}else{const n=Ke(e,this.lastTouches,this.container);this.emit("tap",n),this.lastTap={time:Date.now(),position:n.touches[0]||n.previousTouches[0]}}}this.lastTouches=zt(e.touches),this.startTouchesPositions=[]}}handleMove(e){if(!this.enabled||!this.startTouchesPositions.length)return;const t=this.doesCapture(e.touches.length);t&&e.preventDefault();const i=zt(e.touches),r=i.map(g=>W(g,this.container)),n=this.lastTouches;this.lastTouches=i,this.lastTouchesPositions=r;const o=Ke(e,n,this.container);if(this.emit("touchmove",o),o.sigmaDefaultPrevented||(this.hasMoved||(this.hasMoved=r.some((g,d)=>{const h=this.startTouchesPositions[d];return h&&(g.x!==h.x||g.y!==h.y)})),!this.hasMoved))return;if(!t){this.settings.gestureTarget==="shared"&&this.renderer._showGestureHint("touch");return}this.isMoving=!0,this.syncStageFlags(),this.movingTimeout&&clearTimeout(this.movingTimeout),this.movingTimeout=window.setTimeout(()=>{this.isMoving=!1},this.settings.dragTimeout);const s=this.renderer.getCamera(),l=this.startCameraState,u=this.renderer.getSetting("stagePadding");switch(this.touchMode){case 1:{const{x:g,y:d}=this.renderer.viewportToFramedGraph((this.startTouchesPositions||[])[0]),{x:h,y:c}=this.renderer.viewportToFramedGraph(r[0]);s.setState({x:l.x+g-h,y:l.y+d-c});break}case 2:{const g={x:.5,y:.5,angle:0,ratio:1},{x:d,y:h}=r[0],{x:c,y:f}=r[1],b=Math.atan2(f-h,c-d)-this.startTouchesAngle,v=Math.hypot(f-h,c-d)/this.startTouchesDistance,_=s.getBoundedRatio(l.ratio/v);g.ratio=_,g.angle=l.angle+b;const m=this.getDimensions(),E=this.renderer.viewportToFramedGraph((this.startTouchesPositions||[])[0],{cameraState:l}),p=Math.min(m.width,m.height)-2*u,x=p/m.width,T=p/m.height,y=_/p;let R=d-p/2/x,A=h-p/2/T;({x:R,y:A}=jt({x:R,y:A},g.angle)),g.x=E.x-R*y,g.y=E.y+A*y,s.setState(g);break}}}setSettings(e){this.settings=e}}class Cs{constructor(e,t,i,r){this.graph=e,this.viewportToGraph=t,this.setNodesState=i,this.emit=r,this.pendingNode=null,this.session=null}start(e,t,i,r,n){const o=i(e);let s=!1;if(this.emit("nodeDragStart",{node:e,allDraggedNodes:o,event:t,preventSigmaDefault(){s=!0}}),s)return!1;const l=new Map;for(const u of o)l.set(u,{x:this.graph.getNodeAttribute(u,r),y:this.graph.getNodeAttribute(u,n)});return this.session={node:e,allNodes:o,startPosition:this.viewportToGraph(t),startNodePositions:l,xAttr:r,yAttr:n},this.setNodesState(o,{isDragged:!0}),!0}applyMove(e,t){const{allNodes:i,startNodePositions:r,startPosition:n,xAttr:o,yAttr:s}=this.session,l=this.viewportToGraph(e),u={x:l.x-n.x,y:l.y-n.y};for(const g of i){const d=r.get(g);if(!d||!this.graph.hasNode(g))continue;const h={x:d.x+u.x,y:d.y+u.y};t?this.graph.mergeNodeAttributes(g,t(h,g)):(this.graph.setNodeAttribute(g,o,h.x),this.graph.setNodeAttribute(g,s,h.y))}}end(){if(this.pendingNode=null,!this.session)return null;const{node:e,allNodes:t}=this.session;return this.setNodesState(t,{isDragged:!1}),this.session=null,{node:e,allNodes:t}}removeNode(e){e===this.pendingNode&&(this.pendingNode=null),this.session&&(this.session.node===e?(this.setNodesState(this.session.allNodes,{isDragged:!1}),this.session=null):this.session.allNodes.includes(e)&&(this.session.allNodes=this.session.allNodes.filter(t=>t!==e),this.session.startNodePositions.delete(e)))}clear(){this.pendingNode=null,this.session=null}}function wr(a,e){return a<e?`${a}\0${e}`:`${e}\0${a}`}class Ls{constructor(e,t){this.graph=e,this.onGroupChanged=t,this.groups=new Map,this.edgeToGroupKey=new Map}getGroupKey(e){return wr(this.graph.source(e),this.graph.target(e))}sortGroup(e,t){const i=t.split("\0")[0];e.sort((r,n)=>{const o=this.graph.source(r)===i||!this.graph.isDirected(r)?0:1,s=this.graph.source(n)===i||!this.graph.isDirected(n)?0:1;return o-s})}register(e){const t=this.getGroupKey(e);let i=this.groups.get(t);i||(i=[],this.groups.set(t,i)),i.includes(e)||i.push(e),this.edgeToGroupKey.set(e,t),i.length>1&&this.sortGroup(i,t),this.onGroupChanged(i,i.length)}unregister(e){const t=this.edgeToGroupKey.get(e);if(!t)return;this.edgeToGroupKey.delete(e);const i=this.groups.get(t);if(!i)return;const r=i.indexOf(e);r!==-1&&i.splice(r,1),i.length===0?this.groups.delete(t):this.onGroupChanged(i,i.length)}getGroup(e){const t=this.edgeToGroupKey.get(e);return t?this.groups.get(t)??[]:[]}getSiblings(e){return this.getGroup(e).filter(t=>t!==e)}rebuild(){this.groups.clear(),this.edgeToGroupKey.clear(),this.graph.forEachEdge((e,t,i,r)=>{const n=wr(i,r);let o=this.groups.get(n);o||(o=[],this.groups.set(n,o)),o.push(e),this.edgeToGroupKey.set(e,n)});for(const[e,t]of this.groups)t.length>1&&this.sortGroup(t,e),this.onGroupChanged(t,t.length)}clear(){this.groups.clear(),this.edgeToGroupKey.clear()}}function Ir(a,e){if(a===!1||a==="extend"||a==="separate")return a;const t=a[e];return t!==void 0?t:a.default??!1}function zr(a){return a===!1?!1:a==="extend"||a==="separate"?!0:Object.values(a).some(e=>e==="extend"||e==="separate")}function Mr(a){return a==="separate"?!0:a===!1||a==="extend"?!1:a.default==="separate"?!0:Object.values(a).includes("separate")}const k={node:{parent:null,eventSuffix:"Node",payloadKey:"node",isEnabled:()=>!0,writesPickingThisFrame:()=>!0,setHover:(a,e,t)=>a.setNodeState(e,{isHovered:t}),getCursor:(a,e)=>{var t;return(t=a.nodeDataCache[e])==null?void 0:t.cursor},isHitValid:(a,e)=>{var t;return((t=a.nodeDataCache[e])==null?void 0:t.visibility)!=="hidden"}},edge:{parent:null,eventSuffix:"Edge",payloadKey:"edge",isEnabled:a=>!!a.settings.enableEdgeEvents,writesPickingThisFrame:a=>!!a.settings.enableEdgeEvents,setHover:(a,e,t)=>a.setEdgeState(e,{isHovered:t}),getCursor:(a,e)=>{var t;return(t=a.edgeDataCache[e])==null?void 0:t.cursor}},nodeLabel:{parent:"node",eventSuffix:"NodeLabel",payloadKey:"node",isEnabled:a=>Mr(a.settings.nodeLabelEvents),writesPickingThisFrame:a=>zr(a.settings.nodeLabelEvents),setHover:(a,e,t)=>a.setNodeState(e,{isLabelHovered:t}),getCursor:(a,e)=>{var t;return(t=a.nodeDataCache[e])==null?void 0:t.labelCursor},resolveForVerb:(a,e,t)=>{const i=Ir(t.settings.nodeLabelEvents,e);return i===!1?null:i==="extend"?{kind:"node",key:a.key}:a}},edgeLabel:{parent:"edge",eventSuffix:"EdgeLabel",payloadKey:"edge",isEnabled:a=>Mr(a.settings.edgeLabelEvents),writesPickingThisFrame:a=>zr(a.settings.edgeLabelEvents),setHover:(a,e,t)=>a.setEdgeState(e,{isLabelHovered:t}),getCursor:(a,e)=>{var t;return(t=a.edgeDataCache[e])==null?void 0:t.labelCursor},resolveForVerb:(a,e,t)=>{const i=Ir(t.settings.edgeLabelEvents,e);return i===!1?null:i==="extend"?{kind:"edge",key:a.key}:a}}},he=["node","edge","nodeLabel","edgeLabel"];function Fs(){return{lookup:[null],idsByKind:{node:new Map,edge:new Map,nodeLabel:new Map,edgeLabel:new Map},nextId:1}}function ue(a,e,t){return a.idsByKind[e].get(t)??0}function Sa(a,e){for(const i of he)if(i===e||k[i].parent===e){for(const r of a.idsByKind[i].values())a.lookup[r]=null;a.idsByKind[i]=new Map}let t=1;for(const i of he){if(i===e)break;t+=a.idsByKind[i].size}a.nextId=t}function Nr(a,e,t){const i=a.nextId++;return a.idsByKind[e].set(t,i),a.lookup[i]={kind:e,key:t},i}function Ps(a,e){for(const t of he)if(k[t].parent!==null){for(const i of a.idsByKind[t].values())a.lookup[i]=null;a.idsByKind[t]=new Map}a.nextId=1;for(const t of he)if(k[t].parent===null)for(const i of a.idsByKind[t].values())i>=a.nextId&&(a.nextId=i+1);for(const t of he){const i=k[t];if(!(i.parent===null||!i.isEnabled(e)))for(const r of a.idsByKind[i.parent].keys())a.idsByKind[t].set(r,a.nextId),a.lookup[a.nextId]={kind:t,key:r},a.nextId++}}function ws(a,e){for(const t of he){const i=a[t],r=e[t];if(i!==r){if(i.size!==r.size)return!1;for(const[n,o]of i)if(r.get(n)!==o)return!1}}return!0}function kr(a,e,t){k[e.kind].setHover(a,e.key,t)}function Is(a,e){return k[e.kind].getCursor(a,e.key)}function Qe(a,e){return`${e}${k[a].eventSuffix}`}function Je(a,e){return{...e,[k[a.kind].payloadKey]:a.key}}function zs(a,e,t){const i=k[e.kind].resolveForVerb;return i?i(e,t,a):e}function Ms(a,e){const t=k[e.kind].isHitValid;return t?t(a,e.key):!0}function Ea(a){return he.filter(e=>e===a||k[e].parent===a)}function Gr(a,e,t){return e&&(e=zs(a,e,t)),e&&!Ms(a,e)&&(e=null),e}function Ra(a,e,t){const i=t==="wheel"?a.stateManager.hovered:a.getHitAtPosition(e);return Gr(a,i,t)}function Ns(a,e){return!a||!e?a===e:a.kind===e.kind&&a.key===e.key}function Wr(a,e,t){const i={event:t,preventSigmaDefault:()=>t.preventSigmaDefault()},r=a.dragManager.session,n=r?{kind:"node",key:r.node}:Gr(a,e,"enter"),{stateManager:o}=a,s=o.hovered;Ns(s,n)||(s&&(kr(a,s,!1),a.emit(Qe(s.kind,"leave"),Je(s,i))),o.setHovered(n),n&&(kr(a,n,!0),a.emit(Qe(n.kind,"enter"),Je(n,i))),a.updateContainerCursor())}function ks(a,e,t,i){i.handleResize=()=>a.scheduleRefresh(),window.addEventListener("resize",i.handleResize),i.handleMove=n=>{a.hoverResolver.pointerMoved(ce(n))},i.handleMoveBody=n=>{const o=ce(n),{dragManager:s}=a;if(s.pendingNode&&!s.session){const{xAttribute:l,yAttribute:u}=a.nodeStyleAnalysis,{settings:g}=a;s.start(s.pendingNode,o,g.getDraggedNodes,l||"x",u||"y"),s.pendingNode=null}s.session&&(s.applyMove(o,a.settings.dragPositionToAttributes),a.emit("nodeDrag",{node:s.session.node,allDraggedNodes:s.session.allNodes,event:o}),o.preventSigmaDefault()),a.emit("moveBody",{event:o,preventSigmaDefault:()=>o.preventSigmaDefault()})},i.handleLeave=n=>{const o=ce(n),s={event:o,preventSigmaDefault:()=>o.preventSigmaDefault()};a.hoverResolver.pointerLeft(),Wr(a,null,o),a.emit("leaveStage",s)},i.handleEnter=n=>{const o=ce(n);a.emit("enterStage",{event:o,preventSigmaDefault:()=>o.preventSigmaDefault()})};const r=n=>o=>{const s=ce(o),l={event:s,preventSigmaDefault:()=>s.preventSigmaDefault()},u=Ra(a,s,n);if(u){a.emit(Qe(u.kind,n),Je(u,l));return}a.emit(`${n}Stage`,l)};i.handleClick=r("click"),i.handleRightClick=r("rightClick"),i.handleDoubleClick=r("doubleClick"),i.handleWheel=r("wheel"),i.handleDown=n=>{const o=ce(n),s={event:o,preventSigmaDefault:()=>o.preventSigmaDefault()},l=Ra(a,o,"down");if(l){l.kind==="node"&&a.settings.enableNodeDrag&&(a.dragManager.pendingNode=l.key),a.emit(Qe(l.kind,"down"),Je(l,s));return}a.emit("downStage",s)},i.handleUp=n=>{const o=ce(n),s={event:o,preventSigmaDefault:()=>o.preventSigmaDefault()},l=a.dragManager.end();l&&a.emit("nodeDragEnd",{node:l.node,allDraggedNodes:l.allNodes,...s});const u=Ra(a,o,"up");if(u){a.emit(Qe(u.kind,"up"),Je(u,s));return}a.emit("upStage",s)},e.on("mousemove",i.handleMove),e.on("mousemovebody",i.handleMoveBody),e.on("click",i.handleClick),e.on("rightClick",i.handleRightClick),e.on("doubleClick",i.handleDoubleClick),e.on("wheel",i.handleWheel),e.on("mousedown",i.handleDown),e.on("mouseup",i.handleUp),e.on("mouseleave",i.handleLeave),e.on("mouseenter",i.handleEnter),t.on("touchdown",i.handleDown),t.on("touchdown",i.handleMove),t.on("touchup",i.handleUp),t.on("touchmove",i.handleMove),t.on("tap",i.handleClick),t.on("doubletap",i.handleDoubleClick),t.on("touchmove",i.handleMoveBody)}function Gs(a,e){const{graph:t}=a,i=new Set(["x","y","zIndex","type"]);e.eachNodeAttributesUpdatedGraphUpdate=r=>{var s;const n=(s=r.hints)==null?void 0:s.attributes,o=!n||n.some(l=>i.has(l));a.refresh({partialGraph:{nodes:t.nodes()},skipIndexation:!o,schedule:!0})},e.eachEdgeAttributesUpdatedGraphUpdate=r=>{var s;const n=(s=r.hints)==null?void 0:s.attributes,o=n&&["zIndex","type"].some(l=>n==null?void 0:n.includes(l));a.refresh({partialGraph:{edges:t.edges()},skipIndexation:!o,schedule:!0})},e.addNodeGraphUpdate=r=>{a.addNode(r.key),a.refresh({partialGraph:{nodes:[r.key]},skipIndexation:!1,schedule:!0})},e.updateNodeGraphUpdate=r=>{a.refresh({partialGraph:{nodes:[r.key]},skipIndexation:!1,schedule:!0})},e.dropNodeGraphUpdate=r=>{a.removeNode(r.key),a.refresh({schedule:!0})},e.addEdgeGraphUpdate=r=>{const n=r.key;a.edgeGroups.register(n),a.addEdge(n);const o=a.edgeGroups.getSiblings(n);for(const s of o)a.addEdge(s);a.refresh({partialGraph:{edges:[n,...o]},schedule:!0})},e.updateEdgeGraphUpdate=r=>{a.refresh({partialGraph:{edges:[r.key]},skipIndexation:!1,schedule:!0})},e.dropEdgeGraphUpdate=r=>{const n=r.key,o=a.edgeGroups.getSiblings(n);a.edgeGroups.unregister(n),a.removeEdge(n);for(const s of o)a.addEdge(s);a.refresh({schedule:!0})},e.clearEdgesGraphUpdate=()=>{a.clearEdgeState(),a.clearEdgeIndices(),a.refresh({schedule:!0})},e.clearGraphUpdate=()=>{a.clearEdgeState(),a.clearNodeState(),a.clearEdgeIndices(),a.clearNodeIndices(),a.refresh({schedule:!0})},t.on("nodeAdded",e.addNodeGraphUpdate),t.on("nodeDropped",e.dropNodeGraphUpdate),t.on("nodeAttributesUpdated",e.updateNodeGraphUpdate),t.on("eachNodeAttributesUpdated",e.eachNodeAttributesUpdatedGraphUpdate),t.on("edgeAdded",e.addEdgeGraphUpdate),t.on("edgeDropped",e.dropEdgeGraphUpdate),t.on("edgeAttributesUpdated",e.updateEdgeGraphUpdate),t.on("eachEdgeAttributesUpdated",e.eachEdgeAttributesUpdatedGraphUpdate),t.on("edgesCleared",e.clearEdgesGraphUpdate),t.on("cleared",e.clearGraphUpdate)}function Ws(a,e){a.removeListener("nodeAdded",e.addNodeGraphUpdate),a.removeListener("nodeDropped",e.dropNodeGraphUpdate),a.removeListener("nodeAttributesUpdated",e.updateNodeGraphUpdate),a.removeListener("eachNodeAttributesUpdated",e.eachNodeAttributesUpdatedGraphUpdate),a.removeListener("edgeAdded",e.addEdgeGraphUpdate),a.removeListener("edgeDropped",e.dropEdgeGraphUpdate),a.removeListener("edgeAttributesUpdated",e.updateEdgeGraphUpdate),a.removeListener("eachEdgeAttributesUpdated",e.eachEdgeAttributesUpdatedGraphUpdate),a.removeListener("edgesCleared",e.clearEdgesGraphUpdate),a.removeListener("cleared",e.clearGraphUpdate)}const Bs={x:.5,y:.5,angle:0,ratio:1},Os=8,Us=.001;function $s(a){const{coords:e,nodeData:t,dimensions:i,stagePadding:r,zoomToSizeRatioFunction:n,itemSizesReference:o,fitLabels:s,nodeLabelBox:l}=a,u=Object.keys(e);if(!u.length)return a.extent;const g=n(1)||1,d=o==="positions",{width:h,height:c}=i;let f=a.extent;for(let b=0;b<Os;b++){const v=ft(f),_={width:f.x[1]-f.x[0]||1,height:f.y[1]-f.y[0]||1},m=ee(Bs,i,_,r),E=Br(m,v({x:0,y:0}),h,c),p=Br(m,v({x:1,y:0}),h,c),x=Math.hypot(p.x-E.x,p.y-E.y)||1;let T=1/0,y=-1/0,R=1/0,A=-1/0;for(let D=0,F=u.length;D<F;D++){const P=u[D],w=t[P],M=w.size/g*(d?x:1);let N=-M,z=M,B=-M,G=M;if(s){const Q=l(w,M);Q&&(Q.minX<N&&(N=Q.minX),Q.maxX>z&&(z=Q.maxX),Q.minY<B&&(B=Q.minY),Q.maxY>G&&(G=Q.maxY))}const{x:tt,y:Yr}=e[P];T=Math.min(T,tt+N/x),y=Math.max(y,tt+z/x),R=Math.min(R,Yr-G/x),A=Math.max(A,Yr-B/x)}const S={x:[T,y],y:[R,A]},C=Math.max(S.x[1]-S.x[0],S.y[1]-S.y[0])||1,L=Math.max(Math.abs(S.x[0]-f.x[0]),Math.abs(S.x[1]-f.x[1]),Math.abs(S.y[0]-f.y[0]),Math.abs(S.y[1]-f.y[1]));if(f=S,L/C<Us)break}return f}function Br(a,e,t,i){const r=xe(a,e);return{x:(1+r.x)*t/2,y:(1-r.y)*i/2}}const Hs=typeof navigator<"u"&&/Mac|iP(hone|ad|od)/.test(navigator.platform),Vs=1500,Xs=`.sigma-gesture-hint {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 1em;
background: #00000066;
color: #ffffff;
font-size: 1em;
pointer-events: none;
transition: opacity 0.1s;
}`;function Or(a){var i;const e=Se("style");e.textContent=a,document.head.appendChild(e);const t=(((i=e.sheet)==null?void 0:i.cssRules.length)??0)>0;return e.remove(),t}let fe=null;function qs(){return fe===null&&(fe=Xs,Or("@scope {}")&&(fe=`@scope {
${fe}
}`),Or("@layer sigma-gesture-hint {}")&&(fe=`@layer sigma-gesture-hint {
${fe}
}`)),fe}class js{constructor(e){this.hideTimeout=null,this.styleElement=Se("style"),this.styleElement.textContent=qs(),e.appendChild(this.styleElement),this.element=Se("div",{opacity:"0"},{class:"sigma-gesture-hint"}),e.appendChild(this.element)}show(e,t){const i=e==="touch"?t.sharedGestureTouchMessage:Hs?t.sharedGestureAppleWheelMessage:t.sharedGestureWheelMessage;i&&(this.element.textContent=i,this.element.style.opacity="1",typeof this.hideTimeout=="number"&&clearTimeout(this.hideTimeout),this.hideTimeout=window.setTimeout(()=>{this.hideTimeout=null,this.element.style.opacity="0"},Vs))}kill(){typeof this.hideTimeout=="number"&&clearTimeout(this.hideTimeout),this.styleElement.remove(),this.element.remove()}}class Ys{constructor(e){this.readBuffer=null,this.fence=null,this.result=new Uint8Array(4),this.generation=0,this.readGeneration=0,this.lastEvent=null,this.active=!1,this.dirty=!1,this.rafId=null,this.killed=!1,this.options=e}pointerMoved(e){this.lastEvent=e,this.active=!0,!this.options.isRenderPending()&&this.request()}pointerLeft(){this.active=!1,this.dirty=!1}frameRendered(){this.request()}invalidate(){this.generation++}request(){if(!(this.killed||!this.active||!this.lastEvent)){if(this.fence){this.dirty=!0;return}this.startRead()}}startRead(){const{gl:e,getFrameBuffer:t,getPixelRatio:i,getDownSizingRatio:r}=this.options;this.readGeneration=this.generation;const n=this.lastEvent,[o,s]=Yt(e,n.x,n.y,i(),r());this.readBuffer||(this.readBuffer=e.createBuffer()),e.bindFramebuffer(e.FRAMEBUFFER,t()),e.bindBuffer(e.PIXEL_PACK_BUFFER,this.readBuffer),e.bufferData(e.PIXEL_PACK_BUFFER,4,e.STREAM_READ),e.readPixels(o,s,1,1,e.RGBA,e.UNSIGNED_BYTE,0),e.bindBuffer(e.PIXEL_PACK_BUFFER,null),e.bindFramebuffer(e.FRAMEBUFFER,null),this.fence=e.fenceSync(e.SYNC_GPU_COMMANDS_COMPLETE,0),e.flush(),this.schedulePoll(n)}schedulePoll(e){this.rafId=requestAnimationFrame(()=>{this.rafId=null,this.poll(e)})}poll(e){const{gl:t,onIndex:i}=this.options;if(this.killed||!this.fence)return;const r=t.clientWaitSync(this.fence,0,0);if(r===t.TIMEOUT_EXPIRED){this.schedulePoll(e);return}if(t.deleteSync(this.fence),this.fence=null,r!==t.WAIT_FAILED&&this.readGeneration===this.generation){t.bindBuffer(t.PIXEL_PACK_BUFFER,this.readBuffer),t.getBufferSubData(t.PIXEL_PACK_BUFFER,0,this.result),t.bindBuffer(t.PIXEL_PACK_BUFFER,null);const[n,o,s,l]=this.result;this.active&&i(Kt(n,o,s,l),e)}this.dirty&&(this.dirty=!1,this.active&&this.lastEvent&&!this.options.isRenderPending()&&this.startRead())}reset(){this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=null,this.fence=null,this.readBuffer=null,this.dirty=!1}kill(){this.killed=!0,this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=null;const{gl:e}=this.options;this.fence&&e.deleteSync(this.fence),this.fence=null,this.readBuffer&&e.deleteBuffer(this.readBuffer),this.readBuffer=null}}class Ur{constructor(e,t){this.key=e,this.size=t}static compare(e,t){return e.size>t.size?-1:e.size<t.size||e.key>t.key?1:-1}}class Mt{constructor(){this.width=0,this.height=0,this.cellSize=0,this.columns=0,this.rows=0,this.cells={}}resizeAndClear(e,t){this.width=e.width,this.height=e.height,this.cellSize=t,this.columns=Math.ceil(e.width/t),this.rows=Math.ceil(e.height/t),this.cells={}}getIndex(e){const t=Math.floor(e.x/this.cellSize);return Math.floor(e.y/this.cellSize)*this.columns+t}add(e,t,i){const r=new Ur(e,t),n=this.getIndex(i);let o=this.cells[n];o||(o=[],this.cells[n]=o),o.push(r)}organize(){for(const e in this.cells)this.cells[e].sort(Ur.compare)}getLabelsToDisplay(e,t,i){const r=this.cellSize*this.cellSize,o=r/e/e*t/r,s=Math.ceil(o),l=[];if(i){const u=Math.max(0,Math.floor(i.x1/this.cellSize)),g=Math.min(this.columns-1,Math.floor(i.x2/this.cellSize)),d=Math.max(0,Math.floor(i.y1/this.cellSize)),h=Math.min(this.rows-1,Math.floor(i.y2/this.cellSize));for(let c=d;c<=h;c++)for(let f=u;f<=g;f++){const b=c*this.columns+f,v=this.cells[b];if(v)for(let _=0;_<Math.min(s,v.length);_++)l.push(v[_].key)}}else for(const u in this.cells){const g=this.cells[u];for(let d=0;d<Math.min(s,g.length);d++)l.push(g[d].key)}return l}}function Ks(a){const{graph:e,hoveredNode:t,highlightedNodes:i,displayedNodeLabels:r}=a,n=new Set,o=new Set(i);t&&o.add(t);for(const s of o)e.hasNode(s)&&e.forEachEdge(s,l=>{n.add(l)});for(const s of r)o.has(s)||e.forEachEdge(s,(l,u,g,d)=>{const h=g===s?d:g;r.has(h)&&n.add(l)});return Array.from(n)}const Fe=150,Pe=50,Zs={both:0,node:1,label:2},Qs={over:0,above:1,below:2,auto:3},$r=12,Js=3;class el{constructor(e){this.internals=e,this.labelGrid=new Mt,this.edgeAnchorGrid=new Mt,this.displayedNodeLabels=new Set,this.displayedEdgeLabels=new Set,this.edgeLabelCandidates=[],this.renderedNodeLabels=new Set,this.labelSizeCache=new Map,this.framePassPoints=new Float32Array(0),this.labelsDirty=!0}resetFrame(){this.displayedNodeLabels=new Set,this.renderedNodeLabels=new Set,this.labelSizeCache.clear()}clearEdgeLabels(){this.displayedEdgeLabels=new Set}resetLabelGrid(){this.labelGrid=new Mt,this.edgeAnchorGrid=new Mt}processWebGLLabels(e){var s,l,u;const{labelProgram:t,primitives:i,nodeDataCache:r}=this.internals,n=((u=(l=(s=i==null?void 0:i.nodes)==null?void 0:s.label)==null?void 0:l.font)==null?void 0:u.family)||"sans-serif",o=new Map;for(let g=0,d=e.length;g<d;g++){const h=e[g],c=r[h];if(c.visibility==="hidden"||!c.label)continue;const f=c.labelFont||n,b=o.get(f);b?b.push(c.label):o.set(f,[c.label])}for(const[g,d]of o){const{family:h,weight:c,style:f}=ht(g),b=t.registerFont(h,c,f);t.ensureGlyphsReady(d,b)}}measureNodeLabel(e){var h,c,f;if(!e.label)return{width:0,height:0,textHeight:0};const{labelProgram:t,primitives:i}=this.internals,r=e.labelSize??14,n=e.labelFont||((f=(c=(h=i==null?void 0:i.nodes)==null?void 0:h.label)==null?void 0:c.font)==null?void 0:f.family)||"sans-serif",o=`${e.label}|${r}|${n}`;if(this.labelSizeCache.has(o))return this.labelSizeCache.get(o);const{family:s,weight:l,style:u}=ht(n),g=t.registerFont(s,l,u),d=t.measureLabel(e.label,r,g);return this.labelSizeCache.set(o,d),d}nodeLabelBox(e,t){var y,R,A;if(e.visibility==="hidden"||e.labelVisibility==="hidden")return null;const{width:i,height:r,textHeight:n}=this.measureNodeLabel(e);if(!i)return null;const o=((A=(R=(y=this.internals.primitives)==null?void 0:y.nodes)==null?void 0:R.label)==null?void 0:A.margin)??me,s=t+o,l=e.labelBackgroundColor?e.labelBackgroundPadding??Bt:0,u=i/2,g=r/2,d=u+l,h=g+l,c=n/2;let f=0,b=0;switch(e.labelPosition??"right"){case"left":f=-(s+u);break;case"above":b=-(s+c);break;case"below":b=s+c;break;case"over":break;default:f=s+u}const v=e.labelAngle??0,{x:_,y:m}=jt({x:f,y:b},v),E=Math.abs(Math.cos(v)),p=Math.abs(Math.sin(v)),x=E*d+p*h,T=p*d+E*h;return{minX:_-x,maxX:_+x,minY:m-T,maxY:m+T}}computeDisplayedNodeLabels(){const e=this.internals.getCameraState(),{width:t,height:i}=this.internals.getDimensions(),r=this.internals.viewportToFramedGraph({x:-Fe,y:-Pe}),n=this.internals.viewportToFramedGraph({x:t+Fe,y:-Pe}),o=this.internals.viewportToFramedGraph({x:-Fe,y:i+Pe}),s=this.internals.viewportToFramedGraph({x:t+Fe,y:i+Pe}),l=Math.min(r.x,n.x,o.x,s.x),u=Math.max(r.x,n.x,o.x,s.x),g=Math.min(r.y,n.y,o.y,s.y),d=Math.max(r.y,n.y,o.y,s.y),h=ee({x:.5,y:.5,ratio:1,angle:0},{width:t,height:i},this.internals.getGraphDimensions(),this.internals.getStagePadding()),c=y=>{const R=xe(h,y);return{x:(1+R.x)*t/2,y:(1-R.y)*i/2}},f=c({x:l,y:g}),b=c({x:u,y:g}),v=c({x:l,y:d}),_=c({x:u,y:d}),m={x1:Math.min(f.x,b.x,v.x,_.x),y1:Math.min(f.y,b.y,v.y,_.y),x2:Math.max(f.x,b.x,v.x,_.x),y2:Math.max(f.y,b.y,v.y,_.y)},{settings:E,nodeDataCache:p,nodesWithForcedLabels:x}=this.internals,T=this.labelGrid.getLabelsToDisplay(e.ratio,E.labelDensity,m);Zt(T,x);for(let y=0,R=T.length;y<R;y++){const A=T[y],S=p[A];if(this.displayedNodeLabels.has(A)||S.visibility==="hidden"||S.labelVisibility==="hidden"||!S.label||S.x<l||S.x>u||S.y<g||S.y>d)continue;const{x:C,y:L}=this.internals.framedGraphToViewport(S),D=this.internals.scaleSize(S.size);!te(S)&&D<E.labelRenderedSizeThreshold||C<-Fe-D||C>t+Fe+D||L<-Pe-D||L>i+Pe+D||this.displayedNodeLabels.add(A)}}buildFramePassPoints(){const{nodeDataCache:e,nodeDataTexture:t}=this.internals;if(!t)return{data:this.framePassPoints,count:0};const i=this.displayedNodeLabels.size*3;this.framePassPoints.length<i&&(this.framePassPoints=new Float32Array(i));const r=this.framePassPoints;let n=0;for(const o of this.displayedNodeLabels){const s=e[o];if(!s)continue;const l=t.getIndex(o);l<0||(r[n*3]=l,r[n*3+1]=pe[s.labelPosition||oe.labelPosition]??0,r[n*3+2]=s.labelAngle??0,n++)}return{data:r,count:n}}renderWebGLLabels(e,t){var l,u,g,d,h;const{nodeDataCache:i,labelProgram:r,primitives:n,nodeDataTexture:o}=this.internals,s=[];for(const c of this.displayedNodeLabels){if(this.renderedNodeLabels.has(c))continue;const f=i[c];t&&f.labelDepth!==t||(this.renderedNodeLabels.add(c),s.push(c))}if(s.length!==0){if(this.labelsDirty){let c=0;for(let p=0,x=s.length;p<x;p++){const T=i[s[p]];c+=T.label.length}r.reallocate(c);const f=14,b=((u=(l=n==null?void 0:n.nodes)==null?void 0:l.label)==null?void 0:u.margin)??me,v=oe.labelPosition,_=((h=(d=(g=n==null?void 0:n.nodes)==null?void 0:g.label)==null?void 0:d.font)==null?void 0:h.family)||"sans-serif",m=new Map;let E=0;for(let p=0,x=s.length;p<x;p++){const T=s[p],y=i[T],R=y.labelFont||_;let A=m.get(R);if(A===void 0){const{family:L,weight:D,style:F}=ht(R);A=r.registerFont(L,D,F),m.set(R,A)}const S={text:y.label,x:y.x,y:y.y,size:y.labelSize??f,color:y.labelColor,nodeSize:y.size,margin:b,position:y.labelPosition??v,hidden:!1,forceLabel:te(y),type:"default",zIndex:y.zIndex??0,parentType:"node",parentKey:T,fontKey:A,labelAngle:y.labelAngle??0,nodeIndex:o.getIndex(T)},C=r.processLabel(T,E,S);E+=C}r.invalidateBuffers()}r.render(e)}}renderBackdrops(e,t){const{backdropProgram:i,nodeDataCache:r,nodesWithBackdrop:n,attachmentManager:o,pixelRatio:s,nodeDataTexture:l}=this.internals,u=[];for(const g of n){const d=r[g];if(!d||d.visibility==="hidden"||t&&d.depth!==t)continue;const h=(l==null?void 0:l.getIndex(g))??-1;h<0||u.push({key:g,nodeIndex:h})}if(u.length!==0){i.reallocate(u.length);for(let g=0;g<u.length;g++){const{key:d,nodeIndex:h}=u[g],c=r[d],f=this.displayedNodeLabels.has(d),b=f?this.measureNodeLabel(c):{width:0,height:0,textHeight:0},v=b.textHeight;let _=b.width,m=b.height,E=0,p=0;if(f&&c.labelAttachment&&o){const z=o.getEntry(d,c.labelAttachment);if(z){const B=c.labelAttachmentPlacement||"below";if(B==="below"||B==="above"){const G=z.height/s;m+=G+de,_=Math.max(_,z.width/s),p=B==="below"?(G+de)/2:-(G+de)/2}else{const G=z.width/s;_+=G+de,m=Math.max(m,z.height/s),E=B==="right"?(G+de)/2:-(G+de)/2}}}const x=c.backdropColor?ve(c.backdropColor):[255,255,255,255],T=c.backdropShadowColor?ve(c.backdropShadowColor):[0,0,0,128],y=x.map(z=>z/255),R=T.map(z=>z/255),A=c.backdropShadowBlur??12,S=c.backdropPadding??6,L=(c.backdropBorderColor?ve(c.backdropBorderColor):[0,0,0,0]).map(z=>z/255),D=c.backdropBorderWidth??0,F=c.backdropCornerRadius??0,P=c.backdropLabelPadding??-1,w=P<0?S:P,M=Zs[c.backdropArea??"both"]??0,N={key:d,nodeIndex:h,label:c.label,labelWidth:_,labelHeight:m,textHeight:v,type:"default",position:c.labelPosition||oe.labelPosition,labelAngle:c.labelAngle??0,backdropColor:y,backdropShadowColor:R,backdropShadowBlur:A,backdropPadding:S,backdropBorderColor:L,backdropBorderWidth:D,backdropCornerRadius:F,backdropLabelPadding:w,backdropArea:M,labelBoxOffset:[E,p]};i.processBackdrop(g,N)}i.invalidateBuffers(),i.render(e)}}renderLabelBackgrounds(e,t){const{labelBackgroundProgram:i,nodeDataCache:r,nodeDataTexture:n}=this.internals,o=k.nodeLabel.writesPickingThisFrame(this.internals),s=[];for(const l of this.displayedNodeLabels){const u=r[l];!u||u.visibility==="hidden"||t&&u.labelDepth!==t||!o&&!u.labelBackgroundColor||s.push(l)}if(s.length!==0){i.reallocate(s.length);for(let l=0;l<s.length;l++){const u=s[l],g=r[u],d=(n==null?void 0:n.getIndex(u))??-1;if(d<0)continue;const{width:h,height:c,textHeight:f}=this.measureNodeLabel(g),b=ue(this.internals.pickingState,"nodeLabel",u)||ue(this.internals.pickingState,"node",u),v=g.labelBackgroundColor?H(g.labelBackgroundColor):H("transparent"),_={nodeIndex:d,id:Ge(b),color:v,labelWidth:h,labelHeight:c,textHeight:f,positionMode:pe[g.labelPosition||oe.labelPosition]??0,labelAngle:g.labelAngle??0,padding:g.labelBackgroundPadding??Bt};i.processLabelBackground(l,_)}i.invalidateBuffers(),i.render(e)}}cacheAttachments(e){const{attachmentManager:t,pixelRatio:i,nodeDataCache:r,nodesWithBackdrop:n,graph:o}=this.internals;if(t){for(const s of n){if(!this.displayedNodeLabels.has(s))continue;const l=r[s];if(!l||l.visibility==="hidden"||e&&l.depth!==e||!l.labelAttachment)continue;const u=o.getNodeAttributes(s),{width:g,height:d}=this.measureNodeLabel(l),h={node:s,attributes:u,pixelRatio:i,labelWidth:g,labelHeight:d};t.renderAttachment(s,l.labelAttachment,h)}t.regenerateAtlas()}}renderAttachments(e,t){const{attachmentManager:i,attachmentProgram:r,nodeDataCache:n,nodesWithBackdrop:o,pixelRatio:s,nodeDataTexture:l}=this.internals;if(!i||!r)return;let u=0;r.reallocateAttachments(o.size);for(const g of o){if(!this.displayedNodeLabels.has(g))continue;const d=n[g];if(!d||d.visibility==="hidden"||t&&d.labelDepth!==t||!d.labelAttachment)continue;const h=i.getEntry(g,d.labelAttachment);if(!h)continue;const c=(l==null?void 0:l.getIndex(g))??-1;if(c<0)continue;const{width:f,height:b,textHeight:v}=this.measureNodeLabel(d),_=Fn[d.labelAttachmentPlacement||"below"]??0;r.processAttachment(u,{nodeIndex:c,atlasX:h.x,atlasY:h.y,atlasW:h.width,atlasH:h.height,attachWidth:h.width/s,attachHeight:h.height/s,positionMode:pe[d.labelPosition||oe.labelPosition]??0,attachmentPlacement:_,labelWidth:f,labelHeight:b,textHeight:v,labelAngle:d.labelAngle??0}),u++}u!==0&&(r.reallocateAttachments(u),i.bindTexture(ia),r.invalidateBuffers(),r.render(e))}computeDisplayedEdgeLabels(){const{graph:e,stateManager:t,settings:i,edgesWithForcedLabels:r}=this.internals,n=t.getHighlightedNodes(),o=i.edgeLabelAnchors==="allNodes"?new Set(this.edgeAnchorGrid.getLabelsToDisplay(this.internals.getCameraState().ratio,i.labelDensity)):this.displayedNodeLabels,s=t.hovered,l=Ks({graph:e,hoveredNode:(s==null?void 0:s.kind)==="node"?s.key:null,displayedNodeLabels:o,highlightedNodes:n});Zt(l,r),this.edgeLabelCandidates=l,this.displayedEdgeLabels=new Set}filterEdgeLabelsForDepth(e){const{graph:t,nodeDataCache:i,edgeDataCache:r}=this.internals,n=[],o=new Set;for(let s=0,l=this.edgeLabelCandidates.length;s<l;s++){const u=this.edgeLabelCandidates[s];if(o.has(u))continue;o.add(u);const g=t.extremities(u),d=i[g[0]],h=i[g[1]],c=r[u];!c||!d||!h||c.visibility==="hidden"||c.labelVisibility==="hidden"||d.visibility==="hidden"||h.visibility==="hidden"||e&&c.labelDepth!==e||c.label&&n.push(u)}return n}renderEdgeLabels(e,t){var d,h;const{graph:i,nodeDataCache:r,edgeDataCache:n,primitives:o,edgeLabelProgram:s,nodeDataTexture:l,edgeDataTexture:u}=this.internals;if(!s)return;const g=this.filterEdgeLabelsForDepth(t);for(const c of g)this.displayedEdgeLabels.add(c);if(g.length!==0){if(this.labelsDirty){let c=0;for(const _ of g)c+=n[_].label.length;s.reallocate(c);const f=((h=(d=o==null?void 0:o.edges)==null?void 0:d.label)==null?void 0:h.margin)??5,b="over";let v=0;for(const _ of g){const m=i.extremities(_),E=m[0],p=m[1],x=r[E],T=r[p],y=n[_],R=l.getIndex(E),A=l.getIndex(p),S=u.getIndex(_),C={text:y.label,x:(x.x+T.x)/2,y:(x.y+T.y)/2,size:$r,color:y.labelColor,nodeSize:0,nodeIndex:-1,margin:f,position:y.labelPosition??b,hidden:!1,forceLabel:te(y),type:"default",zIndex:y.zIndex??0,parentType:"edge",parentKey:_,fontKey:"",labelAngle:0,sourceX:x.x,sourceY:x.y,targetX:T.x,targetY:T.y,sourceSize:x.size,targetSize:T.size,sourceShape:x.shape||"circle",targetShape:T.shape||"circle",edgeSize:y.size,offset:0,edgeAttributes:y,sourceNodeIndex:R,targetNodeIndex:A,edgeIndex:S},L=s.processEdgeLabel(_,v,C);v+=L}s.invalidateBuffers()}s.render(e)}}renderEdgeLabelBackgrounds(e,t){var c,f;const{edgeLabelBackgroundProgram:i,edgeLabelProgram:r,edgeDataCache:n,primitives:o,edgeDataTexture:s}=this.internals;if(!s||!r||!i)return;const l=k.edgeLabel.writesPickingThisFrame(this.internals),u=((f=(c=o==null?void 0:o.edges)==null?void 0:c.label)==null?void 0:f.margin)??5,g="over",d=this.filterEdgeLabelsForDepth(t),h=[];for(const b of d)!l&&!n[b].labelBackgroundColor||h.push(b);if(h.length!==0){i.reallocate(h.length);for(let b=0;b<h.length;b++){const v=h[b],_=n[v],m=_.label,E=r.measureLabelAtlasWidth(m),p=_.labelPosition??g,x=typeof p=="string"?Qs[p]??0:0,T=ue(this.internals.pickingState,"edgeLabel",v)||ue(this.internals.pickingState,"edge",v),y=_.labelBackgroundColor?H(_.labelBackgroundColor):H("transparent"),R={edgeIndex:s.getIndex(v),baseFontSize:$r,totalTextWidth:E,positionMode:x,margin:u,padding:_.labelBackgroundPadding??Js,color:y,id:Ge(T),edgeAttributes:_};i.processEdgeLabelBackground(b,v,R)}i.invalidateBuffers(),i.render(e)}}}class tl{constructor(e,t,i,r){this.scheduleRefresh=e,this.customNodeStateDefaults=t,this.customEdgeStateDefaults=i,this.customGraphStateDefaults=r,this.nodeStates=new Map,this.edgeStates=new Map,this.hovered=null,this.dirtyNodes=new Set,this.dirtyEdges=new Set,this.graphStateChanged=!1,this.graphStateFlagsDirty=!1,this.graphState=xr(r)}getNodeState(e){e=""+e;let t=this.nodeStates.get(e);return t||(t=ds(this.customNodeStateDefaults),this.nodeStates.set(e,t)),t}getEdgeState(e){e=""+e;let t=this.edgeStates.get(e);return t||(t=cs(this.customEdgeStateDefaults),this.edgeStates.set(e,t)),t}getGraphState(){return this.flushGraphStateFlags(),this.graphState}getHighlightedNodes(){const e=new Set;for(const[t,i]of this.nodeStates)i.isHighlighted&&e.add(t);return e}setNodeState(e,t){e=""+e;const i=this.getNodeState(e);if(!ye(i,t))return;const r={...i,...t};this.nodeStates.set(e,r),this.dirtyNodes.add(e),this.updateHoveredNodeTracking(e,i,r),this.graphStateFlagsDirty=!0,this.scheduleRefresh()}setEdgeState(e,t){e=""+e;const i=this.getEdgeState(e);if(!ye(i,t))return;const r={...i,...t};this.edgeStates.set(e,r),this.dirtyEdges.add(e),this.updateHoveredEdgeTracking(e,i,r),this.graphStateFlagsDirty=!0,this.scheduleRefresh()}setGraphState(e){if(!ye(this.graphState,e))return;const t={...this.graphState,...e};t.isIdle=!t.isPanning&&!t.isZooming&&!t.isDragging,this.graphState=t,this.graphStateChanged=!0,this.scheduleRefresh()}setNodesState(e,t){let i=!1;for(let r of e){r=""+r;const n=this.getNodeState(r);if(!ye(n,t))continue;const o={...n,...t};this.nodeStates.set(r,o),this.dirtyNodes.add(r),this.updateHoveredNodeTracking(r,n,o),i=!0}i&&(this.graphStateFlagsDirty=!0,this.scheduleRefresh())}setEdgesState(e,t){let i=!1;for(let r of e){r=""+r;const n=this.getEdgeState(r);if(!ye(n,t))continue;const o={...n,...t};this.edgeStates.set(r,o),this.dirtyEdges.add(r),this.updateHoveredEdgeTracking(r,n,o),i=!0}i&&(this.graphStateFlagsDirty=!0,this.scheduleRefresh())}removeNode(e){this.nodeStates.delete(e),this.dirtyNodes.delete(e),this.clearHoveredFor("node",e)}removeEdge(e){this.edgeStates.delete(e),this.dirtyEdges.delete(e),this.clearHoveredFor("edge",e)}pruneNodes(e){for(const t of this.nodeStates.keys())e(t)||this.removeNode(t)}pruneEdges(e){for(const t of this.edgeStates.keys())e(t)||this.removeEdge(t)}clearNodes(){this.nodeStates.clear(),this.dirtyNodes.clear(),this.clearHoveredForKinds(Ea("node"))}clearEdges(){this.edgeStates.clear(),this.dirtyEdges.clear(),this.clearHoveredForKinds(Ea("edge"))}resetGraphState(){this.graphState=xr(this.customGraphStateDefaults),this.graphStateChanged=!1,this.graphStateFlagsDirty=!1}clearDirtyTracking(){this.dirtyNodes.clear(),this.dirtyEdges.clear(),this.graphStateChanged=!1}setHovered(e){this.hovered=e}clearHoveredFor(e,t){this.hovered&&this.hovered.key===t&&Ea(e).includes(this.hovered.kind)&&(this.hovered=null)}clearHoveredForKinds(e){this.hovered&&e.includes(this.hovered.kind)&&(this.hovered=null)}updateGraphStateFromNodes(){var n;let e=!1,t=!1,i=!1;for(const[,o]of this.nodeStates)if(o.isHovered&&(e=!0),o.isHighlighted&&(t=!0),o.isDragged&&(i=!0),e&&t&&i)break;!e&&((n=this.hovered)==null?void 0:n.kind)==="edge"&&(e=!0);const r=!this.graphState.isPanning&&!this.graphState.isZooming&&!i;(this.graphState.hasHovered!==e||this.graphState.hasHighlighted!==t||this.graphState.isDragging!==i||this.graphState.isIdle!==r)&&(this.graphStateChanged=!0),this.graphState={...this.graphState,hasHovered:e,hasHighlighted:t,isDragging:i,isIdle:r}}updateGraphStateFromEdges(){var t;let e=((t=this.hovered)==null?void 0:t.kind)==="node";if(!e){for(const[,i]of this.edgeStates)if(i.isHovered){e=!0;break}}this.graphState.hasHovered!==e&&(this.graphStateChanged=!0),this.graphState={...this.graphState,hasHovered:e}}flushGraphStateFlags(){this.graphStateFlagsDirty&&(this.updateGraphStateFromNodes(),this.updateGraphStateFromEdges(),this.graphStateFlagsDirty=!1)}updateHoveredNodeTracking(e,t,i){var r,n;if(t.isHovered!==i.isHovered)if(i.isHovered){if(((r=this.hovered)==null?void 0:r.kind)==="node"&&this.hovered.key!==e){const o=this.hovered.key,s=this.getNodeState(o);this.nodeStates.set(o,{...s,isHovered:!1}),this.dirtyNodes.add(o)}this.hovered={kind:"node",key:e}}else((n=this.hovered)==null?void 0:n.kind)==="node"&&this.hovered.key===e&&(this.hovered=null)}updateHoveredEdgeTracking(e,t,i){var r,n;if(t.isHovered!==i.isHovered)if(i.isHovered){if(((r=this.hovered)==null?void 0:r.kind)==="edge"&&this.hovered.key!==e){const o=this.hovered.key,s=this.getEdgeState(o);this.edgeStates.set(o,{...s,isHovered:!1}),this.dirtyEdges.add(o)}this.hovered={kind:"edge",key:e}}else((n=this.hovered)==null?void 0:n.kind)==="edge"&&this.hovered.key===e&&(this.hovered=null)}}const Hr=1,Vr=2,Xr=3,qr=4;class jr extends wt{constructor(e,t,i={}){var A;super(),this.nodeReducer=null,this.edgeReducer=null,this.stageCanvas=null,this.mouseLayer=null,this.gestureHint=null,this.extraElements={},this.webGLContext=null,this.pickingFrameBuffer=null,this.pickingTexture=null,this.pickingDepthBuffer=null,this.activeListeners={},this.nodeVariableEntries=[],this.edgeVariableEntries=[],this.edgePathsByName=new Map,this.nodeProgramIndex={},this.edgeProgramIndex={},this.edgeTextureIndexCache={},this.nodeGraphCoords={},this.nodeExtent={x:[0,1],y:[0,1]},this.matrix=O(),this.invMatrix=O(),this.correctionRatio=1,this.frameId=0,this.customBBox=null,this.gpuTimerExt=void 0,this.activeGpuTimerQuery=null,this.pendingGpuTimerQueries=[],this.normalizationFunction=ft({x:[0,1],y:[0,1]}),this.graphToViewportRatio=1,this.pickingState=Fs(),this.prevNodeVisibilities={},this.width=0,this.height=0,this.autoRescaleFrozen=!1,this.stylesDeclaration=null,this.resolvedStageStyle={},this.renderFrame=null,this.contextLost=!1,this.pendingProcess="full",this.needToRefreshState=!1,this.checkEdgesEventsFrame=null,this.edgeStyleAnalysis={dependency:"static",xAttribute:null,yAttribute:null},this.depthLayers=[...Lt],this.customLayerPrograms=new Map,this.nodeShapeSlug=null,this.sdfAtlas=null,this.depthRanges={nodes:{},edges:{}},this.nodeBaseDepth={},this.edgeBaseDepth={};const{primitives:r,styles:n,settings:o={},nodeReducer:s,edgeReducer:l,customNodeState:u,customEdgeState:g,customGraphState:d}=i;this.stateManager=new tl(()=>this.scheduleStateRefresh(),u,g,d);const h=r??Ar;this.stylesDeclaration=n?{nodes:n.nodes??Xe.nodes,edges:n.edges??Xe.edges,stage:n.stage}:Xe,this.nodeReducer=s??null,this.edgeReducer=l??null;const c=ba(this.stylesDeclaration.nodes);this.nodeReducer&&(c.dependency="graph-state"),this.edgeStyleAnalysis=ba(this.stylesDeclaration.edges),this.edgeReducer&&(this.edgeStyleAnalysis.dependency="graph-state"),this.stylesDeclaration.stage&&(this.resolvedStageStyle=Rr(this.stylesDeclaration.stage,this.stateManager.graphState));const f=mr(o);if(Pt(f),f.enableNodeDrag){const{xAttribute:S,yAttribute:C}=c;if((!S||!C)&&!f.dragPositionToAttributes)throw new Error('Sigma: `enableNodeDrag` is true but position attribute names could not be inferred from styles. Either use attribute bindings for x/y in your node styles (e.g. `x: { attribute: "x" }`), or provide a `dragPositionToAttributes` setting.')}if(si(e),!(t instanceof HTMLElement))throw new Error("Sigma: container should be an html element.");this.container=t,this.edgeGroups=new Ls(e,(S,C)=>{for(let L=0;L<S.length;L++){const D=this.stateManager.getEdgeState(S[L]);D.parallelIndex=L,D.parallelCount=C}});const b=new Cs(e,this.viewportToGraph.bind(this),this.setNodesState.bind(this),(S,C)=>this.emit(S,C));if(this.depthLayers=h.depthLayers??[...Lt],!(n!=null&&n.nodes)&&!Dt.every(S=>this.depthLayers.includes(S)))throw new Error(`Sigma: depthLayers must include ${Dt.join(", ")} for the built-in node styles.`);if(!(n!=null&&n.edges)&&!Ct.every(S=>this.depthLayers.includes(S)))throw new Error(`Sigma: depthLayers must include ${Ct.join(", ")} for the built-in edge styles.`);this.itemBuckets={nodes:new da(this.depthLayers),edges:new da(this.depthLayers)},this.initWebGLContext(),this.mouseLayer=Se("div",{position:"absolute",touchAction:"none",userSelect:"none"},{class:"sigma-mouse"}),this.container.appendChild(this.mouseLayer),this.resolvedStageStyle.background&&(this.container.style.backgroundColor=this.resolvedStageStyle.background),this.resolvedStageStyle.cursor&&(this.container.style.cursor=this.resolvedStageStyle.cursor);const v=new ar(this.webGLContext),_=new ca(this.webGLContext,{channels:1}),m=new ir(this.webGLContext),E=new ca(this.webGLContext,{channels:4}),p=this.webGLContext,x=new Ys({gl:p,getFrameBuffer:()=>this.pickingFrameBuffer,getPixelRatio:()=>this.internals.pixelRatio,getDownSizingRatio:()=>this.internals.settings.pickingDownSizingRatio,isRenderPending:()=>this.renderFrame!==null,onIndex:(S,C)=>Wr(this.internals,this.pickingState.lookup[S]??null,C)}),T=this.initPrograms(h,f),y=(A=h==null?void 0:h.nodes)==null?void 0:A.labelAttachments;let R=null;y&&Object.keys(y).length>0&&(R=new ui(p,y,()=>this.scheduleRender())),this.internals={nodeDataCache:{},edgeDataCache:{},nodesWithForcedLabels:new Set,nodesWithBackdrop:new Set,edgesWithForcedLabels:new Set,settings:f,primitives:h,pixelRatio:ut(),graph:e,stateManager:this.stateManager,dragManager:b,hoverResolver:x,nodeStyleAnalysis:c,pickingState:this.pickingState,...T,attachmentManager:R,nodeDataTexture:v,nodeFrameTexture:_,edgeDataTexture:m,edgeFrameTexture:E,getDimensions:()=>this.getDimensions(),getGraphDimensions:()=>this.getGraphDimensions(),getStagePadding:()=>this.getStagePadding(),getCameraState:()=>this.camera.getState(),getHitAtPosition:S=>this.getHitAtPosition(S),setNodeState:(S,C)=>this.setNodeState(S,C),setEdgeState:(S,C)=>this.setEdgeState(S,C),updateContainerCursor:()=>this.updateContainerCursor(),scheduleRefresh:()=>this.scheduleRefresh(),viewportToFramedGraph:S=>this.viewportToFramedGraph(S),viewportToGraph:S=>this.viewportToGraph(S),framedGraphToViewport:S=>this.framedGraphToViewport(S),scaleSize:S=>this.scaleSize(S),emit:(S,C)=>this.emit(S,C)},this.labelRenderer=new el(this.internals),this.resize(),this.initializeWebGLLabels(),this.camera=new Ye,this.bindCameraHandlers(),this.mouseCaptor=new Fr(this.mouseLayer,this),this.mouseCaptor.setSettings(this.internals.settings),this.touchCaptor=new Pr(this.mouseLayer,this),this.touchCaptor.setSettings(this.internals.settings),this.bindEventHandlers(),this.bindGraphHandlers(),this.handleSettingsUpdate(),this.refresh()}initializeWebGLLabels(){this.sdfAtlas=new re,this.sdfAtlas.registerFont({family:"sans-serif",weight:"normal",style:"normal"})}resetWebGLTexture(){const e=this.webGLContext;if(!this.pickingFrameBuffer)return this;const t=Math.ceil(this.width*this.internals.pixelRatio/this.internals.settings.pickingDownSizingRatio),i=Math.ceil(this.height*this.internals.pixelRatio/this.internals.settings.pickingDownSizingRatio);e.bindFramebuffer(e.FRAMEBUFFER,this.pickingFrameBuffer),this.pickingTexture&&e.deleteTexture(this.pickingTexture);const r=e.createTexture();r&&(e.bindTexture(e.TEXTURE_2D,r),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,t,i,0,e.RGBA,e.UNSIGNED_BYTE,null),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,r,0),this.pickingTexture=r),this.pickingDepthBuffer&&e.deleteRenderbuffer(this.pickingDepthBuffer);const n=e.createRenderbuffer();return n&&(e.bindRenderbuffer(e.RENDERBUFFER,n),e.renderbufferStorage(e.RENDERBUFFER,e.DEPTH_COMPONENT16,t,i),e.framebufferRenderbuffer(e.FRAMEBUFFER,e.DEPTH_ATTACHMENT,e.RENDERBUFFER,n),this.pickingDepthBuffer=n),e.bindFramebuffer(e.FRAMEBUFFER,null),this}bindCameraHandlers(){return this.activeListeners.camera=()=>{this.refreshMatrices(),this.labelRenderer.labelsDirty=!0,this.scheduleRender()},this.activeListeners.cameraAnimationStart=({from:e,to:t})=>{e.ratio!==t.ratio&&this.stateManager.setGraphState({isZooming:!0})},this.activeListeners.cameraAnimationEnd=()=>{this.stateManager.setGraphState({isZooming:!1})},this.camera.on("updated",this.activeListeners.camera),this.camera.on("animationStart",this.activeListeners.cameraAnimationStart),this.camera.on("animationEnd",this.activeListeners.cameraAnimationEnd),this}unbindCameraHandlers(){return this.camera.removeListener("updated",this.activeListeners.camera),this.camera.removeListener("animationStart",this.activeListeners.cameraAnimationStart),this.camera.removeListener("animationEnd",this.activeListeners.cameraAnimationEnd),this}getHitAtPosition(e){if(this.contextLost)return null;const t=this.webGLContext;t.bindFramebuffer(t.FRAMEBUFFER,this.pickingFrameBuffer);const i=ai(t,this.pickingFrameBuffer,e.x,e.y,this.internals.pixelRatio,this.internals.settings.pickingDownSizingRatio),r=Kt(...i);return this.pickingState.lookup[r]??null}bindEventHandlers(){return ks(this.internals,this.mouseCaptor,this.touchCaptor,this.activeListeners),this}bindGraphHandlers(){return Gs({graph:this.internals.graph,edgeGroups:this.edgeGroups,addNode:this.addNode.bind(this),updateNode:this.updateNode.bind(this),removeNode:this.removeNode.bind(this),addEdge:this.addEdge.bind(this),updateEdge:this.updateEdge.bind(this),removeEdge:this.removeEdge.bind(this),clearEdgeState:this.clearEdgeState.bind(this),clearNodeState:this.clearNodeState.bind(this),clearEdgeIndices:this.clearEdgeIndices.bind(this),clearNodeIndices:this.clearNodeIndices.bind(this),refresh:this.refresh.bind(this)},this.activeListeners),this}unbindGraphHandlers(){Ws(this.internals.graph,this.activeListeners)}getNodeShapeId(e){return this.internals.nodeShapeMap&&this.internals.nodeGlobalShapeIds&&e.shape&&e.shape in this.internals.nodeShapeMap?this.internals.nodeGlobalShapeIds[this.internals.nodeShapeMap[e.shape]]:Me(e.shape||"circle")}processNodes(){const e=this.internals.graph,t=this.internals.settings,i=this.getDimensions(),{autoRescale:r,autoRescaleContent:n}=t;let o=this.nodeExtent;if(r===!1){const{width:f,height:b}=i;o={x:[-f/2,f/2],y:[-b/2,b/2]}}else(r!=="once"||!this.autoRescaleFrozen)&&(o=this.computeNodeExtent(),n!=="positions"&&!this.customBBox&&(o=$s({extent:o,coords:this.nodeGraphCoords,nodeData:this.internals.nodeDataCache,dimensions:i,stagePadding:this.getStagePadding(),zoomToSizeRatioFunction:t.zoomToSizeRatioFunction,itemSizesReference:t.itemSizesReference,fitLabels:n==="labels",nodeLabelBox:(f,b)=>this.labelRenderer.nodeLabelBox(f,b)})),r==="once"&&(this.autoRescaleFrozen=!0));this.nodeExtent=o,this.normalizationFunction=ft(this.customBBox||this.nodeExtent);const s=new Ye,l=ee(s.getState(),i,this.getGraphDimensions(),this.getStagePadding());this.labelRenderer.labelGrid.resizeAndClear(i,t.labelGridCellSize),this.labelRenderer.edgeAnchorGrid.resizeAndClear(i,t.labelGridCellSize);const u=t.renderEdgeLabels&&t.edgeLabelAnchors==="allNodes";let g=!1;Sa(this.pickingState,"node");const d=e.nodes();for(let f=0,b=d.length;f<b;f++){const v=d[f],_=this.internals.nodeDataCache[v],m=this.nodeGraphCoords[v];_.x=m.x,_.y=m.y,this.normalizationFunction.applyTo(_),_.visibility!==this.prevNodeVisibilities[v]&&(g=!0),typeof _.label=="string"&&_.visibility!=="hidden"&&_.labelVisibility!=="hidden"&&this.labelRenderer.labelGrid.add(v,_.size,this.framedGraphToViewport(_,{matrix:l})),u&&_.visibility!=="hidden"&&this.labelRenderer.edgeAnchorGrid.add(v,_.size,this.framedGraphToViewport(_,{matrix:l}))}this.labelRenderer.labelGrid.organize(),this.labelRenderer.edgeAnchorGrid.organize(),this.nodeProgram.reallocate(d.length);let h=0;this.depthRanges.nodes={},this.nodeBaseDepth={};const c=this.internals.nodeDataCache;for(const f of this.depthLayers){const b=this.itemBuckets.nodes.getSorted(f,v=>c[v].zIndex);if(b.length!==0){this.depthRanges.nodes[f]=[{offset:h,count:b.length}];for(const v of b)this.nodeBaseDepth[v]=f,this.nodeProgram.allocateNode(v),Nr(this.pickingState,"node",v),this.addNodeToProgram(v,h++)}}this.nodeProgram.invalidateBuffers();for(let f=0,b=d.length;f<b;f++)this.prevNodeVisibilities[d[f]]=this.internals.nodeDataCache[d[f]].visibility;this.labelRenderer.processWebGLLabels(d);for(const{program:f}of this.customLayerPrograms.values())f.cacheData&&f.cacheData();return g}processEdges(){const t=this.internals.graph.edges();this.edgeProgram.reallocate(t.length);let i=0;Sa(this.pickingState,"edge"),this.depthRanges.edges={},this.edgeBaseDepth={};const r=this.internals.edgeDataCache;for(const n of this.depthLayers){const o=this.itemBuckets.edges.getSorted(n,s=>r[s].zIndex);if(o.length!==0){this.depthRanges.edges[n]=[{offset:i,count:o.length}];for(const s of o)this.edgeBaseDepth[s]=n,Nr(this.pickingState,"edge",s),this.addEdgeToProgram(s,i++)}}this.edgeProgram.invalidateBuffers()}updateNodeDepthRanges(e,t,i){const r=this.nodeProgramIndex[e];r!==void 0&&(Jt(this.depthRanges.nodes,t,r),ea(this.depthRanges.nodes,i,r))}updateEdgeDepthRanges(e,t,i){const r=this.edgeProgramIndex[e];r!==void 0&&(Jt(this.depthRanges.edges,t,r),ea(this.depthRanges.edges,i,r))}handleSettingsUpdate(){const e=this.internals.settings;return this.camera.minRatio=e.minCameraRatio,this.camera.maxRatio=e.maxCameraRatio,this.camera.enabledZooming=e.enableCameraZooming,this.camera.enabledPanning=e.enableCameraPanning,this.camera.enabledRotation=e.enableCameraRotation,e.cameraPanBoundaries?this.camera.constrainState=t=>this.cleanCameraState(t,e.cameraPanBoundaries&&typeof e.cameraPanBoundaries=="object"?e.cameraPanBoundaries:{}):this.camera.constrainState=null,this.camera.setState(this.camera.getState()),this.mouseLayer.style.touchAction=e.gestureTarget==="graph"?"none":e.gestureTarget==="shared"?"pan-x pan-y":"auto",e.gestureTarget!=="shared"&&this.gestureHint&&(this.gestureHint.kill(),this.gestureHint=null),this.mouseCaptor.setSettings(this.internals.settings),this.touchCaptor.setSettings(this.internals.settings),this}cleanCameraState(e,{tolerance:t=0,boundaries:i}={}){const r={...e},{x:[n,o],y:[s,l]}=i||this.nodeExtent,u=[this.graphToViewport({x:n,y:s},{cameraState:e}),this.graphToViewport({x:o,y:s},{cameraState:e}),this.graphToViewport({x:n,y:l},{cameraState:e}),this.graphToViewport({x:o,y:l},{cameraState:e})];let g=1/0,d=-1/0,h=1/0,c=-1/0;u.forEach(({x:p,y:x})=>{g=Math.min(g,p),d=Math.max(d,p),h=Math.min(h,x),c=Math.max(c,x)});const f=d-g,b=c-h,{width:v,height:_}=this.getDimensions();let m=0,E=0;if(f>=v?d<v-t?m=d-(v-t):g>t&&(m=g-t):d>v+t?m=d-(v+t):g<-t&&(m=g+t),b>=_?c<_-t?E=c-(_-t):h>t&&(E=h-t):c>_+t?E=c-(_+t):h<-t&&(E=h+t),m||E){const p=this.viewportToFramedGraph({x:0,y:0},{cameraState:e}),x=this.viewportToFramedGraph({x:m,y:E},{cameraState:e});m=x.x-p.x,E=x.y-p.y,r.x+=m,r.y+=E}return r}refreshMatrices(){const e=this.camera.getState(),t=this.getDimensions(),i=this.getGraphDimensions(),r=this.getStagePadding();this.matrix=ee(e,t,i,r),this.invMatrix=ee(e,t,i,r,!0),this.correctionRatio=Za(this.matrix,e,t),this.graphToViewportRatio=this.getGraphToViewportRatio()}processData(){var i;this.emit("beforeProcess"),(i=this.internals.attachmentManager)==null||i.clear();const e={...this.pickingState.idsByKind},t=this.processNodes();(this.pendingProcess==="full"||t)&&this.processEdges(),Ps(this.pickingState,this.internals),ws(e,this.pickingState.idsByKind)||this.internals.hoverResolver.invalidate(),this.pendingProcess="none",this.emit("afterProcess")}render(){if(this.contextLost)return this;this.emit("beforeRender");const e=()=>(this.internals.hoverResolver.frameRendered(),this.emit("afterRender"),this);if(this.renderFrame&&(cancelAnimationFrame(this.renderFrame),this.renderFrame=null),this.resize(),(this.pendingProcess!=="none"||this.needToRefreshState)&&(this.labelRenderer.labelsDirty=!0),this.pendingProcess!=="none"&&this.processData(),this.needToRefreshState&&this.refreshState(),this.needToRefreshState=!1,this.stateManager.clearDirtyTracking(),this.pendingProcess!=="none"&&this.processData(),this.clear(),this.resetWebGLTexture(),!this.internals.graph.order)return e();const t=this.mouseCaptor,i=this.camera.isAnimating()||t.isMoving||t.draggedEvents||t.currentWheelDirection;if(this.refreshMatrices(),this.frameId++,this.internals.settings.renderEdgeLabels&&!this.internals.edgeLabelProgram){const{labelProgram:d,labelBackgroundProgram:h}=this.createEdgeLabelPrograms();this.internals.edgeLabelProgram=d,this.internals.edgeLabelBackgroundProgram=h}const r=this.internals.settings.DEBUG_logRenderStats?this.getDebugPrograms():null;r&&r.forEach(d=>d.resetDebugStats()),this.internals.settings.DEBUG_gpuTimerQueries&&this.beginGpuTimerQuery(),this.internals.nodeFrameTexture.ensureCapacity(this.internals.nodeDataTexture.getCapacity()),this.internals.edgeFrameTexture.ensureCapacity(this.internals.edgeDataTexture.getCapacity());const n=this.getRenderParams(),o=k.edge.writesPickingThisFrame(this.internals)?n:{...n,pickingFrameBuffer:null};this.labelRenderer.resetFrame();const s=this.webGLContext,l=Math.ceil(this.width*this.internals.pixelRatio/this.internals.settings.pickingDownSizingRatio),u=Math.ceil(this.height*this.internals.pixelRatio/this.internals.settings.pickingDownSizingRatio);if(s.bindFramebuffer(s.FRAMEBUFFER,this.pickingFrameBuffer),s.viewport(0,0,l,u),s.clear(s.COLOR_BUFFER_BIT|s.DEPTH_BUFFER_BIT),s.bindFramebuffer(s.FRAMEBUFFER,null),s.viewport(0,0,this.width*this.internals.pixelRatio,this.height*this.internals.pixelRatio),s.clear(s.COLOR_BUFFER_BIT|s.DEPTH_BUFFER_BIT),this.internals.nodeDataTexture.upload(),this.internals.edgeDataTexture.upload(),this.nodeProgram.uploadLayerTexture(),this.edgeProgram.uploadAttributeTexture(),this.emit("afterTexturesUpload"),this.internals.nodeDataTexture.bind(Xr),this.internals.edgeDataTexture.bind(qr),this.edgeFramePass.run(n,this.internals.edgeFrameTexture,this.internals.edgeDataTexture.getHighWaterMark(),this.edgeProgram.getAttributeTexture(),this.nodeProgram.getAttributeTexture()),this.internals.edgeFrameTexture.bind(Hr),this.internals.settings.renderLabels){this.labelRenderer.computeDisplayedNodeLabels();const{data:d,count:h}=this.labelRenderer.buildFramePassPoints();this.nodeFramePass.run(d,h,this.internals.nodeFrameTexture,n,this.nodeProgram.getAttributeTexture()),this.internals.nodeFrameTexture.bind(Vr)}this.internals.settings.renderEdgeLabels&&this.labelRenderer.computeDisplayedEdgeLabels();for(const{program:d}of this.customLayerPrograms.values())d.preRender&&d.preRender(n);s.bindFramebuffer(s.FRAMEBUFFER,null),s.viewport(0,0,this.width*this.internals.pixelRatio,this.height*this.internals.pixelRatio),s.blendFunc(s.ONE,s.ONE_MINUS_SRC_ALPHA);const g=!this.internals.settings.hideLabelsOnMove||!i;for(const d of this.depthLayers){for(const f of this.customLayerPrograms.values())f.depth===d&&f.program.render(n);const h=this.depthRanges.edges[d];if(h&&(!this.internals.settings.hideEdgesOnMove||!i))for(const{offset:f,count:b}of h)b>0&&this.edgeProgram.render(o,f,b);this.internals.settings.renderEdgeLabels&&g&&(this.labelRenderer.renderEdgeLabelBackgrounds(k.edgeLabel.writesPickingThisFrame(this.internals)?n:{...n,pickingFrameBuffer:null},d),this.labelRenderer.renderEdgeLabels(n,d)),this.labelRenderer.cacheAttachments(d),this.labelRenderer.renderBackdrops({...n,pickingFrameBuffer:null},d);const c=this.depthRanges.nodes[d];if(c)for(const{offset:f,count:b}of c)b>0&&this.nodeProgram.render(n,f,b);g&&(this.labelRenderer.renderAttachments({...n,pickingFrameBuffer:null},d),this.labelRenderer.renderLabelBackgrounds(k.nodeLabel.writesPickingThisFrame(this.internals)?n:{...n,pickingFrameBuffer:null},d),this.internals.settings.renderLabels&&this.labelRenderer.renderWebGLLabels(n,d))}return this.labelRenderer.labelsDirty=!1,this.internals.settings.DEBUG_displayPickingLayer&&(s.bindFramebuffer(s.READ_FRAMEBUFFER,this.pickingFrameBuffer),s.bindFramebuffer(s.DRAW_FRAMEBUFFER,null),s.blitFramebuffer(0,0,l,u,0,0,this.width*this.internals.pixelRatio,this.height*this.internals.pixelRatio,s.COLOR_BUFFER_BIT,s.NEAREST)),this.internals.settings.DEBUG_gpuTimerQueries&&this.endGpuTimerQuery(),this.pollGpuTimerQueries(),r&&this.logRenderStats(r),e()}getDebugPrograms(){return[this.nodeProgram,this.edgeProgram,this.internals.labelProgram,this.internals.edgeLabelProgram,this.internals.edgeLabelBackgroundProgram,this.internals.backdropProgram,this.internals.labelBackgroundProgram,this.internals.attachmentProgram].filter(e=>e!==null)}logRenderStats(e){const t={};for(const i of e)t[i.constructor.name]={...i.debugStats};console.log(`[sigma] DEBUG_logRenderStats: frame #${this.frameId}`),console.table(t)}getGpuTimerExtension(){return this.gpuTimerExt===void 0&&(this.gpuTimerExt=this.webGLContext.getExtension("EXT_disjoint_timer_query_webgl2"),this.gpuTimerExt||console.warn("Sigma: DEBUG_gpuTimerQueries is enabled, but this browser/driver doesn't support EXT_disjoint_timer_query_webgl2.")),this.gpuTimerExt}beginGpuTimerQuery(){const e=this.getGpuTimerExtension();if(!e)return;const t=this.webGLContext,i=t.createQuery();i&&(t.beginQuery(e.TIME_ELAPSED_EXT,i),this.activeGpuTimerQuery=i)}endGpuTimerQuery(){!this.gpuTimerExt||!this.activeGpuTimerQuery||(this.webGLContext.endQuery(this.gpuTimerExt.TIME_ELAPSED_EXT),this.pendingGpuTimerQueries.push({query:this.activeGpuTimerQuery,frameId:this.frameId}),this.activeGpuTimerQuery=null)}pollGpuTimerQueries(){if(this.pendingGpuTimerQueries.length===0)return;const e=this.webGLContext,t=this.gpuTimerExt;if(!t){this.pendingGpuTimerQueries.forEach(({query:n})=>e.deleteQuery(n)),this.pendingGpuTimerQueries=[];return}const i=e.getParameter(t.GPU_DISJOINT_EXT),r=[];for(const n of this.pendingGpuTimerQueries){if(!e.getQueryParameter(n.query,e.QUERY_RESULT_AVAILABLE)){r.push(n);continue}if(!i){const o=e.getQueryParameter(n.query,e.QUERY_RESULT);console.log(`[sigma] DEBUG_gpuTimerQueries: frame #${n.frameId} GPU time = ${(o/1e6).toFixed(2)}ms`)}e.deleteQuery(n.query)}this.pendingGpuTimerQueries=r}postEvaluateNode(e,t,i){const r=e;r.x===void 0&&(r.x=t.x),r.y===void 0&&(r.y=t.y),e.highlighted=i.isHighlighted;for(let n=0,o=this.nodeVariableEntries.length;n<o;n++){const[s,l]=this.nodeVariableEntries[n];r[s]=r[s]??t[s]??l.default}}addNode(e){const t=this.internals.graph.getNodeAttributes(e),i=this.stateManager.getNodeState(e);let r=this.internals.nodeDataCache[e]||{};if(Ta(this.stylesDeclaration.nodes,t,i,this.stateManager.graphState,this.internals.graph,r),this.postEvaluateNode(r,t,i),this.nodeReducer){const n=this.nodeReducer(e,r,t,i,this.stateManager.graphState,this.internals.graph);r={...r,...n}}if(typeof r.x!="number"||typeof r.y!="number")throw new Error(`Sigma: could not find a valid position (x, y) for node "${e}". Provide coordinates via node attributes, styles, or a nodeReducer.`);this.internals.nodeShapeMap?(!r.shape||!(r.shape in this.internals.nodeShapeMap))&&(r.shape=Object.keys(this.internals.nodeShapeMap)[0]):this.nodeShapeSlug&&(r.shape=this.nodeShapeSlug),this.internals.nodeDataCache[e]=r,this.nodeGraphCoords[e]={x:r.x,y:r.y},le(this.internals.nodesWithForcedLabels,e,te(r)),le(this.internals.nodesWithBackdrop,e,Qt(r)),this.itemBuckets.nodes.set(e,r.depth)}updateNode(e){this.addNode(e);const t=this.internals.nodeDataCache[e];this.normalizationFunction.applyTo(t)}removeNode(e){this.itemBuckets.nodes.remove(e),delete this.internals.nodeDataCache[e],delete this.nodeGraphCoords[e],delete this.nodeProgramIndex[e],this.internals.dragManager.removeNode(e),this.stateManager.removeNode(e),this.internals.nodesWithForcedLabels.delete(e),this.internals.nodesWithBackdrop.delete(e)}postEvaluateEdge(e,t){const i=e;for(let r=0,n=this.edgeVariableEntries.length;r<n;r++){const[o,s]=this.edgeVariableEntries[r];i[o]=i[o]??t[o]??s.default}}applyEdgeSpread(e,t,i){if(i.parallelCount<=1)return;const r=this.internals.graph.source(e),n=this.internals.graph.target(e),o=r===n,s=o?t.selfLoopPath||t.path:t.parallelPath||t.path,l=s?this.edgePathsByName.get(s):void 0;if(!(l!=null&&l.spread))return;const u=t.parallelSpread??.25;let g=l.spread.compute(i.parallelIndex,i.parallelCount,u);!o&&this.internals.graph.isDirected(e)&&r>n&&(g=-g),t[l.spread.variable]=g}addEdge(e){const t=this.internals.graph.getEdgeAttributes(e),i=this.stateManager.getEdgeState(e);let r={};if(va(this.stylesDeclaration.edges,t,i,this.stateManager.graphState,this.internals.graph,r),this.postEvaluateEdge(r,t),this.edgeReducer){const n=this.edgeReducer(e,r,t,i,this.stateManager.graphState,this.internals.graph);r={...r,...n}}this.applyEdgeSpread(e,r,i),this.internals.edgeDataCache[e]=r,le(this.internals.edgesWithForcedLabels,e,te(r)),this.itemBuckets.edges.set(e,r.depth)}updateEdge(e){this.addEdge(e)}removeEdge(e){this.itemBuckets.edges.remove(e),delete this.internals.edgeDataCache[e],delete this.edgeProgramIndex[e],delete this.edgeTextureIndexCache[e],this.internals.edgeDataTexture.free(e),this.stateManager.removeEdge(e),this.internals.edgesWithForcedLabels.delete(e)}clearNodeIndices(){this.labelRenderer.resetLabelGrid(),this.autoRescaleFrozen||(this.nodeExtent={x:[0,1],y:[0,1]}),this.internals.nodeDataCache={},this.nodeGraphCoords={},this.edgeProgramIndex={},this.internals.nodesWithForcedLabels.clear(),this.internals.nodesWithBackdrop.clear(),this.prevNodeVisibilities={},this.itemBuckets.nodes.clearAll(),this.depthRanges.nodes={},this.nodeBaseDepth={}}clearEdgeIndices(){this.internals.edgeDataCache={},this.edgeProgramIndex={},this.edgeTextureIndexCache={},this.internals.edgesWithForcedLabels.clear(),Sa(this.pickingState,"edge"),this.itemBuckets.edges.clearAll(),this.depthRanges.edges={},this.edgeBaseDepth={},this.edgeGroups.clear()}clearIndices(){this.clearEdgeIndices(),this.clearNodeIndices()}clearNodeState(){this.labelRenderer.resetFrame(),this.internals.nodesWithBackdrop.clear(),this.internals.dragManager.clear(),this.autoRescaleFrozen=!1,this.stateManager.clearNodes()}clearEdgeState(){this.labelRenderer.clearEdgeLabels(),this.stateManager.clearEdges()}clearState(){this.clearEdgeState(),this.clearNodeState(),this.stateManager.resetGraphState()}addNodeToProgram(e,t){const i=this.internals.nodeDataCache[e];this.internals.nodeDataTexture.allocate(e),this.internals.nodeDataTexture.updateNode(e,i.x,i.y,i.size,this.getNodeShapeId(i),...ct(i),i.color);const r=this.internals.nodeDataTexture.getIndex(e);this.nodeProgram.process(ue(this.pickingState,"node",e),t,i,r,e),this.nodeProgramIndex[e]=t}addEdgeToProgram(e,t){var f;const i=this.internals.edgeDataCache[e],r=this.internals.graph.source(e),n=this.internals.graph.target(e),o=this.internals.edgeDataTexture.allocate(e);this.edgeTextureIndexCache[e]=o;const s=r===n,l=!s&&(((f=this.stateManager.getEdgeState(e))==null?void 0:f.parallelCount)??1)>1,{pathId:u,headId:g,tailId:d,headLengthRatio:h,tailLengthRatio:c}=this.edgeProgram.resolveEdgeIds(i,s,l);this.internals.edgeDataTexture.updateEdge(e,this.internals.nodeDataTexture.getIndex(r),this.internals.nodeDataTexture.getIndex(n),i.size,h,c,u,g,d),this.edgeProgram.process(ue(this.pickingState,"edge",e),t,this.internals.nodeDataCache[r],this.internals.nodeDataCache[n],i,o),this.edgeProgramIndex[e]=t}getRenderParams(){return{frameId:this.frameId,matrix:this.matrix,invMatrix:this.invMatrix,width:this.width,height:this.height,pixelRatio:this.internals.pixelRatio,zoomRatio:this.camera.ratio,cameraAngle:this.camera.angle,sizeRatio:1/this.scaleSize(),correctionRatio:this.correctionRatio,downSizingRatio:this.internals.settings.pickingDownSizingRatio,minEdgeThickness:this.internals.settings.minEdgeThickness,antiAliasingFeather:this.internals.settings.antiAliasingFeather,nodePickingPadding:this.internals.settings.nodePickingPadding,edgePickingPadding:this.internals.settings.edgePickingPadding,labelPickingPadding:this.internals.settings.labelPickingPadding,nodeDataTextureUnit:Xr,nodeDataTextureWidth:this.internals.nodeDataTexture.getTextureWidth(),nodeFrameTextureUnit:Vr,nodeFrameTextureWidth:this.internals.nodeFrameTexture.getTextureWidth(),edgeDataTextureUnit:qr,edgeDataTextureWidth:this.internals.edgeDataTexture.getTextureWidth(),edgeFrameTextureUnit:Hr,edgeFrameTextureWidth:this.internals.edgeFrameTexture.getTextureWidth(),pickingFrameBuffer:this.pickingFrameBuffer,labelPixelSnapping:this.internals.settings.labelPixelSnapping?1:0}}getStagePadding(){const{stagePadding:e,autoRescale:t}=this.internals.settings;return t&&e||0}getLayerElement(e){if(e==="mouse")return this.mouseLayer;const t=this.extraElements[e];if(!t)throw new Error(`Sigma: layer "${e}" does not exist`);return t}initWebGLContext(){const e=this.createWebGLContext("stage");this.stageCanvas=this.extraElements.stage,this.webGLContext=e,this.stageCanvas.addEventListener("webglcontextlost",t=>{this.webGLContext&&(t.preventDefault(),this.contextLost=!0,this.renderFrame&&(cancelAnimationFrame(this.renderFrame),this.renderFrame=null),this.gpuTimerExt=void 0,this.activeGpuTimerQuery=null,this.pendingGpuTimerQueries=[],this.emit("webglContextLost"))}),this.stageCanvas.addEventListener("webglcontextrestored",()=>{requestAnimationFrame(()=>{this.webGLContext&&!this.webGLContext.isContextLost()&&this.restoreWebGLContext()})}),this.initPickingFramebuffer()}initPickingFramebuffer(){const e=this.webGLContext,t=e.createFramebuffer();if(!t)throw new Error("Sigma: cannot create picking frame buffer");e.bindFramebuffer(e.FRAMEBUFFER,t);const i=e.createTexture();if(!i)throw new Error("Sigma: cannot create picking texture");e.bindTexture(e.TEXTURE_2D,i),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,1,1,0,e.RGBA,e.UNSIGNED_BYTE,null),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,i,0);const r=e.createRenderbuffer();if(!r)throw new Error("Sigma: cannot create picking depth buffer");if(e.bindRenderbuffer(e.RENDERBUFFER,r),e.renderbufferStorage(e.RENDERBUFFER,e.DEPTH_COMPONENT16,1,1),e.framebufferRenderbuffer(e.FRAMEBUFFER,e.DEPTH_ATTACHMENT,e.RENDERBUFFER,r),e.checkFramebufferStatus(e.FRAMEBUFFER)!==e.FRAMEBUFFER_COMPLETE)throw new Error("Sigma: picking framebuffer is not complete");e.bindFramebuffer(e.FRAMEBUFFER,null),this.pickingFrameBuffer=t,this.pickingTexture=i,this.pickingDepthBuffer=r}initPrograms(e,t){const i=this,r=this.webGLContext,{nodeProgram:n,labelProgram:o,backdropProgram:s,labelBackgroundProgram:l,attachmentProgram:u,framePass:g,shapeSlug:d,shapeNameToIndex:h,shapeGlobalIds:c,variables:f}=gr(r,this.pickingFrameBuffer,i,e==null?void 0:e.nodes,t.antialiasNodes);this.nodeProgram=n,this.nodeFramePass=g,this.nodeVariableEntries=Object.entries(f),d&&(this.nodeShapeSlug=d);const{edgeProgram:b,createLabelPrograms:v,framePass:_,variables:m,paths:E}=pr(r,this.pickingFrameBuffer,i,e==null?void 0:e.edges,t.antialiasEdges,e==null?void 0:e.nodes);return this.createEdgeLabelPrograms=v,this.edgeProgram=b,this.edgeFramePass=_,this.edgeVariableEntries=Object.entries(m),this.edgePathsByName=new Map(E.map(p=>[p.name,p])),{labelProgram:o,edgeLabelProgram:null,edgeLabelBackgroundProgram:null,backdropProgram:s,labelBackgroundProgram:l,attachmentProgram:u,nodeShapeMap:h??null,nodeGlobalShapeIds:c??null}}restoreWebGLContext(){var t,i,r,n,o;const e=this.internals;this.initPickingFramebuffer(),(t=e.nodeDataTexture)==null||t.restore(),(i=e.nodeFrameTexture)==null||i.restore(),(r=e.edgeDataTexture)==null||r.restore(),(n=e.edgeFrameTexture)==null||n.restore(),(o=e.attachmentManager)==null||o.restore(),e.hoverResolver.reset(),Object.assign(e,this.initPrograms(e.primitives,e.settings));for(const s of this.customLayerPrograms.values())s.program.kill(),s.program=s.factory(this.webGLContext);this.contextLost=!1,this.emit("webglContextRestored"),this.refresh()}createLayer(e,t,i={}){if(this.extraElements[e])throw new Error(`Sigma: a layer named "${e}" already exists`);const r=Se(t,{position:"absolute"},{class:`sigma-${e}`});return i.style&&Object.assign(r.style,i.style),this.extraElements[e]=r,"beforeLayer"in i&&i.beforeLayer?this.getLayerElement(i.beforeLayer).before(r):"afterLayer"in i&&i.afterLayer?this.getLayerElement(i.afterLayer).after(r):this.container.appendChild(r),r}createCanvas(e,t={}){return this.createLayer(e,"canvas",t)}createWebGLContext(e,t={}){const i=this.createCanvas(e,t);t.hidden&&i.remove();const r=i.getContext("webgl2",{preserveDrawingBuffer:!1,antialias:!1,depth:!0,...t});if(!r)throw new Error("Sigma: WebGL 2 is not supported by your browser. Please use a modern browser (Chrome 56+, Firefox 51+, Safari 15+, Edge 79+).");return r.blendFunc(r.ONE,r.ONE_MINUS_SRC_ALPHA),r}killLayer(e){if(e==="stage"||e==="mouse")throw new Error(`Sigma: cannot kill built-in layer "${e}"`);const t=this.extraElements[e];if(!t)throw new Error(`Sigma: cannot kill layer ${e}, which does not exist`);return t.remove(),delete this.extraElements[e],this}getWebGLContext(){if(!this.webGLContext)throw new Error("Sigma: WebGL context is not available");return this.webGLContext}getNodeDataTexture(){if(!this.internals.nodeDataTexture)throw new Error("Sigma: node data texture is not available");return this.internals.nodeDataTexture}getNormalizationFunction(){return this.normalizationFunction}addCustomLayerProgram(e,t,i){var n;if(!this.depthLayers.includes(t))throw new Error(`Sigma: cannot add custom layer program at depth "${t}", it must be declared in primitives.depthLayers. Current layers: ${this.depthLayers.join(", ")}`);(n=this.customLayerPrograms.get(e))==null||n.program.kill();const r=i(this.webGLContext);return this.customLayerPrograms.set(e,{depth:t,factory:i,program:r}),r.cacheData&&r.cacheData(),this.scheduleRender(),this}removeCustomLayerProgram(e){const t=this.customLayerPrograms.get(e);return t&&(t.program.kill(),this.customLayerPrograms.delete(e),this.scheduleRender()),this}getCamera(){return this.camera}setCamera(e){return this.camera.cancelAnimation(),this.unbindCameraHandlers(),this.camera=e,this.bindCameraHandlers(),this.scheduleRender(),this}getContainer(){return this.container}getGraph(){return this.internals.graph}setGraph(e){return e===this.internals.graph?this:(this.stateManager.pruneNodes(t=>e.hasNode(t)),this.stateManager.pruneEdges(t=>e.hasEdge(t)),this.unbindGraphHandlers(),this.checkEdgesEventsFrame!==null&&(cancelAnimationFrame(this.checkEdgesEventsFrame),this.checkEdgesEventsFrame=null),this.internals.graph=e,this.autoRescaleFrozen=!1,this.bindGraphHandlers(),this.refresh(),this)}getMouseCaptor(){return this.mouseCaptor}getTouchCaptor(){return this.touchCaptor}getDimensions(){return{width:this.width,height:this.height}}getGraphDimensions(){const e=this.customBBox||this.nodeExtent;return{width:e.x[1]-e.x[0]||1,height:e.y[1]-e.y[0]||1}}getNodeDisplayData(e){const t=this.internals.nodeDataCache[e];return t?Object.assign({},t):void 0}getEdgeDisplayData(e){const t=this.internals.edgeDataCache[e];return t?Object.assign({},t):void 0}getNodeState(e){return this.stateManager.getNodeState(e)}getEdgeState(e){return this.stateManager.getEdgeState(e)}getGraphState(){return this.stateManager.getGraphState()}setNodeState(e,t){return this.stateManager.setNodeState(e,t),this}setEdgeState(e,t){return this.stateManager.setEdgeState(e,t),this}setGraphState(e){return this.stateManager.setGraphState(e),this}_setPanning(e){this.stateManager.setGraphState({isPanning:e})}_setZooming(e){this.stateManager.setGraphState({isZooming:e})}_hasNodeDrag(){const{dragManager:e}=this.internals;return!!(e.pendingNode||e.session)}_showGestureHint(e){const t=this.internals.settings;t.gestureTarget==="shared"&&(this.gestureHint=this.gestureHint||new js(this.container),this.gestureHint.show(e,t))}setNodesState(e,t){return this.stateManager.setNodesState(e,t),this}setEdgesState(e,t){return this.stateManager.setEdgesState(e,t),this}updateContainerCursor(){const e=this.stateManager.hovered,t=this.resolvedStageStyle.cursor||"";this.container.style.cursor=e&&Is(this.internals,e)||t}refreshStageStyle(){this.resolvedStageStyle=Rr(this.stylesDeclaration.stage,this.stateManager.graphState),this.resolvedStageStyle.background!==void 0&&(this.container.style.backgroundColor=this.resolvedStageStyle.background),this.updateContainerCursor()}getNodeDisplayedLabels(){return new Set(this.labelRenderer.displayedNodeLabels)}getEdgeDisplayedLabels(){return new Set(this.labelRenderer.displayedEdgeLabels)}getSettings(){return{...this.internals.settings}}getSetting(e){return this.internals.settings[e]}getStyles(){return{...this.stylesDeclaration}}getPrimitives(){return{...this.internals.primitives}}setSetting(e,t){return this.internals.settings[e]=t,Pt(this.internals.settings),this.handleSettingsUpdate(),this.scheduleRefresh(),this}updateSetting(e,t){return this.setSetting(e,t(this.internals.settings[e])),this}setSettings(e){return this.internals.settings={...this.internals.settings,...e},Pt(this.internals.settings),this.handleSettingsUpdate(),this.scheduleRefresh(),this}resize(e){const t=this.width,i=this.height;if(this.width=this.container.offsetWidth,this.height=this.container.offsetHeight,this.internals.pixelRatio=ut(),this.width===0)if(this.internals.settings.allowInvalidContainer)this.width=1;else throw new Error("Sigma: Container has no width. You can set the allowInvalidContainer setting to true to stop seeing this error.");if(this.height===0)if(this.internals.settings.allowInvalidContainer)this.height=1;else throw new Error("Sigma: Container has no height. You can set the allowInvalidContainer setting to true to stop seeing this error.");if(!e&&t===this.width&&i===this.height)return this;this.labelRenderer.labelsDirty=!0;for(const r of[this.mouseLayer,...Object.values(this.extraElements)])r.style.width=this.width+"px",r.style.height=this.height+"px";return this.webGLContext&&(this.stageCanvas.setAttribute("width",this.width*this.internals.pixelRatio+"px"),this.stageCanvas.setAttribute("height",this.height*this.internals.pixelRatio+"px"),this.webGLContext.viewport(0,0,this.width*this.internals.pixelRatio,this.height*this.internals.pixelRatio)),this.emit("resize"),this}clear(){return this.emit("beforeClear"),this.webGLContext.bindFramebuffer(WebGLRenderingContext.FRAMEBUFFER,null),this.webGLContext.clear(WebGLRenderingContext.COLOR_BUFFER_BIT),this.emit("afterClear"),this}scheduleStateRefresh(){this.needToRefreshState=!0,this.scheduleRender()}refreshState(){var r;this.stateManager.flushGraphStateFlags();const e=this.stateManager.graphStateChanged&&this.internals.nodeStyleAnalysis.dependency==="graph-state",t=this.stateManager.graphStateChanged&&this.edgeStyleAnalysis.dependency==="graph-state";let i=!1;if(e)this.internals.graph.forEachNode(n=>{this.refreshNodeState(n)&&(i=!0)});else if(this.internals.nodeStyleAnalysis.dependency!=="static")for(const n of this.stateManager.dirtyNodes)this.refreshNodeState(n)&&(i=!0);if(t)this.internals.graph.forEachEdge(n=>{this.refreshEdgeState(n)&&(i=!0)});else if(this.edgeStyleAnalysis.dependency!=="static")for(const n of this.stateManager.dirtyEdges)this.refreshEdgeState(n)&&(i=!0);this.stateManager.graphStateChanged&&((r=this.stylesDeclaration)!=null&&r.stage)&&this.refreshStageStyle(),this.stateManager.clearDirtyTracking(),i&&(this.pendingProcess="full")}refreshNodeState(e){var v,_,m;const t=this.internals.nodeDataCache[e];if(!t||this.nodeReducer){const E=(v=this.internals.nodeDataCache[e])==null?void 0:v.depth,p=(_=this.internals.nodeDataCache[e])==null?void 0:_.zIndex,x=(m=this.internals.nodeDataCache[e])==null?void 0:m.labelAttachment;this.updateNode(e);const T=this.internals.nodeDataCache[e];this.internals.attachmentManager&&T.labelAttachment!==x&&this.internals.attachmentManager.invalidateNode(e);let y;this.internals.nodeShapeMap&&this.internals.nodeGlobalShapeIds&&T.shape&&T.shape in this.internals.nodeShapeMap?y=this.internals.nodeGlobalShapeIds[this.internals.nodeShapeMap[T.shape]]:y=Me(T.shape||"circle"),this.internals.nodeDataTexture.updateNode(e,T.x,T.y,T.size,y,...ct(T),T.color),E&&T.depth!==E&&this.updateNodeDepthRanges(e,E,T.depth);const R=this.nodeProgramIndex[e];return R!==void 0&&(this.addNodeToProgram(e,R),this.nodeProgram.invalidateBuffers()),p!==void 0&&T.zIndex!==p}const i=this.internals.graph.getNodeAttributes(e),r=this.stateManager.getNodeState(e),n=t.size,o=t.shape,s=t.depth,l=t.zIndex,u=t.labelAttachment,g=t.rotationAlignment,d=t.labelRotationAlignment,h=t.color;Ta(this.stylesDeclaration.nodes,i,r,this.stateManager.graphState,this.internals.graph,t),this.postEvaluateNode(t,i,r);const c=this.nodeGraphCoords[e],f=t.x!==c.x||t.y!==c.y;if(f&&(c.x=t.x,c.y=t.y),this.normalizationFunction.applyTo(t),this.internals.nodeShapeMap?(!t.shape||!(t.shape in this.internals.nodeShapeMap))&&(t.shape=Object.keys(this.internals.nodeShapeMap)[0]):this.nodeShapeSlug&&(t.shape=this.nodeShapeSlug),this.internals.attachmentManager&&t.labelAttachment!==u&&this.internals.attachmentManager.invalidateNode(e),le(this.internals.nodesWithForcedLabels,e,te(t)),le(this.internals.nodesWithBackdrop,e,Qt(t)),f||t.size!==n||t.shape!==o||t.rotationAlignment!==g||t.labelRotationAlignment!==d||t.color!==h){let E;this.internals.nodeShapeMap&&this.internals.nodeGlobalShapeIds&&t.shape&&t.shape in this.internals.nodeShapeMap?E=this.internals.nodeGlobalShapeIds[this.internals.nodeShapeMap[t.shape]]:E=Me(t.shape||"circle"),this.internals.nodeDataTexture.updateNode(e,t.x,t.y,t.size,E,...ct(t),t.color)}this.itemBuckets.nodes.set(e,t.depth),t.depth!==s&&this.updateNodeDepthRanges(e,s,t.depth);const b=this.nodeProgramIndex[e];return b!==void 0&&(this.addNodeToProgram(e,b),this.nodeProgram.invalidateBuffers()),t.zIndex!==l}refreshEdgeState(e){const t=this.internals.edgeDataCache[e];if(!t||this.edgeReducer){const f=t==null?void 0:t.depth,b=t==null?void 0:t.zIndex;this.updateEdge(e);const v=this.internals.edgeDataCache[e];f&&v.depth!==f&&this.updateEdgeDepthRanges(e,f,v.depth);const _=this.edgeProgramIndex[e];return _!==void 0&&(this.addEdgeToProgram(e,_),this.edgeProgram.invalidateBuffers()),b!==void 0&&v.zIndex!==b}const i=this.internals.graph.getEdgeAttributes(e),r=this.stateManager.getEdgeState(e),n=t.depth,o=t.zIndex,s=t.size,l=t.path,u=t.selfLoopPath,g=t.parallelPath,d=t.head,h=t.tail;va(this.stylesDeclaration.edges,i,r,this.stateManager.graphState,this.internals.graph,t),this.postEvaluateEdge(t,i),this.applyEdgeSpread(e,t,r),le(this.internals.edgesWithForcedLabels,e,te(t)),this.itemBuckets.edges.set(e,t.depth),t.depth!==n&&this.updateEdgeDepthRanges(e,n,t.depth);const c=this.edgeProgramIndex[e];if(c!==void 0)if(t.size!==s||t.path!==l||t.selfLoopPath!==u||t.parallelPath!==g||t.head!==d||t.tail!==h)this.addEdgeToProgram(e,c),this.edgeProgram.invalidateBuffers();else{const b=this.internals.graph.source(e),v=this.internals.graph.target(e),_=this.internals.nodeDataCache[b],m=this.internals.nodeDataCache[v],E=this.edgeTextureIndexCache[e];this.edgeProgram.process(ue(this.pickingState,"edge",e),c,_,m,t,E),this.edgeProgram.invalidateBuffers()}return t.zIndex!==o}refresh(e){var n,o,s,l;const t=(e==null?void 0:e.skipIndexation)!==void 0?e==null?void 0:e.skipIndexation:!1,i=(e==null?void 0:e.schedule)!==void 0?e.schedule:!1;if(!e||!e.partialGraph)this.clearEdgeIndices(),this.clearNodeIndices(),this.internals.graph.forEachNode(u=>this.addNode(u)),this.edgeGroups.rebuild(),this.internals.graph.forEachEdge(u=>this.addEdge(u)),this.pendingProcess="full";else{const u=((n=e.partialGraph)==null?void 0:n.nodes)||[];for(let d=0,h=(u==null?void 0:u.length)||0;d<h;d++){const c=""+u[d],f=(o=this.internals.nodeDataCache[c])==null?void 0:o.labelAttachment;if(this.updateNode(c),this.internals.attachmentManager&&((s=this.internals.nodeDataCache[c])!=null&&s.labelAttachment||f)&&this.internals.attachmentManager.invalidateNode(c),t){const b=this.nodeProgramIndex[c];if(b===void 0)throw new Error(`Sigma: node "${c}" can't be repaint`);this.addNodeToProgram(c,b)}}t&&u.length>0&&(this.nodeProgram.invalidateBuffers(),this.labelRenderer.labelsDirty=!0);const g=((l=e==null?void 0:e.partialGraph)==null?void 0:l.edges)||[];for(let d=0,h=g.length;d<h;d++){const c=""+g[d];if(this.updateEdge(c),t){const f=this.edgeProgramIndex[c];if(f===void 0)throw new Error(`Sigma: edge "${c}" can't be repaint`);this.addEdgeToProgram(c,f)}}t&&g.length>0&&(this.edgeProgram.invalidateBuffers(),this.labelRenderer.labelsDirty=!0),!t&&this.pendingProcess!=="full"&&(this.pendingProcess=g.length>0?"full":"nodes")}return i?this.scheduleRender():this.render(),this}scheduleRender(){return this.renderFrame||(this.renderFrame=requestAnimationFrame(()=>{this.render()})),this}scheduleRefresh(e){return this.refresh({...e,schedule:!0})}getViewportZoomedState(e,t){const{ratio:i,angle:r,x:n,y:o}=this.camera.getState(),{minCameraRatio:s,maxCameraRatio:l}=this.internals.settings;typeof l=="number"&&(t=Math.min(t,l)),typeof s=="number"&&(t=Math.max(t,s));const u=t/i,g={x:this.width/2,y:this.height/2},d=this.viewportToFramedGraph(e),h=this.viewportToFramedGraph(g);return{angle:r,x:(d.x-h.x)*(1-u)+n,y:(d.y-h.y)*(1-u)+o,ratio:t}}viewRectangle(){const e=this.viewportToFramedGraph({x:0,y:0}),t=this.viewportToFramedGraph({x:this.width,y:0}),i=this.viewportToFramedGraph({x:0,y:this.height});return{x1:e.x,y1:e.y,x2:t.x,y2:t.y,height:t.y-i.y}}framedGraphToViewport(e,t={}){const i=!!t.cameraState||!!t.viewportDimensions||!!t.graphDimensions||!!t.padding,r=t.matrix||(i?ee(t.cameraState||this.camera.getState(),t.viewportDimensions||this.getDimensions(),t.graphDimensions||this.getGraphDimensions(),t.padding||this.getStagePadding()):this.matrix),n=xe(r,e);return{x:(1+n.x)*this.width/2,y:(1-n.y)*this.height/2}}viewportToFramedGraph(e,t={}){const i=!!t.cameraState||!!t.viewportDimensions||!!t.graphDimensions||!!t.padding,r=t.matrix||(i?ee(t.cameraState||this.camera.getState(),t.viewportDimensions||this.getDimensions(),t.graphDimensions||this.getGraphDimensions(),t.padding||this.getStagePadding(),!0):this.invMatrix),n=xe(r,{x:e.x/this.width*2-1,y:1-e.y/this.height*2});return isNaN(n.x)&&(n.x=0),isNaN(n.y)&&(n.y=0),n}viewportToGraph(e,t={}){return this.normalizationFunction.inverse(this.viewportToFramedGraph(e,t))}graphToViewport(e,t={}){return this.framedGraphToViewport(this.normalizationFunction(e),t)}graphToFramedGraph(e){return this.normalizationFunction(e)}framedGraphToGraph(e){return this.normalizationFunction.inverse(e)}getGraphToViewportRatio(){const e={x:0,y:0},t={x:1,y:1},i=Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)),r=this.graphToViewport(e),n=this.graphToViewport(t);return Math.sqrt(Math.pow(r.x-n.x,2)+Math.pow(r.y-n.y,2))/i}computeNodeExtent(){const e=this.nodeGraphCoords,t=Object.keys(e);if(!t.length)return{x:[0,1],y:[0,1]};let i=1/0,r=-1/0,n=1/0,o=-1/0;for(let s=0,l=t.length;s<l;s++){const{x:u,y:g}=e[t[s]];u<i&&(i=u),u>r&&(r=u),g<n&&(n=g),g>o&&(o=g)}return{x:[i,r],y:[n,o]}}getBBox(){return this.nodeExtent}getCustomBBox(){return this.customBBox}setCustomBBox(e){return this.customBBox=e,this.scheduleRender(),this}kill(){var t,i,r,n,o,s;this.emit("kill"),this.removeAllListeners(),this.camera.cancelAnimation(),this.unbindCameraHandlers(),window.removeEventListener("resize",this.activeListeners.handleResize),this.mouseCaptor.kill(),this.touchCaptor.kill(),this.internals.hoverResolver.kill(),this.unbindGraphHandlers(),this.clearIndices(),this.clearState(),this.internals.nodeDataCache={},this.internals.edgeDataCache={},this.renderFrame&&(cancelAnimationFrame(this.renderFrame),this.renderFrame=null);const e=this.container;for(;e.firstChild;)e.removeChild(e.firstChild);this.nodeProgram.kill(),this.nodeFramePass.kill(),this.edgeProgram.kill(),this.edgeFramePass.kill(),this.internals.labelProgram.kill(),(t=this.internals.edgeLabelProgram)==null||t.kill(),(i=this.internals.edgeLabelBackgroundProgram)==null||i.kill(),this.internals.backdropProgram.kill(),this.internals.labelBackgroundProgram.kill(),(r=this.internals.attachmentProgram)==null||r.kill(),(n=this.internals.attachmentManager)==null||n.kill(),this.internals.attachmentProgram=null,this.internals.attachmentManager=null;for(const{program:l}of this.customLayerPrograms.values())l.kill();this.customLayerPrograms.clear(),this.sdfAtlas&&(this.sdfAtlas=null),this.internals.nodeDataTexture&&(this.internals.nodeDataTexture.kill(),this.internals.nodeDataTexture=null),this.internals.nodeFrameTexture&&(this.internals.nodeFrameTexture.kill(),this.internals.nodeFrameTexture=null),this.internals.edgeDataTexture&&(this.internals.edgeDataTexture.kill(),this.internals.edgeDataTexture=null),this.internals.edgeFrameTexture&&(this.internals.edgeFrameTexture.kill(),this.internals.edgeFrameTexture=null),this.webGLContext&&((o=this.webGLContext.getExtension("WEBGL_lose_context"))==null||o.loseContext(),this.webGLContext=null),(s=this.gestureHint)==null||s.kill(),this.gestureHint=null,this.mouseLayer.remove();for(const l in this.extraElements)this.extraElements[l].remove();this.extraElements={}}scaleSize(e=1,t=this.camera.ratio){return e/this.internals.settings.zoomToSizeRatioFunction(t)*(this.getSetting("itemSizesReference")==="positions"?t*this.graphToViewportRatio:1)}getStageCanvas(){return this.stageCanvas}getMouseLayer(){return this.mouseLayer}}return Object.assign(jr,{Sigma:jr,Camera:Ye,MouseCaptor:Fr,TouchCaptor:Pr,SDFAtlasManager:re,DEFAULT_CAMERA_STATE:It,DEFAULT_STYLES:Xe,DEPTHLESS_STYLES:vr,types:Ss,settings:ns,primitives:rs,rendering:Yo,utils:_n})}));
</script></head><body><main><p>Original fictional exhibit plan · Sigma 4.0.4 / Graphology 0.26.0</p><h1>Three rooms, one reading view</h1><p>Change the drawing panel, release the renderer, then rebuild it from the same room records. Positions below are illustrative coordinates, not distances.</p><div class="controls"><button id="compact">Compact</button><button id="full">Full width</button><button id="release">Release view</button><button id="build">Rebuild view</button></div><p id="status" role="status">Preparing renderer…</p><div id="panel"><div id="graph" aria-label="Fictional room connections"></div></div><h2>Maintained room records</h2><table><thead><tr><th>Room</th><th>x</th><th>y</th></tr></thead><tbody><tr><td>Entry</td><td>0</td><td>0</td></tr><tr><td>Prints</td><td>2</td><td>1</td></tr><tr><td>Reading</td><td>4</td><td>0</td></tr></tbody></table><p>Connections: Entry — Prints; Prints — Reading.</p><p>These controls change a local drawing. They do not save a museum plan, calculate a route or submit work.</p><script>const graph=new graphology.Graph();for(const[id,label,x,y]of[['entry','Entry',0,0],['prints','Prints',2,1],['reading','Reading',4,0]])graph.addNode(id,{label,x,y,size:12,color:'#445c84'});graph.addEdge('entry','prints',{size:2,color:'#788799'});graph.addEdge('prints','reading',{size:2,color:'#788799'});const container=document.querySelector('#graph'),panel=document.querySelector('#panel'),status=document.querySelector('#status'),build=document.querySelector('#build'),release=document.querySelector('#release');let renderer=null;const inspect=()=>({active:!!renderer,dimensions:renderer?renderer.getDimensions():null,container:{width:container.clientWidth,height:container.clientHeight},nodes:graph.order,edges:graph.size,canvases:container.querySelectorAll('canvas').length,panel:panel.style.maxWidth||'100%'});function report(){const s=inspect();status.textContent=s.active?'Rendered view: '+s.dimensions.width+' × '+s.dimensions.height+' pixels. Source: '+s.nodes+' rooms, '+s.edges+' connections.':'View released. Source retained: '+s.nodes+' rooms, '+s.edges+' connections.';build.disabled=s.active;release.disabled=!s.active;}function create(){if(renderer)return;try{renderer=new Sigma(graph,container,{settings:{itemSizesReference:"screen",autoRescaleContent:"labels"}});report();}catch(error){renderer=null;container.replaceChildren();status.textContent='Rendering could not start: '+error.message;build.disabled=false;release.disabled=true;}}document.querySelector('#compact').onclick=()=>{panel.style.maxWidth='280px';if(renderer){renderer.resize();renderer.refresh();}report();};document.querySelector('#full').onclick=()=>{panel.style.maxWidth='100%';if(renderer){renderer.resize();renderer.refresh();}report();};release.onclick=()=>{if(renderer)renderer.kill();renderer=null;report();};build.onclick=create;window.addEventListener('resize',()=>{if(renderer){renderer.resize();renderer.refresh();report();}});window.inspectView=inspect;function start(attempt=0){if(container.offsetWidth>0&&container.offsetHeight>0)create();else if(attempt<60)requestAnimationFrame(()=>start(attempt+1));else status.textContent='Rendering could not start: the graph container has no measurable size.';}requestAnimationFrame(()=>start());</script></main></body></html>