>>=y,p-=y),p<15&&(d+=z[n++]<>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<>>=y,p-=y,(y=s-a)>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg=\"\",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function h(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){e.msg=\"incorrect header check\",r.mode=30;break}if(8!=(15&u)){e.msg=\"unknown compression method\",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg=\"invalid window size\",r.mode=30;break}r.dmax=1<>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,d,k)),512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&d>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break e;o--,u+=n[s++]<>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;u>>>=2,l-=2;break e;case 2:r.mode=17;break;case 3:e.msg=\"invalid block type\",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break e;o--,u+=n[s++]<>>16^65535)){e.msg=\"invalid stored block lengths\",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg=\"invalid code lengths set\",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l>>=_,l-=_,0===r.have){e.msg=\"invalid bit length repeat\",r.mode=30;break}k=r.lens[r.have-1],d=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l>>=_)),u>>>=3,l-=3}else{for(z=_+7;l>>=_)),u>>>=7,l-=7}if(r.have+d>r.nlen+r.ndist){e.msg=\"invalid bit length repeat\",r.mode=30;break}for(;d--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg=\"invalid code -- missing end-of-block\",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg=\"invalid literal/lengths set\",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg=\"invalid distances set\",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=h){e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,R(e,c),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg=\"invalid literal/length code\",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){e.msg=\"invalid distance code\",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg=\"invalid distance too far back\",r.mode=30;break}r.mode=25;case 25:if(0===h)break e;if(d=c-h,r.offset>d){if((d=r.offset-d)>r.whave&&r.sane){e.msg=\"invalid distance too far back\",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=i,p=a-r.offset,d=r.length;for(hd?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=t[r+a[v]]}if(k>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(g+1),a=0;for(n=1;n<=g;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,d=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=g;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<_;r++)p<(s=h[2*h[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),h[2*n+1]=s,u>=7;n>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return h;for(t=32;t>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?J(e,t,r,n):4===e.strategy||s===i?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,m,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{\"../utils/common\":41}],53:[function(e,t,r){\"use strict\";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg=\"\",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){(function(e){!function(r,n){\"use strict\";if(!r.setImmediate){var i,s,t,a,o=1,h={},u=!1,l=r.document,e=Object.getPrototypeOf&&Object.getPrototypeOf(r);e=e&&e.setTimeout?e:r,i=\"[object process]\"==={}.toString.call(r.process)?function(e){process.nextTick(function(){c(e)})}:function(){if(r.postMessage&&!r.importScripts){var e=!0,t=r.onmessage;return r.onmessage=function(){e=!1},r.postMessage(\"\",\"*\"),r.onmessage=t,e}}()?(a=\"setImmediate$\"+Math.random()+\"$\",r.addEventListener?r.addEventListener(\"message\",d,!1):r.attachEvent(\"onmessage\",d),function(e){r.postMessage(a+e,\"*\")}):r.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){t.port2.postMessage(e)}):l&&\"onreadystatechange\"in l.createElement(\"script\")?(s=l.documentElement,function(e){var t=l.createElement(\"script\");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,s.removeChild(t),t=null},s.appendChild(t)}):function(e){setTimeout(c,0,e)},e.setImmediate=function(e){\"function\"!=typeof e&&(e=new Function(\"\"+e));for(var t=new Array(arguments.length-1),r=0;r -1\n };\n }\n\n function normalizeName(name) {\n if (typeof name !== 'string') {\n name = String(name);\n }\n if (/[^a-z0-9\\-#$%&'*+.^_`|~]/i.test(name)) {\n throw new TypeError('Invalid character in header field name')\n }\n return name.toLowerCase()\n }\n\n function normalizeValue(value) {\n if (typeof value !== 'string') {\n value = String(value);\n }\n return value\n }\n\n // Build a destructive iterator for the value list\n function iteratorFor(items) {\n var iterator = {\n next: function() {\n var value = items.shift();\n return {done: value === undefined, value: value}\n }\n };\n\n if (support.iterable) {\n iterator[Symbol.iterator] = function() {\n return iterator\n };\n }\n\n return iterator\n }\n\n function Headers(headers) {\n this.map = {};\n\n if (headers instanceof Headers) {\n headers.forEach(function(value, name) {\n this.append(name, value);\n }, this);\n } else if (Array.isArray(headers)) {\n headers.forEach(function(header) {\n this.append(header[0], header[1]);\n }, this);\n } else if (headers) {\n Object.getOwnPropertyNames(headers).forEach(function(name) {\n this.append(name, headers[name]);\n }, this);\n }\n }\n\n Headers.prototype.append = function(name, value) {\n name = normalizeName(name);\n value = normalizeValue(value);\n var oldValue = this.map[name];\n this.map[name] = oldValue ? oldValue + ', ' + value : value;\n };\n\n Headers.prototype['delete'] = function(name) {\n delete this.map[normalizeName(name)];\n };\n\n Headers.prototype.get = function(name) {\n name = normalizeName(name);\n return this.has(name) ? this.map[name] : null\n };\n\n Headers.prototype.has = function(name) {\n return this.map.hasOwnProperty(normalizeName(name))\n };\n\n Headers.prototype.set = function(name, value) {\n this.map[normalizeName(name)] = normalizeValue(value);\n };\n\n Headers.prototype.forEach = function(callback, thisArg) {\n for (var name in this.map) {\n if (this.map.hasOwnProperty(name)) {\n callback.call(thisArg, this.map[name], name, this);\n }\n }\n };\n\n Headers.prototype.keys = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push(name);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.values = function() {\n var items = [];\n this.forEach(function(value) {\n items.push(value);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.entries = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push([name, value]);\n });\n return iteratorFor(items)\n };\n\n if (support.iterable) {\n Headers.prototype[Symbol.iterator] = Headers.prototype.entries;\n }\n\n function consumed(body) {\n if (body.bodyUsed) {\n return Promise.reject(new TypeError('Already read'))\n }\n body.bodyUsed = true;\n }\n\n function fileReaderReady(reader) {\n return new Promise(function(resolve, reject) {\n reader.onload = function() {\n resolve(reader.result);\n };\n reader.onerror = function() {\n reject(reader.error);\n };\n })\n }\n\n function readBlobAsArrayBuffer(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsArrayBuffer(blob);\n return promise\n }\n\n function readBlobAsText(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsText(blob);\n return promise\n }\n\n function readArrayBufferAsText(buf) {\n var view = new Uint8Array(buf);\n var chars = new Array(view.length);\n\n for (var i = 0; i < view.length; i++) {\n chars[i] = String.fromCharCode(view[i]);\n }\n return chars.join('')\n }\n\n function bufferClone(buf) {\n if (buf.slice) {\n return buf.slice(0)\n } else {\n var view = new Uint8Array(buf.byteLength);\n view.set(new Uint8Array(buf));\n return view.buffer\n }\n }\n\n function Body() {\n this.bodyUsed = false;\n\n this._initBody = function(body) {\n this._bodyInit = body;\n if (!body) {\n this._bodyText = '';\n } else if (typeof body === 'string') {\n this._bodyText = body;\n } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {\n this._bodyBlob = body;\n } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {\n this._bodyFormData = body;\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this._bodyText = body.toString();\n } else if (support.arrayBuffer && support.blob && isDataView(body)) {\n this._bodyArrayBuffer = bufferClone(body.buffer);\n // IE 10-11 can't handle a DataView body.\n this._bodyInit = new Blob([this._bodyArrayBuffer]);\n } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {\n this._bodyArrayBuffer = bufferClone(body);\n } else {\n this._bodyText = body = Object.prototype.toString.call(body);\n }\n\n if (!this.headers.get('content-type')) {\n if (typeof body === 'string') {\n this.headers.set('content-type', 'text/plain;charset=UTF-8');\n } else if (this._bodyBlob && this._bodyBlob.type) {\n this.headers.set('content-type', this._bodyBlob.type);\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');\n }\n }\n };\n\n if (support.blob) {\n this.blob = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return Promise.resolve(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(new Blob([this._bodyArrayBuffer]))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as blob')\n } else {\n return Promise.resolve(new Blob([this._bodyText]))\n }\n };\n\n this.arrayBuffer = function() {\n if (this._bodyArrayBuffer) {\n return consumed(this) || Promise.resolve(this._bodyArrayBuffer)\n } else {\n return this.blob().then(readBlobAsArrayBuffer)\n }\n };\n }\n\n this.text = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return readBlobAsText(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as text')\n } else {\n return Promise.resolve(this._bodyText)\n }\n };\n\n if (support.formData) {\n this.formData = function() {\n return this.text().then(decode)\n };\n }\n\n this.json = function() {\n return this.text().then(JSON.parse)\n };\n\n return this\n }\n\n // HTTP methods whose capitalization should be normalized\n var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];\n\n function normalizeMethod(method) {\n var upcased = method.toUpperCase();\n return methods.indexOf(upcased) > -1 ? upcased : method\n }\n\n function Request(input, options) {\n options = options || {};\n var body = options.body;\n\n if (input instanceof Request) {\n if (input.bodyUsed) {\n throw new TypeError('Already read')\n }\n this.url = input.url;\n this.credentials = input.credentials;\n if (!options.headers) {\n this.headers = new Headers(input.headers);\n }\n this.method = input.method;\n this.mode = input.mode;\n this.signal = input.signal;\n if (!body && input._bodyInit != null) {\n body = input._bodyInit;\n input.bodyUsed = true;\n }\n } else {\n this.url = String(input);\n }\n\n this.credentials = options.credentials || this.credentials || 'same-origin';\n if (options.headers || !this.headers) {\n this.headers = new Headers(options.headers);\n }\n this.method = normalizeMethod(options.method || this.method || 'GET');\n this.mode = options.mode || this.mode || null;\n this.signal = options.signal || this.signal;\n this.referrer = null;\n\n if ((this.method === 'GET' || this.method === 'HEAD') && body) {\n throw new TypeError('Body not allowed for GET or HEAD requests')\n }\n this._initBody(body);\n }\n\n Request.prototype.clone = function() {\n return new Request(this, {body: this._bodyInit})\n };\n\n function decode(body) {\n var form = new FormData();\n body\n .trim()\n .split('&')\n .forEach(function(bytes) {\n if (bytes) {\n var split = bytes.split('=');\n var name = split.shift().replace(/\\+/g, ' ');\n var value = split.join('=').replace(/\\+/g, ' ');\n form.append(decodeURIComponent(name), decodeURIComponent(value));\n }\n });\n return form\n }\n\n function parseHeaders(rawHeaders) {\n var headers = new Headers();\n // Replace instances of \\r\\n and \\n followed by at least one space or horizontal tab with a space\n // https://tools.ietf.org/html/rfc7230#section-3.2\n var preProcessedHeaders = rawHeaders.replace(/\\r?\\n[\\t ]+/g, ' ');\n preProcessedHeaders.split(/\\r?\\n/).forEach(function(line) {\n var parts = line.split(':');\n var key = parts.shift().trim();\n if (key) {\n var value = parts.join(':').trim();\n headers.append(key, value);\n }\n });\n return headers\n }\n\n Body.call(Request.prototype);\n\n function Response(bodyInit, options) {\n if (!options) {\n options = {};\n }\n\n this.type = 'default';\n this.status = options.status === undefined ? 200 : options.status;\n this.ok = this.status >= 200 && this.status < 300;\n this.statusText = 'statusText' in options ? options.statusText : 'OK';\n this.headers = new Headers(options.headers);\n this.url = options.url || '';\n this._initBody(bodyInit);\n }\n\n Body.call(Response.prototype);\n\n Response.prototype.clone = function() {\n return new Response(this._bodyInit, {\n status: this.status,\n statusText: this.statusText,\n headers: new Headers(this.headers),\n url: this.url\n })\n };\n\n Response.error = function() {\n var response = new Response(null, {status: 0, statusText: ''});\n response.type = 'error';\n return response\n };\n\n var redirectStatuses = [301, 302, 303, 307, 308];\n\n Response.redirect = function(url, status) {\n if (redirectStatuses.indexOf(status) === -1) {\n throw new RangeError('Invalid status code')\n }\n\n return new Response(null, {status: status, headers: {location: url}})\n };\n\n exports.DOMException = self.DOMException;\n try {\n new exports.DOMException();\n } catch (err) {\n exports.DOMException = function(message, name) {\n this.message = message;\n this.name = name;\n var error = Error(message);\n this.stack = error.stack;\n };\n exports.DOMException.prototype = Object.create(Error.prototype);\n exports.DOMException.prototype.constructor = exports.DOMException;\n }\n\n function fetch(input, init) {\n return new Promise(function(resolve, reject) {\n var request = new Request(input, init);\n\n if (request.signal && request.signal.aborted) {\n return reject(new exports.DOMException('Aborted', 'AbortError'))\n }\n\n var xhr = new XMLHttpRequest();\n\n function abortXhr() {\n xhr.abort();\n }\n\n xhr.onload = function() {\n var options = {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: parseHeaders(xhr.getAllResponseHeaders() || '')\n };\n options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');\n var body = 'response' in xhr ? xhr.response : xhr.responseText;\n resolve(new Response(body, options));\n };\n\n xhr.onerror = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.ontimeout = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.onabort = function() {\n reject(new exports.DOMException('Aborted', 'AbortError'));\n };\n\n xhr.open(request.method, request.url, true);\n\n if (request.credentials === 'include') {\n xhr.withCredentials = true;\n } else if (request.credentials === 'omit') {\n xhr.withCredentials = false;\n }\n\n if ('responseType' in xhr && support.blob) {\n xhr.responseType = 'blob';\n }\n\n request.headers.forEach(function(value, name) {\n xhr.setRequestHeader(name, value);\n });\n\n if (request.signal) {\n request.signal.addEventListener('abort', abortXhr);\n\n xhr.onreadystatechange = function() {\n // DONE (success or failure)\n if (xhr.readyState === 4) {\n request.signal.removeEventListener('abort', abortXhr);\n }\n };\n }\n\n xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);\n })\n }\n\n fetch.polyfill = true;\n\n if (!self.fetch) {\n self.fetch = fetch;\n self.Headers = Headers;\n self.Request = Request;\n self.Response = Response;\n }\n\n exports.Headers = Headers;\n exports.Request = Request;\n exports.Response = Response;\n exports.fetch = fetch;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n return exports;\n\n})({});\n})(__self__);\n__self__.fetch.ponyfill = true;\n// Remove \"polyfill\" property added by whatwg-fetch\ndelete __self__.fetch.polyfill;\n// Choose between native implementation (global) or custom implementation (__self__)\n// var ctx = global.fetch ? global : __self__;\nvar ctx = __self__; // this line disable service worker support temporarily\nexports = ctx.fetch // To enable: import fetch from 'cross-fetch'\nexports.default = ctx.fetch // For TypeScript consumers without esModuleInterop.\nexports.fetch = ctx.fetch // To enable: import {fetch} from 'cross-fetch'\nexports.Headers = ctx.Headers\nexports.Request = ctx.Request\nexports.Response = ctx.Response\nmodule.exports = exports\n","import crossFetch from 'cross-fetch';\n\nexport async function fetchArrayBuffer(url) {\n const result = await crossFetch(url);\n return result.arrayBuffer();\n}\n","import JSZip from 'jszip';\n\nimport { fetchArrayBuffer } from './util/fetchArrayBuffer';\n\nconst loadingPromises = {};\n\nexport async function loadCommercials(options = {}) {\n const {\n url = 'https://couch.cheminfo.org/cheminfo-public/d2eb480198c80275a1d05dd3609414f9/upload/commercials.zip',\n } = options;\n\n if (!loadingPromises[url]) {\n loadingPromises[url] = fetchArrayBuffer(url);\n }\n const buffer = await loadingPromises[url];\n const jsZip = new JSZip();\n let zip = await jsZip.loadAsync(buffer);\n let fileData = await zip.files['commercials.json'].async('string');\n let data = JSON.parse(fileData);\n\n data.sort((a, b) => a.em - b.em);\n\n return data;\n}\n","/* @license\nPapa Parse\nv5.4.1\nhttps://github.com/mholt/PapaParse\nLicense: MIT\n*/\n!function(e,t){\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof module&&\"undefined\"!=typeof exports?module.exports=t():e.Papa=t()}(this,function s(){\"use strict\";var f=\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:void 0!==f?f:{};var n=!f.document&&!!f.postMessage,o=f.IS_PAPA_WORKER||!1,a={},u=0,b={parse:function(e,t){var r=(t=t||{}).dynamicTyping||!1;J(r)&&(t.dynamicTypingFunction=r,r={});if(t.dynamicTyping=r,t.transform=!!J(t.transform)&&t.transform,t.worker&&b.WORKERS_SUPPORTED){var i=function(){if(!b.WORKERS_SUPPORTED)return!1;var e=(r=f.URL||f.webkitURL||null,i=s.toString(),b.BLOB_URL||(b.BLOB_URL=r.createObjectURL(new Blob([\"var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; \",\"(\",i,\")();\"],{type:\"text/javascript\"})))),t=new f.Worker(e);var r,i;return t.onmessage=_,t.id=u++,a[t.id]=t}();return i.userStep=t.step,i.userChunk=t.chunk,i.userComplete=t.complete,i.userError=t.error,t.step=J(t.step),t.chunk=J(t.chunk),t.complete=J(t.complete),t.error=J(t.error),delete t.worker,void i.postMessage({input:e,config:t,workerId:i.id})}var n=null;b.NODE_STREAM_INPUT,\"string\"==typeof e?(e=function(e){if(65279===e.charCodeAt(0))return e.slice(1);return e}(e),n=t.download?new l(t):new p(t)):!0===e.readable&&J(e.read)&&J(e.on)?n=new g(t):(f.File&&e instanceof File||e instanceof Object)&&(n=new c(t));return n.stream(e)},unparse:function(e,t){var n=!1,_=!0,m=\",\",y=\"\\r\\n\",s='\"',a=s+s,r=!1,i=null,o=!1;!function(){if(\"object\"!=typeof t)return;\"string\"!=typeof t.delimiter||b.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(m=t.delimiter);(\"boolean\"==typeof t.quotes||\"function\"==typeof t.quotes||Array.isArray(t.quotes))&&(n=t.quotes);\"boolean\"!=typeof t.skipEmptyLines&&\"string\"!=typeof t.skipEmptyLines||(r=t.skipEmptyLines);\"string\"==typeof t.newline&&(y=t.newline);\"string\"==typeof t.quoteChar&&(s=t.quoteChar);\"boolean\"==typeof t.header&&(_=t.header);if(Array.isArray(t.columns)){if(0===t.columns.length)throw new Error(\"Option columns is empty\");i=t.columns}void 0!==t.escapeChar&&(a=t.escapeChar+s);(\"boolean\"==typeof t.escapeFormulae||t.escapeFormulae instanceof RegExp)&&(o=t.escapeFormulae instanceof RegExp?t.escapeFormulae:/^[=+\\-@\\t\\r].*$/)}();var u=new RegExp(Q(s),\"g\");\"string\"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return h(null,e,r);if(\"object\"==typeof e[0])return h(i||Object.keys(e[0]),e,r)}else if(\"object\"==typeof e)return\"string\"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||i),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:\"object\"==typeof e.data[0]?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||\"object\"==typeof e.data[0]||(e.data=[e.data])),h(e.fields||[],e.data||[],r);throw new Error(\"Unable to serialize unrecognized input\");function h(e,t,r){var i=\"\";\"string\"==typeof e&&(e=JSON.parse(e)),\"string\"==typeof t&&(t=JSON.parse(t));var n=Array.isArray(e)&&0=this._config.preview;if(o)f.postMessage({results:n,workerId:b.WORKER_ID,finished:a});else if(J(this._config.chunk)&&!t){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);n=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!a||!J(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||n&&n.meta.paused||this._nextChunk(),n}this._halted=!0},this._sendError=function(e){J(this._config.error)?this._config.error(e):o&&this._config.error&&f.postMessage({workerId:b.WORKER_ID,error:e,finished:!1})}}function l(e){var i;(e=e||{}).chunkSize||(e.chunkSize=b.RemoteChunkSize),h.call(this,e),this._nextChunk=n?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(i=new XMLHttpRequest,this._config.withCredentials&&(i.withCredentials=this._config.withCredentials),n||(i.onload=v(this._chunkLoaded,this),i.onerror=v(this._chunkError,this)),i.open(this._config.downloadRequestBody?\"POST\":\"GET\",this._input,!n),this._config.downloadRequestHeaders){var e=this._config.downloadRequestHeaders;for(var t in e)i.setRequestHeader(t,e[t])}if(this._config.chunkSize){var r=this._start+this._config.chunkSize-1;i.setRequestHeader(\"Range\",\"bytes=\"+this._start+\"-\"+r)}try{i.send(this._config.downloadRequestBody)}catch(e){this._chunkError(e.message)}n&&0===i.status&&this._chunkError()}},this._chunkLoaded=function(){4===i.readyState&&(i.status<200||400<=i.status?this._chunkError():(this._start+=this._config.chunkSize?this._config.chunkSize:i.responseText.length,this._finished=!this._config.chunkSize||this._start>=function(e){var t=e.getResponseHeader(\"Content-Range\");if(null===t)return-1;return parseInt(t.substring(t.lastIndexOf(\"/\")+1))}(i),this.parseChunk(i.responseText)))},this._chunkError=function(e){var t=i.statusText||e;this._sendError(new Error(t))}}function c(e){var i,n;(e=e||{}).chunkSize||(e.chunkSize=b.LocalChunkSize),h.call(this,e);var s=\"undefined\"!=typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,s?((i=new FileReader).onload=v(this._chunkLoaded,this),i.onerror=v(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function p(e){var r;h.call(this,e=e||{}),this.stream=function(e){return r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e,t=this._config.chunkSize;return t?(e=r.substring(0,t),r=r.substring(t)):(e=r,r=\"\"),this._finished=!r,this.parseChunk(e)}}}function g(e){h.call(this,e=e||{});var t=[],r=!0,i=!1;this.pause=function(){h.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){h.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on(\"data\",this._streamData),this._input.on(\"end\",this._streamEnd),this._input.on(\"error\",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):r=!0},this._streamData=v(function(e){try{t.push(\"string\"==typeof e?e:e.toString(this._config.encoding)),r&&(r=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=v(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=v(function(){this._streamCleanUp(),i=!0,this._streamData(\"\")},this),this._streamCleanUp=v(function(){this._input.removeListener(\"data\",this._streamData),this._input.removeListener(\"end\",this._streamEnd),this._input.removeListener(\"error\",this._streamError)},this)}function r(m){var a,o,u,i=Math.pow(2,53),n=-i,s=/^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/,h=/^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/,t=this,r=0,f=0,d=!1,e=!1,l=[],c={data:[],errors:[],meta:{}};if(J(m.step)){var p=m.step;m.step=function(e){if(c=e,_())g();else{if(g(),0===c.data.length)return;r+=e.data.length,m.preview&&r>m.preview?o.abort():(c.data=c.data[0],p(c,t))}}}function y(e){return\"greedy\"===m.skipEmptyLines?\"\"===e.join(\"\").trim():1===e.length&&0===e[0].length}function g(){return c&&u&&(k(\"Delimiter\",\"UndetectableDelimiter\",\"Unable to auto-detect delimiting character; defaulted to '\"+b.DefaultDelimiter+\"'\"),u=!1),m.skipEmptyLines&&(c.data=c.data.filter(function(e){return!y(e)})),_()&&function(){if(!c)return;function e(e,t){J(m.transformHeader)&&(e=m.transformHeader(e,t)),l.push(e)}if(Array.isArray(c.data[0])){for(var t=0;_()&&t=l.length?\"__parsed_extra\":l[r]),m.transform&&(s=m.transform(s,n)),s=v(n,s),\"__parsed_extra\"===n?(i[n]=i[n]||[],i[n].push(s)):i[n]=s}return m.header&&(r>l.length?k(\"FieldMismatch\",\"TooManyFields\",\"Too many fields: expected \"+l.length+\" fields but parsed \"+r,f+t):r=i.length/2?\"\\r\\n\":\"\\r\"}(e,i)),u=!1,m.delimiter)J(m.delimiter)&&(m.delimiter=m.delimiter(e),c.meta.delimiter=m.delimiter);else{var n=function(e,t,r,i,n){var s,a,o,u;n=n||[\",\",\"\\t\",\"|\",\";\",b.RECORD_SEP,b.UNIT_SEP];for(var h=0;h=N)return L(!0)}else for(S=W,W++;;){if(-1===(S=i.indexOf(z,S+1)))return r||h.push({type:\"Quotes\",code:\"MissingQuotes\",message:\"Quoted field unterminated\",row:u.length,index:W}),T();if(S===n-1)return T(i.substring(W,S).replace(C,z));if(z!==K||i[S+1]!==K){if(z===K||0===S||i[S-1]!==K){-1!==w&&w=N)return L(!0);break}h.push({type:\"Quotes\",code:\"InvalidQuotes\",message:\"Trailing quote on quoted field is malformed\",row:u.length,index:W}),S++}}else S++}return T();function I(e){u.push(e),d=W}function A(e){var t=0;if(-1!==e){var r=i.substring(S+1,e);r&&\"\"===r.trim()&&(t=r.length)}return t}function T(e){return r||(void 0===e&&(e=i.substring(W)),f.push(e),W=n,I(f),o&&F()),L()}function D(e){W=e,I(f),f=[],R=i.indexOf(P,W)}function L(e){return{data:u,errors:h,meta:{delimiter:M,linebreak:P,aborted:H,truncated:!!e,cursor:d+(t||0)}}}function F(){q(L()),u=[],h=[]}},this.abort=function(){H=!0},this.getCharIndex=function(){return W}}function _(e){var t=e.data,r=a[t.workerId],i=!1;if(t.error)r.userError(t.error,t.file);else if(t.results&&t.results.data){var n={abort:function(){i=!0,m(t.workerId,{data:[],errors:[],meta:{aborted:!0}})},pause:y,resume:y};if(J(r.userStep)){for(var s=0;s -1\n };\n }\n\n function normalizeName(name) {\n if (typeof name !== 'string') {\n name = String(name);\n }\n if (/[^a-z0-9\\-#$%&'*+.^_`|~]/i.test(name)) {\n throw new TypeError('Invalid character in header field name')\n }\n return name.toLowerCase()\n }\n\n function normalizeValue(value) {\n if (typeof value !== 'string') {\n value = String(value);\n }\n return value\n }\n\n // Build a destructive iterator for the value list\n function iteratorFor(items) {\n var iterator = {\n next: function() {\n var value = items.shift();\n return {done: value === undefined, value: value}\n }\n };\n\n if (support.iterable) {\n iterator[Symbol.iterator] = function() {\n return iterator\n };\n }\n\n return iterator\n }\n\n function Headers(headers) {\n this.map = {};\n\n if (headers instanceof Headers) {\n headers.forEach(function(value, name) {\n this.append(name, value);\n }, this);\n } else if (Array.isArray(headers)) {\n headers.forEach(function(header) {\n this.append(header[0], header[1]);\n }, this);\n } else if (headers) {\n Object.getOwnPropertyNames(headers).forEach(function(name) {\n this.append(name, headers[name]);\n }, this);\n }\n }\n\n Headers.prototype.append = function(name, value) {\n name = normalizeName(name);\n value = normalizeValue(value);\n var oldValue = this.map[name];\n this.map[name] = oldValue ? oldValue + ', ' + value : value;\n };\n\n Headers.prototype['delete'] = function(name) {\n delete this.map[normalizeName(name)];\n };\n\n Headers.prototype.get = function(name) {\n name = normalizeName(name);\n return this.has(name) ? this.map[name] : null\n };\n\n Headers.prototype.has = function(name) {\n return this.map.hasOwnProperty(normalizeName(name))\n };\n\n Headers.prototype.set = function(name, value) {\n this.map[normalizeName(name)] = normalizeValue(value);\n };\n\n Headers.prototype.forEach = function(callback, thisArg) {\n for (var name in this.map) {\n if (this.map.hasOwnProperty(name)) {\n callback.call(thisArg, this.map[name], name, this);\n }\n }\n };\n\n Headers.prototype.keys = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push(name);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.values = function() {\n var items = [];\n this.forEach(function(value) {\n items.push(value);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.entries = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push([name, value]);\n });\n return iteratorFor(items)\n };\n\n if (support.iterable) {\n Headers.prototype[Symbol.iterator] = Headers.prototype.entries;\n }\n\n function consumed(body) {\n if (body.bodyUsed) {\n return Promise.reject(new TypeError('Already read'))\n }\n body.bodyUsed = true;\n }\n\n function fileReaderReady(reader) {\n return new Promise(function(resolve, reject) {\n reader.onload = function() {\n resolve(reader.result);\n };\n reader.onerror = function() {\n reject(reader.error);\n };\n })\n }\n\n function readBlobAsArrayBuffer(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsArrayBuffer(blob);\n return promise\n }\n\n function readBlobAsText(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsText(blob);\n return promise\n }\n\n function readArrayBufferAsText(buf) {\n var view = new Uint8Array(buf);\n var chars = new Array(view.length);\n\n for (var i = 0; i < view.length; i++) {\n chars[i] = String.fromCharCode(view[i]);\n }\n return chars.join('')\n }\n\n function bufferClone(buf) {\n if (buf.slice) {\n return buf.slice(0)\n } else {\n var view = new Uint8Array(buf.byteLength);\n view.set(new Uint8Array(buf));\n return view.buffer\n }\n }\n\n function Body() {\n this.bodyUsed = false;\n\n this._initBody = function(body) {\n this._bodyInit = body;\n if (!body) {\n this._bodyText = '';\n } else if (typeof body === 'string') {\n this._bodyText = body;\n } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {\n this._bodyBlob = body;\n } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {\n this._bodyFormData = body;\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this._bodyText = body.toString();\n } else if (support.arrayBuffer && support.blob && isDataView(body)) {\n this._bodyArrayBuffer = bufferClone(body.buffer);\n // IE 10-11 can't handle a DataView body.\n this._bodyInit = new Blob([this._bodyArrayBuffer]);\n } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {\n this._bodyArrayBuffer = bufferClone(body);\n } else {\n this._bodyText = body = Object.prototype.toString.call(body);\n }\n\n if (!this.headers.get('content-type')) {\n if (typeof body === 'string') {\n this.headers.set('content-type', 'text/plain;charset=UTF-8');\n } else if (this._bodyBlob && this._bodyBlob.type) {\n this.headers.set('content-type', this._bodyBlob.type);\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');\n }\n }\n };\n\n if (support.blob) {\n this.blob = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return Promise.resolve(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(new Blob([this._bodyArrayBuffer]))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as blob')\n } else {\n return Promise.resolve(new Blob([this._bodyText]))\n }\n };\n\n this.arrayBuffer = function() {\n if (this._bodyArrayBuffer) {\n return consumed(this) || Promise.resolve(this._bodyArrayBuffer)\n } else {\n return this.blob().then(readBlobAsArrayBuffer)\n }\n };\n }\n\n this.text = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return readBlobAsText(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as text')\n } else {\n return Promise.resolve(this._bodyText)\n }\n };\n\n if (support.formData) {\n this.formData = function() {\n return this.text().then(decode)\n };\n }\n\n this.json = function() {\n return this.text().then(JSON.parse)\n };\n\n return this\n }\n\n // HTTP methods whose capitalization should be normalized\n var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];\n\n function normalizeMethod(method) {\n var upcased = method.toUpperCase();\n return methods.indexOf(upcased) > -1 ? upcased : method\n }\n\n function Request(input, options) {\n options = options || {};\n var body = options.body;\n\n if (input instanceof Request) {\n if (input.bodyUsed) {\n throw new TypeError('Already read')\n }\n this.url = input.url;\n this.credentials = input.credentials;\n if (!options.headers) {\n this.headers = new Headers(input.headers);\n }\n this.method = input.method;\n this.mode = input.mode;\n this.signal = input.signal;\n if (!body && input._bodyInit != null) {\n body = input._bodyInit;\n input.bodyUsed = true;\n }\n } else {\n this.url = String(input);\n }\n\n this.credentials = options.credentials || this.credentials || 'same-origin';\n if (options.headers || !this.headers) {\n this.headers = new Headers(options.headers);\n }\n this.method = normalizeMethod(options.method || this.method || 'GET');\n this.mode = options.mode || this.mode || null;\n this.signal = options.signal || this.signal;\n this.referrer = null;\n\n if ((this.method === 'GET' || this.method === 'HEAD') && body) {\n throw new TypeError('Body not allowed for GET or HEAD requests')\n }\n this._initBody(body);\n }\n\n Request.prototype.clone = function() {\n return new Request(this, {body: this._bodyInit})\n };\n\n function decode(body) {\n var form = new FormData();\n body\n .trim()\n .split('&')\n .forEach(function(bytes) {\n if (bytes) {\n var split = bytes.split('=');\n var name = split.shift().replace(/\\+/g, ' ');\n var value = split.join('=').replace(/\\+/g, ' ');\n form.append(decodeURIComponent(name), decodeURIComponent(value));\n }\n });\n return form\n }\n\n function parseHeaders(rawHeaders) {\n var headers = new Headers();\n // Replace instances of \\r\\n and \\n followed by at least one space or horizontal tab with a space\n // https://tools.ietf.org/html/rfc7230#section-3.2\n var preProcessedHeaders = rawHeaders.replace(/\\r?\\n[\\t ]+/g, ' ');\n preProcessedHeaders.split(/\\r?\\n/).forEach(function(line) {\n var parts = line.split(':');\n var key = parts.shift().trim();\n if (key) {\n var value = parts.join(':').trim();\n headers.append(key, value);\n }\n });\n return headers\n }\n\n Body.call(Request.prototype);\n\n function Response(bodyInit, options) {\n if (!options) {\n options = {};\n }\n\n this.type = 'default';\n this.status = options.status === undefined ? 200 : options.status;\n this.ok = this.status >= 200 && this.status < 300;\n this.statusText = 'statusText' in options ? options.statusText : 'OK';\n this.headers = new Headers(options.headers);\n this.url = options.url || '';\n this._initBody(bodyInit);\n }\n\n Body.call(Response.prototype);\n\n Response.prototype.clone = function() {\n return new Response(this._bodyInit, {\n status: this.status,\n statusText: this.statusText,\n headers: new Headers(this.headers),\n url: this.url\n })\n };\n\n Response.error = function() {\n var response = new Response(null, {status: 0, statusText: ''});\n response.type = 'error';\n return response\n };\n\n var redirectStatuses = [301, 302, 303, 307, 308];\n\n Response.redirect = function(url, status) {\n if (redirectStatuses.indexOf(status) === -1) {\n throw new RangeError('Invalid status code')\n }\n\n return new Response(null, {status: status, headers: {location: url}})\n };\n\n exports.DOMException = self.DOMException;\n try {\n new exports.DOMException();\n } catch (err) {\n exports.DOMException = function(message, name) {\n this.message = message;\n this.name = name;\n var error = Error(message);\n this.stack = error.stack;\n };\n exports.DOMException.prototype = Object.create(Error.prototype);\n exports.DOMException.prototype.constructor = exports.DOMException;\n }\n\n function fetch(input, init) {\n return new Promise(function(resolve, reject) {\n var request = new Request(input, init);\n\n if (request.signal && request.signal.aborted) {\n return reject(new exports.DOMException('Aborted', 'AbortError'))\n }\n\n var xhr = new XMLHttpRequest();\n\n function abortXhr() {\n xhr.abort();\n }\n\n xhr.onload = function() {\n var options = {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: parseHeaders(xhr.getAllResponseHeaders() || '')\n };\n options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');\n var body = 'response' in xhr ? xhr.response : xhr.responseText;\n resolve(new Response(body, options));\n };\n\n xhr.onerror = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.ontimeout = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.onabort = function() {\n reject(new exports.DOMException('Aborted', 'AbortError'));\n };\n\n xhr.open(request.method, request.url, true);\n\n if (request.credentials === 'include') {\n xhr.withCredentials = true;\n } else if (request.credentials === 'omit') {\n xhr.withCredentials = false;\n }\n\n if ('responseType' in xhr && support.blob) {\n xhr.responseType = 'blob';\n }\n\n request.headers.forEach(function(value, name) {\n xhr.setRequestHeader(name, value);\n });\n\n if (request.signal) {\n request.signal.addEventListener('abort', abortXhr);\n\n xhr.onreadystatechange = function() {\n // DONE (success or failure)\n if (xhr.readyState === 4) {\n request.signal.removeEventListener('abort', abortXhr);\n }\n };\n }\n\n xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);\n })\n }\n\n fetch.polyfill = true;\n\n if (!self.fetch) {\n self.fetch = fetch;\n self.Headers = Headers;\n self.Request = Request;\n self.Response = Response;\n }\n\n exports.Headers = Headers;\n exports.Request = Request;\n exports.Response = Response;\n exports.fetch = fetch;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n return exports;\n\n})({});\n})(__self__);\n__self__.fetch.ponyfill = true;\n// Remove \"polyfill\" property added by whatwg-fetch\ndelete __self__.fetch.polyfill;\n// Choose between native implementation (global) or custom implementation (__self__)\n// var ctx = global.fetch ? global : __self__;\nvar ctx = __self__; // this line disable service worker support temporarily\nexports = ctx.fetch // To enable: import fetch from 'cross-fetch'\nexports.default = ctx.fetch // For TypeScript consumers without esModuleInterop.\nexports.fetch = ctx.fetch // To enable: import {fetch} from 'cross-fetch'\nexports.Headers = ctx.Headers\nexports.Request = ctx.Request\nexports.Response = ctx.Response\nmodule.exports = exports\n","import crossFetch from 'cross-fetch';\n\nexport async function fetchText(url) {\n const result = await crossFetch(url);\n if (result.status !== 200) {\n throw new Error(String(result.status));\n }\n return result.text();\n}\n","import { generateMFs } from 'mf-generator';\nimport { MF } from 'mf-parser';\nimport Papa from 'papaparse';\n\nimport { fetchText } from './util/fetchText.js';\n\nexport async function mfFromGoogleSheet(url, options = {}) {\n let { urlReferences } = options;\n\n if (urlReferences) {\n let results = await Promise.all([fetchText(url), fetchText(urlReferences)]);\n return parse(results[0], results[1]);\n } else {\n let result = await fetchText(url);\n return parse(result);\n }\n\n async function parse(tsv, tsvReferences) {\n let parsed = Papa.parse(tsv, {\n delimiter: '\\t',\n header: true,\n });\n let fields = parsed.meta.fields;\n let infoFields = fields.filter(\n (a) =>\n !['mf', 'modif', 'ESI', 'MALDI', 'positive', 'negative'].includes(a),\n );\n let formulas = parsed.data;\n let references = {};\n if (tsvReferences) {\n let referencesArray = Papa.parse(tsvReferences, {\n delimiter: '\\t',\n header: true,\n }).data;\n\n referencesArray.forEach((r) => {\n references[r.label] = r;\n });\n }\n\n let results = [];\n for (let formula of formulas) {\n if (tsvReferences) {\n // we add references\n let refs = formula.references.split(/[ ,]+/);\n formula.references = [];\n for (let ref of refs) {\n formula.references.push(references[ref]);\n }\n }\n // we need to calculate all the possibilities\n try {\n let mfs = await generateMFs([formula.mf], {\n ionizations: formula.modif,\n });\n for (let mf of mfs) {\n mf.info = {};\n for (let infoField of infoFields) {\n mf.info[infoField] = formula[infoField];\n }\n if (\n !formula.ESI &&\n !formula.MALDI &&\n !formula.positive &&\n !formula.negative\n ) {\n mf.filter = {\n ESI: true,\n MALDI: true,\n positive: true,\n negative: true,\n };\n } else {\n mf.filter = {\n ESI: formula.ESI === 'X',\n MALDI: formula.MALDI === 'X',\n positive: formula.positive === 'X',\n negative: formula.negative === 'X',\n };\n }\n mf.mf = new MF(mf.mf).toMF();\n results.push(mf);\n }\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Non parsable molecular formula: ',\n formula.mf,\n formula.modif,\n e.toString(),\n );\n }\n }\n\n results = results.filter((a) => {\n return a.ms.em !== 0;\n });\n\n results.sort((a, b) => {\n return a.ms.em - b.ms.em;\n });\n\n let uniqueResults = [results[0]];\n for (let i = 1; i < results.length; i++) {\n if (results[i - 1].ms.em !== results[i].ms.em) {\n uniqueResults.push(results[i]);\n }\n }\n\n return uniqueResults;\n }\n}\n","import { mfFromGoogleSheet } from 'mf-from-google-sheet';\n\nexport async function loadGoogleSheet(options = {}) {\n let {\n refUUID = '1C_H9aiJyu9M9in7sHMOaz-d3Sv758rE72oLxEKH9ioA',\n uuid = '1LrJCl9-xSZKhGA9Y8nKVkYwB-mEOHBkTXg5qYXeFpZY',\n } = options;\n if (options.uuid && !options.refUUID) refUUID = '';\n\n let url = `https://googledocs.cheminfo.org/spreadsheets/d/${uuid}/export?format=tsv`;\n let refURL = refUUID\n ? `https://googledocs.cheminfo.org/spreadsheets/d/${refUUID}/export?format=tsv`\n : '';\n let data = await mfFromGoogleSheet(url, refURL);\n data.sort((a, b) => a.em - b.em);\n return data;\n}\n","import JSZip from 'jszip';\n\nimport { fetchArrayBuffer } from './util/fetchArrayBuffer';\n\nconst loadingPromises = {};\n\nexport async function loadKnapSack(options = {}) {\n const {\n url = 'https://couch.cheminfo.org/cheminfo-public/d2eb480198c80275a1d05dd3609414f9/upload/ms.zip',\n } = options;\n\n if (!loadingPromises[url]) {\n loadingPromises[url] = fetchArrayBuffer(url);\n }\n const buffer = await loadingPromises[url];\n\n const jsZip = new JSZip();\n let zip = await jsZip.loadAsync(buffer);\n let fileData = await zip.files['ms.json'].async('string');\n let data = JSON.parse(fileData);\n\n data.forEach((d) => {\n d.url = `http://kanaya.naist.jp/knapsack_jsp/information.jsp?word=${d.id}`;\n });\n\n data.sort((a, b) => a.em - b.em);\n\n return data;\n}\n","import { generalMatcher } from 'mf-matcher';\n\n/**\n Searching by various criteria. This mass will not take into account the mass\n of the mass of the electron\n* @param {object} [filter={}]\n* @param {number} [filter.minMW=0] - Minimal molecular weight\n* @param {number} [filter.maxMW=+Infinity] - Maximal molecular weight\n* @param {number} [filter.minEM=0] - Minimal monoisotopic mass\n* @param {number} [filter.maxEM=+Infinity] - Maximal monoisotopic mass\n* @param {number} [filter.minCharge=-Infinity] - Minimal charge\n* @param {number} [filter.maxCharge=+Infinity] - Maximal charge\n* @param {boolean} [filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)\n* @param {object} [filter.unsaturation={}]\n* @param {number} [filter.unsaturation.min=-Infinity] - Minimal unsaturation\n* @param {number} [filter.unsaturation.max=+Infinity] - Maximal unsaturation\n* @param {boolean} [filter.unsaturation.onlyInteger=false] - Integer unsaturation\n* @param {boolean} [filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation\n* @param {object} [filter.atoms] - object of atom:{min, max}\n\n* @param {object} [options={}]\n* @param {array} [options.databases] - an array containing the name of the databases so search, by default all\n* @param {boolean} [options.flatten=false] - should we return the array as a flat result\n*/\n\nexport function search(emdb, filter, options = {}) {\n let { databases = Object.keys(emdb.databases), flatten = false } = options;\n\n let results = {};\n for (let database of databases) {\n results[database] = emdb.databases[database].filter((entry) =>\n generalMatcher(entry, filter),\n );\n }\n\n if (flatten) {\n let flattenResults = [];\n for (let database of databases) {\n for (let entry of results[database]) {\n entry.database = database;\n flattenResults.push(entry);\n }\n }\n return flattenResults;\n } else {\n return results;\n }\n}\n","import { msemMatcher } from 'mf-matcher';\nimport { preprocessIonizations } from 'mf-utilities';\n\n/**\nSearch for an experimental monoisotopic mass\n* @param {number} msem - The observed monoisotopic mass\n* @param {object} [options={}]\n* @param {array} [options.databases] - an array containing the name of the databases so search, by default all\n* @param {boolean} [options.flatten=false] - should we return the array as a flat result\n* @param {string} [options.ionizations] - list the allowed ionizations possibilities\n* @param {object} [options.filter={}]\n* @param {number} [options.filter.targetMass] - Target mass, allows to calculate error and filter results\n* @param {number[]} [options.filter.targetMasses] - Target masses: SORTED array of numbers\n* @param {number[]} [options.filter.targetIntensities] - Target intensities: SORTED array of numbers\n* @param {number} [options.filter.minEM=0] - Minimal monoisotopic mass\n* @param {number} [options.filter.maxEM=+Infinity] - Maximal monoisotopic mass\n* @param {number} [options.filter.minMSEM=0] - Minimal monoisotopic mass observed by mass\n* @param {number} [options.filter.maxMSEM=+Infinity] - Maximal monoisotopic mass observed by mass\n* @param {number} [options.filter.minCharge=-Infinity] - Minimal charge\n* @param {number} [options.filter.maxCharge=+Infinity] - Maximal charge\n* @param {boolean} [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)\n* @param {object} [options.filter.unsaturation={}]\n* @param {number} [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation\n* @param {number} [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation\n* @param {boolean} [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation\n* @param {boolean} [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation\n* @param {object} [options.filter.callback] - a function to filter the MF\n* @param {object} [options.filter.atoms] - object of atom:{min, max}\n*/\n\nexport function searchMSEM(emdb, msem, options = {}) {\n let filter = { ...(options.filter || {}), targetMass: msem };\n let { databases = Object.keys(emdb.databases), flatten = false } = options;\n\n let ionizations = preprocessIonizations(options.ionizations);\n let results = {};\n for (let database of databases) {\n results[database] = [];\n }\n for (let ionization of ionizations) {\n filter.ionization = ionization;\n for (let database of databases) {\n for (let entry of emdb.databases[database]) {\n let match = msemMatcher(entry, filter);\n if (match) {\n results[database].push({\n ...entry,\n ms: match.ms,\n ionization: match.ionization,\n });\n }\n }\n }\n }\n if (flatten) {\n let flattenResults = [];\n for (let database of databases) {\n for (let entry of results[database]) {\n entry.database = database;\n flattenResults.push(entry);\n }\n }\n flattenResults.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));\n return flattenResults;\n } else {\n Object.keys(results).forEach((k) =>\n results[k].sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm)),\n );\n return results;\n }\n}\n","export const GAUSSIAN_EXP_FACTOR = -4 * Math.LN2;\nexport const ROOT_PI_OVER_LN2 = Math.sqrt(Math.PI / Math.LN2);\nexport const ROOT_THREE = Math.sqrt(3);\nexport const ROOT_2LN2 = Math.sqrt(2 * Math.LN2);\nexport const ROOT_2LN2_MINUS_ONE = Math.sqrt(2 * Math.LN2) - 1;\n//# sourceMappingURL=constants.js.map","// https://en.wikipedia.org/wiki/Error_function#Inverse_functions\n// This code yields to a good approximation\n// If needed a better implementation using polynomial can be found on https://en.wikipedia.org/wiki/Error_function#Inverse_functions\nexport default function erfinv(x) {\n let a = 0.147;\n if (x === 0)\n return 0;\n let ln1MinusXSqrd = Math.log(1 - x * x);\n let lnEtcBy2Plus2 = ln1MinusXSqrd / 2 + 2 / (Math.PI * a);\n let firstSqrt = Math.sqrt(lnEtcBy2Plus2 ** 2 - ln1MinusXSqrd / a);\n let secondSqrt = Math.sqrt(firstSqrt - lnEtcBy2Plus2);\n return secondSqrt * (x > 0 ? 1 : -1);\n}\n//# sourceMappingURL=erfinv.js.map","import { ROOT_2LN2, GAUSSIAN_EXP_FACTOR, ROOT_PI_OVER_LN2, } from '../../../util/constants';\nimport erfinv from '../../../util/erfinv';\nexport class Gaussian {\n constructor(options = {}) {\n const { fwhm = 500, sd } = options;\n this.fwhm = sd ? gaussianWidthToFWHM(2 * sd) : fwhm;\n }\n fwhmToWidth(fwhm = this.fwhm) {\n return gaussianFwhmToWidth(fwhm);\n }\n widthToFWHM(width) {\n return gaussianWidthToFWHM(width);\n }\n fct(x) {\n return gaussianFct(x, this.fwhm);\n }\n getArea(height = calculateGaussianHeight({ fwhm: this.fwhm })) {\n return getGaussianArea({ fwhm: this.fwhm, height });\n }\n getFactor(area) {\n return getGaussianFactor(area);\n }\n getData(options = {}) {\n return getGaussianData(this, options);\n }\n calculateHeight(area = 1) {\n return calculateGaussianHeight({ fwhm: this.fwhm, area });\n }\n getParameters() {\n return ['fwhm'];\n }\n}\nexport function calculateGaussianHeight(options) {\n let { fwhm = 500, area = 1, sd } = options;\n if (sd)\n fwhm = gaussianWidthToFWHM(2 * sd);\n return (2 * area) / ROOT_PI_OVER_LN2 / fwhm;\n}\n/**\n * Calculate the height of the gaussian function of a specific width (fwhm) at a speicifc\n * x position (the gaussian is centered on x=0)\n * @param x\n * @param fwhm\n * @returns y\n */\nexport function gaussianFct(x, fwhm) {\n return Math.exp(GAUSSIAN_EXP_FACTOR * Math.pow(x / fwhm, 2));\n}\nexport function gaussianWidthToFWHM(width) {\n return width * ROOT_2LN2;\n}\nexport function gaussianFwhmToWidth(fwhm) {\n return fwhm / ROOT_2LN2;\n}\nexport function getGaussianArea(options) {\n let { fwhm = 500, sd, height = 1 } = options;\n if (sd)\n fwhm = gaussianWidthToFWHM(2 * sd);\n return (height * ROOT_PI_OVER_LN2 * fwhm) / 2;\n}\nexport function getGaussianFactor(area = 0.9999) {\n return Math.sqrt(2) * erfinv(area);\n}\nexport function getGaussianData(shape = {}, options = {}) {\n let { fwhm = 500, sd } = shape;\n if (sd)\n fwhm = gaussianWidthToFWHM(2 * sd);\n let { length, factor = getGaussianFactor(), height = calculateGaussianHeight({ fwhm }), } = options;\n if (!length) {\n length = Math.min(Math.ceil(fwhm * factor), Math.pow(2, 25) - 1);\n if (length % 2 === 0)\n length++;\n }\n const center = (length - 1) / 2;\n const data = new Float64Array(length);\n for (let i = 0; i <= center; i++) {\n data[i] = gaussianFct(i - center, fwhm) * height;\n data[length - 1 - i] = data[i];\n }\n return data;\n}\n//# sourceMappingURL=Gaussian.js.map","import { ROOT_THREE } from '../../../util/constants';\nexport class Lorentzian {\n constructor(options = {}) {\n const { fwhm = 500 } = options;\n this.fwhm = fwhm;\n }\n fwhmToWidth(fwhm = this.fwhm) {\n return lorentzianFwhmToWidth(fwhm);\n }\n widthToFWHM(width) {\n return lorentzianWidthToFWHM(width);\n }\n fct(x) {\n return lorentzianFct(x, this.fwhm);\n }\n getArea(height = 1) {\n return getLorentzianArea({ fwhm: this.fwhm, height });\n }\n getFactor(area) {\n return getLorentzianFactor(area);\n }\n getData(options = {}) {\n return getLorentzianData(this, options);\n }\n calculateHeight(area = 1) {\n return calculateLorentzianHeight({ fwhm: this.fwhm, area });\n }\n getParameters() {\n return ['fwhm'];\n }\n}\nexport const calculateLorentzianHeight = ({ fwhm = 1, area = 1 }) => {\n return (2 * area) / Math.PI / fwhm;\n};\nexport const getLorentzianArea = (options) => {\n const { fwhm = 500, height = 1 } = options;\n return (height * Math.PI * fwhm) / 2;\n};\nexport const lorentzianFct = (x, fwhm) => {\n return fwhm ** 2 / (4 * x ** 2 + fwhm ** 2);\n};\nexport const lorentzianWidthToFWHM = (width) => {\n return width * ROOT_THREE;\n};\nexport const lorentzianFwhmToWidth = (fwhm) => {\n return fwhm / ROOT_THREE;\n};\nexport const getLorentzianFactor = (area = 0.9999) => {\n if (area >= 1) {\n throw new Error('area should be (0 - 1)');\n }\n const halfResidual = (1 - area) * 0.5;\n const quantileFunction = (p) => Math.tan(Math.PI * (p - 0.5));\n return ((quantileFunction(1 - halfResidual) - quantileFunction(halfResidual)) / 2);\n};\nexport const getLorentzianData = (shape = {}, options = {}) => {\n let { fwhm = 500 } = shape;\n let { length, factor = getLorentzianFactor(), height = calculateLorentzianHeight({ fwhm, area: 1 }), } = options;\n if (!length) {\n length = Math.min(Math.ceil(fwhm * factor), Math.pow(2, 25) - 1);\n if (length % 2 === 0)\n length++;\n }\n const center = (length - 1) / 2;\n const data = new Float64Array(length);\n for (let i = 0; i <= center; i++) {\n data[i] = lorentzianFct(i - center, fwhm) * height;\n data[length - 1 - i] = data[i];\n }\n return data;\n};\n//# sourceMappingURL=Lorentzian.js.map","import { GAUSSIAN_EXP_FACTOR, ROOT_2LN2_MINUS_ONE, ROOT_PI_OVER_LN2, } from '../../../util/constants';\nimport { gaussianFct, getGaussianFactor } from '../gaussian/Gaussian';\nimport { lorentzianFct, getLorentzianFactor } from '../lorentzian/Lorentzian';\nexport class PseudoVoigt {\n constructor(options = {}) {\n const { fwhm = 500, mu = 0.5 } = options;\n this.mu = mu;\n this.fwhm = fwhm;\n }\n fwhmToWidth(fwhm = this.fwhm, mu = this.mu) {\n return pseudoVoigtFwhmToWidth(fwhm, mu);\n }\n widthToFWHM(width, mu = this.mu) {\n return pseudoVoigtWidthToFWHM(width, mu);\n }\n fct(x) {\n return pseudoVoigtFct(x, this.fwhm, this.mu);\n }\n getArea(height = 1) {\n return getPseudoVoigtArea({ fwhm: this.fwhm, height, mu: this.mu });\n }\n getFactor(area) {\n return getPseudoVoigtFactor(area);\n }\n getData(options = {}) {\n const { length, factor, height = calculatePseudoVoigtHeight({\n fwhm: this.fwhm,\n mu: this.mu,\n area: 1,\n }), } = options;\n return getPseudoVoigtData(this, { factor, length, height });\n }\n calculateHeight(area = 1) {\n return calculatePseudoVoigtHeight({ fwhm: this.fwhm, mu: this.mu, area });\n }\n getParameters() {\n return ['fwhm', 'mu'];\n }\n}\nexport const calculatePseudoVoigtHeight = (options = {}) => {\n let { fwhm = 1, mu = 0.5, area = 1 } = options;\n return (2 * area) / (fwhm * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI));\n};\nexport const pseudoVoigtFct = (x, fwhm, mu) => {\n return (1 - mu) * lorentzianFct(x, fwhm) + mu * gaussianFct(x, fwhm);\n};\nexport const pseudoVoigtWidthToFWHM = (width, mu = 0.5) => {\n return width * (mu * ROOT_2LN2_MINUS_ONE + 1);\n};\nexport const pseudoVoigtFwhmToWidth = (fwhm, mu = 0.5) => {\n return fwhm / (mu * ROOT_2LN2_MINUS_ONE + 1);\n};\nexport const getPseudoVoigtArea = (options) => {\n const { fwhm = 500, height = 1, mu = 0.5 } = options;\n return (fwhm * height * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI)) / 2;\n};\nexport const getPseudoVoigtFactor = (area = 0.9999, mu = 0.5) => {\n return mu < 1 ? getLorentzianFactor(area) : getGaussianFactor(area);\n};\nexport const getPseudoVoigtData = (shape = {}, options = {}) => {\n let { fwhm = 500, mu = 0.5 } = shape;\n let { length, factor = getPseudoVoigtFactor(0.999, mu), height = calculatePseudoVoigtHeight({ fwhm, mu, area: 1 }), } = options;\n if (!height) {\n height =\n 1 /\n ((mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI)) * fwhm +\n ((1 - mu) * fwhm * Math.PI) / 2);\n }\n if (!length) {\n length = Math.min(Math.ceil(fwhm * factor), Math.pow(2, 25) - 1);\n if (length % 2 === 0)\n length++;\n }\n const center = (length - 1) / 2;\n const data = new Float64Array(length);\n for (let i = 0; i <= center; i++) {\n data[i] = pseudoVoigtFct(i - center, fwhm, mu) * height;\n data[length - 1 - i] = data[i];\n }\n return data;\n};\n//# sourceMappingURL=PseudoVoigt.js.map","import { Gaussian } from './gaussian/Gaussian';\nimport { Lorentzian } from './lorentzian/Lorentzian';\nimport { PseudoVoigt } from './pseudoVoigt/PseudoVoigt';\n/**\n * Generate a instance of a specific kind of shape.\n */\nexport function getShape1D(shape) {\n const { kind } = shape;\n switch (kind) {\n case 'gaussian':\n return new Gaussian(shape);\n case 'lorentzian':\n return new Lorentzian(shape);\n case 'pseudoVoigt':\n return new PseudoVoigt(shape);\n default: {\n throw Error(`Unknown distribution ${kind}`);\n }\n }\n}\n//# sourceMappingURL=getShape1D.js.map","export default function addBaseline(data, baselineFct) {\n if (!baselineFct)\n return data;\n let xs = data.x;\n let ys = data.y;\n for (let i = 0; i < xs.length; i++) {\n ys[i] += baselineFct(xs[i]);\n }\n return data;\n}\n//# sourceMappingURL=addBaseline.js.map","// eslint-disable-next-line @typescript-eslint/unbound-method\nconst toString = Object.prototype.toString;\n/**\n * Checks if an object is an instance of an Array (array or typed array, except those that contain bigint values).\n *\n * @param value - Object to check.\n * @returns True if the object is an array or a typed array.\n */\nexport function isAnyArray(value) {\n const tag = toString.call(value);\n return tag.endsWith('Array]') && !tag.includes('Big');\n}\n//# sourceMappingURL=index.js.map","import { isAnyArray } from 'is-any-array';\n/**\n * This function xAdd the first array by the second array or a constant value to each element of the first array\n *\n * @param array1 - the first array\n * @param array2 - the second array or number\n */\nexport function xAdd(array1, array2) {\n let isConstant = false;\n let constant = 0;\n if (isAnyArray(array2)) {\n if (array1.length !== array2.length) {\n throw new Error('xAdd: size of array1 and array2 must be identical');\n }\n }\n else {\n isConstant = true;\n constant = array2;\n }\n let array3 = new Float64Array(array1.length);\n if (isConstant) {\n for (let i = 0; i < array1.length; i++) {\n array3[i] = array1[i] + constant;\n }\n }\n else {\n for (let i = 0; i < array1.length; i++) {\n array3[i] = array1[i] + array2[i];\n }\n }\n return array3;\n}\n//# sourceMappingURL=xAdd.js.map","import { isAnyArray } from 'is-any-array';\n/**\n * Checks if input is of type array\n *\n * @param input - input\n */\nexport function xCheck(input, options = {}) {\n const { minLength } = options;\n if (!isAnyArray(input)) {\n throw new TypeError('input must be an array');\n }\n if (input.length === 0) {\n throw new TypeError('input must not be empty');\n }\n if (minLength && input.length < minLength) {\n throw new Error(`input must have a length of at least ${minLength}`);\n }\n}\n//# sourceMappingURL=xCheck.js.map","/**\n * Returns the closest index of a `target`\n *\n * @param array - array of numbers\n * @param target - target\n * @returns - closest index\n */\nexport function xFindClosestIndex(array, target, options = {}) {\n const { sorted = true } = options;\n if (sorted) {\n let low = 0;\n let high = array.length - 1;\n let middle = 0;\n while (high - low > 1) {\n middle = low + ((high - low) >> 1);\n if (array[middle] < target) {\n low = middle;\n }\n else if (array[middle] > target) {\n high = middle;\n }\n else {\n return middle;\n }\n }\n if (low < array.length - 1) {\n if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {\n return low;\n }\n else {\n return low + 1;\n }\n }\n else {\n return low;\n }\n }\n else {\n let index = 0;\n let diff = Number.POSITIVE_INFINITY;\n for (let i = 0; i < array.length; i++) {\n const currentDiff = Math.abs(array[i] - target);\n if (currentDiff < diff) {\n diff = currentDiff;\n index = i;\n }\n }\n return index;\n }\n}\n//# sourceMappingURL=xFindClosestIndex.js.map","import { xFindClosestIndex } from './xFindClosestIndex';\n/**\n * Returns an object with {fromIndex, toIndex} for a specific from / to\n *\n * @param x - array of numbers\n * @param options - Options\n */\nexport function xGetFromToIndex(x, options = {}) {\n let { fromIndex, toIndex, from, to } = options;\n if (fromIndex === undefined) {\n if (from !== undefined) {\n fromIndex = xFindClosestIndex(x, from);\n }\n else {\n fromIndex = 0;\n }\n }\n if (toIndex === undefined) {\n if (to !== undefined) {\n toIndex = xFindClosestIndex(x, to);\n }\n else {\n toIndex = x.length - 1;\n }\n }\n if (fromIndex < 0)\n fromIndex = 0;\n if (toIndex < 0)\n toIndex = 0;\n if (fromIndex >= x.length)\n fromIndex = x.length - 1;\n if (toIndex >= x.length)\n toIndex = x.length - 1;\n if (fromIndex > toIndex)\n [fromIndex, toIndex] = [toIndex, fromIndex];\n return { fromIndex, toIndex };\n}\n//# sourceMappingURL=xGetFromToIndex.js.map","import { xCheck } from './xCheck';\nimport { xGetFromToIndex } from './xGetFromToIndex';\n/**\n * Computes the maximal value of an array of values\n *\n * @param array - array of numbers\n * @param options - options\n */\nexport function xMaxValue(array, options = {}) {\n xCheck(array);\n const { fromIndex, toIndex } = xGetFromToIndex(array, options);\n let maxValue = array[fromIndex];\n for (let i = fromIndex + 1; i <= toIndex; i++) {\n if (array[i] > maxValue) {\n maxValue = array[i];\n }\n }\n return maxValue;\n}\n//# sourceMappingURL=xMaxValue.js.map","const LOOP = 8;\nconst FLOAT_MUL = 1 / 16777216;\nconst sh1 = 15;\nconst sh2 = 18;\nconst sh3 = 11;\nfunction multiply_uint32(n, m) {\n n >>>= 0;\n m >>>= 0;\n const nlo = n & 0xffff;\n const nhi = n - nlo;\n return (((nhi * m) >>> 0) + nlo * m) >>> 0;\n}\nexport default class XSadd {\n constructor(seed = Date.now()) {\n this.state = new Uint32Array(4);\n this.init(seed);\n this.random = this.getFloat.bind(this);\n }\n /**\n * Returns a 32-bit integer r (0 <= r < 2^32)\n */\n getUint32() {\n this.nextState();\n return (this.state[3] + this.state[2]) >>> 0;\n }\n /**\n * Returns a floating point number r (0.0 <= r < 1.0)\n */\n getFloat() {\n return (this.getUint32() >>> 8) * FLOAT_MUL;\n }\n init(seed) {\n if (!Number.isInteger(seed)) {\n throw new TypeError('seed must be an integer');\n }\n this.state[0] = seed;\n this.state[1] = 0;\n this.state[2] = 0;\n this.state[3] = 0;\n for (let i = 1; i < LOOP; i++) {\n this.state[i & 3] ^=\n (i +\n multiply_uint32(1812433253, this.state[(i - 1) & 3] ^ ((this.state[(i - 1) & 3] >>> 30) >>> 0))) >>>\n 0;\n }\n this.periodCertification();\n for (let i = 0; i < LOOP; i++) {\n this.nextState();\n }\n }\n periodCertification() {\n if (this.state[0] === 0 &&\n this.state[1] === 0 &&\n this.state[2] === 0 &&\n this.state[3] === 0) {\n this.state[0] = 88; // X\n this.state[1] = 83; // S\n this.state[2] = 65; // A\n this.state[3] = 68; // D\n }\n }\n nextState() {\n let t = this.state[0];\n t ^= t << sh1;\n t ^= t >>> sh2;\n t ^= this.state[3] << sh3;\n this.state[0] = this.state[1];\n this.state[1] = this.state[2];\n this.state[2] = this.state[3];\n this.state[3] = t;\n }\n}\n","import XSAdd from 'ml-xsadd';\n/**\n * Create a random array of numbers of a specific length\n *\n * @return - array of random floats normally distributed\n */\nlet spare;\nlet hasSpare = false;\nexport function createRandomArray(options = {}) {\n const { mean = 0, standardDeviation = 1, length = 1000, range = 1, seed, distribution = 'normal', } = options;\n const generator = new XSAdd(seed);\n let returnArray = new Float64Array(length);\n switch (distribution) {\n case 'normal':\n for (let i = 0; i < length; i++) {\n returnArray[i] = generateGaussian(mean, standardDeviation, generator);\n }\n break;\n case 'uniform':\n for (let i = 0; i < length; i++) {\n returnArray[i] = (generator.random() - 0.5) * range + mean;\n }\n break;\n default:\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n throw new Error(`unknown distribution: ${distribution}`);\n }\n return returnArray;\n}\nfunction generateGaussian(mean, standardDeviation, generator) {\n let val, u, v, s;\n if (hasSpare) {\n hasSpare = false;\n val = spare * standardDeviation + mean;\n }\n else {\n do {\n u = generator.random() * 2 - 1;\n v = generator.random() * 2 - 1;\n s = u * u + v * v;\n } while (s >= 1 || s === 0);\n s = Math.sqrt((-2 * Math.log(s)) / s);\n spare = v * s;\n hasSpare = true;\n val = mean + standardDeviation * u * s;\n }\n return val;\n}\n//# sourceMappingURL=createRandomArray.js.map","import { xMaxValue, xAdd, createRandomArray } from 'ml-spectra-processing';\nexport default function addNoise(data, options = {}) {\n const { seed = 0, distribution = 'normal', percent = 1 } = options;\n const range = (xMaxValue(data.y) * percent) / 100;\n const noise = createRandomArray({\n distribution,\n seed,\n mean: 0,\n standardDeviation: range,\n range,\n length: data.x.length,\n });\n data.y = xAdd(data.y, noise);\n return data;\n}\n//# sourceMappingURL=addNoise.js.map","import { getShape1D } from 'ml-peak-shape-generator';\nimport addBaseline from './util/addBaseline';\nimport addNoise from './util/addNoise';\nexport class SpectrumGenerator {\n constructor(options = {}) {\n const { from = 0, to = 1000, nbPoints = 10001, peakWidthFct, shape = { kind: 'gaussian', fwhm: 5 }, } = options;\n this.from = from;\n this.to = to;\n this.nbPoints = nbPoints;\n this.interval = (this.to - this.from) / (this.nbPoints - 1);\n this.peakWidthFct = peakWidthFct;\n this.maxPeakHeight = Number.MIN_SAFE_INTEGER;\n this.data = {\n x: new Float64Array(this.nbPoints),\n y: new Float64Array(this.nbPoints),\n };\n let shapeGenerator = getShape1D(shape);\n this.shape = shapeGenerator;\n assertNumber(this.from, 'from');\n assertNumber(this.to, 'to');\n assertInteger(this.nbPoints, 'nbPoints');\n if (this.to <= this.from) {\n throw new RangeError('to option must be larger than from');\n }\n if (this.peakWidthFct && typeof this.peakWidthFct !== 'function') {\n throw new TypeError('peakWidthFct option must be a function');\n }\n this.reset();\n }\n /**\n * Add a series of peaks to the spectrum.\n * @param peaks - Peaks to add.\n */\n addPeaks(peaks, options) {\n if (!Array.isArray(peaks) &&\n (typeof peaks !== 'object' ||\n peaks.x === undefined ||\n peaks.y === undefined ||\n !Array.isArray(peaks.x) ||\n !Array.isArray(peaks.y) ||\n peaks.x.length !== peaks.y.length)) {\n throw new TypeError('peaks must be an array or an object containing x[] and y[]');\n }\n if (Array.isArray(peaks)) {\n for (const peak of peaks) {\n this.addPeak(peak, options);\n }\n }\n else {\n for (let i = 0; i < peaks.x.length; i++) {\n this.addPeak([peaks.x[i], peaks.y[i]], options);\n }\n }\n }\n /**\n * Add a single peak to the spectrum.\n * A peak may be either defined as [x,y,fwhm,...] or as {x, y, shape}\n * @param peak\n * @param options\n */\n addPeak(peak, options = {}) {\n if (Array.isArray(peak) && peak.length < 2) {\n throw new Error('peak must be an array with two (or three) values or an object with {x,y,width?}');\n }\n if (!Array.isArray(peak) &&\n (peak.x === undefined || peak.y === undefined)) {\n throw new Error('peak must be an array with two (or three) values or an object with {x,y,width?}');\n }\n let xPosition;\n let intensity;\n let peakFWHM;\n let peakWidth;\n let peakShapeOptions;\n if (Array.isArray(peak)) {\n [xPosition, intensity, peakFWHM, peakShapeOptions] = peak;\n }\n else {\n xPosition = peak.x;\n intensity = peak.y;\n peakWidth = peak.width;\n peakShapeOptions = peak.shape;\n }\n if (intensity > this.maxPeakHeight)\n this.maxPeakHeight = intensity;\n let { shape: shapeOptions } = options;\n if (peakShapeOptions) {\n shapeOptions = shapeOptions\n ? { ...shapeOptions, ...peakShapeOptions }\n : peakShapeOptions;\n }\n const shape = shapeOptions\n ? getShape1D(shapeOptions)\n : Object.assign(Object.create(Object.getPrototypeOf(this.shape)), this.shape);\n let { width, widthLeft, widthRight } = options;\n /*\n if we don't force the fwhm we just take the one from the shape\n however we have many way to force it:\n - use [x,y,fwhm]\n - define `width` that will be converted to fwhm\n - define `widthLeft` and `widthRight` to define asymmetric peaks\n - have a callback `peakWidthFct`\n This should evolve in the future because we will not always have `fwhm`\n */\n const fwhm = peakFWHM !== undefined\n ? peakFWHM\n : peakWidth\n ? shape.widthToFWHM(peakWidth)\n : this.peakWidthFct\n ? this.peakWidthFct(xPosition)\n : width !== undefined\n ? width\n : shape.fwhm;\n if (!widthLeft)\n widthLeft = fwhm;\n if (!widthRight)\n widthRight = fwhm;\n if (!widthLeft || !widthRight) {\n throw new Error('Width left or right is undefined or zero');\n }\n let factor = options.factor === undefined ? shape.getFactor() : options.factor;\n const firstValue = xPosition - (widthLeft / 2) * factor;\n const lastValue = xPosition + (widthRight / 2) * factor;\n const firstPoint = Math.max(0, Math.floor((firstValue - this.from) / this.interval));\n const lastPoint = Math.min(this.nbPoints - 1, Math.ceil((lastValue - this.from) / this.interval));\n const middlePoint = Math.round((xPosition - this.from) / this.interval);\n // PEAK SHAPE MAY BE ASYMMETRC (widthLeft and widthRight) !\n // we calculate the left part of the shape\n shape.fwhm = widthLeft;\n for (let index = firstPoint; index < Math.max(middlePoint, 0); index++) {\n this.data.y[index] +=\n intensity * shape.fct(this.data.x[index] - xPosition);\n }\n // we calculate the right part of the gaussian\n shape.fwhm = widthRight;\n for (let index = Math.min(middlePoint, lastPoint); index <= lastPoint; index++) {\n this.data.y[index] +=\n intensity * shape.fct(this.data.x[index] - xPosition);\n }\n }\n /**\n * Add a baseline to the spectrum.\n * @param baselineFct - Mathematical function producing the baseline you want.\n */\n addBaseline(baselineFct) {\n addBaseline(this.data, baselineFct);\n return this;\n }\n /**\n * Add noise to the spectrum.\n *\n * @param percent - Noise's amplitude in percents of the spectrum max value. Default: 1.\n */\n addNoise(options) {\n addNoise(this.data, options);\n return this;\n }\n /**\n * Get the generated spectrum.\n */\n getSpectrum(options = {}) {\n if (typeof options === 'boolean') {\n options = { copy: options };\n }\n const { copy = true, threshold = 0 } = options;\n if (threshold) {\n let minPeakHeight = this.maxPeakHeight * threshold;\n let x = [];\n let y = [];\n for (let i = 0; i < this.data.x.length; i++) {\n if (this.data.y[i] >= minPeakHeight) {\n x.push(this.data.x[i]);\n y.push(this.data.y[i]);\n }\n }\n return { x, y };\n }\n if (copy) {\n return {\n x: this.data.x.slice(),\n y: this.data.y.slice(),\n };\n }\n else {\n return this.data;\n }\n }\n /**\n * Resets the generator with an empty spectrum.\n */\n reset() {\n const spectrum = this.data;\n for (let i = 0; i < this.nbPoints; i++) {\n spectrum.x[i] = this.from + i * this.interval;\n }\n return this;\n }\n}\nfunction assertInteger(value, name) {\n if (!Number.isInteger(value)) {\n throw new TypeError(`${name} option must be an integer`);\n }\n}\nfunction assertNumber(value, name) {\n if (!Number.isFinite(value)) {\n throw new TypeError(`${name} option must be a number`);\n }\n}\n/**\n * Generates a spectrum and returns it.\n * @param peaks - List of peaks to put in the spectrum.\n * @param options\n */\nexport function generateSpectrum(peaks, options = {}) {\n const { generator: generatorOptions, noise, baseline, threshold, peakOptions, } = options;\n const generator = new SpectrumGenerator(generatorOptions);\n generator.addPeaks(peaks, peakOptions);\n if (baseline)\n generator.addBaseline(baseline);\n if (noise) {\n generator.addNoise(noise);\n }\n return generator.getSpectrum({\n threshold,\n });\n}\n//# sourceMappingURL=SpectrumGenerator.js.map","export function closestPointX(array, target) {\n let low = 0;\n let high = array.length - 1;\n let middle = 0;\n while (high - low > 1) {\n middle = low + ((high - low) >> 1);\n if (array[middle].x < target) {\n low = middle;\n } else if (array[middle].x > target) {\n high = middle;\n } else {\n return array[middle];\n }\n }\n\n if (low < array.length - 1) {\n if (Math.abs(target - array[low].x) < Math.abs(array[low + 1].x - target)) {\n return array[low];\n } else {\n return array[low + 1];\n }\n } else {\n return array[low];\n }\n}\n","/**\n * Join x values if there are similar\n */\n\nexport function joinX(self, threshold = Number.EPSILON) {\n // when we join we will use the center of mass\n if (self.array.length === 0) return [];\n self.sortX();\n let current = self.array[0];\n let result = [current];\n for (let i = 1; i < self.array.length; i++) {\n const item = self.array[i];\n if (item.x - current.x <= threshold) {\n // weighted sum\n current.x =\n (item.y / (current.y + item.y)) * (item.x - current.x) + current.x;\n current.y += item.y;\n } else {\n current = {\n x: item.x,\n y: item.y,\n };\n if (item.composition) current.composition = item.composition;\n result.push(current);\n }\n }\n self.array = result;\n self.ySorted = false;\n return self;\n}\n","export function multiply(a, b, options = {}) {\n const { minY = 1e-8, maxLines = 5000, deltaX = 1e-2 } = options;\n const result = new a.constructor();\n\n a.sortY();\n b.sortY();\n\n for (let entryA of a.array) {\n for (let entryB of b.array) {\n let y = entryA.y * entryB.y;\n if (y > minY) {\n const composition = calculateComposition(entryA, entryB);\n if (composition) {\n result.push({ x: entryA.x + entryB.x, y, composition });\n } else {\n result.push({ x: entryA.x + entryB.x, y });\n }\n }\n if (result.length > maxLines * 2) {\n result.joinX(deltaX);\n result.topY(maxLines);\n }\n }\n }\n result.joinX(deltaX);\n result.topY(maxLines);\n a.move(result);\n return a;\n}\n\nfunction calculateComposition(entryA, entryB) {\n if (!entryA.composition || !entryB.composition) return;\n let toReturn = {};\n const keys = [\n ...new Set(\n Object.keys(entryA.composition).concat(Object.keys(entryB.composition)),\n ),\n ];\n for (let key of keys) {\n toReturn[key] =\n (entryA.composition[key] || 0) + (entryB.composition[key] || 0);\n }\n return toReturn;\n}\n","// https://en.wikipedia.org/wiki/Exponentiation_by_squaring\n\nexport function power(a, p, options = {}) {\n if (p <= 0) throw new Error('power must be larger than 0');\n if (p === 1) return a;\n if (p === 2) {\n return a.square();\n }\n\n p--;\n let base = a.copy(); // linear time\n while (p !== 0) {\n if ((p & 1) !== 0) {\n a.multiply(base, options); // executed <= log2(p) times\n }\n p >>= 1;\n if (p !== 0) base.square(options); // executed <= log2(p) times\n }\n\n return a;\n}\n","import { closestPointX } from './utils/closestPointX.js';\nimport { joinX } from './utils/joinX.js';\nimport { multiply } from './utils/multiply.js';\nimport { power } from './utils/power.js';\n\n/**\n * Internal class to deal with isotopic distribution calculations\n */\nexport class Distribution {\n constructor(array) {\n if (Array.isArray(array)) {\n this.array = array;\n this.xSorted = false;\n this.ySorted = false;\n } else {\n this.array = [];\n this.xSorted = true;\n this.ySorted = true;\n }\n }\n\n get length() {\n return this.array.length;\n }\n\n get xs() {\n return this.array.map((p) => p.x);\n }\n\n get ys() {\n return this.array.map((p) => p.y);\n }\n\n get minX() {\n if (!this.xSorted) this.sortX();\n return this.array[0].x;\n }\n\n get maxX() {\n if (!this.xSorted) this.sortX();\n return this.array[this.array.length - 1].x;\n }\n\n get minY() {\n if (!this.ySorted) this.sortY();\n return this.array[0].y;\n }\n\n get maxY() {\n if (!this.ySorted) this.sortY();\n return this.array[this.array.length - 1];\n }\n\n multiplyY(value) {\n this.array.forEach((item) => (item.y *= value));\n }\n\n setArray(array) {\n this.array = array;\n this.xSorted = false;\n this.ySorted = false;\n }\n\n move(other) {\n this.xSorted = other.xSorted;\n this.ySorted = other.ySorted;\n this.array = other.array;\n }\n\n push(point) {\n this.array.push(point);\n\n this.xSorted = false;\n this.ySorted = false;\n }\n\n sortX() {\n this.ySorted = false;\n if (this.xSorted) return this;\n this.array.sort((a, b) => a.x - b.x);\n this.xSorted = true;\n return this;\n }\n\n sortY() {\n this.xSorted = false;\n if (this.ySorted) return this;\n this.array.sort((a, b) => b.y - a.y);\n this.ySorted = true;\n return this;\n }\n\n normalize() {\n let sum = 0;\n for (let item of this.array) {\n sum += item.y;\n }\n for (let item of this.array) {\n item.y /= sum;\n }\n return this;\n }\n\n topY(limit) {\n if (!limit) return this;\n if (this.array.length <= limit) return this;\n this.sortY();\n this.array.splice(limit);\n return this;\n }\n\n square(options = {}) {\n return this.multiply(this, options);\n }\n\n multiply(b, options) {\n return multiply(this, b, options);\n }\n\n power(p, options) {\n return power(this, p, options);\n }\n\n copy() {\n let distCopy = new Distribution();\n distCopy.xSorted = this.xSorted;\n distCopy.ySorted = this.ySorted;\n distCopy.array = JSON.parse(JSON.stringify(this.array));\n return distCopy;\n }\n\n maxToOne() {\n if (this.array.length === 0) return this;\n let currentMax = this.array[0].y;\n for (let item of this.array) {\n if (item.y > currentMax) currentMax = item.y;\n }\n for (let item of this.array) {\n item.y /= currentMax;\n }\n return this;\n }\n\n joinX(threshold) {\n return joinX(this, threshold);\n }\n\n append(distribution) {\n for (let item of distribution.array) {\n this.array.push(item);\n }\n this.xSorted = false;\n this.ySorted = false;\n }\n\n closestPointX(target) {\n this.sortX();\n return closestPointX(this.array, target);\n }\n}\n","import { stableIsotopesObject } from 'chemical-elements';\nimport { subscript, superscript } from 'mf-parser';\n\nexport function getDerivedCompositionInfo(composition) {\n const shortComposition = {};\n let label = '';\n let shortLabel = '';\n for (let key in composition) {\n let isotopeLabel = '';\n for (let i = 0; i < key.length; i++) {\n if (superscript[key[i]]) {\n isotopeLabel += superscript[key[i]];\n } else {\n isotopeLabel += key[i];\n }\n }\n if (composition[key] > 1) {\n const number = String(composition[key]);\n for (let i = 0; i < number.length; i++) {\n isotopeLabel += subscript[number[i]];\n }\n }\n label += isotopeLabel;\n if (stableIsotopesObject[key].mostAbundant) continue;\n shortLabel += isotopeLabel;\n shortComposition[key] = composition[key];\n }\n\n return { label, shortComposition, shortLabel };\n}\n","import { ELECTRON_MASS } from 'chemical-elements';\nimport { MF } from 'mf-parser';\nimport { preprocessIonizations, getMsInfo } from 'mf-utilities';\nimport { SpectrumGenerator } from 'spectrum-generator';\n\nimport { Distribution } from './Distribution';\nimport { getDerivedCompositionInfo } from './utils/getDerivedCompositionInfo';\n\nconst MINIMAL_FWHM = 1e-8;\n\n/**\n * An object containing two arrays\n * @typedef {object} XY\n * @property {number[]} x - The x array\n * @property {number[]} y - The y array\n */\n\n/**\n * A class that allows to manage isotopic distribution\n */\nexport class IsotopicDistribution {\n /**\n * Class that manage isotopic distribution\n * @param {string|array} value - Molecular formula or an array of parts\n * @param {object} [options={}]\n * @param {string} [options.ionizations=''] - string containing a comma separated list of modifications\n * @param {number} [options.fwhm=0.01] - Amount of Dalton under which 2 peaks are joined\n * @param {number} [options.maxLines=5000] - Maximal number of lines during calculations\n * @param {number} [options.minY=1e-8] - Minimal signal height during calculations\n * @param {number} [options.ensureCase=false] - Ensure uppercase / lowercase\n * @param {number} [options.allowNeutral=true] - Should we keep the distribution if the molecule has no charge\n */\n\n constructor(value, options = {}) {\n if (Array.isArray(value)) {\n this.parts = JSON.parse(JSON.stringify(value));\n for (let part of this.parts) {\n part.confidence = 0;\n part.isotopesInfo = new MF(\n `${part.mf}(${part.ionization.mf})`,\n ).getIsotopesInfo();\n }\n } else {\n let mf = new MF(value, { ensureCase: options.ensureCase });\n let mfInfo = mf.getInfo();\n let ionizations = preprocessIonizations(options.ionizations);\n let parts = mfInfo.parts || [mfInfo];\n this.parts = [];\n for (let partOriginal of parts) {\n // we calculate information for each part\n for (const ionization of ionizations) {\n let part = JSON.parse(JSON.stringify(partOriginal));\n part.em = part.monoisotopicMass; // TODO: To remove !!! we change the name !?\n part.isotopesInfo = new MF(\n `${part.mf}(${ionization.mf})`,\n ).getIsotopesInfo();\n part.confidence = 0;\n let msInfo = getMsInfo(part, {\n ionization,\n });\n part.ionization = msInfo.ionization;\n part.ms = msInfo.ms;\n this.parts.push(part);\n }\n }\n }\n\n this.cachedDistribution = undefined;\n this.fwhm = options.fwhm === undefined ? 0.01 : options.fwhm;\n // if fwhm is under 1e-8 there are some artifacts in the spectra\n if (this.fwhm < MINIMAL_FWHM) this.fwhm = MINIMAL_FWHM;\n this.minY = options.minY === undefined ? MINIMAL_FWHM : options.minY;\n this.maxLines = options.maxLines || 5000;\n this.allowNeutral =\n options.allowNeutral === undefined ? true : options.allowNeutral;\n }\n\n getParts() {\n return this.parts;\n }\n\n /**\n * @return {Distribution} returns the total distribution (for all parts)\n */\n getDistribution() {\n if (this.cachedDistribution) return this.cachedDistribution;\n let options = {\n maxLines: this.maxLines,\n minY: this.minY,\n deltaX: this.fwhm,\n };\n let finalDistribution = new Distribution();\n this.confidence = 0;\n // TODO need to cache each part without ionization\n // in case of many ionization we don't need to recalculate everything !\n for (let part of this.parts) {\n let totalDistribution = new Distribution([\n {\n x: 0,\n y: 1,\n composition: this.fwhm === MINIMAL_FWHM ? {} : undefined,\n },\n ]);\n let charge = part.ms.charge;\n let absoluteCharge = Math.abs(charge);\n if (charge || this.allowNeutral) {\n for (let isotope of part.isotopesInfo.isotopes) {\n if (isotope.number < 0) return { array: [] };\n if (isotope.number > 0) {\n const newDistribution = JSON.parse(\n JSON.stringify(isotope.distribution),\n );\n if (this.fwhm === MINIMAL_FWHM) {\n // add composition\n for (const entry of newDistribution) {\n entry.composition = { [Math.round(entry.x) + isotope.atom]: 1 };\n }\n }\n let distribution = new Distribution(newDistribution);\n distribution.power(isotope.number, options);\n totalDistribution.multiply(distribution, options);\n }\n }\n this.confidence += totalDistribution.array.reduce(\n (sum, value) => sum + value.y,\n 0,\n );\n\n // we finally deal with the charge\n\n if (charge) {\n totalDistribution.array.forEach((e) => {\n e.x = (e.x - ELECTRON_MASS * charge) / absoluteCharge;\n });\n }\n\n if (totalDistribution.array && totalDistribution.array.length > 0) {\n totalDistribution.sortX();\n part.fromX = totalDistribution.array[0].x;\n part.toX =\n totalDistribution.array[totalDistribution.array.length - 1].x;\n }\n\n if (\n part.ms.target &&\n part.ms.target.intensity &&\n part.ms.target.intensity !== 1\n ) {\n // intensity is the value of the monoisotopic mass !\n // need to find the intensity of the peak corresponding\n // to the monoisotopic mass\n if (part.ms.target.mass) {\n let target = totalDistribution.closestPointX(part.ms.target.mass);\n totalDistribution.multiplyY(part.ms.target.intensity / target.y);\n } else {\n totalDistribution.multiplyY(part.ms.target.intensity);\n }\n } else if (part.intensity && part.intensity !== 1) {\n totalDistribution.multiplyY(part.intensity);\n }\n\n part.isotopicDistribution = totalDistribution.array;\n\n if (finalDistribution.array.length === 0) {\n finalDistribution = totalDistribution;\n } else {\n finalDistribution.append(totalDistribution);\n }\n }\n }\n if (finalDistribution) finalDistribution.joinX(this.fwhm);\n\n for (let entry of finalDistribution.array) {\n if (!entry.composition) continue;\n Object.assign(entry, getDerivedCompositionInfo(entry.composition));\n }\n\n this.confidence /= this.parts.length;\n this.cachedDistribution = finalDistribution;\n return finalDistribution;\n }\n\n getCSV() {\n return this.getText({ delimiter: ', ' });\n }\n\n getTSV() {\n return this.getText({ delimiter: '\\t' });\n }\n\n getTable(options = {}) {\n const { maxValue, xLabel = 'x', yLabel = 'y' } = options;\n let points = this.getDistribution().array;\n let factor = 1;\n if (maxValue) {\n let maxY = this.getMaxY(points);\n factor = maxValue / maxY;\n }\n return points.map((point) => {\n let newPoint = {};\n newPoint[xLabel] = point.x;\n newPoint[yLabel] = point.y * factor;\n return newPoint;\n });\n }\n\n getText(options = {}) {\n const { delimiter = '\\t', numberDecimals = 3 } = options;\n let points = this.getDistribution().array;\n let csv = [];\n for (let point of points) {\n csv.push(\n `${point.x.toFixed(5)}${delimiter}${(point.y * 100).toFixed(\n numberDecimals,\n )}`,\n );\n }\n return csv.join('\\n');\n }\n\n getMaxY(points) {\n let maxY = points[0].y;\n for (let point of points) {\n if (point.y > maxY) maxY = point.y;\n }\n return maxY;\n }\n\n getSumY(points) {\n let sumY = 0;\n for (let point of points) {\n sumY += point.y;\n }\n return sumY;\n }\n\n /**\n * Returns the isotopic distribution as an array of peaks\n * @param {object} [options={}]\n * @param {number} [options.maxValue=100]\n * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored\n * @return {Array} an object containing at least the 2 properties: x:[] and y:[]\n */\n getPeaks(options = {}) {\n const { maxValue = 100, sumValue } = options;\n let peaks = this.getDistribution().array;\n if (peaks.length === 0) return [];\n let factor = 1;\n if (sumValue) {\n let sumY = this.getSumY(peaks);\n factor = sumY / sumValue;\n } else if (maxValue) {\n let maxY = this.getMaxY(peaks);\n factor = maxY / maxValue;\n }\n if (factor !== 1) {\n // we need to copy the array because we prefer no side effects\n peaks = JSON.parse(JSON.stringify(peaks));\n for (const peak of peaks) {\n peak.y = peak.y / factor;\n }\n }\n return peaks;\n }\n\n /**\n * Returns the isotopic distirubtion\n * @param {object} [options={}]\n * @param {number} [options.maxValue=100]\n * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored\n * @return {XY} an object containing at least the 2 properties: x:[] and y:[]\n */\n getXY(options = {}) {\n let peaks = this.getPeaks(options);\n\n if (peaks.length === 0) {\n return { x: [], y: [] };\n }\n\n const result = {\n x: peaks.map((a) => a.x),\n y: peaks.map((a) => a.y),\n };\n\n for (let key of Object.keys(peaks[0]).filter(\n (k) => k !== 'x' && k !== 'y',\n )) {\n result[key] = peaks.map((a) => a[key]);\n }\n\n return result;\n }\n\n /**\n * Returns the isotopic distirubtion\n * @param {object} [options={}]\n * @param {number} [options.maxValue=100]\n * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored\n * @return {import('cheminfo-types').MeasurementXYVariables} an object containing at least the 2 properties: x:[] and y:[]\n */\n getVariables(options = {}) {\n const xy = this.getXY(options);\n\n return {\n x: { data: xy.x, label: 'm/z', units: 'u' },\n y: { data: xy.y, label: 'Relative intensity', units: '%' },\n };\n }\n\n /**\n * Returns the isotopic distribution as the sum of gaussian\n * @param {object} [options={}]\n * @param {number} [options.gaussianWidth=10]\n * @param {number} [options.threshold=0.00001] // minimal height to return point\n * @param {number} [options.maxLength=1e6] // minimal height to return point\n * @param {number} [options.maxValue] // rescale Y to reach maxValue\n * @param {function} [options.peakWidthFct=(mz)=>(this.fwhm)]\n * @return {XY} isotopic distribution as an object containing 2 properties: x:[] and y:[]\n */\n\n getGaussian(options = {}) {\n const {\n peakWidthFct = () => this.fwhm,\n threshold = 0.00001,\n gaussianWidth = 10,\n maxValue,\n maxLength = 1e6,\n } = options;\n\n let points = this.getTable({ maxValue });\n if (points.length === 0) return { x: [], y: [] };\n const from = Math.floor(options.from || points[0].x - 2);\n const to = Math.ceil(options.to || points[points.length - 1].x + 2);\n const nbPoints = Math.round(((to - from) * gaussianWidth) / this.fwhm + 1);\n if (nbPoints > maxLength) {\n throw Error(\n `Number of points is over the maxLength: ${nbPoints}>${maxLength}`,\n );\n }\n let gaussianOptions = {\n from,\n to,\n nbPoints,\n peakWidthFct,\n };\n\n let spectrumGenerator = new SpectrumGenerator(gaussianOptions);\n for (let point of points) {\n spectrumGenerator.addPeak([point.x, point.y]);\n }\n let spectrum = spectrumGenerator.getSpectrum({ threshold });\n return spectrum;\n }\n}\n","export function calculateOverlapFromDiff(diffs) {\n if (diffs[1].length === 0)\n return 0;\n let sumPos = 0;\n for (let i = 0; i < diffs[1].length; i++) {\n sumPos += Math.abs(diffs[1][i]);\n }\n return 1 - sumPos;\n}\n//# sourceMappingURL=calculateOverlapFromDiff.js.map","/**\n * This code requires the use of an array like [[x1,y1],[x2,y2], ...]\n * If it is not the right format, we will just convert it\n * Otherwise we return the correct format\n * @param {Peaks} peaks\n * @returns [number[], number[]]\n */\nexport function checkPeaks(peaks) {\n // if it is already a 2D array of points, we just return them\n if (Array.isArray(peaks) && Array.isArray(peaks[0]) && peaks.length === 2) {\n return peaks;\n }\n if (Array.isArray(peaks.x) && Array.isArray(peaks.y)) {\n return [peaks.x, peaks.y];\n }\n const x = new Array(peaks.length);\n const y = new Array(peaks.length);\n for (let i = 0; i < peaks.length; i++) {\n x[i] = peaks[i][0];\n y[i] = peaks[i][1];\n }\n return [x, y];\n}\n//# sourceMappingURL=checkPeaks.js.map","export function extract(array, from, to) {\n const newArray = [[], []];\n let j = 0;\n const length = array[0] ? array[0].length : 0;\n for (let i = 0; i < length; i++) {\n if ((!from || array[0][i] >= from) && (!to || array[0][i] <= to)) {\n newArray[0][j] = array[0][i];\n newArray[1][j] = array[1][i];\n j++;\n }\n }\n return newArray;\n}\n//# sourceMappingURL=extract.js.map","// returns an new array based on array1 where there is a peak of array2 at a distance under width/2\nexport function getCommonArray(array1, array2, width) {\n const newArray = [[], []];\n let pos2 = 0;\n width /= 2;\n let j = 0;\n const array1Length = array1[0] ? array1[0].length : 0;\n const array2Length = array2[0] ? array2[0].length : 0;\n for (let i = 0; i < array1Length; i++) {\n while (pos2 < array2Length && array1[0][i] > array2[0][pos2] + width) {\n pos2++;\n }\n if (pos2 < array2Length && array1[0][i] > array2[0][pos2] - width) {\n newArray[0][j] = array1[0][i];\n newArray[1][j] = array1[1][i];\n j++;\n }\n }\n return newArray;\n}\n//# sourceMappingURL=getCommonArray.js.map","'use strict';\n\nfunction compareNumbers(a, b) {\n return a - b;\n}\n\n/**\n * Computes the sum of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.sum = function sum(values) {\n var sum = 0;\n for (var i = 0; i < values.length; i++) {\n sum += values[i];\n }\n return sum;\n};\n\n/**\n * Computes the maximum of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.max = function max(values) {\n var max = values[0];\n var l = values.length;\n for (var i = 1; i < l; i++) {\n if (values[i] > max) max = values[i];\n }\n return max;\n};\n\n/**\n * Computes the minimum of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.min = function min(values) {\n var min = values[0];\n var l = values.length;\n for (var i = 1; i < l; i++) {\n if (values[i] < min) min = values[i];\n }\n return min;\n};\n\n/**\n * Computes the min and max of the given values\n * @param {Array} values\n * @returns {{min: number, max: number}}\n */\nexports.minMax = function minMax(values) {\n var min = values[0];\n var max = values[0];\n var l = values.length;\n for (var i = 1; i < l; i++) {\n if (values[i] < min) min = values[i];\n if (values[i] > max) max = values[i];\n }\n return {\n min: min,\n max: max\n };\n};\n\n/**\n * Computes the arithmetic mean of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.arithmeticMean = function arithmeticMean(values) {\n var sum = 0;\n var l = values.length;\n for (var i = 0; i < l; i++) {\n sum += values[i];\n }\n return sum / l;\n};\n\n/**\n * {@link arithmeticMean}\n */\nexports.mean = exports.arithmeticMean;\n\n/**\n * Computes the geometric mean of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.geometricMean = function geometricMean(values) {\n var mul = 1;\n var l = values.length;\n for (var i = 0; i < l; i++) {\n mul *= values[i];\n }\n return Math.pow(mul, 1 / l);\n};\n\n/**\n * Computes the mean of the log of the given values\n * If the return value is exponentiated, it gives the same result as the\n * geometric mean.\n * @param {Array} values\n * @returns {number}\n */\nexports.logMean = function logMean(values) {\n var lnsum = 0;\n var l = values.length;\n for (var i = 0; i < l; i++) {\n lnsum += Math.log(values[i]);\n }\n return lnsum / l;\n};\n\n/**\n * Computes the weighted grand mean for a list of means and sample sizes\n * @param {Array} means - Mean values for each set of samples\n * @param {Array} samples - Number of original values for each set of samples\n * @returns {number}\n */\nexports.grandMean = function grandMean(means, samples) {\n var sum = 0;\n var n = 0;\n var l = means.length;\n for (var i = 0; i < l; i++) {\n sum += samples[i] * means[i];\n n += samples[i];\n }\n return sum / n;\n};\n\n/**\n * Computes the truncated mean of the given values using a given percentage\n * @param {Array} values\n * @param {number} percent - The percentage of values to keep (range: [0,1])\n * @param {boolean} [alreadySorted=false]\n * @returns {number}\n */\nexports.truncatedMean = function truncatedMean(values, percent, alreadySorted) {\n if (alreadySorted === undefined) alreadySorted = false;\n if (!alreadySorted) {\n values = [].concat(values).sort(compareNumbers);\n }\n var l = values.length;\n var k = Math.floor(l * percent);\n var sum = 0;\n for (var i = k; i < (l - k); i++) {\n sum += values[i];\n }\n return sum / (l - 2 * k);\n};\n\n/**\n * Computes the harmonic mean of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.harmonicMean = function harmonicMean(values) {\n var sum = 0;\n var l = values.length;\n for (var i = 0; i < l; i++) {\n if (values[i] === 0) {\n throw new RangeError('value at index ' + i + 'is zero');\n }\n sum += 1 / values[i];\n }\n return l / sum;\n};\n\n/**\n * Computes the contraharmonic mean of the given values\n * @param {Array} values\n * @returns {number}\n */\nexports.contraHarmonicMean = function contraHarmonicMean(values) {\n var r1 = 0;\n var r2 = 0;\n var l = values.length;\n for (var i = 0; i < l; i++) {\n r1 += values[i] * values[i];\n r2 += values[i];\n }\n if (r2 < 0) {\n throw new RangeError('sum of values is negative');\n }\n return r1 / r2;\n};\n\n/**\n * Computes the median of the given values\n * @param {Array} values\n * @param {boolean} [alreadySorted=false]\n * @returns {number}\n */\nexports.median = function median(values, alreadySorted) {\n if (alreadySorted === undefined) alreadySorted = false;\n if (!alreadySorted) {\n values = [].concat(values).sort(compareNumbers);\n }\n var l = values.length;\n var half = Math.floor(l / 2);\n if (l % 2 === 0) {\n return (values[half - 1] + values[half]) * 0.5;\n } else {\n return values[half];\n }\n};\n\n/**\n * Computes the variance of the given values\n * @param {Array} values\n * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.\n * @returns {number}\n */\nexports.variance = function variance(values, unbiased) {\n if (unbiased === undefined) unbiased = true;\n var theMean = exports.mean(values);\n var theVariance = 0;\n var l = values.length;\n\n for (var i = 0; i < l; i++) {\n var x = values[i] - theMean;\n theVariance += x * x;\n }\n\n if (unbiased) {\n return theVariance / (l - 1);\n } else {\n return theVariance / l;\n }\n};\n\n/**\n * Computes the standard deviation of the given values\n * @param {Array} values\n * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.\n * @returns {number}\n */\nexports.standardDeviation = function standardDeviation(values, unbiased) {\n return Math.sqrt(exports.variance(values, unbiased));\n};\n\nexports.standardError = function standardError(values) {\n return exports.standardDeviation(values) / Math.sqrt(values.length);\n};\n\n/**\n * IEEE Transactions on biomedical engineering, vol. 52, no. 1, january 2005, p. 76-\n * Calculate the standard deviation via the Median of the absolute deviation\n * The formula for the standard deviation only holds for Gaussian random variables.\n * @returns {{mean: number, stdev: number}}\n */\nexports.robustMeanAndStdev = function robustMeanAndStdev(y) {\n var mean = 0, stdev = 0;\n var length = y.length, i = 0;\n for (i = 0; i < length; i++) {\n mean += y[i];\n }\n mean /= length;\n var averageDeviations = new Array(length);\n for (i = 0; i < length; i++)\n averageDeviations[i] = Math.abs(y[i] - mean);\n averageDeviations.sort(compareNumbers);\n if (length % 2 === 1) {\n stdev = averageDeviations[(length - 1) / 2] / 0.6745;\n } else {\n stdev = 0.5 * (averageDeviations[length / 2] + averageDeviations[length / 2 - 1]) / 0.6745;\n }\n\n return {\n mean: mean,\n stdev: stdev\n };\n};\n\nexports.quartiles = function quartiles(values, alreadySorted) {\n if (typeof (alreadySorted) === 'undefined') alreadySorted = false;\n if (!alreadySorted) {\n values = [].concat(values).sort(compareNumbers);\n }\n\n var quart = values.length / 4;\n var q1 = values[Math.ceil(quart) - 1];\n var q2 = exports.median(values, true);\n var q3 = values[Math.ceil(quart * 3) - 1];\n\n return {q1: q1, q2: q2, q3: q3};\n};\n\nexports.pooledStandardDeviation = function pooledStandardDeviation(samples, unbiased) {\n return Math.sqrt(exports.pooledVariance(samples, unbiased));\n};\n\nexports.pooledVariance = function pooledVariance(samples, unbiased) {\n if (typeof (unbiased) === 'undefined') unbiased = true;\n var sum = 0;\n var length = 0, l = samples.length;\n for (var i = 0; i < l; i++) {\n var values = samples[i];\n var vari = exports.variance(values);\n\n sum += (values.length - 1) * vari;\n\n if (unbiased)\n length += values.length - 1;\n else\n length += values.length;\n }\n return sum / length;\n};\n\nexports.mode = function mode(values) {\n var l = values.length,\n itemCount = new Array(l),\n i;\n for (i = 0; i < l; i++) {\n itemCount[i] = 0;\n }\n var itemArray = new Array(l);\n var count = 0;\n\n for (i = 0; i < l; i++) {\n var index = itemArray.indexOf(values[i]);\n if (index >= 0)\n itemCount[index]++;\n else {\n itemArray[count] = values[i];\n itemCount[count] = 1;\n count++;\n }\n }\n\n var maxValue = 0, maxIndex = 0;\n for (i = 0; i < count; i++) {\n if (itemCount[i] > maxValue) {\n maxValue = itemCount[i];\n maxIndex = i;\n }\n }\n\n return itemArray[maxIndex];\n};\n\nexports.covariance = function covariance(vector1, vector2, unbiased) {\n if (typeof (unbiased) === 'undefined') unbiased = true;\n var mean1 = exports.mean(vector1);\n var mean2 = exports.mean(vector2);\n\n if (vector1.length !== vector2.length)\n throw 'Vectors do not have the same dimensions';\n\n var cov = 0, l = vector1.length;\n for (var i = 0; i < l; i++) {\n var x = vector1[i] - mean1;\n var y = vector2[i] - mean2;\n cov += x * y;\n }\n\n if (unbiased)\n return cov / (l - 1);\n else\n return cov / l;\n};\n\nexports.skewness = function skewness(values, unbiased) {\n if (typeof (unbiased) === 'undefined') unbiased = true;\n var theMean = exports.mean(values);\n\n var s2 = 0, s3 = 0, l = values.length;\n for (var i = 0; i < l; i++) {\n var dev = values[i] - theMean;\n s2 += dev * dev;\n s3 += dev * dev * dev;\n }\n var m2 = s2 / l;\n var m3 = s3 / l;\n\n var g = m3 / (Math.pow(m2, 3 / 2.0));\n if (unbiased) {\n var a = Math.sqrt(l * (l - 1));\n var b = l - 2;\n return (a / b) * g;\n } else {\n return g;\n }\n};\n\nexports.kurtosis = function kurtosis(values, unbiased) {\n if (typeof (unbiased) === 'undefined') unbiased = true;\n var theMean = exports.mean(values);\n var n = values.length, s2 = 0, s4 = 0;\n\n for (var i = 0; i < n; i++) {\n var dev = values[i] - theMean;\n s2 += dev * dev;\n s4 += dev * dev * dev * dev;\n }\n var m2 = s2 / n;\n var m4 = s4 / n;\n\n if (unbiased) {\n var v = s2 / (n - 1);\n var a = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3));\n var b = s4 / (v * v);\n var c = ((n - 1) * (n - 1)) / ((n - 2) * (n - 3));\n\n return a * b - 3 * c;\n } else {\n return m4 / (m2 * m2) - 3;\n }\n};\n\nexports.entropy = function entropy(values, eps) {\n if (typeof (eps) === 'undefined') eps = 0;\n var sum = 0, l = values.length;\n for (var i = 0; i < l; i++)\n sum += values[i] * Math.log(values[i] + eps);\n return -sum;\n};\n\nexports.weightedMean = function weightedMean(values, weights) {\n var sum = 0, l = values.length;\n for (var i = 0; i < l; i++)\n sum += values[i] * weights[i];\n return sum;\n};\n\nexports.weightedStandardDeviation = function weightedStandardDeviation(values, weights) {\n return Math.sqrt(exports.weightedVariance(values, weights));\n};\n\nexports.weightedVariance = function weightedVariance(values, weights) {\n var theMean = exports.weightedMean(values, weights);\n var vari = 0, l = values.length;\n var a = 0, b = 0;\n\n for (var i = 0; i < l; i++) {\n var z = values[i] - theMean;\n var w = weights[i];\n\n vari += w * (z * z);\n b += w;\n a += w * w;\n }\n\n return vari * (b / (b * b - a));\n};\n\nexports.center = function center(values, inPlace) {\n if (typeof (inPlace) === 'undefined') inPlace = false;\n\n var result = values;\n if (!inPlace)\n result = [].concat(values);\n\n var theMean = exports.mean(result), l = result.length;\n for (var i = 0; i < l; i++)\n result[i] -= theMean;\n};\n\nexports.standardize = function standardize(values, standardDev, inPlace) {\n if (typeof (standardDev) === 'undefined') standardDev = exports.standardDeviation(values);\n if (typeof (inPlace) === 'undefined') inPlace = false;\n var l = values.length;\n var result = inPlace ? values : new Array(l);\n for (var i = 0; i < l; i++)\n result[i] = values[i] / standardDev;\n return result;\n};\n\nexports.cumulativeSum = function cumulativeSum(array) {\n var l = array.length;\n var result = new Array(l);\n result[0] = array[0];\n for (var i = 1; i < l; i++)\n result[i] = result[i - 1] + array[i];\n return result;\n};\n","'use strict';\n\nvar arrayStat = require('./array');\n\nfunction compareNumbers(a, b) {\n return a - b;\n}\n\nexports.max = function max(matrix) {\n var max = -Infinity;\n for (var i = 0; i < matrix.length; i++) {\n for (var j = 0; j < matrix[i].length; j++) {\n if (matrix[i][j] > max) max = matrix[i][j];\n }\n }\n return max;\n};\n\nexports.min = function min(matrix) {\n var min = Infinity;\n for (var i = 0; i < matrix.length; i++) {\n for (var j = 0; j < matrix[i].length; j++) {\n if (matrix[i][j] < min) min = matrix[i][j];\n }\n }\n return min;\n};\n\nexports.minMax = function minMax(matrix) {\n var min = Infinity;\n var max = -Infinity;\n for (var i = 0; i < matrix.length; i++) {\n for (var j = 0; j < matrix[i].length; j++) {\n if (matrix[i][j] < min) min = matrix[i][j];\n if (matrix[i][j] > max) max = matrix[i][j];\n }\n }\n return {\n min:min,\n max:max\n };\n};\n\nexports.entropy = function entropy(matrix, eps) {\n if (typeof (eps) === 'undefined') {\n eps = 0;\n }\n var sum = 0,\n l1 = matrix.length,\n l2 = matrix[0].length;\n for (var i = 0; i < l1; i++) {\n for (var j = 0; j < l2; j++) {\n sum += matrix[i][j] * Math.log(matrix[i][j] + eps);\n }\n }\n return -sum;\n};\n\nexports.mean = function mean(matrix, dimension) {\n if (typeof (dimension) === 'undefined') {\n dimension = 0;\n }\n var rows = matrix.length,\n cols = matrix[0].length,\n theMean, N, i, j;\n\n if (dimension === -1) {\n theMean = [0];\n N = rows * cols;\n for (i = 0; i < rows; i++) {\n for (j = 0; j < cols; j++) {\n theMean[0] += matrix[i][j];\n }\n }\n theMean[0] /= N;\n } else if (dimension === 0) {\n theMean = new Array(cols);\n N = rows;\n for (j = 0; j < cols; j++) {\n theMean[j] = 0;\n for (i = 0; i < rows; i++) {\n theMean[j] += matrix[i][j];\n }\n theMean[j] /= N;\n }\n } else if (dimension === 1) {\n theMean = new Array(rows);\n N = cols;\n for (j = 0; j < rows; j++) {\n theMean[j] = 0;\n for (i = 0; i < cols; i++) {\n theMean[j] += matrix[j][i];\n }\n theMean[j] /= N;\n }\n } else {\n throw new Error('Invalid dimension');\n }\n return theMean;\n};\n\nexports.sum = function sum(matrix, dimension) {\n if (typeof (dimension) === 'undefined') {\n dimension = 0;\n }\n var rows = matrix.length,\n cols = matrix[0].length,\n theSum, i, j;\n\n if (dimension === -1) {\n theSum = [0];\n for (i = 0; i < rows; i++) {\n for (j = 0; j < cols; j++) {\n theSum[0] += matrix[i][j];\n }\n }\n } else if (dimension === 0) {\n theSum = new Array(cols);\n for (j = 0; j < cols; j++) {\n theSum[j] = 0;\n for (i = 0; i < rows; i++) {\n theSum[j] += matrix[i][j];\n }\n }\n } else if (dimension === 1) {\n theSum = new Array(rows);\n for (j = 0; j < rows; j++) {\n theSum[j] = 0;\n for (i = 0; i < cols; i++) {\n theSum[j] += matrix[j][i];\n }\n }\n } else {\n throw new Error('Invalid dimension');\n }\n return theSum;\n};\n\nexports.product = function product(matrix, dimension) {\n if (typeof (dimension) === 'undefined') {\n dimension = 0;\n }\n var rows = matrix.length,\n cols = matrix[0].length,\n theProduct, i, j;\n\n if (dimension === -1) {\n theProduct = [1];\n for (i = 0; i < rows; i++) {\n for (j = 0; j < cols; j++) {\n theProduct[0] *= matrix[i][j];\n }\n }\n } else if (dimension === 0) {\n theProduct = new Array(cols);\n for (j = 0; j < cols; j++) {\n theProduct[j] = 1;\n for (i = 0; i < rows; i++) {\n theProduct[j] *= matrix[i][j];\n }\n }\n } else if (dimension === 1) {\n theProduct = new Array(rows);\n for (j = 0; j < rows; j++) {\n theProduct[j] = 1;\n for (i = 0; i < cols; i++) {\n theProduct[j] *= matrix[j][i];\n }\n }\n } else {\n throw new Error('Invalid dimension');\n }\n return theProduct;\n};\n\nexports.standardDeviation = function standardDeviation(matrix, means, unbiased) {\n var vari = exports.variance(matrix, means, unbiased), l = vari.length;\n for (var i = 0; i < l; i++) {\n vari[i] = Math.sqrt(vari[i]);\n }\n return vari;\n};\n\nexports.variance = function variance(matrix, means, unbiased) {\n if (typeof (unbiased) === 'undefined') {\n unbiased = true;\n }\n means = means || exports.mean(matrix);\n var rows = matrix.length;\n if (rows === 0) return [];\n var cols = matrix[0].length;\n var vari = new Array(cols);\n\n for (var j = 0; j < cols; j++) {\n var sum1 = 0, sum2 = 0, x = 0;\n for (var i = 0; i < rows; i++) {\n x = matrix[i][j] - means[j];\n sum1 += x;\n sum2 += x * x;\n }\n if (unbiased) {\n vari[j] = (sum2 - ((sum1 * sum1) / rows)) / (rows - 1);\n } else {\n vari[j] = (sum2 - ((sum1 * sum1) / rows)) / rows;\n }\n }\n return vari;\n};\n\nexports.median = function median(matrix) {\n var rows = matrix.length, cols = matrix[0].length;\n var medians = new Array(cols);\n\n for (var i = 0; i < cols; i++) {\n var data = new Array(rows);\n for (var j = 0; j < rows; j++) {\n data[j] = matrix[j][i];\n }\n data.sort(compareNumbers);\n var N = data.length;\n if (N % 2 === 0) {\n medians[i] = (data[N / 2] + data[(N / 2) - 1]) * 0.5;\n } else {\n medians[i] = data[Math.floor(N / 2)];\n }\n }\n return medians;\n};\n\nexports.mode = function mode(matrix) {\n var rows = matrix.length,\n cols = matrix[0].length,\n modes = new Array(cols),\n i, j;\n for (i = 0; i < cols; i++) {\n var itemCount = new Array(rows);\n for (var k = 0; k < rows; k++) {\n itemCount[k] = 0;\n }\n var itemArray = new Array(rows);\n var count = 0;\n\n for (j = 0; j < rows; j++) {\n var index = itemArray.indexOf(matrix[j][i]);\n if (index >= 0) {\n itemCount[index]++;\n } else {\n itemArray[count] = matrix[j][i];\n itemCount[count] = 1;\n count++;\n }\n }\n\n var maxValue = 0, maxIndex = 0;\n for (j = 0; j < count; j++) {\n if (itemCount[j] > maxValue) {\n maxValue = itemCount[j];\n maxIndex = j;\n }\n }\n\n modes[i] = itemArray[maxIndex];\n }\n return modes;\n};\n\nexports.skewness = function skewness(matrix, unbiased) {\n if (typeof (unbiased) === 'undefined') unbiased = true;\n var means = exports.mean(matrix);\n var n = matrix.length, l = means.length;\n var skew = new Array(l);\n\n for (var j = 0; j < l; j++) {\n var s2 = 0, s3 = 0;\n for (var i = 0; i < n; i++) {\n var dev = matrix[i][j] - means[j];\n s2 += dev * dev;\n s3 += dev * dev * dev;\n }\n\n var m2 = s2 / n;\n var m3 = s3 / n;\n var g = m3 / Math.pow(m2, 3 / 2);\n\n if (unbiased) {\n var a = Math.sqrt(n * (n - 1));\n var b = n - 2;\n skew[j] = (a / b) * g;\n } else {\n skew[j] = g;\n }\n }\n return skew;\n};\n\nexports.kurtosis = function kurtosis(matrix, unbiased) {\n if (typeof (unbiased) === 'undefined') unbiased = true;\n var means = exports.mean(matrix);\n var n = matrix.length, m = matrix[0].length;\n var kurt = new Array(m);\n\n for (var j = 0; j < m; j++) {\n var s2 = 0, s4 = 0;\n for (var i = 0; i < n; i++) {\n var dev = matrix[i][j] - means[j];\n s2 += dev * dev;\n s4 += dev * dev * dev * dev;\n }\n var m2 = s2 / n;\n var m4 = s4 / n;\n\n if (unbiased) {\n var v = s2 / (n - 1);\n var a = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3));\n var b = s4 / (v * v);\n var c = ((n - 1) * (n - 1)) / ((n - 2) * (n - 3));\n kurt[j] = a * b - 3 * c;\n } else {\n kurt[j] = m4 / (m2 * m2) - 3;\n }\n }\n return kurt;\n};\n\nexports.standardError = function standardError(matrix) {\n var samples = matrix.length;\n var standardDeviations = exports.standardDeviation(matrix);\n var l = standardDeviations.length;\n var standardErrors = new Array(l);\n var sqrtN = Math.sqrt(samples);\n\n for (var i = 0; i < l; i++) {\n standardErrors[i] = standardDeviations[i] / sqrtN;\n }\n return standardErrors;\n};\n\nexports.covariance = function covariance(matrix, dimension) {\n return exports.scatter(matrix, undefined, dimension);\n};\n\nexports.scatter = function scatter(matrix, divisor, dimension) {\n if (typeof (dimension) === 'undefined') {\n dimension = 0;\n }\n if (typeof (divisor) === 'undefined') {\n if (dimension === 0) {\n divisor = matrix.length - 1;\n } else if (dimension === 1) {\n divisor = matrix[0].length - 1;\n }\n }\n var means = exports.mean(matrix, dimension);\n var rows = matrix.length;\n if (rows === 0) {\n return [[]];\n }\n var cols = matrix[0].length,\n cov, i, j, s, k;\n\n if (dimension === 0) {\n cov = new Array(cols);\n for (i = 0; i < cols; i++) {\n cov[i] = new Array(cols);\n }\n for (i = 0; i < cols; i++) {\n for (j = i; j < cols; j++) {\n s = 0;\n for (k = 0; k < rows; k++) {\n s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);\n }\n s /= divisor;\n cov[i][j] = s;\n cov[j][i] = s;\n }\n }\n } else if (dimension === 1) {\n cov = new Array(rows);\n for (i = 0; i < rows; i++) {\n cov[i] = new Array(rows);\n }\n for (i = 0; i < rows; i++) {\n for (j = i; j < rows; j++) {\n s = 0;\n for (k = 0; k < cols; k++) {\n s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);\n }\n s /= divisor;\n cov[i][j] = s;\n cov[j][i] = s;\n }\n }\n } else {\n throw new Error('Invalid dimension');\n }\n\n return cov;\n};\n\nexports.correlation = function correlation(matrix) {\n var means = exports.mean(matrix),\n standardDeviations = exports.standardDeviation(matrix, true, means),\n scores = exports.zScores(matrix, means, standardDeviations),\n rows = matrix.length,\n cols = matrix[0].length,\n i, j;\n\n var cor = new Array(cols);\n for (i = 0; i < cols; i++) {\n cor[i] = new Array(cols);\n }\n for (i = 0; i < cols; i++) {\n for (j = i; j < cols; j++) {\n var c = 0;\n for (var k = 0, l = scores.length; k < l; k++) {\n c += scores[k][j] * scores[k][i];\n }\n c /= rows - 1;\n cor[i][j] = c;\n cor[j][i] = c;\n }\n }\n return cor;\n};\n\nexports.zScores = function zScores(matrix, means, standardDeviations) {\n means = means || exports.mean(matrix);\n if (typeof (standardDeviations) === 'undefined') standardDeviations = exports.standardDeviation(matrix, true, means);\n return exports.standardize(exports.center(matrix, means, false), standardDeviations, true);\n};\n\nexports.center = function center(matrix, means, inPlace) {\n means = means || exports.mean(matrix);\n var result = matrix,\n l = matrix.length,\n i, j, jj;\n\n if (!inPlace) {\n result = new Array(l);\n for (i = 0; i < l; i++) {\n result[i] = new Array(matrix[i].length);\n }\n }\n\n for (i = 0; i < l; i++) {\n var row = result[i];\n for (j = 0, jj = row.length; j < jj; j++) {\n row[j] = matrix[i][j] - means[j];\n }\n }\n return result;\n};\n\nexports.standardize = function standardize(matrix, standardDeviations, inPlace) {\n if (typeof (standardDeviations) === 'undefined') standardDeviations = exports.standardDeviation(matrix);\n var result = matrix,\n l = matrix.length,\n i, j, jj;\n\n if (!inPlace) {\n result = new Array(l);\n for (i = 0; i < l; i++) {\n result[i] = new Array(matrix[i].length);\n }\n }\n\n for (i = 0; i < l; i++) {\n var resultRow = result[i];\n var sourceRow = matrix[i];\n for (j = 0, jj = resultRow.length; j < jj; j++) {\n if (standardDeviations[j] !== 0 && !isNaN(standardDeviations[j])) {\n resultRow[j] = sourceRow[j] / standardDeviations[j];\n }\n }\n }\n return result;\n};\n\nexports.weightedVariance = function weightedVariance(matrix, weights) {\n var means = exports.mean(matrix);\n var rows = matrix.length;\n if (rows === 0) return [];\n var cols = matrix[0].length;\n var vari = new Array(cols);\n\n for (var j = 0; j < cols; j++) {\n var sum = 0;\n var a = 0, b = 0;\n\n for (var i = 0; i < rows; i++) {\n var z = matrix[i][j] - means[j];\n var w = weights[i];\n\n sum += w * (z * z);\n b += w;\n a += w * w;\n }\n\n vari[j] = sum * (b / (b * b - a));\n }\n\n return vari;\n};\n\nexports.weightedMean = function weightedMean(matrix, weights, dimension) {\n if (typeof (dimension) === 'undefined') {\n dimension = 0;\n }\n var rows = matrix.length;\n if (rows === 0) return [];\n var cols = matrix[0].length,\n means, i, ii, j, w, row;\n\n if (dimension === 0) {\n means = new Array(cols);\n for (i = 0; i < cols; i++) {\n means[i] = 0;\n }\n for (i = 0; i < rows; i++) {\n row = matrix[i];\n w = weights[i];\n for (j = 0; j < cols; j++) {\n means[j] += row[j] * w;\n }\n }\n } else if (dimension === 1) {\n means = new Array(rows);\n for (i = 0; i < rows; i++) {\n means[i] = 0;\n }\n for (j = 0; j < rows; j++) {\n row = matrix[j];\n w = weights[j];\n for (i = 0; i < cols; i++) {\n means[j] += row[i] * w;\n }\n }\n } else {\n throw new Error('Invalid dimension');\n }\n\n var weightSum = arrayStat.sum(weights);\n if (weightSum !== 0) {\n for (i = 0, ii = means.length; i < ii; i++) {\n means[i] /= weightSum;\n }\n }\n return means;\n};\n\nexports.weightedCovariance = function weightedCovariance(matrix, weights, means, dimension) {\n dimension = dimension || 0;\n means = means || exports.weightedMean(matrix, weights, dimension);\n var s1 = 0, s2 = 0;\n for (var i = 0, ii = weights.length; i < ii; i++) {\n s1 += weights[i];\n s2 += weights[i] * weights[i];\n }\n var factor = s1 / (s1 * s1 - s2);\n return exports.weightedScatter(matrix, weights, means, factor, dimension);\n};\n\nexports.weightedScatter = function weightedScatter(matrix, weights, means, factor, dimension) {\n dimension = dimension || 0;\n means = means || exports.weightedMean(matrix, weights, dimension);\n if (typeof (factor) === 'undefined') {\n factor = 1;\n }\n var rows = matrix.length;\n if (rows === 0) {\n return [[]];\n }\n var cols = matrix[0].length,\n cov, i, j, k, s;\n\n if (dimension === 0) {\n cov = new Array(cols);\n for (i = 0; i < cols; i++) {\n cov[i] = new Array(cols);\n }\n for (i = 0; i < cols; i++) {\n for (j = i; j < cols; j++) {\n s = 0;\n for (k = 0; k < rows; k++) {\n s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);\n }\n cov[i][j] = s * factor;\n cov[j][i] = s * factor;\n }\n }\n } else if (dimension === 1) {\n cov = new Array(rows);\n for (i = 0; i < rows; i++) {\n cov[i] = new Array(rows);\n }\n for (i = 0; i < rows; i++) {\n for (j = i; j < rows; j++) {\n s = 0;\n for (k = 0; k < cols; k++) {\n s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);\n }\n cov[i][j] = s * factor;\n cov[j][i] = s * factor;\n }\n }\n } else {\n throw new Error('Invalid dimension');\n }\n\n return cov;\n};\n","'use strict';\n\nexports.array = require('./array');\nexports.matrix = require('./matrix');\n","import { array as StatArray } from 'ml-stat';\nexport function normalize(array) {\n const min = StatArray.min(array[1]);\n const max = StatArray.max(array[1]);\n const sum = StatArray.sum(array[1]);\n const length = array[1] ? array[1].length : 0;\n if (sum !== 0) {\n for (let i = 0; i < length; i++) {\n array[1][i] /= sum;\n }\n }\n return {\n sum,\n min,\n max,\n };\n}\n//# sourceMappingURL=normalize.js.map","import { extract } from './extract';\nimport { getCommonArray } from './getCommonArray';\nimport { normalize } from './normalize';\nimport { COMMON_SECOND, COMMON_FIRST } from './index';\n// this method will systematically take care of both array\nexport function commonExtractAndNormalize(array1, array2, width, from, to, common) {\n if (!Array.isArray(array1) || !Array.isArray(array2)) {\n return {\n info: undefined,\n data: undefined,\n };\n }\n const extract1 = extract(array1, from, to);\n const extract2 = extract(array2, from, to);\n let common1, common2, info1, info2;\n if (common & COMMON_SECOND) {\n common1 = getCommonArray(extract1, extract2, width);\n info1 = normalize(common1);\n }\n else {\n common1 = extract1;\n info1 = normalize(common1);\n }\n if (common & COMMON_FIRST) {\n common2 = getCommonArray(extract2, extract1, width);\n info2 = normalize(common2);\n }\n else {\n common2 = extract2;\n info2 = normalize(common2);\n }\n return {\n info1,\n info2,\n data1: common1,\n data2: common2,\n };\n}\n//# sourceMappingURL=commonExtractAndNormalize.js.map","import { extract } from './extract';\nimport { normalize } from './normalize';\nexport function extractAndNormalize(array, from, to) {\n if (!Array.isArray(array)) {\n return {\n info: undefined,\n data: undefined,\n };\n }\n const newArray = extract(array, from, to);\n const info = normalize(newArray);\n return {\n info,\n data: newArray,\n };\n}\n//# sourceMappingURL=extractAndNormalize.js.map","// Adapted from: http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1968345#1968345\nexport function getIntersection(segment1, segment2) {\n const p0X = segment1[0][0];\n const p0Y = segment1[0][1];\n const p1X = segment1[1][0];\n const p1Y = segment1[1][1];\n const p2X = segment2[0][0];\n const p2Y = segment2[0][1];\n const p3X = segment2[1][0];\n const p3Y = segment2[1][1];\n const s1X = p1X - p0X;\n const s1Y = p1Y - p0Y;\n const s2X = p3X - p2X;\n const s2Y = p3Y - p2Y;\n const s = (-s1Y * (p0X - p2X) + s1X * (p0Y - p2Y)) / (-s2X * s1Y + s1X * s2Y);\n const t = (s2X * (p0Y - p2Y) - s2Y * (p0X - p2X)) / (-s2X * s1Y + s1X * s2Y);\n if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {\n return {\n x: p0X + t * s1X,\n y: p0Y + t * s1Y,\n };\n }\n return null; // No collision\n}\n//# sourceMappingURL=getIntersection.js.map","import { calculateOverlapFromDiff } from './calculateOverlapFromDiff';\nimport { checkPeaks } from './checkPeaks';\nimport { commonExtractAndNormalize } from './commonExtractAndNormalize';\nimport { extract } from './extract';\nimport { extractAndNormalize } from './extractAndNormalize';\nimport { getCommonArray } from './getCommonArray.js';\nimport { getIntersection } from './getIntersection';\nimport { normalize } from './normalize';\nexport const COMMON_NO = 0;\nexport const COMMON_FIRST = 1;\nexport const COMMON_SECOND = 2;\nexport const COMMON_BOTH = 3;\n/**\n * A number, or a string containing a number.\n * @typedef {([number[],number[]]|[number,number][]|{x:number[],y:number[]})} Peaks\n */\n/**\n * Create a comparator class\n * {object} [options={}]\n * {string} [options.common=''] should we take only common peaks 'first', 'second', 'both', ''\n * {number} [options.widthBottom=2] bottom trapezoid width for similarity evaluation\n * {number} [options.widthTop=1] top trapezoid width for similarity evaluation\n * {number} [options.from] from region used for similarity calculation\n * {number} [options.to] to region used for similarity calculation\n */\nexport class Comparator {\n constructor(options = {}) {\n this.array1 = [];\n this.array2 = [];\n this.setOptions(options);\n }\n /*\n 2 formats are allowed:\n [[x1,x2,...],[y1,y2,...]] or [[x1,y1],[x2,y2], ...]\n */\n setOptions(options = {}) {\n if (typeof options.common === 'string') {\n if (options.common.toLowerCase() === 'first') {\n this.common = COMMON_FIRST;\n }\n else if (options.common.toLowerCase() === 'second') {\n this.common = COMMON_SECOND;\n }\n else if (options.common.toLowerCase() === 'both') {\n this.common = COMMON_BOTH;\n }\n else {\n this.common = COMMON_NO;\n }\n }\n else if (options.common === true) {\n this.common = COMMON_BOTH;\n }\n else {\n this.common = COMMON_NO;\n }\n this.trapezoid = options.trapezoid;\n this.commonFactor = options.commonFactor || this.commonFactor || 4;\n const { widthBottom = this.widthBottom || 2, widthTop = this.widthTop || 1, from = this.from, to = this.to, } = options;\n this.setTrapezoid(widthBottom, widthTop);\n this.setFromTo(from, to);\n }\n /**\n *\n * @param {Peaks} peaks\n */\n setPeaks1(peaks) {\n this.array1 = checkPeaks(peaks);\n if (this.common) {\n const extracts = commonExtractAndNormalize(this.array1, this.array2, this.widthBottom, this.from, this.to, this.common);\n this.array1Extract = extracts.data1;\n this.array1ExtractInfo = extracts.info1;\n this.array2Extract = extracts.data2;\n this.array2ExtractInfo = extracts.info2;\n }\n else {\n const extract = extractAndNormalize(this.array1, this.from, this.to);\n this.array1Extract = extract.data;\n this.array1ExtractInfo = extract.info;\n }\n }\n /**\n *\n * @param {Peaks} peaks\n */\n setPeaks2(peaks) {\n this.array2 = checkPeaks(peaks);\n if (this.common) {\n const extracts = commonExtractAndNormalize(this.array1, this.array2, this.widthBottom, this.from, this.to, this.common);\n this.array1Extract = extracts.data1;\n this.array1ExtractInfo = extracts.info1;\n this.array2Extract = extracts.data2;\n this.array2ExtractInfo = extracts.info2;\n }\n else {\n const extract = extractAndNormalize(this.array2, this.from, this.to);\n this.array2Extract = extract.data;\n this.array2ExtractInfo = extract.info;\n }\n }\n getExtract1() {\n return this.array1Extract;\n }\n getExtract2() {\n return this.array2Extract;\n }\n getExtractInfo1() {\n return this.array1ExtractInfo;\n }\n getExtractInfo2() {\n return this.array2ExtractInfo;\n }\n /**\n * Set the new bottom and top width of the trapezoid\n * @param {number} newWidthBottom\n * @param {number} newWidthTop\n */\n setTrapezoid(newWidthBottom, newWidthTop) {\n this.widthTop = newWidthTop;\n this.widthBottom = newWidthBottom;\n this.widthSlope = (this.widthBottom - this.widthTop) / 2;\n if (this.widthBottom < this.widthTop) {\n throw new Error('widthBottom has to be larger than widthTop');\n }\n }\n /**\n * Set the from / to for comparison\n * @param {number} newFrom - set the new from value\n * @param {number} newTo - set the new to value\n * @returns\n */\n setFromTo(newFrom, newTo) {\n if (newFrom === this.from && newTo === this.to)\n return;\n this.from = newFrom;\n this.to = newTo;\n if (this.common) {\n const extracts = commonExtractAndNormalize(this.array1, this.array2, this.widthBottom, this.from, this.to, this.common, this.commonFactor);\n this.array1Extract = extracts.data1;\n this.array1ExtractInfo = extracts.info1;\n this.array2Extract = extracts.data2;\n this.array2ExtractInfo = extracts.info2;\n }\n else {\n let extract1 = extractAndNormalize(this.array1, this.from, this.to);\n this.array1Extract = extract1.data;\n this.array1ExtractInfo = extract1.info;\n let extract2 = extractAndNormalize(this.array2, this.from, this.to);\n this.array2Extract = extract2.data;\n this.array2ExtractInfo = extract2.info;\n }\n }\n /**\n *\n * @param {number} x1\n * @param {number} y1\n * @param {number} x2\n * @param {number} y2\n * @returns\n */\n getOverlap(x1, y1, x2, y2) {\n if (y1 === 0 || y2 === 0)\n return 0;\n // TAKE CARE !!! We multiply the diff by 2 !!!\n const diff = Math.abs(x1 - x2) * 2;\n if (diff > this.widthBottom)\n return 0;\n if (diff <= this.widthTop) {\n return Math.min(y1, y2);\n }\n const maxValue = (Math.max(y1, y2) * (this.widthBottom - diff)) /\n (this.widthBottom - this.widthTop);\n return Math.min(y1, y2, maxValue);\n }\n /**\n * This is the old trapezoid similarity\n * @param {number} x1\n * @param {number} y1\n * @param {number} x2\n * @param {number} y2\n * @param {number} widthTop\n * @param {number} widthBottom\n * @returns\n */\n getOverlapTrapezoid(x1, y1, x2, y2, widthTop, widthBottom) {\n // eslint-disable-next-line no-console\n console.error('getOverlapTrapezoid should not be used anymore');\n const factor = 2 / (widthTop + widthBottom); // correction for surface=1\n if (y1 === 0 || y2 === 0)\n return 0;\n if (x1 === x2) {\n // they have the same position\n return Math.min(y1, y2);\n }\n const diff = Math.abs(x1 - x2);\n if (diff >= widthBottom)\n return 0;\n if (y1 === y2) {\n // do they have the same height ???\n // we need to find the common length\n if (diff <= widthTop) {\n return ((widthTop + widthBottom) / 2 - diff) * y1 * factor;\n }\n else if (diff <= widthBottom) {\n return ((((((widthBottom - diff) * y1) / 2) * (diff - widthTop)) /\n (widthBottom - widthTop)) *\n factor);\n }\n return 0;\n }\n else {\n // the height are different and not the same position ...\n // we need to consider only one segment to find its intersection\n const small = Math.min(y1, y2);\n const big = Math.max(y1, y2);\n const targets = [\n [\n [0, 0],\n [this.widthSlope, small],\n ],\n [\n [this.widthSlope, small],\n [this.widthSlope + widthTop, small],\n ],\n [\n [widthTop + this.widthSlope, small],\n [widthBottom, 0],\n ],\n ];\n let segment;\n if ((x1 > x2 && y1 > y2) || (x1 < x2 && y1 < y2)) {\n segment = [\n [diff, 0],\n [diff + this.widthSlope, big],\n ];\n }\n else {\n segment = [\n [diff + this.widthSlope, big],\n [diff, 0],\n ];\n }\n for (let i = 0; i < 3; i++) {\n const intersection = getIntersection(targets[i], segment);\n if (intersection) {\n switch (i) {\n case 0:\n return small - ((diff * intersection.y) / 2) * factor;\n case 1: // to simplify ...\n // console.log(\" \",widthSlope,small,big,intersection.x)\n return ((((this.widthSlope * small) / (2 * big)) * small +\n (widthTop + this.widthSlope - intersection.x) * small +\n (this.widthSlope * small) / 2) *\n factor);\n case 2:\n return (((widthBottom - diff) * intersection.y) / 2) * factor;\n default:\n throw new Error(`unexpected intersection value: ${i}`);\n }\n }\n }\n }\n return NaN;\n }\n /**\n * This method calculates the total diff. The sum of positive value will yield to overlap\n * @returns\n */\n calculateDiff() {\n // we need to take 2 pointers\n // and travel progressively between them ...\n const newFirst = [\n this.array1Extract[0].slice(),\n this.array1Extract[1].slice(),\n ];\n const newSecond = [\n this.array2Extract[0].slice(),\n this.array2Extract[1].slice(),\n ];\n const array1Length = this.array1Extract[0]\n ? this.array1Extract[0].length\n : 0;\n const array2Length = this.array2Extract[0]\n ? this.array2Extract[0].length\n : 0;\n let pos1 = 0;\n let pos2 = 0;\n let previous2 = 0;\n while (pos1 < array1Length) {\n const diff = newFirst[0][pos1] - this.array2Extract[0][pos2];\n if (Math.abs(diff) < this.widthBottom) {\n // there is some overlap\n let overlap;\n if (this.trapezoid) {\n // old trapezoid overlap similarity\n overlap = this.getOverlapTrapezoid(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2], this.widthTop, this.widthBottom);\n }\n else {\n overlap = this.getOverlap(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2], this.widthTop, this.widthBottom);\n }\n newFirst[1][pos1] -= overlap;\n newSecond[1][pos2] -= overlap;\n if (pos2 < array2Length - 1) {\n pos2++;\n }\n else {\n pos1++;\n pos2 = previous2;\n }\n }\n else if (diff > 0 && pos2 < array2Length - 1) {\n pos2++;\n previous2 = pos2;\n }\n else {\n pos1++;\n pos2 = previous2;\n }\n }\n return newSecond;\n }\n /**\n * Set the new peaks and return info\n * @param {Peaks} newPeaks1\n * @param {Peaks} newPeaks2\n * @returns\n */\n getSimilarity(newPeaks1, newPeaks2) {\n if (newPeaks1)\n this.setPeaks1(newPeaks1);\n if (newPeaks2)\n this.setPeaks2(newPeaks2);\n const diff = this.calculateDiff();\n return {\n diff,\n extract1: this.getExtract1(),\n extract2: this.getExtract2(),\n extractInfo1: this.getExtractInfo1(),\n extractInfo2: this.getExtractInfo2(),\n similarity: calculateOverlapFromDiff(diff),\n widthBottom: this.widthBottom,\n widthTop: this.widthTop,\n };\n }\n /**\n * This works mainly when you have a array1 that is fixed\n * newPeaks2 have to be normalized ! (sum to 1)\n * @param {Peaks} newPeaks2\n * @param {number} from\n * @param {number} to\n * @returns\n */\n fastSimilarity(newPeaks2, from, to) {\n this.array1Extract = extract(this.array1, from, to);\n this.array2Extract = newPeaks2;\n if (this.common & COMMON_SECOND) {\n this.array1Extract = getCommonArray(this.array1Extract, this.array2Extract, this.widthBottom);\n }\n normalize(this.array1Extract);\n const diff = this.calculateDiff();\n return calculateOverlapFromDiff(diff);\n }\n}\n//# sourceMappingURL=index.js.map","import { IsotopicDistribution } from 'isotopic-distribution';\nimport { Comparator } from 'peaks-similarity';\n/**\nSearch for an experimental monoisotopic mass and calculate the similarity\n* @param {object} [options={}]\n* @param {array} [options.databases] - an array containing the name of the databases so search, by default all\n* @param {boolean} [options.flatten] - should we return the array as a flat result\n* @param {function} [options.onStep] - Callback to do after each step\n* @param {string} [options.ionizations=''] - Comma separated list of ionizations (to charge the molecule)\n* @param {object} [options.minSimilarity=0.5] - min similarity value\n\n* @param {object} [options.filter={}]\n* @param {boolean} [options.filter.forceIonization=false] - If true ignore existing ionizations\n* @param {number} [options.filter.msem] - Observed monoisotopic mass in mass spectrometer\n* @param {number} [options.filter.precision=1000] - The precision on the experimental mass\n* @param {number} [options.filter.minCharge=-Infinity] - Minimal charge\n* @param {number} [options.filter.maxCharge=+Infinity] - Maximal charge\n* @param {boolean} [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)\n* @param {object} [options.filter.unsaturation={}]\n* @param {number} [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation\n* @param {number} [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation\n* @param {boolean} [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation\n* @param {boolean} [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation\n* @param {object} [options.filter.atoms] - object of atom:{min, max}\n* @param {object} [options.filter.callback] - a function to filter the MF\n* @param {object} [options.similarity={}]\n* @param {object} [options.similarity.widthBottom]\n* @param {object} [options.similarity.widthTop]\n* @param {object} [options.similarity.widthFunction] - function called with mass that should return an object width containing top and bottom\n* @param {object} [options.similarity.zone={}]\n* @param {object} [options.similarity.zone.low=-0.5] - window shift based on observed monoisotopic mass\n* @param {object} [options.similarity.zone.high=2.5] - to value for the comparison window\n* @param {object} [options.similarity.common]\n* @returns {Promise}\n*/\n\nexport async function searchSimilarity(emdb, options = {}) {\n const { similarity = {}, minSimilarity = 0.5, filter = {}, onStep } = options;\n\n let width = {\n bottom: similarity.widthBottom,\n top: similarity.widthTop,\n };\n\n if (\n !emdb.experimentalSpectrum ||\n !emdb.experimentalSpectrum.data.x.length > 0\n ) {\n throw Error(\n 'You need to add an experimental spectrum first using setMassSpectrum',\n );\n }\n\n let experimentalData = emdb.experimentalSpectrum.data;\n let sumY = emdb.experimentalSpectrum.sumY();\n\n // the result of emdb query will be stored in a property 'ms'\n\n let results = emdb.searchMSEM(filter.msem, options);\n let flatEntries = [];\n if (!options.flatten) {\n for (let database of Object.keys(results)) {\n for (let entry of results[database]) {\n flatEntries.push(entry);\n }\n }\n } else {\n flatEntries = results;\n }\n\n let { widthFunction, zone = {} } = similarity;\n\n if (widthFunction && typeof widthFunction === 'string') {\n // eslint-disable-next-line no-new-func\n widthFunction = new Function('mass', widthFunction);\n let checkTopBottom = widthFunction(123);\n if (!checkTopBottom.bottom || !checkTopBottom.top) {\n throw Error(\n 'widthFunction should return an object with bottom and top properties',\n );\n }\n }\n const { low = -0.5, high = 2.5 } = zone;\n\n // we need to calculate the similarity of the isotopic distribution\n let similarityProcessor = new Comparator(similarity);\n similarityProcessor.setPeaks1([experimentalData.x, experimentalData.y]);\n\n for (let i = 0; i < flatEntries.length; i++) {\n const entry = flatEntries[i];\n if (onStep) await onStep(i);\n let isotopicDistribution = new IsotopicDistribution(entry.mf, {\n allowNeutral: false,\n ionizations: [entry.ionization],\n });\n\n let distribution = isotopicDistribution.getDistribution();\n // we need to define the comparison zone that depends of the charge\n let from = entry.ms.em + low / Math.abs(entry.ms.charge);\n let to = entry.ms.em + high / Math.abs(entry.ms.charge);\n similarityProcessor.setFromTo(from, to);\n\n if (widthFunction) {\n width = widthFunction(entry.ms.em);\n similarityProcessor.setTrapezoid(width.bottom, width.top);\n }\n similarityProcessor.setPeaks2([distribution.xs, distribution.ys]);\n let result = similarityProcessor.getSimilarity();\n\n result.extractInfo1.from = from;\n result.extractInfo1.to = to;\n\n if (result.similarity > minSimilarity) {\n entry.ms.similarity = {\n value: result.similarity,\n experimental: result.extract1,\n theoretical: result.extract2,\n difference: result.diff,\n experimentalInfo: result.extractInfo1,\n thereoticalInfo: result.extractInfo2,\n quantity: result.extractInfo1.sum / sumY,\n width,\n };\n }\n }\n\n if (!options.flatten) {\n for (let database of Object.keys(results)) {\n results[database] = results[database]\n .filter((entry) => entry.ms.similarity)\n .sort((a, b) => b.ms.similarity.value - a.ms.similarity.value);\n for (let entry of results[database]) {\n flatEntries.push(entry);\n }\n }\n } else {\n results = results\n .filter((entry) => entry.ms.similarity)\n .sort((a, b) => b.ms.similarity.value - a.ms.similarity.value);\n }\n\n return results;\n}\n","// eslint-disable-next-line @typescript-eslint/unbound-method\nconst toString = Object.prototype.toString;\n/**\n * Checks if an object is an instance of an Array (array or typed array, except those that contain bigint values).\n *\n * @param value - Object to check.\n * @returns True if the object is an array or a typed array.\n */\nexport function isAnyArray(value) {\n const tag = toString.call(value);\n return tag.endsWith('Array]') && !tag.includes('Big');\n}\n//# sourceMappingURL=index.js.map","export default function maybeToPrecision(value, digits) {\n if (value < 0) {\n value = 0 - value;\n if (typeof digits === 'number') {\n return `- ${value.toPrecision(digits)}`;\n } else {\n return `- ${value.toString()}`;\n }\n } else {\n if (typeof digits === 'number') {\n return value.toPrecision(digits);\n } else {\n return value.toString();\n }\n }\n}\n","import { isAnyArray } from 'is-any-array';\n\nexport default function checkArraySize(x, y) {\n if (!isAnyArray(x) || !isAnyArray(y)) {\n throw new TypeError('x and y must be arrays');\n }\n if (x.length !== y.length) {\n throw new RangeError('x and y arrays must have the same length');\n }\n}\n","import { isAnyArray } from 'is-any-array';\n\nexport { default as maybeToPrecision } from './maybeToPrecision';\nexport { default as checkArrayLength } from './checkArrayLength';\n\nexport default class BaseRegression {\n constructor() {\n if (new.target === BaseRegression) {\n throw new Error('BaseRegression must be subclassed');\n }\n }\n\n predict(x) {\n if (typeof x === 'number') {\n return this._predict(x);\n } else if (isAnyArray(x)) {\n const y = [];\n for (let i = 0; i < x.length; i++) {\n y.push(this._predict(x[i]));\n }\n return y;\n } else {\n throw new TypeError('x must be a number or array');\n }\n }\n\n _predict() {\n throw new Error('_predict must be implemented');\n }\n\n train() {\n // Do nothing for this package\n }\n\n toString() {\n return '';\n }\n\n toLaTeX() {\n return '';\n }\n\n /**\n * Return the correlation coefficient of determination (r) and chi-square.\n * @param {Array} x\n * @param {Array} y\n * @return {object}\n */\n score(x, y) {\n if (!isAnyArray(x) || !isAnyArray(y) || x.length !== y.length) {\n throw new Error('x and y must be arrays of the same length');\n }\n\n const n = x.length;\n const y2 = new Array(n);\n for (let i = 0; i < n; i++) {\n y2[i] = this._predict(x[i]);\n }\n\n let xSum = 0;\n let ySum = 0;\n let chi2 = 0;\n let rmsd = 0;\n let xSquared = 0;\n let ySquared = 0;\n let xY = 0;\n for (let i = 0; i < n; i++) {\n xSum += y2[i];\n ySum += y[i];\n xSquared += y2[i] * y2[i];\n ySquared += y[i] * y[i];\n xY += y2[i] * y[i];\n if (y[i] !== 0) {\n chi2 += ((y[i] - y2[i]) * (y[i] - y2[i])) / y[i];\n }\n rmsd += (y[i] - y2[i]) * (y[i] - y2[i]);\n }\n\n const r =\n (n * xY - xSum * ySum) /\n Math.sqrt((n * xSquared - xSum * xSum) * (n * ySquared - ySum * ySum));\n\n return {\n r: r,\n r2: r * r,\n chi2: chi2,\n rmsd: Math.sqrt(rmsd / n),\n };\n }\n}\n","(function(){function a(d){for(var e=0,f=d.length-1,g=void 0,h=void 0,i=void 0,j=c(e,f);!0;){if(f<=e)return d[j];if(f==e+1)return d[e]>d[f]&&b(d,e,f),d[j];for(g=c(e,f),d[g]>d[f]&&b(d,g,f),d[e]>d[f]&&b(d,e,f),d[g]>d[e]&&b(d,g,e),b(d,g,e+1),h=e+1,i=f;!0;){do h++;while(d[e]>d[h]);do i--;while(d[i]>d[e]);if(i=j&&(f=i-1)}}var b=function b(d,e,f){var _ref;return _ref=[d[f],d[e]],d[e]=_ref[0],d[f]=_ref[1],_ref},c=function c(d,e){return~~((d+e)/2)};'undefined'!=typeof module&&module.exports?module.exports=a:window.median=a})();\n","import { isAnyArray } from 'is-any-array';\nimport quickSelectMedian from 'median-quickselect';\n\nfunction median(input) {\n if (!isAnyArray(input)) {\n throw new TypeError('input must be an array');\n }\n\n if (input.length === 0) {\n throw new TypeError('input must not be empty');\n }\n\n return quickSelectMedian(input.slice());\n}\n\nexport { median as default };\n","import BaseRegression, {\n checkArrayLength,\n maybeToPrecision\n} from 'ml-regression-base';\nimport median from 'ml-array-median';\n\nexport default class TheilSenRegression extends BaseRegression {\n /**\n * Theil–Sen estimator\n * https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator\n * @param {Array|boolean} x\n * @param {Array|object} y\n * @constructor\n */\n constructor(x, y) {\n super();\n if (x === true) {\n // loads the model\n this.slope = y.slope;\n this.intercept = y.intercept;\n this.coefficients = y.coefficients;\n } else {\n // creates the model\n checkArrayLength(x, y);\n theilSen(this, x, y);\n }\n }\n\n toJSON() {\n return {\n name: 'TheilSenRegression',\n slope: this.slope,\n intercept: this.intercept\n };\n }\n\n _predict(input) {\n return this.slope * input + this.intercept;\n }\n\n computeX(input) {\n return (input - this.intercept) / this.slope;\n }\n\n toString(precision) {\n var result = 'f(x) = ';\n if (this.slope) {\n var xFactor = maybeToPrecision(this.slope, precision);\n result += `${Math.abs(xFactor - 1) < 1e-5 ? '' : `${xFactor} * `}x`;\n if (this.intercept) {\n var absIntercept = Math.abs(this.intercept);\n var operator = absIntercept === this.intercept ? '+' : '-';\n result +=\n ` ${operator} ${maybeToPrecision(absIntercept, precision)}`;\n }\n } else {\n result += maybeToPrecision(this.intercept, precision);\n }\n return result;\n }\n\n toLaTeX(precision) {\n return this.toString(precision);\n }\n\n static load(json) {\n if (json.name !== 'TheilSenRegression') {\n throw new TypeError('not a Theil-Sen model');\n }\n return new TheilSenRegression(true, json);\n }\n}\n\nfunction theilSen(regression, x, y) {\n let len = x.length;\n let slopes = new Array(len * len);\n let count = 0;\n for (let i = 0; i < len; ++i) {\n for (let j = i + 1; j < len; ++j) {\n if (x[i] !== x[j]) {\n slopes[count++] = (y[j] - y[i]) / (x[j] - x[i]);\n }\n }\n }\n slopes.length = count;\n let medianSlope = median(slopes);\n\n let cuts = new Array(len);\n for (let i = 0; i < len; ++i) {\n cuts[i] = y[i] - medianSlope * x[i];\n }\n\n regression.slope = medianSlope;\n regression.intercept = median(cuts);\n regression.coefficients = [regression.intercept, regression.slope];\n}\n","import { isAnyArray } from 'is-any-array';\n/**\n * Checks if input is of type array\n *\n * @param input - input\n */\nexport function xCheck(input, options = {}) {\n const { minLength } = options;\n if (!isAnyArray(input)) {\n throw new TypeError('input must be an array');\n }\n if (input.length === 0) {\n throw new TypeError('input must not be empty');\n }\n if (minLength && input.length < minLength) {\n throw new Error(`input must have a length of at least ${minLength}`);\n }\n}\n//# sourceMappingURL=xCheck.js.map","/**\n * Returns the closest index of a `target`\n *\n * @param array - array of numbers\n * @param target - target\n * @returns - closest index\n */\nexport function xFindClosestIndex(array, target, options = {}) {\n const { sorted = true } = options;\n if (sorted) {\n let low = 0;\n let high = array.length - 1;\n let middle = 0;\n while (high - low > 1) {\n middle = low + ((high - low) >> 1);\n if (array[middle] < target) {\n low = middle;\n }\n else if (array[middle] > target) {\n high = middle;\n }\n else {\n return middle;\n }\n }\n if (low < array.length - 1) {\n if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {\n return low;\n }\n else {\n return low + 1;\n }\n }\n else {\n return low;\n }\n }\n else {\n let index = 0;\n let diff = Number.POSITIVE_INFINITY;\n for (let i = 0; i < array.length; i++) {\n const currentDiff = Math.abs(array[i] - target);\n if (currentDiff < diff) {\n diff = currentDiff;\n index = i;\n }\n }\n return index;\n }\n}\n//# sourceMappingURL=xFindClosestIndex.js.map","import { xFindClosestIndex } from './xFindClosestIndex';\n/**\n * Returns an object with {fromIndex, toIndex} for a specific from / to\n *\n * @param x - array of numbers\n * @param options - Options\n */\nexport function xGetFromToIndex(x, options = {}) {\n let { fromIndex, toIndex, from, to } = options;\n if (fromIndex === undefined) {\n if (from !== undefined) {\n fromIndex = xFindClosestIndex(x, from);\n }\n else {\n fromIndex = 0;\n }\n }\n if (toIndex === undefined) {\n if (to !== undefined) {\n toIndex = xFindClosestIndex(x, to);\n }\n else {\n toIndex = x.length - 1;\n }\n }\n if (fromIndex < 0)\n fromIndex = 0;\n if (toIndex < 0)\n toIndex = 0;\n if (fromIndex >= x.length)\n fromIndex = x.length - 1;\n if (toIndex >= x.length)\n toIndex = x.length - 1;\n if (fromIndex > toIndex)\n [fromIndex, toIndex] = [toIndex, fromIndex];\n return { fromIndex, toIndex };\n}\n//# sourceMappingURL=xGetFromToIndex.js.map","import { xCheck } from './xCheck';\nimport { xGetFromToIndex } from './xGetFromToIndex';\n/**\n * Computes the maximal value of an array of values\n *\n * @param array - array of numbers\n * @param options - options\n */\nexport function xMaxValue(array, options = {}) {\n xCheck(array);\n const { fromIndex, toIndex } = xGetFromToIndex(array, options);\n let maxValue = array[fromIndex];\n for (let i = fromIndex + 1; i <= toIndex; i++) {\n if (array[i] > maxValue) {\n maxValue = array[i];\n }\n }\n return maxValue;\n}\n//# sourceMappingURL=xMaxValue.js.map","import { xCheck } from './xCheck';\nimport { xGetFromToIndex } from './xGetFromToIndex';\n/**\n * Computes the minimal value of an array of values\n *\n * @param array - array of numbers\n * @param options - options\n */\nexport function xMinValue(array, options = {}) {\n xCheck(array);\n const { fromIndex, toIndex } = xGetFromToIndex(array, options);\n let minValue = array[fromIndex];\n for (let i = fromIndex + 1; i <= toIndex; i++) {\n if (array[i] < minValue) {\n minValue = array[i];\n }\n }\n return minValue;\n}\n//# sourceMappingURL=xMinValue.js.map","import Regression from 'ml-regression-theil-sen';\nimport { xMinValue, xMaxValue } from 'ml-spectra-processing';\n\n/**\n * Calculates a function that allows post-calibration on mass spectra based on the error in assignment\n * @param {*} similarities\n * @param {object} [options={}]\n * @returns\n */\nexport function massShifts(similarities, options = {}) {\n const { minSimilarity = 0.95, minLength = 10 } = options;\n\n let results = [];\n if (!Array.isArray(similarities)) {\n for (let key of results) {\n for (let entry of results[key]) {\n results.push(entry);\n }\n }\n } else {\n results = similarities;\n }\n\n results = results.filter(\n (result) =>\n result.ms &&\n result.ms.similarity &&\n result.ms.similarity.value > minSimilarity,\n );\n\n if (results.length < minLength) {\n throw new Error(\n `X rescale can not be applied. We need at least ${minLength} peaks with over ${Math.round(\n minSimilarity * 100,\n )}% similarity`,\n );\n }\n\n const data = results\n .map((result) => {\n return {\n em: result.ms.em,\n delta: result.ms.delta,\n };\n })\n .sort((a, b) => a.em - b.em);\n\n let shifts = { x: [], y: [] };\n data.forEach((datum) => {\n shifts.x.push(Number(datum.em));\n shifts.y.push(Number(datum.delta));\n });\n\n const regression = new Regression(shifts.x, shifts.y);\n\n let minX = xMinValue(shifts.x);\n let maxX = xMaxValue(shifts.x);\n\n let shiftsPPM = { x: shifts.x, y: [] };\n data.forEach((datum) => {\n shiftsPPM.y.push(Number((datum.delta / datum.em) * 1e6));\n });\n\n let regressionChart = { x: [], y: [] };\n\n for (let i = minX; i < maxX; i += (maxX - minX) / 1000) {\n regressionChart.x.push(i);\n regressionChart.y.push(regression.predict(i));\n }\n\n return {\n shifts,\n shiftsPPM,\n fit: regressionChart,\n score: regression.score(shifts.x, shifts.y),\n\n predictFct: regression.predict.bind(regression),\n tex: regression.toLaTeX(3),\n slope: regression.slope,\n intercept: regression.intercept,\n predictFctString: `${regression.slope} * mass + ${regression.intercept}`,\n };\n}\n","import { Spectrum } from 'ms-spectrum';\n\nimport { appendFragmentsInfo } from './append/appendFragmentsInfo.js';\nimport { fromArray } from './from/fromArray.js';\nimport { fromMolecules } from './from/fromMolecules.js';\nimport { fromMonoisotopicMass } from './from/fromMonoisotopicMass.js';\nimport { fromNucleicSequence } from './from/fromNucleicSequence.js';\nimport { fromPeptidicSequence } from './from/fromPeptidicSequence.js';\nimport { fromRange } from './from/fromRange.js';\nimport { loadCommercials } from './loadCommercials.js';\nimport { loadGoogleSheet } from './loadGoogleSheet.js';\nimport { loadKnapSack } from './loadKnapSack.js';\nimport { search } from './search.js';\nimport { searchMSEM } from './searchMSEM.js';\nimport { searchSimilarity } from './searchSimilarity.js';\n\nexport * from './massShifts.js';\nexport * from './util/fetchJSON.js';\n/**\n * A class that deals with database of monoisotopic mass and molecular formula\n */\nexport class EMDB {\n constructor() {\n this.databases = {};\n this.experimentalSpectrum = undefined;\n }\n\n /**\n *\n * @param {*} data\n * @param {object} [options={}]\n * @param {number} [options.normed=true] Should we normed (sum Y to 1) the experimental spectrum ?\n */\n setExperimentalSpectrum(data, options = {}) {\n const { normed = true } = options;\n if (normed) {\n this.experimentalSpectrum = new Spectrum(data).normedY();\n } else {\n this.experimentalSpectrum = new Spectrum(data);\n }\n return this.experimentalSpectrum;\n }\n\n /**\n * Add a new database using the KnapSack content\n * @param {*} options\n */\n async loadKnapSack(options = {}) {\n const { databaseName = 'knapSack', forceReload = false } = options;\n if (this.databases[databaseName] && !forceReload) return;\n this.databases[databaseName] = await loadKnapSack();\n }\n\n /**\n * Add a new database of 12000 commercial products\n * @param {*} options\n */\n async loadCommercials(options = {}) {\n const { databaseName = 'commercials', forceReload = false } = options;\n if (this.databases[databaseName] && !forceReload) return;\n this.databases[databaseName] = await loadCommercials();\n }\n\n get(databaseName) {\n return this.databases[databaseName];\n }\n\n /**\n * Load the contaminants database from a google sheet document\n * @param {object} [options={}]\n * @param {string} [options.databaseName='contaminants']\n * @param {string} [options.forceReload=false]\n */\n async loadContaminants(options = {}) {\n const { databaseName = 'contaminants', forceReload = false } = options;\n if (this.databases[databaseName] && !forceReload) return;\n this.databases[databaseName] = await loadGoogleSheet();\n }\n\n /**\n * Load a google sheet containing MF information\n * @param {object} [options={}]\n * @param {string} [options.databaseName='sheet']\n * @param {string} [options.forceReload=false]\n */\n\n async loadGoogleSheet(options = {}) {\n const { databaseName = 'sheet', forceReload = false } = options;\n if (this.databases[databaseName] && !forceReload) return;\n this.databases[databaseName] = await loadGoogleSheet();\n }\n\n async loadTest() {\n await this.fromArray(['C1-100'], {\n databaseName: 'test',\n ionizations: '+',\n });\n }\n\n async loadNeutralTest(options = {}) {\n const { maxC = 100 } = options;\n await this.fromArray([`C1-${maxC}`], { databaseName: 'test' });\n }\n\n async fromMonoisotopicMass(mass, options = {}) {\n const { databaseName = 'monoisotopic', append = false } = options;\n let result = await fromMonoisotopicMass(mass, options);\n replaceOrAppend(this, databaseName, result.mfs, append);\n return result;\n }\n\n async fromArray(sequence, options = {}) {\n const { databaseName = 'generated', append = false, estimate } = options;\n const results = await fromArray(sequence, options);\n if (estimate) return results;\n replaceOrAppend(this, databaseName, results, append);\n }\n\n async fromMolecules(entries, ocl, options = {}) {\n const { databaseName = 'molecules', append = false } = options;\n const results = await fromMolecules(entries, ocl, options);\n replaceOrAppend(this, databaseName, results, append);\n }\n\n async fromRange(sequence, options = {}) {\n const { databaseName = 'generated', append = false, estimate } = options;\n const results = await fromRange(sequence, options);\n if (estimate) return results;\n replaceOrAppend(this, databaseName, results, append);\n }\n\n async fromPeptidicSequence(sequence, options = {}) {\n const { databaseName = 'peptidic', append = false, estimate } = options;\n const results = await fromPeptidicSequence(sequence, options);\n if (estimate) return results;\n replaceOrAppend(this, databaseName, results, append);\n }\n\n /**\n *\n * @param {string} databaseName\n * @param {object} [options={}]\n * @param {number} [options.precision=100]\n * @param {string} [options.ionizations='']\n * @returns\n */\n async appendFragmentsInfo(databaseName, options = {}) {\n const database = this.databases[databaseName];\n await appendFragmentsInfo(this.experimentalSpectrum, database, options);\n return database;\n }\n\n async fromNucleicSequence(sequence, options = {}) {\n const { databaseName = 'nucleic', append = false, estimate } = options;\n const results = await fromNucleicSequence(sequence, options);\n if (estimate) return results;\n replaceOrAppend(this, databaseName, results, append);\n }\n\n listDatabases() {\n return Object.keys(this.databases).sort();\n }\n\n getInfo() {\n return {\n databases: Object.keys(this.databases)\n .sort()\n .map((key) => {\n return { name: key, nbEntries: this.databases[key].length };\n }),\n };\n }\n\n search(filter, options = {}) {\n return search(this, filter, options);\n }\n\n searchMSEM(filter, options = {}) {\n return searchMSEM(this, filter, options);\n }\n\n searchSimilarity(options = {}) {\n return searchSimilarity(this, options);\n }\n}\n\nfunction replaceOrAppend(emdb, databaseName, results, append = false) {\n if (!emdb.databases[databaseName] || !append) {\n emdb.databases[databaseName] = results;\n return;\n }\n emdb.databases[databaseName] = emdb.databases[databaseName].concat(results);\n}\n","export function appendInternals(data) {\n // for each line (internal fragment) we calculate the vertical position\n // where it should be drawn as well and the maximal number of lines\n let maxNumberLines = 0;\n for (let result of data.results) {\n if (result.internal) {\n result.slot = assignSlot(result.from, result.to, data.residues.residues);\n if (result.slot > maxNumberLines) maxNumberLines = result.slot;\n }\n }\n for (let row of data.rows) {\n row.info.internals = maxNumberLines;\n }\n}\n\n// we need to define the height of the line.\n// we need to find a height that is not yet used.\nfunction assignSlot(from, to, residues) {\n let used = {};\n if (from > 0) from--; // we ensure that we don't put on the same line to sequences that are consecutive\n for (let i = from; i < to; i++) {\n let residue = residues[i];\n residue.paper.usedSlots.forEach((usedSlot, index) => {\n used[index] = true;\n });\n }\n let counter = 0;\n while (true) {\n if (!used[counter]) {\n break;\n }\n counter++;\n }\n for (let i = from; i < to; i++) {\n residues[i].paper.usedSlots[counter] = true;\n }\n return counter;\n}\n","import { groupsObject } from 'chemical-groups';\nimport * as Nucleotide from 'nucleotide';\nimport * as Peptide from 'peptide';\n\nconst ALTERNATIVES = ['', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];\nconst SYMBOLS = ['Θ', 'Δ', 'Λ', 'Φ', 'Ω', 'Γ', 'Χ'];\n\nlet currentSymbol = 0;\n\n/**\n * Code that allows to split a sequence of amino acids or nucleotides natural or non natural\n * @param {string} [sequence]\n * @param {object} [options={}]\n * @param {string} [options.kind] - peptide, rna, ds-dna or dna. Default if contains U: rna, otherwise ds-dna\n * @param {string} [options.fivePrime=monophosphate] - alcohol, monophosphate, diphosphate, triphosphate\n * @param {string} [options.circular=false]\n */\n\nexport function appendResidues(data, sequence, options = {}) {\n const { kind = 'peptide' } = options;\n\n currentSymbol = 0;\n // we normalize the sequence to 3 letter codes\n\n if (kind === 'peptide') {\n sequence = Peptide.sequenceToMF(sequence);\n } else {\n sequence = Nucleotide.sequenceToMF(sequence, options);\n }\n\n const result = {\n begin: '',\n end: '',\n residues: [],\n };\n\n const STATE_BEGIN = 0;\n const STATE_MIDDLE = 1;\n const STATE_END = 2;\n\n let parenthesisLevel = 0;\n let state = STATE_BEGIN; // as long as we don't have an uppercase followed by 2 lowercases\n for (let i = 0; i < sequence.length; i++) {\n let currentChar = sequence.charAt(i);\n let nextChar = i < sequence.length - 1 ? sequence.charAt(i + 1) : '';\n let nextNextChar = i < sequence.length - 2 ? sequence.charAt(i + 2) : '';\n\n if (\n state === STATE_BEGIN &&\n currentChar.match(/[A-Z]/) &&\n nextChar.match(/[a-z]/) &&\n nextNextChar.match(/[a-z]/) &&\n parenthesisLevel === 0\n ) {\n state = STATE_MIDDLE;\n }\n\n if (\n state === STATE_MIDDLE &&\n !sequence.substring(i).match(/[A-Z][a-z][a-z]/) &&\n !currentChar.match(/[a-z]/) &&\n parenthesisLevel === 0\n ) {\n state = STATE_END;\n } else if (\n currentChar.match(/[A-Z]/) &&\n nextChar.match(/[a-z]/) &&\n nextNextChar.match(/[a-z]/) &&\n parenthesisLevel === 0\n ) {\n result.residues.push('');\n }\n\n switch (state) {\n case STATE_BEGIN:\n result.begin = result.begin + currentChar;\n break;\n case STATE_MIDDLE:\n result.residues[result.residues.length - 1] =\n result.residues[result.residues.length - 1] + currentChar;\n break;\n case STATE_END:\n result.end = result.end + currentChar;\n break;\n default:\n }\n\n if (currentChar === '(') {\n parenthesisLevel++;\n } else if (currentChar === ')') {\n parenthesisLevel--;\n }\n }\n\n // we process all the residues\n let alternatives = {};\n let replacements = {};\n for (let i = 0; i < result.residues.length; i++) {\n let label = result.residues[i];\n let residue = {\n value: label,\n results: {\n begin: [],\n end: [],\n },\n };\n residue.fromBegin = i + 1;\n residue.fromEnd = result.residues.length - i;\n residue.kind = 'residue';\n if (label.includes('(')) {\n getModifiedReplacement(label, residue, alternatives, replacements);\n } else if (groupsObject[label] && groupsObject[label].oneLetter) {\n residue.label = groupsObject[label].oneLetter;\n } else {\n getUnknownReplacement(label, residue, replacements);\n }\n result.residues[i] = residue;\n }\n result.begin = removeStartEndParenthesis(result.begin);\n result.end = removeStartEndParenthesis(result.end);\n if (result.begin.length > 2) {\n let label = options.kind === 'peptide' ? 'Nter' : \"5'\";\n replacements[result.begin] = {\n label,\n };\n result.begin = label;\n }\n if (result.end.length > 2) {\n let label = options.kind === 'peptide' ? 'Cter' : \"3'\";\n replacements[result.end] = {\n label,\n };\n result.end = label;\n }\n\n result.begin = { label: result.begin, kind: 'begin' };\n result.end = { label: result.end, kind: 'end' };\n result.alternatives = alternatives;\n result.replacements = replacements;\n\n result.all = [result.begin].concat(result.residues, [result.end]);\n\n result.all.forEach((entry) => {\n entry.info = {\n nbOver: 0,\n nbUnder: 0,\n };\n });\n\n data.residues = result;\n}\n\nfunction getUnknownReplacement(unknownResidue, residue, replacements) {\n if (!replacements[unknownResidue]) {\n replacements[unknownResidue] = {\n label: SYMBOLS[currentSymbol] || '?',\n id: unknownResidue,\n };\n }\n currentSymbol++;\n residue.replaced = true;\n residue.label = replacements[unknownResidue].label;\n}\n\nfunction getModifiedReplacement(\n modifiedResidue,\n residue,\n alternatives,\n replacements,\n) {\n if (!replacements[modifiedResidue]) {\n let position = modifiedResidue.indexOf('(');\n let residueCode = modifiedResidue.substring(0, position);\n let modification = removeStartEndParenthesis(\n modifiedResidue.substring(position),\n );\n\n if (\n groupsObject[residueCode] &&\n groupsObject[residueCode].alternativeOneLetter\n ) {\n let alternativeOneLetter = groupsObject[residueCode].alternativeOneLetter;\n\n if (!alternatives[alternativeOneLetter]) {\n alternatives[alternativeOneLetter] = { count: 1 };\n } else {\n alternatives[alternativeOneLetter].count++;\n }\n replacements[modifiedResidue] = {\n label:\n ALTERNATIVES[alternatives[alternativeOneLetter].count - 1] +\n alternativeOneLetter,\n residue: residueCode,\n modification,\n };\n } else {\n getUnknownReplacement(modifiedResidue, residue, replacements);\n }\n }\n residue.replaced = true;\n residue.label = replacements[modifiedResidue].label;\n}\n\nfunction removeStartEndParenthesis(mf) {\n if (mf[0] === '(' && mf[mf.length - 1] === ')') {\n return mf.substring(1, mf.length - 1);\n }\n return mf;\n}\n","/*!\n* @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG.\n* @version 3.1.1\n* https://svgjs.dev/\n*\n* @copyright Wout Fierens \n* @license MIT\n*\n* BUILT: Fri Jun 25 2021 15:37:54 GMT+0200 (Mitteleuropäische Sommerzeit)\n*/;\nconst methods$1 = {};\nconst names = [];\nfunction registerMethods(name, m) {\n if (Array.isArray(name)) {\n for (const _name of name) {\n registerMethods(_name, m);\n }\n\n return;\n }\n\n if (typeof name === 'object') {\n for (const _name in name) {\n registerMethods(_name, name[_name]);\n }\n\n return;\n }\n\n addMethodNames(Object.getOwnPropertyNames(m));\n methods$1[name] = Object.assign(methods$1[name] || {}, m);\n}\nfunction getMethodsFor(name) {\n return methods$1[name] || {};\n}\nfunction getMethodNames() {\n return [...new Set(names)];\n}\nfunction addMethodNames(_names) {\n names.push(..._names);\n}\n\n// Map function\nfunction map(array, block) {\n let i;\n const il = array.length;\n const result = [];\n\n for (i = 0; i < il; i++) {\n result.push(block(array[i]));\n }\n\n return result;\n} // Filter function\n\nfunction filter(array, block) {\n let i;\n const il = array.length;\n const result = [];\n\n for (i = 0; i < il; i++) {\n if (block(array[i])) {\n result.push(array[i]);\n }\n }\n\n return result;\n} // Degrees to radians\n\nfunction radians(d) {\n return d % 360 * Math.PI / 180;\n} // Radians to degrees\n\nfunction degrees(r) {\n return r * 180 / Math.PI % 360;\n} // Convert dash-separated-string to camelCase\n\nfunction camelCase(s) {\n return s.toLowerCase().replace(/-(.)/g, function (m, g) {\n return g.toUpperCase();\n });\n} // Convert camel cased string to dash separated\n\nfunction unCamelCase(s) {\n return s.replace(/([A-Z])/g, function (m, g) {\n return '-' + g.toLowerCase();\n });\n} // Capitalize first letter of a string\n\nfunction capitalize(s) {\n return s.charAt(0).toUpperCase() + s.slice(1);\n} // Calculate proportional width and height values when necessary\n\nfunction proportionalSize(element, width, height, box) {\n if (width == null || height == null) {\n box = box || element.bbox();\n\n if (width == null) {\n width = box.width / box.height * height;\n } else if (height == null) {\n height = box.height / box.width * width;\n }\n }\n\n return {\n width: width,\n height: height\n };\n}\n/**\r\n * This function adds support for string origins.\r\n * It searches for an origin in o.origin o.ox and o.originX.\r\n * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50\r\n**/\n\nfunction getOrigin(o, element) {\n const origin = o.origin; // First check if origin is in ox or originX\n\n let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center';\n let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'; // Then check if origin was used and overwrite in that case\n\n if (origin != null) {\n [ox, oy] = Array.isArray(origin) ? origin : typeof origin === 'object' ? [origin.x, origin.y] : [origin, origin];\n } // Make sure to only call bbox when actually needed\n\n\n const condX = typeof ox === 'string';\n const condY = typeof oy === 'string';\n\n if (condX || condY) {\n const {\n height,\n width,\n x,\n y\n } = element.bbox(); // And only overwrite if string was passed for this specific axis\n\n if (condX) {\n ox = ox.includes('left') ? x : ox.includes('right') ? x + width : x + width / 2;\n }\n\n if (condY) {\n oy = oy.includes('top') ? y : oy.includes('bottom') ? y + height : y + height / 2;\n }\n } // Return the origin as it is if it wasn't a string\n\n\n return [ox, oy];\n}\n\nvar utils = {\n __proto__: null,\n map: map,\n filter: filter,\n radians: radians,\n degrees: degrees,\n camelCase: camelCase,\n unCamelCase: unCamelCase,\n capitalize: capitalize,\n proportionalSize: proportionalSize,\n getOrigin: getOrigin\n};\n\n// Default namespaces\nconst svg = 'http://www.w3.org/2000/svg';\nconst html = 'http://www.w3.org/1999/xhtml';\nconst xmlns = 'http://www.w3.org/2000/xmlns/';\nconst xlink = 'http://www.w3.org/1999/xlink';\nconst svgjs = 'http://svgjs.dev/svgjs';\n\nvar namespaces = {\n __proto__: null,\n svg: svg,\n html: html,\n xmlns: xmlns,\n xlink: xlink,\n svgjs: svgjs\n};\n\nconst globals = {\n window: typeof window === 'undefined' ? null : window,\n document: typeof document === 'undefined' ? null : document\n};\nfunction registerWindow(win = null, doc = null) {\n globals.window = win;\n globals.document = doc;\n}\nconst save = {};\nfunction saveWindow() {\n save.window = globals.window;\n save.document = globals.document;\n}\nfunction restoreWindow() {\n globals.window = save.window;\n globals.document = save.document;\n}\nfunction withWindow(win, fn) {\n saveWindow();\n registerWindow(win, win.document);\n fn(win, win.document);\n restoreWindow();\n}\nfunction getWindow() {\n return globals.window;\n}\n\nclass Base {// constructor (node/*, {extensions = []} */) {\n // // this.tags = []\n // //\n // // for (let extension of extensions) {\n // // extension.setup.call(this, node)\n // // this.tags.push(extension.name)\n // // }\n // }\n}\n\nconst elements = {};\nconst root = '___SYMBOL___ROOT___'; // Method for element creation\n\nfunction create(name, ns = svg) {\n // create element\n return globals.document.createElementNS(ns, name);\n}\nfunction makeInstance(element, isHTML = false) {\n if (element instanceof Base) return element;\n\n if (typeof element === 'object') {\n return adopter(element);\n }\n\n if (element == null) {\n return new elements[root]();\n }\n\n if (typeof element === 'string' && element.charAt(0) !== '<') {\n return adopter(globals.document.querySelector(element));\n } // Make sure, that HTML elements are created with the correct namespace\n\n\n const wrapper = isHTML ? globals.document.createElement('div') : create('svg');\n wrapper.innerHTML = element; // We can use firstChild here because we know,\n // that the first char is < and thus an element\n\n element = adopter(wrapper.firstChild); // make sure, that element doesnt have its wrapper attached\n\n wrapper.removeChild(wrapper.firstChild);\n return element;\n}\nfunction nodeOrNew(name, node) {\n return node instanceof globals.window.Node ? node : create(name);\n} // Adopt existing svg elements\n\nfunction adopt(node) {\n // check for presence of node\n if (!node) return null; // make sure a node isn't already adopted\n\n if (node.instance instanceof Base) return node.instance;\n\n if (node.nodeName === '#document-fragment') {\n return new elements.Fragment(node);\n } // initialize variables\n\n\n let className = capitalize(node.nodeName || 'Dom'); // Make sure that gradients are adopted correctly\n\n if (className === 'LinearGradient' || className === 'RadialGradient') {\n className = 'Gradient'; // Fallback to Dom if element is not known\n } else if (!elements[className]) {\n className = 'Dom';\n }\n\n return new elements[className](node);\n}\nlet adopter = adopt;\nfunction mockAdopt(mock = adopt) {\n adopter = mock;\n}\nfunction register(element, name = element.name, asRoot = false) {\n elements[name] = element;\n if (asRoot) elements[root] = element;\n addMethodNames(Object.getOwnPropertyNames(element.prototype));\n return element;\n}\nfunction getClass(name) {\n return elements[name];\n} // Element id sequence\n\nlet did = 1000; // Get next named element id\n\nfunction eid(name) {\n return 'Svgjs' + capitalize(name) + did++;\n} // Deep new id assignment\n\nfunction assignNewId(node) {\n // do the same for SVG child nodes as well\n for (let i = node.children.length - 1; i >= 0; i--) {\n assignNewId(node.children[i]);\n }\n\n if (node.id) {\n node.id = eid(node.nodeName);\n return node;\n }\n\n return node;\n} // Method for extending objects\n\nfunction extend(modules, methods) {\n let key, i;\n modules = Array.isArray(modules) ? modules : [modules];\n\n for (i = modules.length - 1; i >= 0; i--) {\n for (key in methods) {\n modules[i].prototype[key] = methods[key];\n }\n }\n}\nfunction wrapWithAttrCheck(fn) {\n return function (...args) {\n const o = args[args.length - 1];\n\n if (o && o.constructor === Object && !(o instanceof Array)) {\n return fn.apply(this, args.slice(0, -1)).attr(o);\n } else {\n return fn.apply(this, args);\n }\n };\n}\n\nfunction siblings() {\n return this.parent().children();\n} // Get the current position siblings\n\nfunction position() {\n return this.parent().index(this);\n} // Get the next element (will return null if there is none)\n\nfunction next() {\n return this.siblings()[this.position() + 1];\n} // Get the next element (will return null if there is none)\n\nfunction prev() {\n return this.siblings()[this.position() - 1];\n} // Send given element one step forward\n\nfunction forward() {\n const i = this.position();\n const p = this.parent(); // move node one step forward\n\n p.add(this.remove(), i + 1);\n return this;\n} // Send given element one step backward\n\nfunction backward() {\n const i = this.position();\n const p = this.parent();\n p.add(this.remove(), i ? i - 1 : 0);\n return this;\n} // Send given element all the way to the front\n\nfunction front() {\n const p = this.parent(); // Move node forward\n\n p.add(this.remove());\n return this;\n} // Send given element all the way to the back\n\nfunction back() {\n const p = this.parent(); // Move node back\n\n p.add(this.remove(), 0);\n return this;\n} // Inserts a given element before the targeted element\n\nfunction before(element) {\n element = makeInstance(element);\n element.remove();\n const i = this.position();\n this.parent().add(element, i);\n return this;\n} // Inserts a given element after the targeted element\n\nfunction after(element) {\n element = makeInstance(element);\n element.remove();\n const i = this.position();\n this.parent().add(element, i + 1);\n return this;\n}\nfunction insertBefore(element) {\n element = makeInstance(element);\n element.before(this);\n return this;\n}\nfunction insertAfter(element) {\n element = makeInstance(element);\n element.after(this);\n return this;\n}\nregisterMethods('Dom', {\n siblings,\n position,\n next,\n prev,\n forward,\n backward,\n front,\n back,\n before,\n after,\n insertBefore,\n insertAfter\n});\n\n// Parse unit value\nconst numberAndUnit = /^([+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?)([a-z%]*)$/i; // Parse hex value\n\nconst hex = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i; // Parse rgb value\n\nconst rgb = /rgb\\((\\d+),(\\d+),(\\d+)\\)/; // Parse reference id\n\nconst reference = /(#[a-z_][a-z0-9\\-_]*)/i; // splits a transformation chain\n\nconst transforms = /\\)\\s*,?\\s*/; // Whitespace\n\nconst whitespace = /\\s/g; // Test hex value\n\nconst isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i; // Test rgb value\n\nconst isRgb = /^rgb\\(/; // Test for blank string\n\nconst isBlank = /^(\\s+)?$/; // Test for numeric string\n\nconst isNumber = /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i; // Test for image url\n\nconst isImage = /\\.(jpg|jpeg|png|gif|svg)(\\?[^=]+.*)?/i; // split at whitespace and comma\n\nconst delimiter = /[\\s,]+/; // Test for path letter\n\nconst isPathLetter = /[MLHVCSQTAZ]/i;\n\nvar regex = {\n __proto__: null,\n numberAndUnit: numberAndUnit,\n hex: hex,\n rgb: rgb,\n reference: reference,\n transforms: transforms,\n whitespace: whitespace,\n isHex: isHex,\n isRgb: isRgb,\n isBlank: isBlank,\n isNumber: isNumber,\n isImage: isImage,\n delimiter: delimiter,\n isPathLetter: isPathLetter\n};\n\nfunction classes() {\n const attr = this.attr('class');\n return attr == null ? [] : attr.trim().split(delimiter);\n} // Return true if class exists on the node, false otherwise\n\nfunction hasClass(name) {\n return this.classes().indexOf(name) !== -1;\n} // Add class to the node\n\nfunction addClass(name) {\n if (!this.hasClass(name)) {\n const array = this.classes();\n array.push(name);\n this.attr('class', array.join(' '));\n }\n\n return this;\n} // Remove class from the node\n\nfunction removeClass(name) {\n if (this.hasClass(name)) {\n this.attr('class', this.classes().filter(function (c) {\n return c !== name;\n }).join(' '));\n }\n\n return this;\n} // Toggle the presence of a class on the node\n\nfunction toggleClass(name) {\n return this.hasClass(name) ? this.removeClass(name) : this.addClass(name);\n}\nregisterMethods('Dom', {\n classes,\n hasClass,\n addClass,\n removeClass,\n toggleClass\n});\n\nfunction css(style, val) {\n const ret = {};\n\n if (arguments.length === 0) {\n // get full style as object\n this.node.style.cssText.split(/\\s*;\\s*/).filter(function (el) {\n return !!el.length;\n }).forEach(function (el) {\n const t = el.split(/\\s*:\\s*/);\n ret[t[0]] = t[1];\n });\n return ret;\n }\n\n if (arguments.length < 2) {\n // get style properties as array\n if (Array.isArray(style)) {\n for (const name of style) {\n const cased = camelCase(name);\n ret[cased] = this.node.style[cased];\n }\n\n return ret;\n } // get style for property\n\n\n if (typeof style === 'string') {\n return this.node.style[camelCase(style)];\n } // set styles in object\n\n\n if (typeof style === 'object') {\n for (const name in style) {\n // set empty string if null/undefined/'' was given\n this.node.style[camelCase(name)] = style[name] == null || isBlank.test(style[name]) ? '' : style[name];\n }\n }\n } // set style for property\n\n\n if (arguments.length === 2) {\n this.node.style[camelCase(style)] = val == null || isBlank.test(val) ? '' : val;\n }\n\n return this;\n} // Show element\n\nfunction show() {\n return this.css('display', '');\n} // Hide element\n\nfunction hide() {\n return this.css('display', 'none');\n} // Is element visible?\n\nfunction visible() {\n return this.css('display') !== 'none';\n}\nregisterMethods('Dom', {\n css,\n show,\n hide,\n visible\n});\n\nfunction data(a, v, r) {\n if (a == null) {\n // get an object of attributes\n return this.data(map(filter(this.node.attributes, el => el.nodeName.indexOf('data-') === 0), el => el.nodeName.slice(5)));\n } else if (a instanceof Array) {\n const data = {};\n\n for (const key of a) {\n data[key] = this.data(key);\n }\n\n return data;\n } else if (typeof a === 'object') {\n for (v in a) {\n this.data(v, a[v]);\n }\n } else if (arguments.length < 2) {\n try {\n return JSON.parse(this.attr('data-' + a));\n } catch (e) {\n return this.attr('data-' + a);\n }\n } else {\n this.attr('data-' + a, v === null ? null : r === true || typeof v === 'string' || typeof v === 'number' ? v : JSON.stringify(v));\n }\n\n return this;\n}\nregisterMethods('Dom', {\n data\n});\n\nfunction remember(k, v) {\n // remember every item in an object individually\n if (typeof arguments[0] === 'object') {\n for (const key in k) {\n this.remember(key, k[key]);\n }\n } else if (arguments.length === 1) {\n // retrieve memory\n return this.memory()[k];\n } else {\n // store memory\n this.memory()[k] = v;\n }\n\n return this;\n} // Erase a given memory\n\nfunction forget() {\n if (arguments.length === 0) {\n this._memory = {};\n } else {\n for (let i = arguments.length - 1; i >= 0; i--) {\n delete this.memory()[arguments[i]];\n }\n }\n\n return this;\n} // This triggers creation of a new hidden class which is not performant\n// However, this function is not rarely used so it will not happen frequently\n// Return local memory object\n\nfunction memory() {\n return this._memory = this._memory || {};\n}\nregisterMethods('Dom', {\n remember,\n forget,\n memory\n});\n\nfunction sixDigitHex(hex) {\n return hex.length === 4 ? ['#', hex.substring(1, 2), hex.substring(1, 2), hex.substring(2, 3), hex.substring(2, 3), hex.substring(3, 4), hex.substring(3, 4)].join('') : hex;\n}\n\nfunction componentHex(component) {\n const integer = Math.round(component);\n const bounded = Math.max(0, Math.min(255, integer));\n const hex = bounded.toString(16);\n return hex.length === 1 ? '0' + hex : hex;\n}\n\nfunction is(object, space) {\n for (let i = space.length; i--;) {\n if (object[space[i]] == null) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction getParameters(a, b) {\n const params = is(a, 'rgb') ? {\n _a: a.r,\n _b: a.g,\n _c: a.b,\n _d: 0,\n space: 'rgb'\n } : is(a, 'xyz') ? {\n _a: a.x,\n _b: a.y,\n _c: a.z,\n _d: 0,\n space: 'xyz'\n } : is(a, 'hsl') ? {\n _a: a.h,\n _b: a.s,\n _c: a.l,\n _d: 0,\n space: 'hsl'\n } : is(a, 'lab') ? {\n _a: a.l,\n _b: a.a,\n _c: a.b,\n _d: 0,\n space: 'lab'\n } : is(a, 'lch') ? {\n _a: a.l,\n _b: a.c,\n _c: a.h,\n _d: 0,\n space: 'lch'\n } : is(a, 'cmyk') ? {\n _a: a.c,\n _b: a.m,\n _c: a.y,\n _d: a.k,\n space: 'cmyk'\n } : {\n _a: 0,\n _b: 0,\n _c: 0,\n space: 'rgb'\n };\n params.space = b || params.space;\n return params;\n}\n\nfunction cieSpace(space) {\n if (space === 'lab' || space === 'xyz' || space === 'lch') {\n return true;\n } else {\n return false;\n }\n}\n\nfunction hueToRgb(p, q, t) {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n}\n\nclass Color {\n constructor(...inputs) {\n this.init(...inputs);\n } // Test if given value is a color\n\n\n static isColor(color) {\n return color && (color instanceof Color || this.isRgb(color) || this.test(color));\n } // Test if given value is an rgb object\n\n\n static isRgb(color) {\n return color && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number';\n }\n /*\r\n Generating random colors\r\n */\n\n\n static random(mode = 'vibrant', t, u) {\n // Get the math modules\n const {\n random,\n round,\n sin,\n PI: pi\n } = Math; // Run the correct generator\n\n if (mode === 'vibrant') {\n const l = (81 - 57) * random() + 57;\n const c = (83 - 45) * random() + 45;\n const h = 360 * random();\n const color = new Color(l, c, h, 'lch');\n return color;\n } else if (mode === 'sine') {\n t = t == null ? random() : t;\n const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150);\n const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200);\n const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150);\n const color = new Color(r, g, b);\n return color;\n } else if (mode === 'pastel') {\n const l = (94 - 86) * random() + 86;\n const c = (26 - 9) * random() + 9;\n const h = 360 * random();\n const color = new Color(l, c, h, 'lch');\n return color;\n } else if (mode === 'dark') {\n const l = 10 + 10 * random();\n const c = (125 - 75) * random() + 86;\n const h = 360 * random();\n const color = new Color(l, c, h, 'lch');\n return color;\n } else if (mode === 'rgb') {\n const r = 255 * random();\n const g = 255 * random();\n const b = 255 * random();\n const color = new Color(r, g, b);\n return color;\n } else if (mode === 'lab') {\n const l = 100 * random();\n const a = 256 * random() - 128;\n const b = 256 * random() - 128;\n const color = new Color(l, a, b, 'lab');\n return color;\n } else if (mode === 'grey') {\n const grey = 255 * random();\n const color = new Color(grey, grey, grey);\n return color;\n } else {\n throw new Error('Unsupported random color mode');\n }\n } // Test if given value is a color string\n\n\n static test(color) {\n return typeof color === 'string' && (isHex.test(color) || isRgb.test(color));\n }\n\n cmyk() {\n // Get the rgb values for the current color\n const {\n _a,\n _b,\n _c\n } = this.rgb();\n const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Get the cmyk values in an unbounded format\n\n const k = Math.min(1 - r, 1 - g, 1 - b);\n\n if (k === 1) {\n // Catch the black case\n return new Color(0, 0, 0, 1, 'cmyk');\n }\n\n const c = (1 - r - k) / (1 - k);\n const m = (1 - g - k) / (1 - k);\n const y = (1 - b - k) / (1 - k); // Construct the new color\n\n const color = new Color(c, m, y, k, 'cmyk');\n return color;\n }\n\n hsl() {\n // Get the rgb values\n const {\n _a,\n _b,\n _c\n } = this.rgb();\n const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Find the maximum and minimum values to get the lightness\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2; // If the r, g, v values are identical then we are grey\n\n const isGrey = max === min; // Calculate the hue and saturation\n\n const delta = max - min;\n const s = isGrey ? 0 : l > 0.5 ? delta / (2 - max - min) : delta / (max + min);\n const h = isGrey ? 0 : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / delta + 2) / 6 : max === b ? ((r - g) / delta + 4) / 6 : 0; // Construct and return the new color\n\n const color = new Color(360 * h, 100 * s, 100 * l, 'hsl');\n return color;\n }\n\n init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') {\n // This catches the case when a falsy value is passed like ''\n a = !a ? 0 : a; // Reset all values in case the init function is rerun with new color space\n\n if (this.space) {\n for (const component in this.space) {\n delete this[this.space[component]];\n }\n }\n\n if (typeof a === 'number') {\n // Allow for the case that we don't need d...\n space = typeof d === 'string' ? d : space;\n d = typeof d === 'string' ? 0 : d; // Assign the values straight to the color\n\n Object.assign(this, {\n _a: a,\n _b: b,\n _c: c,\n _d: d,\n space\n }); // If the user gave us an array, make the color from it\n } else if (a instanceof Array) {\n this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb';\n Object.assign(this, {\n _a: a[0],\n _b: a[1],\n _c: a[2],\n _d: a[3] || 0\n });\n } else if (a instanceof Object) {\n // Set the object up and assign its values directly\n const values = getParameters(a, b);\n Object.assign(this, values);\n } else if (typeof a === 'string') {\n if (isRgb.test(a)) {\n const noWhitespace = a.replace(whitespace, '');\n const [_a, _b, _c] = rgb.exec(noWhitespace).slice(1, 4).map(v => parseInt(v));\n Object.assign(this, {\n _a,\n _b,\n _c,\n _d: 0,\n space: 'rgb'\n });\n } else if (isHex.test(a)) {\n const hexParse = v => parseInt(v, 16);\n\n const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse);\n Object.assign(this, {\n _a,\n _b,\n _c,\n _d: 0,\n space: 'rgb'\n });\n } else throw Error('Unsupported string format, can\\'t construct Color');\n } // Now add the components as a convenience\n\n\n const {\n _a,\n _b,\n _c,\n _d\n } = this;\n const components = this.space === 'rgb' ? {\n r: _a,\n g: _b,\n b: _c\n } : this.space === 'xyz' ? {\n x: _a,\n y: _b,\n z: _c\n } : this.space === 'hsl' ? {\n h: _a,\n s: _b,\n l: _c\n } : this.space === 'lab' ? {\n l: _a,\n a: _b,\n b: _c\n } : this.space === 'lch' ? {\n l: _a,\n c: _b,\n h: _c\n } : this.space === 'cmyk' ? {\n c: _a,\n m: _b,\n y: _c,\n k: _d\n } : {};\n Object.assign(this, components);\n }\n\n lab() {\n // Get the xyz color\n const {\n x,\n y,\n z\n } = this.xyz(); // Get the lab components\n\n const l = 116 * y - 16;\n const a = 500 * (x - y);\n const b = 200 * (y - z); // Construct and return a new color\n\n const color = new Color(l, a, b, 'lab');\n return color;\n }\n\n lch() {\n // Get the lab color directly\n const {\n l,\n a,\n b\n } = this.lab(); // Get the chromaticity and the hue using polar coordinates\n\n const c = Math.sqrt(a ** 2 + b ** 2);\n let h = 180 * Math.atan2(b, a) / Math.PI;\n\n if (h < 0) {\n h *= -1;\n h = 360 - h;\n } // Make a new color and return it\n\n\n const color = new Color(l, c, h, 'lch');\n return color;\n }\n /*\r\n Conversion Methods\r\n */\n\n\n rgb() {\n if (this.space === 'rgb') {\n return this;\n } else if (cieSpace(this.space)) {\n // Convert to the xyz color space\n let {\n x,\n y,\n z\n } = this;\n\n if (this.space === 'lab' || this.space === 'lch') {\n // Get the values in the lab space\n let {\n l,\n a,\n b\n } = this;\n\n if (this.space === 'lch') {\n const {\n c,\n h\n } = this;\n const dToR = Math.PI / 180;\n a = c * Math.cos(dToR * h);\n b = c * Math.sin(dToR * h);\n } // Undo the nonlinear function\n\n\n const yL = (l + 16) / 116;\n const xL = a / 500 + yL;\n const zL = yL - b / 200; // Get the xyz values\n\n const ct = 16 / 116;\n const mx = 0.008856;\n const nm = 7.787;\n x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm);\n y = 1.00000 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm);\n z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm);\n } // Convert xyz to unbounded rgb values\n\n\n const rU = x * 3.2406 + y * -1.5372 + z * -0.4986;\n const gU = x * -0.9689 + y * 1.8758 + z * 0.0415;\n const bU = x * 0.0557 + y * -0.2040 + z * 1.0570; // Convert the values to true rgb values\n\n const pow = Math.pow;\n const bd = 0.0031308;\n const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU;\n const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU;\n const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU; // Make and return the color\n\n const color = new Color(255 * r, 255 * g, 255 * b);\n return color;\n } else if (this.space === 'hsl') {\n // https://bgrins.github.io/TinyColor/docs/tinycolor.html\n // Get the current hsl values\n let {\n h,\n s,\n l\n } = this;\n h /= 360;\n s /= 100;\n l /= 100; // If we are grey, then just make the color directly\n\n if (s === 0) {\n l *= 255;\n const color = new Color(l, l, l);\n return color;\n } // TODO I have no idea what this does :D If you figure it out, tell me!\n\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q; // Get the rgb values\n\n const r = 255 * hueToRgb(p, q, h + 1 / 3);\n const g = 255 * hueToRgb(p, q, h);\n const b = 255 * hueToRgb(p, q, h - 1 / 3); // Make a new color\n\n const color = new Color(r, g, b);\n return color;\n } else if (this.space === 'cmyk') {\n // https://gist.github.com/felipesabino/5066336\n // Get the normalised cmyk values\n const {\n c,\n m,\n y,\n k\n } = this; // Get the rgb values\n\n const r = 255 * (1 - Math.min(1, c * (1 - k) + k));\n const g = 255 * (1 - Math.min(1, m * (1 - k) + k));\n const b = 255 * (1 - Math.min(1, y * (1 - k) + k)); // Form the color and return it\n\n const color = new Color(r, g, b);\n return color;\n } else {\n return this;\n }\n }\n\n toArray() {\n const {\n _a,\n _b,\n _c,\n _d,\n space\n } = this;\n return [_a, _b, _c, _d, space];\n }\n\n toHex() {\n const [r, g, b] = this._clamped().map(componentHex);\n\n return `#${r}${g}${b}`;\n }\n\n toRgb() {\n const [rV, gV, bV] = this._clamped();\n\n const string = `rgb(${rV},${gV},${bV})`;\n return string;\n }\n\n toString() {\n return this.toHex();\n }\n\n xyz() {\n // Normalise the red, green and blue values\n const {\n _a: r255,\n _b: g255,\n _c: b255\n } = this.rgb();\n const [r, g, b] = [r255, g255, b255].map(v => v / 255); // Convert to the lab rgb space\n\n const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;\n const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;\n const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; // Convert to the xyz color space without bounding the values\n\n const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047;\n const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000;\n const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883; // Get the proper xyz values by applying the bounding\n\n const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116;\n const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116;\n const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116; // Make and return the color\n\n const color = new Color(x, y, z, 'xyz');\n return color;\n }\n /*\r\n Input and Output methods\r\n */\n\n\n _clamped() {\n const {\n _a,\n _b,\n _c\n } = this.rgb();\n const {\n max,\n min,\n round\n } = Math;\n\n const format = v => max(0, min(round(v), 255));\n\n return [_a, _b, _c].map(format);\n }\n /*\r\n Constructing colors\r\n */\n\n\n}\n\nclass Point {\n // Initialize\n constructor(...args) {\n this.init(...args);\n } // Clone point\n\n\n clone() {\n return new Point(this);\n }\n\n init(x, y) {\n const base = {\n x: 0,\n y: 0\n }; // ensure source as object\n\n const source = Array.isArray(x) ? {\n x: x[0],\n y: x[1]\n } : typeof x === 'object' ? {\n x: x.x,\n y: x.y\n } : {\n x: x,\n y: y\n }; // merge source\n\n this.x = source.x == null ? base.x : source.x;\n this.y = source.y == null ? base.y : source.y;\n return this;\n }\n\n toArray() {\n return [this.x, this.y];\n }\n\n transform(m) {\n return this.clone().transformO(m);\n } // Transform point with matrix\n\n\n transformO(m) {\n if (!Matrix.isMatrixLike(m)) {\n m = new Matrix(m);\n }\n\n const {\n x,\n y\n } = this; // Perform the matrix multiplication\n\n this.x = m.a * x + m.c * y + m.e;\n this.y = m.b * x + m.d * y + m.f;\n return this;\n }\n\n}\nfunction point(x, y) {\n return new Point(x, y).transform(this.screenCTM().inverse());\n}\n\nfunction closeEnough(a, b, threshold) {\n return Math.abs(b - a) < (threshold || 1e-6);\n}\n\nclass Matrix {\n constructor(...args) {\n this.init(...args);\n }\n\n static formatTransforms(o) {\n // Get all of the parameters required to form the matrix\n const flipBoth = o.flip === 'both' || o.flip === true;\n const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1;\n const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1;\n const skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew : isFinite(o.skewX) ? o.skewX : 0;\n const skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew : isFinite(o.skewY) ? o.skewY : 0;\n const scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX : isFinite(o.scaleX) ? o.scaleX * flipX : flipX;\n const scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY : isFinite(o.scaleY) ? o.scaleY * flipY : flipY;\n const shear = o.shear || 0;\n const theta = o.rotate || o.theta || 0;\n const origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY);\n const ox = origin.x;\n const oy = origin.y; // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN\n\n const position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN);\n const px = position.x;\n const py = position.y;\n const translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY);\n const tx = translate.x;\n const ty = translate.y;\n const relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY);\n const rx = relative.x;\n const ry = relative.y; // Populate all of the values\n\n return {\n scaleX,\n scaleY,\n skewX,\n skewY,\n shear,\n theta,\n rx,\n ry,\n tx,\n ty,\n ox,\n oy,\n px,\n py\n };\n }\n\n static fromArray(a) {\n return {\n a: a[0],\n b: a[1],\n c: a[2],\n d: a[3],\n e: a[4],\n f: a[5]\n };\n }\n\n static isMatrixLike(o) {\n return o.a != null || o.b != null || o.c != null || o.d != null || o.e != null || o.f != null;\n } // left matrix, right matrix, target matrix which is overwritten\n\n\n static matrixMultiply(l, r, o) {\n // Work out the product directly\n const a = l.a * r.a + l.c * r.b;\n const b = l.b * r.a + l.d * r.b;\n const c = l.a * r.c + l.c * r.d;\n const d = l.b * r.c + l.d * r.d;\n const e = l.e + l.a * r.e + l.c * r.f;\n const f = l.f + l.b * r.e + l.d * r.f; // make sure to use local variables because l/r and o could be the same\n\n o.a = a;\n o.b = b;\n o.c = c;\n o.d = d;\n o.e = e;\n o.f = f;\n return o;\n }\n\n around(cx, cy, matrix) {\n return this.clone().aroundO(cx, cy, matrix);\n } // Transform around a center point\n\n\n aroundO(cx, cy, matrix) {\n const dx = cx || 0;\n const dy = cy || 0;\n return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy);\n } // Clones this matrix\n\n\n clone() {\n return new Matrix(this);\n } // Decomposes this matrix into its affine parameters\n\n\n decompose(cx = 0, cy = 0) {\n // Get the parameters from the matrix\n const a = this.a;\n const b = this.b;\n const c = this.c;\n const d = this.d;\n const e = this.e;\n const f = this.f; // Figure out if the winding direction is clockwise or counterclockwise\n\n const determinant = a * d - b * c;\n const ccw = determinant > 0 ? 1 : -1; // Since we only shear in x, we can use the x basis to get the x scale\n // and the rotation of the resulting matrix\n\n const sx = ccw * Math.sqrt(a * a + b * b);\n const thetaRad = Math.atan2(ccw * b, ccw * a);\n const theta = 180 / Math.PI * thetaRad;\n const ct = Math.cos(thetaRad);\n const st = Math.sin(thetaRad); // We can then solve the y basis vector simultaneously to get the other\n // two affine parameters directly from these parameters\n\n const lam = (a * c + b * d) / determinant;\n const sy = c * sx / (lam * a - b) || d * sx / (lam * b + a); // Use the translations\n\n const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy);\n const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy); // Construct the decomposition and return it\n\n return {\n // Return the affine parameters\n scaleX: sx,\n scaleY: sy,\n shear: lam,\n rotate: theta,\n translateX: tx,\n translateY: ty,\n originX: cx,\n originY: cy,\n // Return the matrix parameters\n a: this.a,\n b: this.b,\n c: this.c,\n d: this.d,\n e: this.e,\n f: this.f\n };\n } // Check if two matrices are equal\n\n\n equals(other) {\n if (other === this) return true;\n const comp = new Matrix(other);\n return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f);\n } // Flip matrix on x or y, at a given offset\n\n\n flip(axis, around) {\n return this.clone().flipO(axis, around);\n }\n\n flipO(axis, around) {\n return axis === 'x' ? this.scaleO(-1, 1, around, 0) : axis === 'y' ? this.scaleO(1, -1, 0, around) : this.scaleO(-1, -1, axis, around || axis); // Define an x, y flip point\n } // Initialize\n\n\n init(source) {\n const base = Matrix.fromArray([1, 0, 0, 1, 0, 0]); // ensure source as object\n\n source = source instanceof Element ? source.matrixify() : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat)) : Array.isArray(source) ? Matrix.fromArray(source) : typeof source === 'object' && Matrix.isMatrixLike(source) ? source : typeof source === 'object' ? new Matrix().transform(source) : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments)) : base; // Merge the source matrix with the base matrix\n\n this.a = source.a != null ? source.a : base.a;\n this.b = source.b != null ? source.b : base.b;\n this.c = source.c != null ? source.c : base.c;\n this.d = source.d != null ? source.d : base.d;\n this.e = source.e != null ? source.e : base.e;\n this.f = source.f != null ? source.f : base.f;\n return this;\n }\n\n inverse() {\n return this.clone().inverseO();\n } // Inverses matrix\n\n\n inverseO() {\n // Get the current parameters out of the matrix\n const a = this.a;\n const b = this.b;\n const c = this.c;\n const d = this.d;\n const e = this.e;\n const f = this.f; // Invert the 2x2 matrix in the top left\n\n const det = a * d - b * c;\n if (!det) throw new Error('Cannot invert ' + this); // Calculate the top 2x2 matrix\n\n const na = d / det;\n const nb = -b / det;\n const nc = -c / det;\n const nd = a / det; // Apply the inverted matrix to the top right\n\n const ne = -(na * e + nc * f);\n const nf = -(nb * e + nd * f); // Construct the inverted matrix\n\n this.a = na;\n this.b = nb;\n this.c = nc;\n this.d = nd;\n this.e = ne;\n this.f = nf;\n return this;\n }\n\n lmultiply(matrix) {\n return this.clone().lmultiplyO(matrix);\n }\n\n lmultiplyO(matrix) {\n const r = this;\n const l = matrix instanceof Matrix ? matrix : new Matrix(matrix);\n return Matrix.matrixMultiply(l, r, this);\n } // Left multiplies by the given matrix\n\n\n multiply(matrix) {\n return this.clone().multiplyO(matrix);\n }\n\n multiplyO(matrix) {\n // Get the matrices\n const l = this;\n const r = matrix instanceof Matrix ? matrix : new Matrix(matrix);\n return Matrix.matrixMultiply(l, r, this);\n } // Rotate matrix\n\n\n rotate(r, cx, cy) {\n return this.clone().rotateO(r, cx, cy);\n }\n\n rotateO(r, cx = 0, cy = 0) {\n // Convert degrees to radians\n r = radians(r);\n const cos = Math.cos(r);\n const sin = Math.sin(r);\n const {\n a,\n b,\n c,\n d,\n e,\n f\n } = this;\n this.a = a * cos - b * sin;\n this.b = b * cos + a * sin;\n this.c = c * cos - d * sin;\n this.d = d * cos + c * sin;\n this.e = e * cos - f * sin + cy * sin - cx * cos + cx;\n this.f = f * cos + e * sin - cx * sin - cy * cos + cy;\n return this;\n } // Scale matrix\n\n\n scale(x, y, cx, cy) {\n return this.clone().scaleO(...arguments);\n }\n\n scaleO(x, y = x, cx = 0, cy = 0) {\n // Support uniform scaling\n if (arguments.length === 3) {\n cy = cx;\n cx = y;\n y = x;\n }\n\n const {\n a,\n b,\n c,\n d,\n e,\n f\n } = this;\n this.a = a * x;\n this.b = b * y;\n this.c = c * x;\n this.d = d * y;\n this.e = e * x - cx * x + cx;\n this.f = f * y - cy * y + cy;\n return this;\n } // Shear matrix\n\n\n shear(a, cx, cy) {\n return this.clone().shearO(a, cx, cy);\n }\n\n shearO(lx, cx = 0, cy = 0) {\n const {\n a,\n b,\n c,\n d,\n e,\n f\n } = this;\n this.a = a + b * lx;\n this.c = c + d * lx;\n this.e = e + f * lx - cy * lx;\n return this;\n } // Skew Matrix\n\n\n skew(x, y, cx, cy) {\n return this.clone().skewO(...arguments);\n }\n\n skewO(x, y = x, cx = 0, cy = 0) {\n // support uniformal skew\n if (arguments.length === 3) {\n cy = cx;\n cx = y;\n y = x;\n } // Convert degrees to radians\n\n\n x = radians(x);\n y = radians(y);\n const lx = Math.tan(x);\n const ly = Math.tan(y);\n const {\n a,\n b,\n c,\n d,\n e,\n f\n } = this;\n this.a = a + b * lx;\n this.b = b + a * ly;\n this.c = c + d * lx;\n this.d = d + c * ly;\n this.e = e + f * lx - cy * lx;\n this.f = f + e * ly - cx * ly;\n return this;\n } // SkewX\n\n\n skewX(x, cx, cy) {\n return this.skew(x, 0, cx, cy);\n } // SkewY\n\n\n skewY(y, cx, cy) {\n return this.skew(0, y, cx, cy);\n }\n\n toArray() {\n return [this.a, this.b, this.c, this.d, this.e, this.f];\n } // Convert matrix to string\n\n\n toString() {\n return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')';\n } // Transform a matrix into another matrix by manipulating the space\n\n\n transform(o) {\n // Check if o is a matrix and then left multiply it directly\n if (Matrix.isMatrixLike(o)) {\n const matrix = new Matrix(o);\n return matrix.multiplyO(this);\n } // Get the proposed transformations and the current transformations\n\n\n const t = Matrix.formatTransforms(o);\n const current = this;\n const {\n x: ox,\n y: oy\n } = new Point(t.ox, t.oy).transform(current); // Construct the resulting matrix\n\n const transformer = new Matrix().translateO(t.rx, t.ry).lmultiplyO(current).translateO(-ox, -oy).scaleO(t.scaleX, t.scaleY).skewO(t.skewX, t.skewY).shearO(t.shear).rotateO(t.theta).translateO(ox, oy); // If we want the origin at a particular place, we force it there\n\n if (isFinite(t.px) || isFinite(t.py)) {\n const origin = new Point(ox, oy).transform(transformer); // TODO: Replace t.px with isFinite(t.px)\n // Doesnt work because t.px is also 0 if it wasnt passed\n\n const dx = isFinite(t.px) ? t.px - origin.x : 0;\n const dy = isFinite(t.py) ? t.py - origin.y : 0;\n transformer.translateO(dx, dy);\n } // Translate now after positioning\n\n\n transformer.translateO(t.tx, t.ty);\n return transformer;\n } // Translate matrix\n\n\n translate(x, y) {\n return this.clone().translateO(x, y);\n }\n\n translateO(x, y) {\n this.e += x || 0;\n this.f += y || 0;\n return this;\n }\n\n valueOf() {\n return {\n a: this.a,\n b: this.b,\n c: this.c,\n d: this.d,\n e: this.e,\n f: this.f\n };\n }\n\n}\nfunction ctm() {\n return new Matrix(this.node.getCTM());\n}\nfunction screenCTM() {\n /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537\r\n This is needed because FF does not return the transformation matrix\r\n for the inner coordinate system when getScreenCTM() is called on nested svgs.\r\n However all other Browsers do that */\n if (typeof this.isRoot === 'function' && !this.isRoot()) {\n const rect = this.rect(1, 1);\n const m = rect.node.getScreenCTM();\n rect.remove();\n return new Matrix(m);\n }\n\n return new Matrix(this.node.getScreenCTM());\n}\nregister(Matrix, 'Matrix');\n\nfunction parser() {\n // Reuse cached element if possible\n if (!parser.nodes) {\n const svg = makeInstance().size(2, 0);\n svg.node.style.cssText = ['opacity: 0', 'position: absolute', 'left: -100%', 'top: -100%', 'overflow: hidden'].join(';');\n svg.attr('focusable', 'false');\n svg.attr('aria-hidden', 'true');\n const path = svg.path().node;\n parser.nodes = {\n svg,\n path\n };\n }\n\n if (!parser.nodes.svg.node.parentNode) {\n const b = globals.document.body || globals.document.documentElement;\n parser.nodes.svg.addTo(b);\n }\n\n return parser.nodes;\n}\n\nfunction isNulledBox(box) {\n return !box.width && !box.height && !box.x && !box.y;\n}\nfunction domContains(node) {\n return node === globals.document || (globals.document.documentElement.contains || function (node) {\n // This is IE - it does not support contains() for top-level SVGs\n while (node.parentNode) {\n node = node.parentNode;\n }\n\n return node === globals.document;\n }).call(globals.document.documentElement, node);\n}\nclass Box {\n constructor(...args) {\n this.init(...args);\n }\n\n addOffset() {\n // offset by window scroll position, because getBoundingClientRect changes when window is scrolled\n this.x += globals.window.pageXOffset;\n this.y += globals.window.pageYOffset;\n return new Box(this);\n }\n\n init(source) {\n const base = [0, 0, 0, 0];\n source = typeof source === 'string' ? source.split(delimiter).map(parseFloat) : Array.isArray(source) ? source : typeof source === 'object' ? [source.left != null ? source.left : source.x, source.top != null ? source.top : source.y, source.width, source.height] : arguments.length === 4 ? [].slice.call(arguments) : base;\n this.x = source[0] || 0;\n this.y = source[1] || 0;\n this.width = this.w = source[2] || 0;\n this.height = this.h = source[3] || 0; // Add more bounding box properties\n\n this.x2 = this.x + this.w;\n this.y2 = this.y + this.h;\n this.cx = this.x + this.w / 2;\n this.cy = this.y + this.h / 2;\n return this;\n }\n\n isNulled() {\n return isNulledBox(this);\n } // Merge rect box with another, return a new instance\n\n\n merge(box) {\n const x = Math.min(this.x, box.x);\n const y = Math.min(this.y, box.y);\n const width = Math.max(this.x + this.width, box.x + box.width) - x;\n const height = Math.max(this.y + this.height, box.y + box.height) - y;\n return new Box(x, y, width, height);\n }\n\n toArray() {\n return [this.x, this.y, this.width, this.height];\n }\n\n toString() {\n return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;\n }\n\n transform(m) {\n if (!(m instanceof Matrix)) {\n m = new Matrix(m);\n }\n\n let xMin = Infinity;\n let xMax = -Infinity;\n let yMin = Infinity;\n let yMax = -Infinity;\n const pts = [new Point(this.x, this.y), new Point(this.x2, this.y), new Point(this.x, this.y2), new Point(this.x2, this.y2)];\n pts.forEach(function (p) {\n p = p.transform(m);\n xMin = Math.min(xMin, p.x);\n xMax = Math.max(xMax, p.x);\n yMin = Math.min(yMin, p.y);\n yMax = Math.max(yMax, p.y);\n });\n return new Box(xMin, yMin, xMax - xMin, yMax - yMin);\n }\n\n}\n\nfunction getBox(el, getBBoxFn, retry) {\n let box;\n\n try {\n // Try to get the box with the provided function\n box = getBBoxFn(el.node); // If the box is worthless and not even in the dom, retry\n // by throwing an error here...\n\n if (isNulledBox(box) && !domContains(el.node)) {\n throw new Error('Element not in the dom');\n }\n } catch (e) {\n // ... and calling the retry handler here\n box = retry(el);\n }\n\n return box;\n}\n\nfunction bbox() {\n // Function to get bbox is getBBox()\n const getBBox = node => node.getBBox(); // Take all measures so that a stupid browser renders the element\n // so we can get the bbox from it when we try again\n\n\n const retry = el => {\n try {\n const clone = el.clone().addTo(parser().svg).show();\n const box = clone.node.getBBox();\n clone.remove();\n return box;\n } catch (e) {\n // We give up...\n throw new Error(`Getting bbox of element \"${el.node.nodeName}\" is not possible: ${e.toString()}`);\n }\n };\n\n const box = getBox(this, getBBox, retry);\n const bbox = new Box(box);\n return bbox;\n}\nfunction rbox(el) {\n const getRBox = node => node.getBoundingClientRect();\n\n const retry = el => {\n // There is no point in trying tricks here because if we insert the element into the dom ourselves\n // it obviously will be at the wrong position\n throw new Error(`Getting rbox of element \"${el.node.nodeName}\" is not possible`);\n };\n\n const box = getBox(this, getRBox, retry);\n const rbox = new Box(box); // If an element was passed, we want the bbox in the coordinate system of that element\n\n if (el) {\n return rbox.transform(el.screenCTM().inverseO());\n } // Else we want it in absolute screen coordinates\n // Therefore we need to add the scrollOffset\n\n\n return rbox.addOffset();\n} // Checks whether the given point is inside the bounding box\n\nfunction inside(x, y) {\n const box = this.bbox();\n return x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height;\n}\nregisterMethods({\n viewbox: {\n viewbox(x, y, width, height) {\n // act as getter\n if (x == null) return new Box(this.attr('viewBox')); // act as setter\n\n return this.attr('viewBox', new Box(x, y, width, height));\n },\n\n zoom(level, point) {\n // Its best to rely on the attributes here and here is why:\n // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!)\n // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely\n // that means, their clientRect is always as big as the content.\n // Furthermore this size is incorrect if the element is further transformed by its parents\n // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here!\n // getBBox: returns the bounding box of its content - that doesnt help!\n let {\n width,\n height\n } = this.attr(['width', 'height']); // Width and height is a string when a number with a unit is present which we can't use\n // So we try clientXYZ\n\n if (!width && !height || typeof width === 'string' || typeof height === 'string') {\n width = this.node.clientWidth;\n height = this.node.clientHeight;\n } // Giving up...\n\n\n if (!width || !height) {\n throw new Error('Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element');\n }\n\n const v = this.viewbox();\n const zoomX = width / v.width;\n const zoomY = height / v.height;\n const zoom = Math.min(zoomX, zoomY);\n\n if (level == null) {\n return zoom;\n }\n\n let zoomAmount = zoom / level; // Set the zoomAmount to the highest value which is safe to process and recover from\n // The * 100 is a bit of wiggle room for the matrix transformation\n\n if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100;\n point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y);\n const box = new Box(v).transform(new Matrix({\n scale: zoomAmount,\n origin: point\n }));\n return this.viewbox(box);\n }\n\n }\n});\nregister(Box, 'Box');\n\nclass List extends Array {\n constructor(arr = [], ...args) {\n super(arr, ...args);\n if (typeof arr === 'number') return this;\n this.length = 0;\n this.push(...arr);\n }\n\n}\nextend([List], {\n each(fnOrMethodName, ...args) {\n if (typeof fnOrMethodName === 'function') {\n return this.map((el, i, arr) => {\n return fnOrMethodName.call(el, el, i, arr);\n });\n } else {\n return this.map(el => {\n return el[fnOrMethodName](...args);\n });\n }\n },\n\n toArray() {\n return Array.prototype.concat.apply([], this);\n }\n\n});\nconst reserved = ['toArray', 'constructor', 'each'];\n\nList.extend = function (methods) {\n methods = methods.reduce((obj, name) => {\n // Don't overwrite own methods\n if (reserved.includes(name)) return obj; // Don't add private methods\n\n if (name[0] === '_') return obj; // Relay every call to each()\n\n obj[name] = function (...attrs) {\n return this.each(name, ...attrs);\n };\n\n return obj;\n }, {});\n extend([List], methods);\n};\n\nfunction baseFind(query, parent) {\n return new List(map((parent || globals.document).querySelectorAll(query), function (node) {\n return adopt(node);\n }));\n} // Scoped find method\n\nfunction find(query) {\n return baseFind(query, this.node);\n}\nfunction findOne(query) {\n return adopt(this.node.querySelector(query));\n}\n\nlet listenerId = 0;\nconst windowEvents = {};\nfunction getEvents(instance) {\n let n = instance.getEventHolder(); // We dont want to save events in global space\n\n if (n === globals.window) n = windowEvents;\n if (!n.events) n.events = {};\n return n.events;\n}\nfunction getEventTarget(instance) {\n return instance.getEventTarget();\n}\nfunction clearEvents(instance) {\n let n = instance.getEventHolder();\n if (n === globals.window) n = windowEvents;\n if (n.events) n.events = {};\n} // Add event binder in the SVG namespace\n\nfunction on(node, events, listener, binding, options) {\n const l = listener.bind(binding || node);\n const instance = makeInstance(node);\n const bag = getEvents(instance);\n const n = getEventTarget(instance); // events can be an array of events or a string of events\n\n events = Array.isArray(events) ? events : events.split(delimiter); // add id to listener\n\n if (!listener._svgjsListenerId) {\n listener._svgjsListenerId = ++listenerId;\n }\n\n events.forEach(function (event) {\n const ev = event.split('.')[0];\n const ns = event.split('.')[1] || '*'; // ensure valid object\n\n bag[ev] = bag[ev] || {};\n bag[ev][ns] = bag[ev][ns] || {}; // reference listener\n\n bag[ev][ns][listener._svgjsListenerId] = l; // add listener\n\n n.addEventListener(ev, l, options || false);\n });\n} // Add event unbinder in the SVG namespace\n\nfunction off(node, events, listener, options) {\n const instance = makeInstance(node);\n const bag = getEvents(instance);\n const n = getEventTarget(instance); // listener can be a function or a number\n\n if (typeof listener === 'function') {\n listener = listener._svgjsListenerId;\n if (!listener) return;\n } // events can be an array of events or a string or undefined\n\n\n events = Array.isArray(events) ? events : (events || '').split(delimiter);\n events.forEach(function (event) {\n const ev = event && event.split('.')[0];\n const ns = event && event.split('.')[1];\n let namespace, l;\n\n if (listener) {\n // remove listener reference\n if (bag[ev] && bag[ev][ns || '*']) {\n // removeListener\n n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false);\n delete bag[ev][ns || '*'][listener];\n }\n } else if (ev && ns) {\n // remove all listeners for a namespaced event\n if (bag[ev] && bag[ev][ns]) {\n for (l in bag[ev][ns]) {\n off(n, [ev, ns].join('.'), l);\n }\n\n delete bag[ev][ns];\n }\n } else if (ns) {\n // remove all listeners for a specific namespace\n for (event in bag) {\n for (namespace in bag[event]) {\n if (ns === namespace) {\n off(n, [event, ns].join('.'));\n }\n }\n }\n } else if (ev) {\n // remove all listeners for the event\n if (bag[ev]) {\n for (namespace in bag[ev]) {\n off(n, [ev, namespace].join('.'));\n }\n\n delete bag[ev];\n }\n } else {\n // remove all listeners on a given node\n for (event in bag) {\n off(n, event);\n }\n\n clearEvents(instance);\n }\n });\n}\nfunction dispatch(node, event, data, options) {\n const n = getEventTarget(node); // Dispatch event\n\n if (event instanceof globals.window.Event) {\n n.dispatchEvent(event);\n } else {\n event = new globals.window.CustomEvent(event, {\n detail: data,\n cancelable: true,\n ...options\n });\n n.dispatchEvent(event);\n }\n\n return event;\n}\n\nclass EventTarget extends Base {\n addEventListener() {}\n\n dispatch(event, data, options) {\n return dispatch(this, event, data, options);\n }\n\n dispatchEvent(event) {\n const bag = this.getEventHolder().events;\n if (!bag) return true;\n const events = bag[event.type];\n\n for (const i in events) {\n for (const j in events[i]) {\n events[i][j](event);\n }\n }\n\n return !event.defaultPrevented;\n } // Fire given event\n\n\n fire(event, data, options) {\n this.dispatch(event, data, options);\n return this;\n }\n\n getEventHolder() {\n return this;\n }\n\n getEventTarget() {\n return this;\n } // Unbind event from listener\n\n\n off(event, listener) {\n off(this, event, listener);\n return this;\n } // Bind given event to listener\n\n\n on(event, listener, binding, options) {\n on(this, event, listener, binding, options);\n return this;\n }\n\n removeEventListener() {}\n\n}\nregister(EventTarget, 'EventTarget');\n\nfunction noop() {} // Default animation values\n\nconst timeline = {\n duration: 400,\n ease: '>',\n delay: 0\n}; // Default attribute values\n\nconst attrs = {\n // fill and stroke\n 'fill-opacity': 1,\n 'stroke-opacity': 1,\n 'stroke-width': 0,\n 'stroke-linejoin': 'miter',\n 'stroke-linecap': 'butt',\n fill: '#000000',\n stroke: '#000000',\n opacity: 1,\n // position\n x: 0,\n y: 0,\n cx: 0,\n cy: 0,\n // size\n width: 0,\n height: 0,\n // radius\n r: 0,\n rx: 0,\n ry: 0,\n // gradient\n offset: 0,\n 'stop-opacity': 1,\n 'stop-color': '#000000',\n // text\n 'text-anchor': 'start'\n};\n\nvar defaults = {\n __proto__: null,\n noop: noop,\n timeline: timeline,\n attrs: attrs\n};\n\nclass SVGArray extends Array {\n constructor(...args) {\n super(...args);\n this.init(...args);\n }\n\n clone() {\n return new this.constructor(this);\n }\n\n init(arr) {\n // This catches the case, that native map tries to create an array with new Array(1)\n if (typeof arr === 'number') return this;\n this.length = 0;\n this.push(...this.parse(arr));\n return this;\n } // Parse whitespace separated string\n\n\n parse(array = []) {\n // If already is an array, no need to parse it\n if (array instanceof Array) return array;\n return array.trim().split(delimiter).map(parseFloat);\n }\n\n toArray() {\n return Array.prototype.concat.apply([], this);\n }\n\n toSet() {\n return new Set(this);\n }\n\n toString() {\n return this.join(' ');\n } // Flattens the array if needed\n\n\n valueOf() {\n const ret = [];\n ret.push(...this);\n return ret;\n }\n\n}\n\nclass SVGNumber {\n // Initialize\n constructor(...args) {\n this.init(...args);\n }\n\n convert(unit) {\n return new SVGNumber(this.value, unit);\n } // Divide number\n\n\n divide(number) {\n number = new SVGNumber(number);\n return new SVGNumber(this / number, this.unit || number.unit);\n }\n\n init(value, unit) {\n unit = Array.isArray(value) ? value[1] : unit;\n value = Array.isArray(value) ? value[0] : value; // initialize defaults\n\n this.value = 0;\n this.unit = unit || ''; // parse value\n\n if (typeof value === 'number') {\n // ensure a valid numeric value\n this.value = isNaN(value) ? 0 : !isFinite(value) ? value < 0 ? -3.4e+38 : +3.4e+38 : value;\n } else if (typeof value === 'string') {\n unit = value.match(numberAndUnit);\n\n if (unit) {\n // make value numeric\n this.value = parseFloat(unit[1]); // normalize\n\n if (unit[5] === '%') {\n this.value /= 100;\n } else if (unit[5] === 's') {\n this.value *= 1000;\n } // store unit\n\n\n this.unit = unit[5];\n }\n } else {\n if (value instanceof SVGNumber) {\n this.value = value.valueOf();\n this.unit = value.unit;\n }\n }\n\n return this;\n } // Subtract number\n\n\n minus(number) {\n number = new SVGNumber(number);\n return new SVGNumber(this - number, this.unit || number.unit);\n } // Add number\n\n\n plus(number) {\n number = new SVGNumber(number);\n return new SVGNumber(this + number, this.unit || number.unit);\n } // Multiply number\n\n\n times(number) {\n number = new SVGNumber(number);\n return new SVGNumber(this * number, this.unit || number.unit);\n }\n\n toArray() {\n return [this.value, this.unit];\n }\n\n toJSON() {\n return this.toString();\n }\n\n toString() {\n return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 : this.value) + this.unit;\n }\n\n valueOf() {\n return this.value;\n }\n\n}\n\nconst hooks = [];\nfunction registerAttrHook(fn) {\n hooks.push(fn);\n} // Set svg element attribute\n\nfunction attr(attr, val, ns) {\n // act as full getter\n if (attr == null) {\n // get an object of attributes\n attr = {};\n val = this.node.attributes;\n\n for (const node of val) {\n attr[node.nodeName] = isNumber.test(node.nodeValue) ? parseFloat(node.nodeValue) : node.nodeValue;\n }\n\n return attr;\n } else if (attr instanceof Array) {\n // loop through array and get all values\n return attr.reduce((last, curr) => {\n last[curr] = this.attr(curr);\n return last;\n }, {});\n } else if (typeof attr === 'object' && attr.constructor === Object) {\n // apply every attribute individually if an object is passed\n for (val in attr) this.attr(val, attr[val]);\n } else if (val === null) {\n // remove value\n this.node.removeAttribute(attr);\n } else if (val == null) {\n // act as a getter if the first and only argument is not an object\n val = this.node.getAttribute(attr);\n return val == null ? attrs[attr] : isNumber.test(val) ? parseFloat(val) : val;\n } else {\n // Loop through hooks and execute them to convert value\n val = hooks.reduce((_val, hook) => {\n return hook(attr, _val, this);\n }, val); // ensure correct numeric values (also accepts NaN and Infinity)\n\n if (typeof val === 'number') {\n val = new SVGNumber(val);\n } else if (Color.isColor(val)) {\n // ensure full hex color\n val = new Color(val);\n } else if (val.constructor === Array) {\n // Check for plain arrays and parse array values\n val = new SVGArray(val);\n } // if the passed attribute is leading...\n\n\n if (attr === 'leading') {\n // ... call the leading method instead\n if (this.leading) {\n this.leading(val);\n }\n } else {\n // set given attribute on node\n typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString()) : this.node.setAttribute(attr, val.toString());\n } // rebuild if required\n\n\n if (this.rebuild && (attr === 'font-size' || attr === 'x')) {\n this.rebuild();\n }\n }\n\n return this;\n}\n\nclass Dom extends EventTarget {\n constructor(node, attrs) {\n super();\n this.node = node;\n this.type = node.nodeName;\n\n if (attrs && node !== attrs) {\n this.attr(attrs);\n }\n } // Add given element at a position\n\n\n add(element, i) {\n element = makeInstance(element); // If non-root svg nodes are added we have to remove their namespaces\n\n if (element.removeNamespace && this.node instanceof globals.window.SVGElement) {\n element.removeNamespace();\n }\n\n if (i == null) {\n this.node.appendChild(element.node);\n } else if (element.node !== this.node.childNodes[i]) {\n this.node.insertBefore(element.node, this.node.childNodes[i]);\n }\n\n return this;\n } // Add element to given container and return self\n\n\n addTo(parent, i) {\n return makeInstance(parent).put(this, i);\n } // Returns all child elements\n\n\n children() {\n return new List(map(this.node.children, function (node) {\n return adopt(node);\n }));\n } // Remove all elements in this container\n\n\n clear() {\n // remove children\n while (this.node.hasChildNodes()) {\n this.node.removeChild(this.node.lastChild);\n }\n\n return this;\n } // Clone element\n\n\n clone(deep = true) {\n // write dom data to the dom so the clone can pickup the data\n this.writeDataToDom(); // clone element and assign new id\n\n return new this.constructor(assignNewId(this.node.cloneNode(deep)));\n } // Iterates over all children and invokes a given block\n\n\n each(block, deep) {\n const children = this.children();\n let i, il;\n\n for (i = 0, il = children.length; i < il; i++) {\n block.apply(children[i], [i, children]);\n\n if (deep) {\n children[i].each(block, deep);\n }\n }\n\n return this;\n }\n\n element(nodeName, attrs) {\n return this.put(new Dom(create(nodeName), attrs));\n } // Get first child\n\n\n first() {\n return adopt(this.node.firstChild);\n } // Get a element at the given index\n\n\n get(i) {\n return adopt(this.node.childNodes[i]);\n }\n\n getEventHolder() {\n return this.node;\n }\n\n getEventTarget() {\n return this.node;\n } // Checks if the given element is a child\n\n\n has(element) {\n return this.index(element) >= 0;\n }\n\n html(htmlOrFn, outerHTML) {\n return this.xml(htmlOrFn, outerHTML, html);\n } // Get / set id\n\n\n id(id) {\n // generate new id if no id set\n if (typeof id === 'undefined' && !this.node.id) {\n this.node.id = eid(this.type);\n } // don't set directly with this.node.id to make `null` work correctly\n\n\n return this.attr('id', id);\n } // Gets index of given element\n\n\n index(element) {\n return [].slice.call(this.node.childNodes).indexOf(element.node);\n } // Get the last child\n\n\n last() {\n return adopt(this.node.lastChild);\n } // matches the element vs a css selector\n\n\n matches(selector) {\n const el = this.node;\n const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null;\n return matcher && matcher.call(el, selector);\n } // Returns the parent element instance\n\n\n parent(type) {\n let parent = this; // check for parent\n\n if (!parent.node.parentNode) return null; // get parent element\n\n parent = adopt(parent.node.parentNode);\n if (!type) return parent; // loop trough ancestors if type is given\n\n do {\n if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent;\n } while (parent = adopt(parent.node.parentNode));\n\n return parent;\n } // Basically does the same as `add()` but returns the added element instead\n\n\n put(element, i) {\n element = makeInstance(element);\n this.add(element, i);\n return element;\n } // Add element to given container and return container\n\n\n putIn(parent, i) {\n return makeInstance(parent).add(this, i);\n } // Remove element\n\n\n remove() {\n if (this.parent()) {\n this.parent().removeElement(this);\n }\n\n return this;\n } // Remove a given child\n\n\n removeElement(element) {\n this.node.removeChild(element.node);\n return this;\n } // Replace this with element\n\n\n replace(element) {\n element = makeInstance(element);\n\n if (this.node.parentNode) {\n this.node.parentNode.replaceChild(element.node, this.node);\n }\n\n return element;\n }\n\n round(precision = 2, map = null) {\n const factor = 10 ** precision;\n const attrs = this.attr(map);\n\n for (const i in attrs) {\n if (typeof attrs[i] === 'number') {\n attrs[i] = Math.round(attrs[i] * factor) / factor;\n }\n }\n\n this.attr(attrs);\n return this;\n } // Import / Export raw svg\n\n\n svg(svgOrFn, outerSVG) {\n return this.xml(svgOrFn, outerSVG, svg);\n } // Return id on string conversion\n\n\n toString() {\n return this.id();\n }\n\n words(text) {\n // This is faster than removing all children and adding a new one\n this.node.textContent = text;\n return this;\n }\n\n wrap(node) {\n const parent = this.parent();\n\n if (!parent) {\n return this.addTo(node);\n }\n\n const position = parent.index(this);\n return parent.put(node, position).put(this);\n } // write svgjs data to the dom\n\n\n writeDataToDom() {\n // dump variables recursively\n this.each(function () {\n this.writeDataToDom();\n });\n return this;\n } // Import / Export raw svg\n\n\n xml(xmlOrFn, outerXML, ns) {\n if (typeof xmlOrFn === 'boolean') {\n ns = outerXML;\n outerXML = xmlOrFn;\n xmlOrFn = null;\n } // act as getter if no svg string is given\n\n\n if (xmlOrFn == null || typeof xmlOrFn === 'function') {\n // The default for exports is, that the outerNode is included\n outerXML = outerXML == null ? true : outerXML; // write svgjs data to the dom\n\n this.writeDataToDom();\n let current = this; // An export modifier was passed\n\n if (xmlOrFn != null) {\n current = adopt(current.node.cloneNode(true)); // If the user wants outerHTML we need to process this node, too\n\n if (outerXML) {\n const result = xmlOrFn(current);\n current = result || current; // The user does not want this node? Well, then he gets nothing\n\n if (result === false) return '';\n } // Deep loop through all children and apply modifier\n\n\n current.each(function () {\n const result = xmlOrFn(this);\n\n const _this = result || this; // If modifier returns false, discard node\n\n\n if (result === false) {\n this.remove(); // If modifier returns new node, use it\n } else if (result && this !== _this) {\n this.replace(_this);\n }\n }, true);\n } // Return outer or inner content\n\n\n return outerXML ? current.node.outerHTML : current.node.innerHTML;\n } // Act as setter if we got a string\n // The default for import is, that the current node is not replaced\n\n\n outerXML = outerXML == null ? false : outerXML; // Create temporary holder\n\n const well = create('wrapper', ns);\n const fragment = globals.document.createDocumentFragment(); // Dump raw svg\n\n well.innerHTML = xmlOrFn; // Transplant nodes into the fragment\n\n for (let len = well.children.length; len--;) {\n fragment.appendChild(well.firstElementChild);\n }\n\n const parent = this.parent(); // Add the whole fragment at once\n\n return outerXML ? this.replace(fragment) && parent : this.add(fragment);\n }\n\n}\nextend(Dom, {\n attr,\n find,\n findOne\n});\nregister(Dom, 'Dom');\n\nclass Element extends Dom {\n constructor(node, attrs) {\n super(node, attrs); // initialize data object\n\n this.dom = {}; // create circular reference\n\n this.node.instance = this;\n\n if (node.hasAttribute('svgjs:data')) {\n // pull svgjs data from the dom (getAttributeNS doesn't work in html5)\n this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {});\n }\n } // Move element by its center\n\n\n center(x, y) {\n return this.cx(x).cy(y);\n } // Move by center over x-axis\n\n\n cx(x) {\n return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2);\n } // Move by center over y-axis\n\n\n cy(y) {\n return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2);\n } // Get defs\n\n\n defs() {\n const root = this.root();\n return root && root.defs();\n } // Relative move over x and y axes\n\n\n dmove(x, y) {\n return this.dx(x).dy(y);\n } // Relative move over x axis\n\n\n dx(x = 0) {\n return this.x(new SVGNumber(x).plus(this.x()));\n } // Relative move over y axis\n\n\n dy(y = 0) {\n return this.y(new SVGNumber(y).plus(this.y()));\n }\n\n getEventHolder() {\n return this;\n } // Set height of element\n\n\n height(height) {\n return this.attr('height', height);\n } // Move element to given x and y values\n\n\n move(x, y) {\n return this.x(x).y(y);\n } // return array of all ancestors of given type up to the root svg\n\n\n parents(until = this.root()) {\n until = makeInstance(until);\n const parents = new List();\n let parent = this;\n\n while ((parent = parent.parent()) && parent.node !== globals.document && parent.nodeName !== '#document-fragment') {\n parents.push(parent);\n\n if (parent.node === until.node) {\n break;\n }\n }\n\n return parents;\n } // Get referenced element form attribute value\n\n\n reference(attr) {\n attr = this.attr(attr);\n if (!attr) return null;\n const m = (attr + '').match(reference);\n return m ? makeInstance(m[1]) : null;\n } // Get parent document\n\n\n root() {\n const p = this.parent(getClass(root));\n return p && p.root();\n } // set given data to the elements data property\n\n\n setData(o) {\n this.dom = o;\n return this;\n } // Set element size to given width and height\n\n\n size(width, height) {\n const p = proportionalSize(this, width, height);\n return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height));\n } // Set width of element\n\n\n width(width) {\n return this.attr('width', width);\n } // write svgjs data to the dom\n\n\n writeDataToDom() {\n // remove previously set data\n this.node.removeAttribute('svgjs:data');\n\n if (Object.keys(this.dom).length) {\n this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)); // see #428\n }\n\n return super.writeDataToDom();\n } // Move over x-axis\n\n\n x(x) {\n return this.attr('x', x);\n } // Move over y-axis\n\n\n y(y) {\n return this.attr('y', y);\n }\n\n}\nextend(Element, {\n bbox,\n rbox,\n inside,\n point,\n ctm,\n screenCTM\n});\nregister(Element, 'Element');\n\nconst sugar = {\n stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],\n fill: ['color', 'opacity', 'rule'],\n prefix: function (t, a) {\n return a === 'color' ? t : t + '-' + a;\n }\n} // Add sugar for fill and stroke\n;\n['fill', 'stroke'].forEach(function (m) {\n const extension = {};\n let i;\n\n extension[m] = function (o) {\n if (typeof o === 'undefined') {\n return this.attr(m);\n }\n\n if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || o instanceof Element) {\n this.attr(m, o);\n } else {\n // set all attributes from sugar.fill and sugar.stroke list\n for (i = sugar[m].length - 1; i >= 0; i--) {\n if (o[sugar[m][i]] != null) {\n this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]);\n }\n }\n }\n\n return this;\n };\n\n registerMethods(['Element', 'Runner'], extension);\n});\nregisterMethods(['Element', 'Runner'], {\n // Let the user set the matrix directly\n matrix: function (mat, b, c, d, e, f) {\n // Act as a getter\n if (mat == null) {\n return new Matrix(this);\n } // Act as a setter, the user can pass a matrix or a set of numbers\n\n\n return this.attr('transform', new Matrix(mat, b, c, d, e, f));\n },\n // Map rotation to transform\n rotate: function (angle, cx, cy) {\n return this.transform({\n rotate: angle,\n ox: cx,\n oy: cy\n }, true);\n },\n // Map skew to transform\n skew: function (x, y, cx, cy) {\n return arguments.length === 1 || arguments.length === 3 ? this.transform({\n skew: x,\n ox: y,\n oy: cx\n }, true) : this.transform({\n skew: [x, y],\n ox: cx,\n oy: cy\n }, true);\n },\n shear: function (lam, cx, cy) {\n return this.transform({\n shear: lam,\n ox: cx,\n oy: cy\n }, true);\n },\n // Map scale to transform\n scale: function (x, y, cx, cy) {\n return arguments.length === 1 || arguments.length === 3 ? this.transform({\n scale: x,\n ox: y,\n oy: cx\n }, true) : this.transform({\n scale: [x, y],\n ox: cx,\n oy: cy\n }, true);\n },\n // Map translate to transform\n translate: function (x, y) {\n return this.transform({\n translate: [x, y]\n }, true);\n },\n // Map relative translations to transform\n relative: function (x, y) {\n return this.transform({\n relative: [x, y]\n }, true);\n },\n // Map flip to transform\n flip: function (direction = 'both', origin = 'center') {\n if ('xybothtrue'.indexOf(direction) === -1) {\n origin = direction;\n direction = 'both';\n }\n\n return this.transform({\n flip: direction,\n origin: origin\n }, true);\n },\n // Opacity\n opacity: function (value) {\n return this.attr('opacity', value);\n }\n});\nregisterMethods('radius', {\n // Add x and y radius\n radius: function (x, y = x) {\n const type = (this._element || this).type;\n return type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y);\n }\n});\nregisterMethods('Path', {\n // Get path length\n length: function () {\n return this.node.getTotalLength();\n },\n // Get point at length\n pointAt: function (length) {\n return new Point(this.node.getPointAtLength(length));\n }\n});\nregisterMethods(['Element', 'Runner'], {\n // Set font\n font: function (a, v) {\n if (typeof a === 'object') {\n for (v in a) this.font(v, a[v]);\n\n return this;\n }\n\n return a === 'leading' ? this.leading(v) : a === 'anchor' ? this.attr('text-anchor', v) : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' ? this.attr('font-' + a, v) : this.attr(a, v);\n }\n}); // Add events to elements\n\nconst methods = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {\n // add event to Element\n const fn = function (f) {\n if (f === null) {\n this.off(event);\n } else {\n this.on(event, f);\n }\n\n return this;\n };\n\n last[event] = fn;\n return last;\n}, {});\nregisterMethods('Element', methods);\n\nfunction untransform() {\n return this.attr('transform', null);\n} // merge the whole transformation chain into one matrix and returns it\n\nfunction matrixify() {\n const matrix = (this.attr('transform') || ''). // split transformations\n split(transforms).slice(0, -1).map(function (str) {\n // generate key => value pairs\n const kv = str.trim().split('(');\n return [kv[0], kv[1].split(delimiter).map(function (str) {\n return parseFloat(str);\n })];\n }).reverse() // merge every transformation into one matrix\n .reduce(function (matrix, transform) {\n if (transform[0] === 'matrix') {\n return matrix.lmultiply(Matrix.fromArray(transform[1]));\n }\n\n return matrix[transform[0]].apply(matrix, transform[1]);\n }, new Matrix());\n return matrix;\n} // add an element to another parent without changing the visual representation on the screen\n\nfunction toParent(parent, i) {\n if (this === parent) return this;\n const ctm = this.screenCTM();\n const pCtm = parent.screenCTM().inverse();\n this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm));\n return this;\n} // same as above with parent equals root-svg\n\nfunction toRoot(i) {\n return this.toParent(this.root(), i);\n} // Add transformations\n\nfunction transform(o, relative) {\n // Act as a getter if no object was passed\n if (o == null || typeof o === 'string') {\n const decomposed = new Matrix(this).decompose();\n return o == null ? decomposed : decomposed[o];\n }\n\n if (!Matrix.isMatrixLike(o)) {\n // Set the origin according to the defined transform\n o = { ...o,\n origin: getOrigin(o, this)\n };\n } // The user can pass a boolean, an Element or an Matrix or nothing\n\n\n const cleanRelative = relative === true ? this : relative || false;\n const result = new Matrix(cleanRelative).transform(o);\n return this.attr('transform', result);\n}\nregisterMethods('Element', {\n untransform,\n matrixify,\n toParent,\n toRoot,\n transform\n});\n\nclass Container extends Element {\n flatten(parent = this, index) {\n this.each(function () {\n if (this instanceof Container) {\n return this.flatten().ungroup();\n }\n });\n return this;\n }\n\n ungroup(parent = this.parent(), index = parent.index(this)) {\n // when parent != this, we want append all elements to the end\n index = index === -1 ? parent.children().length : index;\n this.each(function (i, children) {\n // reverse each\n return children[children.length - i - 1].toParent(parent, index);\n });\n return this.remove();\n }\n\n}\nregister(Container, 'Container');\n\nclass Defs extends Container {\n constructor(node, attrs = node) {\n super(nodeOrNew('defs', node), attrs);\n }\n\n flatten() {\n return this;\n }\n\n ungroup() {\n return this;\n }\n\n}\nregister(Defs, 'Defs');\n\nclass Shape extends Element {}\nregister(Shape, 'Shape');\n\nfunction rx(rx) {\n return this.attr('rx', rx);\n} // Radius y value\n\nfunction ry(ry) {\n return this.attr('ry', ry);\n} // Move over x-axis\n\nfunction x$3(x) {\n return x == null ? this.cx() - this.rx() : this.cx(x + this.rx());\n} // Move over y-axis\n\nfunction y$3(y) {\n return y == null ? this.cy() - this.ry() : this.cy(y + this.ry());\n} // Move by center over x-axis\n\nfunction cx$1(x) {\n return this.attr('cx', x);\n} // Move by center over y-axis\n\nfunction cy$1(y) {\n return this.attr('cy', y);\n} // Set width of element\n\nfunction width$2(width) {\n return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2));\n} // Set height of element\n\nfunction height$2(height) {\n return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));\n}\n\nvar circled = {\n __proto__: null,\n rx: rx,\n ry: ry,\n x: x$3,\n y: y$3,\n cx: cx$1,\n cy: cy$1,\n width: width$2,\n height: height$2\n};\n\nclass Ellipse extends Shape {\n constructor(node, attrs = node) {\n super(nodeOrNew('ellipse', node), attrs);\n }\n\n size(width, height) {\n const p = proportionalSize(this, width, height);\n return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));\n }\n\n}\nextend(Ellipse, circled);\nregisterMethods('Container', {\n // Create an ellipse\n ellipse: wrapWithAttrCheck(function (width = 0, height = width) {\n return this.put(new Ellipse()).size(width, height).move(0, 0);\n })\n});\nregister(Ellipse, 'Ellipse');\n\nclass Fragment extends Dom {\n constructor(node = globals.document.createDocumentFragment()) {\n super(node);\n } // Import / Export raw xml\n\n\n xml(xmlOrFn, outerXML, ns) {\n if (typeof xmlOrFn === 'boolean') {\n ns = outerXML;\n outerXML = xmlOrFn;\n xmlOrFn = null;\n } // because this is a fragment we have to put all elements into a wrapper first\n // before we can get the innerXML from it\n\n\n if (xmlOrFn == null || typeof xmlOrFn === 'function') {\n const wrapper = new Dom(create('wrapper', ns));\n wrapper.add(this.node.cloneNode(true));\n return wrapper.xml(false, ns);\n } // Act as setter if we got a string\n\n\n return super.xml(xmlOrFn, false, ns);\n }\n\n}\n\nregister(Fragment, 'Fragment');\n\nfunction from(x, y) {\n return (this._element || this).type === 'radialGradient' ? this.attr({\n fx: new SVGNumber(x),\n fy: new SVGNumber(y)\n }) : this.attr({\n x1: new SVGNumber(x),\n y1: new SVGNumber(y)\n });\n}\nfunction to(x, y) {\n return (this._element || this).type === 'radialGradient' ? this.attr({\n cx: new SVGNumber(x),\n cy: new SVGNumber(y)\n }) : this.attr({\n x2: new SVGNumber(x),\n y2: new SVGNumber(y)\n });\n}\n\nvar gradiented = {\n __proto__: null,\n from: from,\n to: to\n};\n\nclass Gradient extends Container {\n constructor(type, attrs) {\n super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs);\n } // custom attr to handle transform\n\n\n attr(a, b, c) {\n if (a === 'transform') a = 'gradientTransform';\n return super.attr(a, b, c);\n }\n\n bbox() {\n return new Box();\n }\n\n targets() {\n return baseFind('svg [fill*=\"' + this.id() + '\"]');\n } // Alias string conversion to fill\n\n\n toString() {\n return this.url();\n } // Update gradient\n\n\n update(block) {\n // remove all stops\n this.clear(); // invoke passed block\n\n if (typeof block === 'function') {\n block.call(this, this);\n }\n\n return this;\n } // Return the fill id\n\n\n url() {\n return 'url(\"#' + this.id() + '\")';\n }\n\n}\nextend(Gradient, gradiented);\nregisterMethods({\n Container: {\n // Create gradient element in defs\n gradient(...args) {\n return this.defs().gradient(...args);\n }\n\n },\n // define gradient\n Defs: {\n gradient: wrapWithAttrCheck(function (type, block) {\n return this.put(new Gradient(type)).update(block);\n })\n }\n});\nregister(Gradient, 'Gradient');\n\nclass Pattern extends Container {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('pattern', node), attrs);\n } // custom attr to handle transform\n\n\n attr(a, b, c) {\n if (a === 'transform') a = 'patternTransform';\n return super.attr(a, b, c);\n }\n\n bbox() {\n return new Box();\n }\n\n targets() {\n return baseFind('svg [fill*=\"' + this.id() + '\"]');\n } // Alias string conversion to fill\n\n\n toString() {\n return this.url();\n } // Update pattern by rebuilding\n\n\n update(block) {\n // remove content\n this.clear(); // invoke passed block\n\n if (typeof block === 'function') {\n block.call(this, this);\n }\n\n return this;\n } // Return the fill id\n\n\n url() {\n return 'url(\"#' + this.id() + '\")';\n }\n\n}\nregisterMethods({\n Container: {\n // Create pattern element in defs\n pattern(...args) {\n return this.defs().pattern(...args);\n }\n\n },\n Defs: {\n pattern: wrapWithAttrCheck(function (width, height, block) {\n return this.put(new Pattern()).update(block).attr({\n x: 0,\n y: 0,\n width: width,\n height: height,\n patternUnits: 'userSpaceOnUse'\n });\n })\n }\n});\nregister(Pattern, 'Pattern');\n\nclass Image extends Shape {\n constructor(node, attrs = node) {\n super(nodeOrNew('image', node), attrs);\n } // (re)load image\n\n\n load(url, callback) {\n if (!url) return this;\n const img = new globals.window.Image();\n on(img, 'load', function (e) {\n const p = this.parent(Pattern); // ensure image size\n\n if (this.width() === 0 && this.height() === 0) {\n this.size(img.width, img.height);\n }\n\n if (p instanceof Pattern) {\n // ensure pattern size if not set\n if (p.width() === 0 && p.height() === 0) {\n p.size(this.width(), this.height());\n }\n }\n\n if (typeof callback === 'function') {\n callback.call(this, e);\n }\n }, this);\n on(img, 'load error', function () {\n // dont forget to unbind memory leaking events\n off(img);\n });\n return this.attr('href', img.src = url, xlink);\n }\n\n}\nregisterAttrHook(function (attr, val, _this) {\n // convert image fill and stroke to patterns\n if (attr === 'fill' || attr === 'stroke') {\n if (isImage.test(val)) {\n val = _this.root().defs().image(val);\n }\n }\n\n if (val instanceof Image) {\n val = _this.root().defs().pattern(0, 0, pattern => {\n pattern.add(val);\n });\n }\n\n return val;\n});\nregisterMethods({\n Container: {\n // create image element, load image and set its size\n image: wrapWithAttrCheck(function (source, callback) {\n return this.put(new Image()).size(0, 0).load(source, callback);\n })\n }\n});\nregister(Image, 'Image');\n\nclass PointArray extends SVGArray {\n // Get bounding box of points\n bbox() {\n let maxX = -Infinity;\n let maxY = -Infinity;\n let minX = Infinity;\n let minY = Infinity;\n this.forEach(function (el) {\n maxX = Math.max(el[0], maxX);\n maxY = Math.max(el[1], maxY);\n minX = Math.min(el[0], minX);\n minY = Math.min(el[1], minY);\n });\n return new Box(minX, minY, maxX - minX, maxY - minY);\n } // Move point string\n\n\n move(x, y) {\n const box = this.bbox(); // get relative offset\n\n x -= box.x;\n y -= box.y; // move every point\n\n if (!isNaN(x) && !isNaN(y)) {\n for (let i = this.length - 1; i >= 0; i--) {\n this[i] = [this[i][0] + x, this[i][1] + y];\n }\n }\n\n return this;\n } // Parse point string and flat array\n\n\n parse(array = [0, 0]) {\n const points = []; // if it is an array, we flatten it and therefore clone it to 1 depths\n\n if (array instanceof Array) {\n array = Array.prototype.concat.apply([], array);\n } else {\n // Else, it is considered as a string\n // parse points\n array = array.trim().split(delimiter).map(parseFloat);\n } // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints\n // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.\n\n\n if (array.length % 2 !== 0) array.pop(); // wrap points in two-tuples\n\n for (let i = 0, len = array.length; i < len; i = i + 2) {\n points.push([array[i], array[i + 1]]);\n }\n\n return points;\n } // Resize poly string\n\n\n size(width, height) {\n let i;\n const box = this.bbox(); // recalculate position of all points according to new size\n\n for (i = this.length - 1; i >= 0; i--) {\n if (box.width) this[i][0] = (this[i][0] - box.x) * width / box.width + box.x;\n if (box.height) this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;\n }\n\n return this;\n } // Convert array to line object\n\n\n toLine() {\n return {\n x1: this[0][0],\n y1: this[0][1],\n x2: this[1][0],\n y2: this[1][1]\n };\n } // Convert array to string\n\n\n toString() {\n const array = []; // convert to a poly point string\n\n for (let i = 0, il = this.length; i < il; i++) {\n array.push(this[i].join(','));\n }\n\n return array.join(' ');\n }\n\n transform(m) {\n return this.clone().transformO(m);\n } // transform points with matrix (similar to Point.transform)\n\n\n transformO(m) {\n if (!Matrix.isMatrixLike(m)) {\n m = new Matrix(m);\n }\n\n for (let i = this.length; i--;) {\n // Perform the matrix multiplication\n const [x, y] = this[i];\n this[i][0] = m.a * x + m.c * y + m.e;\n this[i][1] = m.b * x + m.d * y + m.f;\n }\n\n return this;\n }\n\n}\n\nconst MorphArray = PointArray; // Move by left top corner over x-axis\n\nfunction x$2(x) {\n return x == null ? this.bbox().x : this.move(x, this.bbox().y);\n} // Move by left top corner over y-axis\n\nfunction y$2(y) {\n return y == null ? this.bbox().y : this.move(this.bbox().x, y);\n} // Set width of element\n\nfunction width$1(width) {\n const b = this.bbox();\n return width == null ? b.width : this.size(width, b.height);\n} // Set height of element\n\nfunction height$1(height) {\n const b = this.bbox();\n return height == null ? b.height : this.size(b.width, height);\n}\n\nvar pointed = {\n __proto__: null,\n MorphArray: MorphArray,\n x: x$2,\n y: y$2,\n width: width$1,\n height: height$1\n};\n\nclass Line extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('line', node), attrs);\n } // Get array\n\n\n array() {\n return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]);\n } // Move by left top corner\n\n\n move(x, y) {\n return this.attr(this.array().move(x, y).toLine());\n } // Overwrite native plot() method\n\n\n plot(x1, y1, x2, y2) {\n if (x1 == null) {\n return this.array();\n } else if (typeof y1 !== 'undefined') {\n x1 = {\n x1,\n y1,\n x2,\n y2\n };\n } else {\n x1 = new PointArray(x1).toLine();\n }\n\n return this.attr(x1);\n } // Set element size to given width and height\n\n\n size(width, height) {\n const p = proportionalSize(this, width, height);\n return this.attr(this.array().size(p.width, p.height).toLine());\n }\n\n}\nextend(Line, pointed);\nregisterMethods({\n Container: {\n // Create a line element\n line: wrapWithAttrCheck(function (...args) {\n // make sure plot is called as a setter\n // x1 is not necessarily a number, it can also be an array, a string and a PointArray\n return Line.prototype.plot.apply(this.put(new Line()), args[0] != null ? args : [0, 0, 0, 0]);\n })\n }\n});\nregister(Line, 'Line');\n\nclass Marker extends Container {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('marker', node), attrs);\n } // Set height of element\n\n\n height(height) {\n return this.attr('markerHeight', height);\n }\n\n orient(orient) {\n return this.attr('orient', orient);\n } // Set marker refX and refY\n\n\n ref(x, y) {\n return this.attr('refX', x).attr('refY', y);\n } // Return the fill id\n\n\n toString() {\n return 'url(#' + this.id() + ')';\n } // Update marker\n\n\n update(block) {\n // remove all content\n this.clear(); // invoke passed block\n\n if (typeof block === 'function') {\n block.call(this, this);\n }\n\n return this;\n } // Set width of element\n\n\n width(width) {\n return this.attr('markerWidth', width);\n }\n\n}\nregisterMethods({\n Container: {\n marker(...args) {\n // Create marker element in defs\n return this.defs().marker(...args);\n }\n\n },\n Defs: {\n // Create marker\n marker: wrapWithAttrCheck(function (width, height, block) {\n // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto\n return this.put(new Marker()).size(width, height).ref(width / 2, height / 2).viewbox(0, 0, width, height).attr('orient', 'auto').update(block);\n })\n },\n marker: {\n // Create and attach markers\n marker(marker, width, height, block) {\n let attr = ['marker']; // Build attribute name\n\n if (marker !== 'all') attr.push(marker);\n attr = attr.join('-'); // Set marker attribute\n\n marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block);\n return this.attr(attr, marker);\n }\n\n }\n});\nregister(Marker, 'Marker');\n\n/***\r\nBase Class\r\n==========\r\nThe base stepper class that will be\r\n***/\n\nfunction makeSetterGetter(k, f) {\n return function (v) {\n if (v == null) return this[k];\n this[k] = v;\n if (f) f.call(this);\n return this;\n };\n}\n\nconst easing = {\n '-': function (pos) {\n return pos;\n },\n '<>': function (pos) {\n return -Math.cos(pos * Math.PI) / 2 + 0.5;\n },\n '>': function (pos) {\n return Math.sin(pos * Math.PI / 2);\n },\n '<': function (pos) {\n return -Math.cos(pos * Math.PI / 2) + 1;\n },\n bezier: function (x1, y1, x2, y2) {\n // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo\n return function (t) {\n if (t < 0) {\n if (x1 > 0) {\n return y1 / x1 * t;\n } else if (x2 > 0) {\n return y2 / x2 * t;\n } else {\n return 0;\n }\n } else if (t > 1) {\n if (x2 < 1) {\n return (1 - y2) / (1 - x2) * t + (y2 - x2) / (1 - x2);\n } else if (x1 < 1) {\n return (1 - y1) / (1 - x1) * t + (y1 - x1) / (1 - x1);\n } else {\n return 1;\n }\n } else {\n return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3;\n }\n };\n },\n // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo\n steps: function (steps, stepPosition = 'end') {\n // deal with \"jump-\" prefix\n stepPosition = stepPosition.split('-').reverse()[0];\n let jumps = steps;\n\n if (stepPosition === 'none') {\n --jumps;\n } else if (stepPosition === 'both') {\n ++jumps;\n } // The beforeFlag is essentially useless\n\n\n return (t, beforeFlag = false) => {\n // Step is called currentStep in referenced url\n let step = Math.floor(t * steps);\n const jumping = t * step % 1 === 0;\n\n if (stepPosition === 'start' || stepPosition === 'both') {\n ++step;\n }\n\n if (beforeFlag && jumping) {\n --step;\n }\n\n if (t >= 0 && step < 0) {\n step = 0;\n }\n\n if (t <= 1 && step > jumps) {\n step = jumps;\n }\n\n return step / jumps;\n };\n }\n};\nclass Stepper {\n done() {\n return false;\n }\n\n}\n/***\r\nEasing Functions\r\n================\r\n***/\n\nclass Ease extends Stepper {\n constructor(fn = timeline.ease) {\n super();\n this.ease = easing[fn] || fn;\n }\n\n step(from, to, pos) {\n if (typeof from !== 'number') {\n return pos < 1 ? from : to;\n }\n\n return from + (to - from) * this.ease(pos);\n }\n\n}\n/***\r\nController Types\r\n================\r\n***/\n\nclass Controller extends Stepper {\n constructor(fn) {\n super();\n this.stepper = fn;\n }\n\n done(c) {\n return c.done;\n }\n\n step(current, target, dt, c) {\n return this.stepper(current, target, dt, c);\n }\n\n}\n\nfunction recalculate() {\n // Apply the default parameters\n const duration = (this._duration || 500) / 1000;\n const overshoot = this._overshoot || 0; // Calculate the PID natural response\n\n const eps = 1e-10;\n const pi = Math.PI;\n const os = Math.log(overshoot / 100 + eps);\n const zeta = -os / Math.sqrt(pi * pi + os * os);\n const wn = 3.9 / (zeta * duration); // Calculate the Spring values\n\n this.d = 2 * zeta * wn;\n this.k = wn * wn;\n}\n\nclass Spring extends Controller {\n constructor(duration = 500, overshoot = 0) {\n super();\n this.duration(duration).overshoot(overshoot);\n }\n\n step(current, target, dt, c) {\n if (typeof current === 'string') return current;\n c.done = dt === Infinity;\n if (dt === Infinity) return target;\n if (dt === 0) return current;\n if (dt > 100) dt = 16;\n dt /= 1000; // Get the previous velocity\n\n const velocity = c.velocity || 0; // Apply the control to get the new position and store it\n\n const acceleration = -this.d * velocity - this.k * (current - target);\n const newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity\n\n c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value\n\n c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;\n return c.done ? target : newPosition;\n }\n\n}\nextend(Spring, {\n duration: makeSetterGetter('_duration', recalculate),\n overshoot: makeSetterGetter('_overshoot', recalculate)\n});\nclass PID extends Controller {\n constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) {\n super();\n this.p(p).i(i).d(d).windup(windup);\n }\n\n step(current, target, dt, c) {\n if (typeof current === 'string') return current;\n c.done = dt === Infinity;\n if (dt === Infinity) return target;\n if (dt === 0) return current;\n const p = target - current;\n let i = (c.integral || 0) + p * dt;\n const d = (p - (c.error || 0)) / dt;\n const windup = this._windup; // antiwindup\n\n if (windup !== false) {\n i = Math.max(-windup, Math.min(i, windup));\n }\n\n c.error = p;\n c.integral = i;\n c.done = Math.abs(p) < 0.001;\n return c.done ? target : current + (this.P * p + this.I * i + this.D * d);\n }\n\n}\nextend(PID, {\n windup: makeSetterGetter('_windup'),\n p: makeSetterGetter('P'),\n i: makeSetterGetter('I'),\n d: makeSetterGetter('D')\n});\n\nconst segmentParameters = {\n M: 2,\n L: 2,\n H: 1,\n V: 1,\n C: 6,\n S: 4,\n Q: 4,\n T: 2,\n A: 7,\n Z: 0\n};\nconst pathHandlers = {\n M: function (c, p, p0) {\n p.x = p0.x = c[0];\n p.y = p0.y = c[1];\n return ['M', p.x, p.y];\n },\n L: function (c, p) {\n p.x = c[0];\n p.y = c[1];\n return ['L', c[0], c[1]];\n },\n H: function (c, p) {\n p.x = c[0];\n return ['H', c[0]];\n },\n V: function (c, p) {\n p.y = c[0];\n return ['V', c[0]];\n },\n C: function (c, p) {\n p.x = c[4];\n p.y = c[5];\n return ['C', c[0], c[1], c[2], c[3], c[4], c[5]];\n },\n S: function (c, p) {\n p.x = c[2];\n p.y = c[3];\n return ['S', c[0], c[1], c[2], c[3]];\n },\n Q: function (c, p) {\n p.x = c[2];\n p.y = c[3];\n return ['Q', c[0], c[1], c[2], c[3]];\n },\n T: function (c, p) {\n p.x = c[0];\n p.y = c[1];\n return ['T', c[0], c[1]];\n },\n Z: function (c, p, p0) {\n p.x = p0.x;\n p.y = p0.y;\n return ['Z'];\n },\n A: function (c, p) {\n p.x = c[5];\n p.y = c[6];\n return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];\n }\n};\nconst mlhvqtcsaz = 'mlhvqtcsaz'.split('');\n\nfor (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {\n pathHandlers[mlhvqtcsaz[i]] = function (i) {\n return function (c, p, p0) {\n if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {\n c[5] = c[5] + p.x;\n c[6] = c[6] + p.y;\n } else {\n for (let j = 0, jl = c.length; j < jl; ++j) {\n c[j] = c[j] + (j % 2 ? p.y : p.x);\n }\n }\n return pathHandlers[i](c, p, p0);\n };\n }(mlhvqtcsaz[i].toUpperCase());\n}\n\nfunction makeAbsolut(parser) {\n const command = parser.segment[0];\n return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0);\n}\n\nfunction segmentComplete(parser) {\n return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()];\n}\n\nfunction startNewSegment(parser, token) {\n parser.inNumber && finalizeNumber(parser, false);\n const pathLetter = isPathLetter.test(token);\n\n if (pathLetter) {\n parser.segment = [token];\n } else {\n const lastCommand = parser.lastCommand;\n const small = lastCommand.toLowerCase();\n const isSmall = lastCommand === small;\n parser.segment = [small === 'm' ? isSmall ? 'l' : 'L' : lastCommand];\n }\n\n parser.inSegment = true;\n parser.lastCommand = parser.segment[0];\n return pathLetter;\n}\n\nfunction finalizeNumber(parser, inNumber) {\n if (!parser.inNumber) throw new Error('Parser Error');\n parser.number && parser.segment.push(parseFloat(parser.number));\n parser.inNumber = inNumber;\n parser.number = '';\n parser.pointSeen = false;\n parser.hasExponent = false;\n\n if (segmentComplete(parser)) {\n finalizeSegment(parser);\n }\n}\n\nfunction finalizeSegment(parser) {\n parser.inSegment = false;\n\n if (parser.absolute) {\n parser.segment = makeAbsolut(parser);\n }\n\n parser.segments.push(parser.segment);\n}\n\nfunction isArcFlag(parser) {\n if (!parser.segment.length) return false;\n const isArc = parser.segment[0].toUpperCase() === 'A';\n const length = parser.segment.length;\n return isArc && (length === 4 || length === 5);\n}\n\nfunction isExponential(parser) {\n return parser.lastToken.toUpperCase() === 'E';\n}\n\nfunction pathParser(d, toAbsolute = true) {\n let index = 0;\n let token = '';\n const parser = {\n segment: [],\n inNumber: false,\n number: '',\n lastToken: '',\n inSegment: false,\n segments: [],\n pointSeen: false,\n hasExponent: false,\n absolute: toAbsolute,\n p0: new Point(),\n p: new Point()\n };\n\n while (parser.lastToken = token, token = d.charAt(index++)) {\n if (!parser.inSegment) {\n if (startNewSegment(parser, token)) {\n continue;\n }\n }\n\n if (token === '.') {\n if (parser.pointSeen || parser.hasExponent) {\n finalizeNumber(parser, false);\n --index;\n continue;\n }\n\n parser.inNumber = true;\n parser.pointSeen = true;\n parser.number += token;\n continue;\n }\n\n if (!isNaN(parseInt(token))) {\n if (parser.number === '0' || isArcFlag(parser)) {\n parser.inNumber = true;\n parser.number = token;\n finalizeNumber(parser, true);\n continue;\n }\n\n parser.inNumber = true;\n parser.number += token;\n continue;\n }\n\n if (token === ' ' || token === ',') {\n if (parser.inNumber) {\n finalizeNumber(parser, false);\n }\n\n continue;\n }\n\n if (token === '-') {\n if (parser.inNumber && !isExponential(parser)) {\n finalizeNumber(parser, false);\n --index;\n continue;\n }\n\n parser.number += token;\n parser.inNumber = true;\n continue;\n }\n\n if (token.toUpperCase() === 'E') {\n parser.number += token;\n parser.hasExponent = true;\n continue;\n }\n\n if (isPathLetter.test(token)) {\n if (parser.inNumber) {\n finalizeNumber(parser, false);\n } else if (!segmentComplete(parser)) {\n throw new Error('parser Error');\n } else {\n finalizeSegment(parser);\n }\n\n --index;\n }\n }\n\n if (parser.inNumber) {\n finalizeNumber(parser, false);\n }\n\n if (parser.inSegment && segmentComplete(parser)) {\n finalizeSegment(parser);\n }\n\n return parser.segments;\n}\n\nfunction arrayToString(a) {\n let s = '';\n\n for (let i = 0, il = a.length; i < il; i++) {\n s += a[i][0];\n\n if (a[i][1] != null) {\n s += a[i][1];\n\n if (a[i][2] != null) {\n s += ' ';\n s += a[i][2];\n\n if (a[i][3] != null) {\n s += ' ';\n s += a[i][3];\n s += ' ';\n s += a[i][4];\n\n if (a[i][5] != null) {\n s += ' ';\n s += a[i][5];\n s += ' ';\n s += a[i][6];\n\n if (a[i][7] != null) {\n s += ' ';\n s += a[i][7];\n }\n }\n }\n }\n }\n }\n\n return s + ' ';\n}\n\nclass PathArray extends SVGArray {\n // Get bounding box of path\n bbox() {\n parser().path.setAttribute('d', this.toString());\n return new Box(parser.nodes.path.getBBox());\n } // Move path string\n\n\n move(x, y) {\n // get bounding box of current situation\n const box = this.bbox(); // get relative offset\n\n x -= box.x;\n y -= box.y;\n\n if (!isNaN(x) && !isNaN(y)) {\n // move every point\n for (let l, i = this.length - 1; i >= 0; i--) {\n l = this[i][0];\n\n if (l === 'M' || l === 'L' || l === 'T') {\n this[i][1] += x;\n this[i][2] += y;\n } else if (l === 'H') {\n this[i][1] += x;\n } else if (l === 'V') {\n this[i][1] += y;\n } else if (l === 'C' || l === 'S' || l === 'Q') {\n this[i][1] += x;\n this[i][2] += y;\n this[i][3] += x;\n this[i][4] += y;\n\n if (l === 'C') {\n this[i][5] += x;\n this[i][6] += y;\n }\n } else if (l === 'A') {\n this[i][6] += x;\n this[i][7] += y;\n }\n }\n }\n\n return this;\n } // Absolutize and parse path to array\n\n\n parse(d = 'M0 0') {\n if (Array.isArray(d)) {\n d = Array.prototype.concat.apply([], d).toString();\n }\n\n return pathParser(d);\n } // Resize path string\n\n\n size(width, height) {\n // get bounding box of current situation\n const box = this.bbox();\n let i, l; // If the box width or height is 0 then we ignore\n // transformations on the respective axis\n\n box.width = box.width === 0 ? 1 : box.width;\n box.height = box.height === 0 ? 1 : box.height; // recalculate position of all points according to new size\n\n for (i = this.length - 1; i >= 0; i--) {\n l = this[i][0];\n\n if (l === 'M' || l === 'L' || l === 'T') {\n this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;\n this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;\n } else if (l === 'H') {\n this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;\n } else if (l === 'V') {\n this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;\n } else if (l === 'C' || l === 'S' || l === 'Q') {\n this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;\n this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;\n this[i][3] = (this[i][3] - box.x) * width / box.width + box.x;\n this[i][4] = (this[i][4] - box.y) * height / box.height + box.y;\n\n if (l === 'C') {\n this[i][5] = (this[i][5] - box.x) * width / box.width + box.x;\n this[i][6] = (this[i][6] - box.y) * height / box.height + box.y;\n }\n } else if (l === 'A') {\n // resize radii\n this[i][1] = this[i][1] * width / box.width;\n this[i][2] = this[i][2] * height / box.height; // move position values\n\n this[i][6] = (this[i][6] - box.x) * width / box.width + box.x;\n this[i][7] = (this[i][7] - box.y) * height / box.height + box.y;\n }\n }\n\n return this;\n } // Convert array to string\n\n\n toString() {\n return arrayToString(this);\n }\n\n}\n\nconst getClassForType = value => {\n const type = typeof value;\n\n if (type === 'number') {\n return SVGNumber;\n } else if (type === 'string') {\n if (Color.isColor(value)) {\n return Color;\n } else if (delimiter.test(value)) {\n return isPathLetter.test(value) ? PathArray : SVGArray;\n } else if (numberAndUnit.test(value)) {\n return SVGNumber;\n } else {\n return NonMorphable;\n }\n } else if (morphableTypes.indexOf(value.constructor) > -1) {\n return value.constructor;\n } else if (Array.isArray(value)) {\n return SVGArray;\n } else if (type === 'object') {\n return ObjectBag;\n } else {\n return NonMorphable;\n }\n};\n\nclass Morphable {\n constructor(stepper) {\n this._stepper = stepper || new Ease('-');\n this._from = null;\n this._to = null;\n this._type = null;\n this._context = null;\n this._morphObj = null;\n }\n\n at(pos) {\n const _this = this;\n\n return this._morphObj.fromArray(this._from.map(function (i, index) {\n return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context);\n }));\n }\n\n done() {\n const complete = this._context.map(this._stepper.done).reduce(function (last, curr) {\n return last && curr;\n }, true);\n\n return complete;\n }\n\n from(val) {\n if (val == null) {\n return this._from;\n }\n\n this._from = this._set(val);\n return this;\n }\n\n stepper(stepper) {\n if (stepper == null) return this._stepper;\n this._stepper = stepper;\n return this;\n }\n\n to(val) {\n if (val == null) {\n return this._to;\n }\n\n this._to = this._set(val);\n return this;\n }\n\n type(type) {\n // getter\n if (type == null) {\n return this._type;\n } // setter\n\n\n this._type = type;\n return this;\n }\n\n _set(value) {\n if (!this._type) {\n this.type(getClassForType(value));\n }\n\n let result = new this._type(value);\n\n if (this._type === Color) {\n result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result;\n }\n\n if (this._type === ObjectBag) {\n result = this._to ? result.align(this._to) : this._from ? result.align(this._from) : result;\n }\n\n result = result.toArray();\n this._morphObj = this._morphObj || new this._type();\n this._context = this._context || Array.apply(null, Array(result.length)).map(Object).map(function (o) {\n o.done = true;\n return o;\n });\n return result;\n }\n\n}\nclass NonMorphable {\n constructor(...args) {\n this.init(...args);\n }\n\n init(val) {\n val = Array.isArray(val) ? val[0] : val;\n this.value = val;\n return this;\n }\n\n toArray() {\n return [this.value];\n }\n\n valueOf() {\n return this.value;\n }\n\n}\nclass TransformBag {\n constructor(...args) {\n this.init(...args);\n }\n\n init(obj) {\n if (Array.isArray(obj)) {\n obj = {\n scaleX: obj[0],\n scaleY: obj[1],\n shear: obj[2],\n rotate: obj[3],\n translateX: obj[4],\n translateY: obj[5],\n originX: obj[6],\n originY: obj[7]\n };\n }\n\n Object.assign(this, TransformBag.defaults, obj);\n return this;\n }\n\n toArray() {\n const v = this;\n return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];\n }\n\n}\nTransformBag.defaults = {\n scaleX: 1,\n scaleY: 1,\n shear: 0,\n rotate: 0,\n translateX: 0,\n translateY: 0,\n originX: 0,\n originY: 0\n};\n\nconst sortByKey = (a, b) => {\n return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;\n};\n\nclass ObjectBag {\n constructor(...args) {\n this.init(...args);\n }\n\n align(other) {\n for (let i = 0, il = this.values.length; i < il; ++i) {\n if (this.values[i] === Color) {\n const space = other[i + 6];\n const color = new Color(this.values.splice(i + 2, 5))[space]().toArray();\n this.values.splice(i + 2, 0, ...color);\n }\n }\n\n return this;\n }\n\n init(objOrArr) {\n this.values = [];\n\n if (Array.isArray(objOrArr)) {\n this.values = objOrArr.slice();\n return;\n }\n\n objOrArr = objOrArr || {};\n const entries = [];\n\n for (const i in objOrArr) {\n const Type = getClassForType(objOrArr[i]);\n const val = new Type(objOrArr[i]).toArray();\n entries.push([i, Type, val.length, ...val]);\n }\n\n entries.sort(sortByKey);\n this.values = entries.reduce((last, curr) => last.concat(curr), []);\n return this;\n }\n\n toArray() {\n return this.values;\n }\n\n valueOf() {\n const obj = {};\n const arr = this.values; // for (var i = 0, len = arr.length; i < len; i += 2) {\n\n while (arr.length) {\n const key = arr.shift();\n const Type = arr.shift();\n const num = arr.shift();\n const values = arr.splice(0, num);\n obj[key] = new Type(values).valueOf();\n }\n\n return obj;\n }\n\n}\nconst morphableTypes = [NonMorphable, TransformBag, ObjectBag];\nfunction registerMorphableType(type = []) {\n morphableTypes.push(...[].concat(type));\n}\nfunction makeMorphable() {\n extend(morphableTypes, {\n to(val) {\n return new Morphable().type(this.constructor).from(this.valueOf()).to(val);\n },\n\n fromArray(arr) {\n this.init(arr);\n return this;\n }\n\n });\n}\n\nclass Path extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('path', node), attrs);\n } // Get array\n\n\n array() {\n return this._array || (this._array = new PathArray(this.attr('d')));\n } // Clear array cache\n\n\n clear() {\n delete this._array;\n return this;\n } // Set height of element\n\n\n height(height) {\n return height == null ? this.bbox().height : this.size(this.bbox().width, height);\n } // Move by left top corner\n\n\n move(x, y) {\n return this.attr('d', this.array().move(x, y));\n } // Plot new path\n\n\n plot(d) {\n return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));\n } // Set element size to given width and height\n\n\n size(width, height) {\n const p = proportionalSize(this, width, height);\n return this.attr('d', this.array().size(p.width, p.height));\n } // Set width of element\n\n\n width(width) {\n return width == null ? this.bbox().width : this.size(width, this.bbox().height);\n } // Move by left top corner over x-axis\n\n\n x(x) {\n return x == null ? this.bbox().x : this.move(x, this.bbox().y);\n } // Move by left top corner over y-axis\n\n\n y(y) {\n return y == null ? this.bbox().y : this.move(this.bbox().x, y);\n }\n\n} // Define morphable array\n\nPath.prototype.MorphArray = PathArray; // Add parent method\n\nregisterMethods({\n Container: {\n // Create a wrapped path element\n path: wrapWithAttrCheck(function (d) {\n // make sure plot is called as a setter\n return this.put(new Path()).plot(d || new PathArray());\n })\n }\n});\nregister(Path, 'Path');\n\nfunction array() {\n return this._array || (this._array = new PointArray(this.attr('points')));\n} // Clear array cache\n\nfunction clear() {\n delete this._array;\n return this;\n} // Move by left top corner\n\nfunction move$2(x, y) {\n return this.attr('points', this.array().move(x, y));\n} // Plot new path\n\nfunction plot(p) {\n return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));\n} // Set element size to given width and height\n\nfunction size$1(width, height) {\n const p = proportionalSize(this, width, height);\n return this.attr('points', this.array().size(p.width, p.height));\n}\n\nvar poly = {\n __proto__: null,\n array: array,\n clear: clear,\n move: move$2,\n plot: plot,\n size: size$1\n};\n\nclass Polygon extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('polygon', node), attrs);\n }\n\n}\nregisterMethods({\n Container: {\n // Create a wrapped polygon element\n polygon: wrapWithAttrCheck(function (p) {\n // make sure plot is called as a setter\n return this.put(new Polygon()).plot(p || new PointArray());\n })\n }\n});\nextend(Polygon, pointed);\nextend(Polygon, poly);\nregister(Polygon, 'Polygon');\n\nclass Polyline extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('polyline', node), attrs);\n }\n\n}\nregisterMethods({\n Container: {\n // Create a wrapped polygon element\n polyline: wrapWithAttrCheck(function (p) {\n // make sure plot is called as a setter\n return this.put(new Polyline()).plot(p || new PointArray());\n })\n }\n});\nextend(Polyline, pointed);\nextend(Polyline, poly);\nregister(Polyline, 'Polyline');\n\nclass Rect extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('rect', node), attrs);\n }\n\n}\nextend(Rect, {\n rx,\n ry\n});\nregisterMethods({\n Container: {\n // Create a rect element\n rect: wrapWithAttrCheck(function (width, height) {\n return this.put(new Rect()).size(width, height);\n })\n }\n});\nregister(Rect, 'Rect');\n\nclass Queue {\n constructor() {\n this._first = null;\n this._last = null;\n } // Shows us the first item in the list\n\n\n first() {\n return this._first && this._first.value;\n } // Shows us the last item in the list\n\n\n last() {\n return this._last && this._last.value;\n }\n\n push(value) {\n // An item stores an id and the provided value\n const item = typeof value.next !== 'undefined' ? value : {\n value: value,\n next: null,\n prev: null\n }; // Deal with the queue being empty or populated\n\n if (this._last) {\n item.prev = this._last;\n this._last.next = item;\n this._last = item;\n } else {\n this._last = item;\n this._first = item;\n } // Return the current item\n\n\n return item;\n } // Removes the item that was returned from the push\n\n\n remove(item) {\n // Relink the previous item\n if (item.prev) item.prev.next = item.next;\n if (item.next) item.next.prev = item.prev;\n if (item === this._last) this._last = item.prev;\n if (item === this._first) this._first = item.next; // Invalidate item\n\n item.prev = null;\n item.next = null;\n }\n\n shift() {\n // Check if we have a value\n const remove = this._first;\n if (!remove) return null; // If we do, remove it and relink things\n\n this._first = remove.next;\n if (this._first) this._first.prev = null;\n this._last = this._first ? this._last : null;\n return remove.value;\n }\n\n}\n\nconst Animator = {\n nextDraw: null,\n frames: new Queue(),\n timeouts: new Queue(),\n immediates: new Queue(),\n timer: () => globals.window.performance || globals.window.Date,\n transforms: [],\n\n frame(fn) {\n // Store the node\n const node = Animator.frames.push({\n run: fn\n }); // Request an animation frame if we don't have one\n\n if (Animator.nextDraw === null) {\n Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);\n } // Return the node so we can remove it easily\n\n\n return node;\n },\n\n timeout(fn, delay) {\n delay = delay || 0; // Work out when the event should fire\n\n const time = Animator.timer().now() + delay; // Add the timeout to the end of the queue\n\n const node = Animator.timeouts.push({\n run: fn,\n time: time\n }); // Request another animation frame if we need one\n\n if (Animator.nextDraw === null) {\n Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);\n }\n\n return node;\n },\n\n immediate(fn) {\n // Add the immediate fn to the end of the queue\n const node = Animator.immediates.push(fn); // Request another animation frame if we need one\n\n if (Animator.nextDraw === null) {\n Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);\n }\n\n return node;\n },\n\n cancelFrame(node) {\n node != null && Animator.frames.remove(node);\n },\n\n clearTimeout(node) {\n node != null && Animator.timeouts.remove(node);\n },\n\n cancelImmediate(node) {\n node != null && Animator.immediates.remove(node);\n },\n\n _draw(now) {\n // Run all the timeouts we can run, if they are not ready yet, add them\n // to the end of the queue immediately! (bad timeouts!!! [sarcasm])\n let nextTimeout = null;\n const lastTimeout = Animator.timeouts.last();\n\n while (nextTimeout = Animator.timeouts.shift()) {\n // Run the timeout if its time, or push it to the end\n if (now >= nextTimeout.time) {\n nextTimeout.run();\n } else {\n Animator.timeouts.push(nextTimeout);\n } // If we hit the last item, we should stop shifting out more items\n\n\n if (nextTimeout === lastTimeout) break;\n } // Run all of the animation frames\n\n\n let nextFrame = null;\n const lastFrame = Animator.frames.last();\n\n while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {\n nextFrame.run(now);\n }\n\n let nextImmediate = null;\n\n while (nextImmediate = Animator.immediates.shift()) {\n nextImmediate();\n } // If we have remaining timeouts or frames, draw until we don't anymore\n\n\n Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null;\n }\n\n};\n\nconst makeSchedule = function (runnerInfo) {\n const start = runnerInfo.start;\n const duration = runnerInfo.runner.duration();\n const end = start + duration;\n return {\n start: start,\n duration: duration,\n end: end,\n runner: runnerInfo.runner\n };\n};\n\nconst defaultSource = function () {\n const w = globals.window;\n return (w.performance || w.Date).now();\n};\n\nclass Timeline extends EventTarget {\n // Construct a new timeline on the given element\n constructor(timeSource = defaultSource) {\n super();\n this._timeSource = timeSource; // Store the timing variables\n\n this._startTime = 0;\n this._speed = 1.0; // Determines how long a runner is hold in memory. Can be a dt or true/false\n\n this._persist = 0; // Keep track of the running animations and their starting parameters\n\n this._nextFrame = null;\n this._paused = true;\n this._runners = [];\n this._runnerIds = [];\n this._lastRunnerId = -1;\n this._time = 0;\n this._lastSourceTime = 0;\n this._lastStepTime = 0; // Make sure that step is always called in class context\n\n this._step = this._stepFn.bind(this, false);\n this._stepImmediate = this._stepFn.bind(this, true);\n }\n\n active() {\n return !!this._nextFrame;\n }\n\n finish() {\n // Go to end and pause\n this.time(this.getEndTimeOfTimeline() + 1);\n return this.pause();\n } // Calculates the end of the timeline\n\n\n getEndTime() {\n const lastRunnerInfo = this.getLastRunnerInfo();\n const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0;\n const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;\n return lastStartTime + lastDuration;\n }\n\n getEndTimeOfTimeline() {\n const endTimes = this._runners.map(i => i.start + i.runner.duration());\n\n return Math.max(0, ...endTimes);\n }\n\n getLastRunnerInfo() {\n return this.getRunnerInfoById(this._lastRunnerId);\n }\n\n getRunnerInfoById(id) {\n return this._runners[this._runnerIds.indexOf(id)] || null;\n }\n\n pause() {\n this._paused = true;\n return this._continue();\n }\n\n persist(dtOrForever) {\n if (dtOrForever == null) return this._persist;\n this._persist = dtOrForever;\n return this;\n }\n\n play() {\n // Now make sure we are not paused and continue the animation\n this._paused = false;\n return this.updateTime()._continue();\n }\n\n reverse(yes) {\n const currentSpeed = this.speed();\n if (yes == null) return this.speed(-currentSpeed);\n const positive = Math.abs(currentSpeed);\n return this.speed(yes ? -positive : positive);\n } // schedules a runner on the timeline\n\n\n schedule(runner, delay, when) {\n if (runner == null) {\n return this._runners.map(makeSchedule);\n } // The start time for the next animation can either be given explicitly,\n // derived from the current timeline time or it can be relative to the\n // last start time to chain animations directly\n\n\n let absoluteStartTime = 0;\n const endTime = this.getEndTime();\n delay = delay || 0; // Work out when to start the animation\n\n if (when == null || when === 'last' || when === 'after') {\n // Take the last time and increment\n absoluteStartTime = endTime;\n } else if (when === 'absolute' || when === 'start') {\n absoluteStartTime = delay;\n delay = 0;\n } else if (when === 'now') {\n absoluteStartTime = this._time;\n } else if (when === 'relative') {\n const runnerInfo = this.getRunnerInfoById(runner.id);\n\n if (runnerInfo) {\n absoluteStartTime = runnerInfo.start + delay;\n delay = 0;\n }\n } else if (when === 'with-last') {\n const lastRunnerInfo = this.getLastRunnerInfo();\n const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;\n absoluteStartTime = lastStartTime;\n } else {\n throw new Error('Invalid value for the \"when\" parameter');\n } // Manage runner\n\n\n runner.unschedule();\n runner.timeline(this);\n const persist = runner.persist();\n const runnerInfo = {\n persist: persist === null ? this._persist : persist,\n start: absoluteStartTime + delay,\n runner\n };\n this._lastRunnerId = runner.id;\n\n this._runners.push(runnerInfo);\n\n this._runners.sort((a, b) => a.start - b.start);\n\n this._runnerIds = this._runners.map(info => info.runner.id);\n\n this.updateTime()._continue();\n\n return this;\n }\n\n seek(dt) {\n return this.time(this._time + dt);\n }\n\n source(fn) {\n if (fn == null) return this._timeSource;\n this._timeSource = fn;\n return this;\n }\n\n speed(speed) {\n if (speed == null) return this._speed;\n this._speed = speed;\n return this;\n }\n\n stop() {\n // Go to start and pause\n this.time(0);\n return this.pause();\n }\n\n time(time) {\n if (time == null) return this._time;\n this._time = time;\n return this._continue(true);\n } // Remove the runner from this timeline\n\n\n unschedule(runner) {\n const index = this._runnerIds.indexOf(runner.id);\n\n if (index < 0) return this;\n\n this._runners.splice(index, 1);\n\n this._runnerIds.splice(index, 1);\n\n runner.timeline(null);\n return this;\n } // Makes sure, that after pausing the time doesn't jump\n\n\n updateTime() {\n if (!this.active()) {\n this._lastSourceTime = this._timeSource();\n }\n\n return this;\n } // Checks if we are running and continues the animation\n\n\n _continue(immediateStep = false) {\n Animator.cancelFrame(this._nextFrame);\n this._nextFrame = null;\n if (immediateStep) return this._stepImmediate();\n if (this._paused) return this;\n this._nextFrame = Animator.frame(this._step);\n return this;\n }\n\n _stepFn(immediateStep = false) {\n // Get the time delta from the last time and update the time\n const time = this._timeSource();\n\n let dtSource = time - this._lastSourceTime;\n if (immediateStep) dtSource = 0;\n const dtTime = this._speed * dtSource + (this._time - this._lastStepTime);\n this._lastSourceTime = time; // Only update the time if we use the timeSource.\n // Otherwise use the current time\n\n if (!immediateStep) {\n // Update the time\n this._time += dtTime;\n this._time = this._time < 0 ? 0 : this._time;\n }\n\n this._lastStepTime = this._time;\n this.fire('time', this._time); // This is for the case that the timeline was seeked so that the time\n // is now before the startTime of the runner. Thats why we need to set\n // the runner to position 0\n // FIXME:\n // However, reseting in insertion order leads to bugs. Considering the case,\n // where 2 runners change the same attribute but in different times,\n // reseting both of them will lead to the case where the later defined\n // runner always wins the reset even if the other runner started earlier\n // and therefore should win the attribute battle\n // this can be solved by reseting them backwards\n\n for (let k = this._runners.length; k--;) {\n // Get and run the current runner and ignore it if its inactive\n const runnerInfo = this._runners[k];\n const runner = runnerInfo.runner; // Make sure that we give the actual difference\n // between runner start time and now\n\n const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet\n // and try to reset it\n\n if (dtToStart <= 0) {\n runner.reset();\n }\n } // Run all of the runners directly\n\n\n let runnersLeft = false;\n\n for (let i = 0, len = this._runners.length; i < len; i++) {\n // Get and run the current runner and ignore it if its inactive\n const runnerInfo = this._runners[i];\n const runner = runnerInfo.runner;\n let dt = dtTime; // Make sure that we give the actual difference\n // between runner start time and now\n\n const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet\n\n if (dtToStart <= 0) {\n runnersLeft = true;\n continue;\n } else if (dtToStart < dt) {\n // Adjust dt to make sure that animation is on point\n dt = dtToStart;\n }\n\n if (!runner.active()) continue; // If this runner is still going, signal that we need another animation\n // frame, otherwise, remove the completed runner\n\n const finished = runner.step(dt).done;\n\n if (!finished) {\n runnersLeft = true; // continue\n } else if (runnerInfo.persist !== true) {\n // runner is finished. And runner might get removed\n const endTime = runner.duration() - runner.time() + this._time;\n\n if (endTime + runnerInfo.persist < this._time) {\n // Delete runner and correct index\n runner.unschedule();\n --i;\n --len;\n }\n }\n } // Basically: we continue when there are runners right from us in time\n // when -->, and when runners are left from us when <--\n\n\n if (runnersLeft && !(this._speed < 0 && this._time === 0) || this._runnerIds.length && this._speed < 0 && this._time > 0) {\n this._continue();\n } else {\n this.pause();\n this.fire('finished');\n }\n\n return this;\n }\n\n}\nregisterMethods({\n Element: {\n timeline: function (timeline) {\n if (timeline == null) {\n this._timeline = this._timeline || new Timeline();\n return this._timeline;\n } else {\n this._timeline = timeline;\n return this;\n }\n }\n }\n});\n\nclass Runner extends EventTarget {\n constructor(options) {\n super(); // Store a unique id on the runner, so that we can identify it later\n\n this.id = Runner.id++; // Ensure a default value\n\n options = options == null ? timeline.duration : options; // Ensure that we get a controller\n\n options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables\n\n this._element = null;\n this._timeline = null;\n this.done = false;\n this._queue = []; // Work out the stepper and the duration\n\n this._duration = typeof options === 'number' && options;\n this._isDeclarative = options instanceof Controller;\n this._stepper = this._isDeclarative ? options : new Ease(); // We copy the current values from the timeline because they can change\n\n this._history = {}; // Store the state of the runner\n\n this.enabled = true;\n this._time = 0;\n this._lastTime = 0; // At creation, the runner is in reseted state\n\n this._reseted = true; // Save transforms applied to this runner\n\n this.transforms = new Matrix();\n this.transformId = 1; // Looping variables\n\n this._haveReversed = false;\n this._reverse = false;\n this._loopsDone = 0;\n this._swing = false;\n this._wait = 0;\n this._times = 1;\n this._frameId = null; // Stores how long a runner is stored after beeing done\n\n this._persist = this._isDeclarative ? true : null;\n }\n\n static sanitise(duration, delay, when) {\n // Initialise the default parameters\n let times = 1;\n let swing = false;\n let wait = 0;\n duration = duration || timeline.duration;\n delay = delay || timeline.delay;\n when = when || 'last'; // If we have an object, unpack the values\n\n if (typeof duration === 'object' && !(duration instanceof Stepper)) {\n delay = duration.delay || delay;\n when = duration.when || when;\n swing = duration.swing || swing;\n times = duration.times || times;\n wait = duration.wait || wait;\n duration = duration.duration || timeline.duration;\n }\n\n return {\n duration: duration,\n delay: delay,\n swing: swing,\n times: times,\n wait: wait,\n when: when\n };\n }\n\n active(enabled) {\n if (enabled == null) return this.enabled;\n this.enabled = enabled;\n return this;\n }\n /*\r\n Private Methods\r\n ===============\r\n Methods that shouldn't be used externally\r\n */\n\n\n addTransform(transform, index) {\n this.transforms.lmultiplyO(transform);\n return this;\n }\n\n after(fn) {\n return this.on('finished', fn);\n }\n\n animate(duration, delay, when) {\n const o = Runner.sanitise(duration, delay, when);\n const runner = new Runner(o.duration);\n if (this._timeline) runner.timeline(this._timeline);\n if (this._element) runner.element(this._element);\n return runner.loop(o).schedule(o.delay, o.when);\n }\n\n clearTransform() {\n this.transforms = new Matrix();\n return this;\n } // TODO: Keep track of all transformations so that deletion is faster\n\n\n clearTransformsFromQueue() {\n if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {\n this._queue = this._queue.filter(item => {\n return !item.isTransform;\n });\n }\n }\n\n delay(delay) {\n return this.animate(0, delay);\n }\n\n duration() {\n return this._times * (this._wait + this._duration) - this._wait;\n }\n\n during(fn) {\n return this.queue(null, fn);\n }\n\n ease(fn) {\n this._stepper = new Ease(fn);\n return this;\n }\n /*\r\n Runner Definitions\r\n ==================\r\n These methods help us define the runtime behaviour of the Runner or they\r\n help us make new runners from the current runner\r\n */\n\n\n element(element) {\n if (element == null) return this._element;\n this._element = element;\n\n element._prepareRunner();\n\n return this;\n }\n\n finish() {\n return this.step(Infinity);\n }\n\n loop(times, swing, wait) {\n // Deal with the user passing in an object\n if (typeof times === 'object') {\n swing = times.swing;\n wait = times.wait;\n times = times.times;\n } // Sanitise the values and store them\n\n\n this._times = times || Infinity;\n this._swing = swing || false;\n this._wait = wait || 0; // Allow true to be passed\n\n if (this._times === true) {\n this._times = Infinity;\n }\n\n return this;\n }\n\n loops(p) {\n const loopDuration = this._duration + this._wait;\n\n if (p == null) {\n const loopsDone = Math.floor(this._time / loopDuration);\n const relativeTime = this._time - loopsDone * loopDuration;\n const position = relativeTime / this._duration;\n return Math.min(loopsDone + position, this._times);\n }\n\n const whole = Math.floor(p);\n const partial = p % 1;\n const time = loopDuration * whole + this._duration * partial;\n return this.time(time);\n }\n\n persist(dtOrForever) {\n if (dtOrForever == null) return this._persist;\n this._persist = dtOrForever;\n return this;\n }\n\n position(p) {\n // Get all of the variables we need\n const x = this._time;\n const d = this._duration;\n const w = this._wait;\n const t = this._times;\n const s = this._swing;\n const r = this._reverse;\n let position;\n\n if (p == null) {\n /*\r\n This function converts a time to a position in the range [0, 1]\r\n The full explanation can be found in this desmos demonstration\r\n https://www.desmos.com/calculator/u4fbavgche\r\n The logic is slightly simplified here because we can use booleans\r\n */\n // Figure out the value without thinking about the start or end time\n const f = function (x) {\n const swinging = s * Math.floor(x % (2 * (w + d)) / (w + d));\n const backwards = swinging && !r || !swinging && r;\n const uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards;\n const clipped = Math.max(Math.min(uncliped, 1), 0);\n return clipped;\n }; // Figure out the value by incorporating the start time\n\n\n const endTime = t * (w + d) - w;\n position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) : Math.round(f(endTime - 1e-5));\n return position;\n } // Work out the loops done and add the position to the loops done\n\n\n const loopsDone = Math.floor(this.loops());\n const swingForward = s && loopsDone % 2 === 0;\n const forwards = swingForward && !r || r && swingForward;\n position = loopsDone + (forwards ? p : 1 - p);\n return this.loops(position);\n }\n\n progress(p) {\n if (p == null) {\n return Math.min(1, this._time / this.duration());\n }\n\n return this.time(p * this.duration());\n }\n /*\r\n Basic Functionality\r\n ===================\r\n These methods allow us to attach basic functions to the runner directly\r\n */\n\n\n queue(initFn, runFn, retargetFn, isTransform) {\n this._queue.push({\n initialiser: initFn || noop,\n runner: runFn || noop,\n retarget: retargetFn,\n isTransform: isTransform,\n initialised: false,\n finished: false\n });\n\n const timeline = this.timeline();\n timeline && this.timeline()._continue();\n return this;\n }\n\n reset() {\n if (this._reseted) return this;\n this.time(0);\n this._reseted = true;\n return this;\n }\n\n reverse(reverse) {\n this._reverse = reverse == null ? !this._reverse : reverse;\n return this;\n }\n\n schedule(timeline, delay, when) {\n // The user doesn't need to pass a timeline if we already have one\n if (!(timeline instanceof Timeline)) {\n when = delay;\n delay = timeline;\n timeline = this.timeline();\n } // If there is no timeline, yell at the user...\n\n\n if (!timeline) {\n throw Error('Runner cannot be scheduled without timeline');\n } // Schedule the runner on the timeline provided\n\n\n timeline.schedule(this, delay, when);\n return this;\n }\n\n step(dt) {\n // If we are inactive, this stepper just gets skipped\n if (!this.enabled) return this; // Update the time and get the new position\n\n dt = dt == null ? 16 : dt;\n this._time += dt;\n const position = this.position(); // Figure out if we need to run the stepper in this frame\n\n const running = this._lastPosition !== position && this._time >= 0;\n this._lastPosition = position; // Figure out if we just started\n\n const duration = this.duration();\n const justStarted = this._lastTime <= 0 && this._time > 0;\n const justFinished = this._lastTime < duration && this._time >= duration;\n this._lastTime = this._time;\n\n if (justStarted) {\n this.fire('start', this);\n } // Work out if the runner is finished set the done flag here so animations\n // know, that they are running in the last step (this is good for\n // transformations which can be merged)\n\n\n const declarative = this._isDeclarative;\n this.done = !declarative && !justFinished && this._time >= duration; // Runner is running. So its not in reseted state anymore\n\n this._reseted = false;\n let converged = false; // Call initialise and the run function\n\n if (running || declarative) {\n this._initialise(running); // clear the transforms on this runner so they dont get added again and again\n\n\n this.transforms = new Matrix();\n converged = this._run(declarative ? dt : position);\n this.fire('step', this);\n } // correct the done flag here\n // declaritive animations itself know when they converged\n\n\n this.done = this.done || converged && declarative;\n\n if (justFinished) {\n this.fire('finished', this);\n }\n\n return this;\n }\n /*\r\n Runner animation methods\r\n ========================\r\n Control how the animation plays\r\n */\n\n\n time(time) {\n if (time == null) {\n return this._time;\n }\n\n const dt = time - this._time;\n this.step(dt);\n return this;\n }\n\n timeline(timeline) {\n // check explicitly for undefined so we can set the timeline to null\n if (typeof timeline === 'undefined') return this._timeline;\n this._timeline = timeline;\n return this;\n }\n\n unschedule() {\n const timeline = this.timeline();\n timeline && timeline.unschedule(this);\n return this;\n } // Run each initialise function in the runner if required\n\n\n _initialise(running) {\n // If we aren't running, we shouldn't initialise when not declarative\n if (!running && !this._isDeclarative) return; // Loop through all of the initialisers\n\n for (let i = 0, len = this._queue.length; i < len; ++i) {\n // Get the current initialiser\n const current = this._queue[i]; // Determine whether we need to initialise\n\n const needsIt = this._isDeclarative || !current.initialised && running;\n running = !current.finished; // Call the initialiser if we need to\n\n if (needsIt && running) {\n current.initialiser.call(this);\n current.initialised = true;\n }\n }\n } // Save a morpher to the morpher list so that we can retarget it later\n\n\n _rememberMorpher(method, morpher) {\n this._history[method] = {\n morpher: morpher,\n caller: this._queue[this._queue.length - 1]\n }; // We have to resume the timeline in case a controller\n // is already done without being ever run\n // This can happen when e.g. this is done:\n // anim = el.animate(new SVG.Spring)\n // and later\n // anim.move(...)\n\n if (this._isDeclarative) {\n const timeline = this.timeline();\n timeline && timeline.play();\n }\n } // Try to set the target for a morpher if the morpher exists, otherwise\n // Run each run function for the position or dt given\n\n\n _run(positionOrDt) {\n // Run all of the _queue directly\n let allfinished = true;\n\n for (let i = 0, len = this._queue.length; i < len; ++i) {\n // Get the current function to run\n const current = this._queue[i]; // Run the function if its not finished, we keep track of the finished\n // flag for the sake of declarative _queue\n\n const converged = current.runner.call(this, positionOrDt);\n current.finished = current.finished || converged === true;\n allfinished = allfinished && current.finished;\n } // We report when all of the constructors are finished\n\n\n return allfinished;\n } // do nothing and return false\n\n\n _tryRetarget(method, target, extra) {\n if (this._history[method]) {\n // if the last method wasnt even initialised, throw it away\n if (!this._history[method].caller.initialised) {\n const index = this._queue.indexOf(this._history[method].caller);\n\n this._queue.splice(index, 1);\n\n return false;\n } // for the case of transformations, we use the special retarget function\n // which has access to the outer scope\n\n\n if (this._history[method].caller.retarget) {\n this._history[method].caller.retarget.call(this, target, extra); // for everything else a simple morpher change is sufficient\n\n } else {\n this._history[method].morpher.to(target);\n }\n\n this._history[method].caller.finished = false;\n const timeline = this.timeline();\n timeline && timeline.play();\n return true;\n }\n\n return false;\n }\n\n}\nRunner.id = 0;\nclass FakeRunner {\n constructor(transforms = new Matrix(), id = -1, done = true) {\n this.transforms = transforms;\n this.id = id;\n this.done = done;\n }\n\n clearTransformsFromQueue() {}\n\n}\nextend([Runner, FakeRunner], {\n mergeWith(runner) {\n return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);\n }\n\n}); // FakeRunner.emptyRunner = new FakeRunner()\n\nconst lmultiply = (last, curr) => last.lmultiplyO(curr);\n\nconst getRunnerTransform = runner => runner.transforms;\n\nfunction mergeTransforms() {\n // Find the matrix to apply to the element and apply it\n const runners = this._transformationRunners.runners;\n const netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());\n this.transform(netTransform);\n\n this._transformationRunners.merge();\n\n if (this._transformationRunners.length() === 1) {\n this._frameId = null;\n }\n}\n\nclass RunnerArray {\n constructor() {\n this.runners = [];\n this.ids = [];\n }\n\n add(runner) {\n if (this.runners.includes(runner)) return;\n const id = runner.id + 1;\n this.runners.push(runner);\n this.ids.push(id);\n return this;\n }\n\n clearBefore(id) {\n const deleteCnt = this.ids.indexOf(id + 1) || 1;\n this.ids.splice(0, deleteCnt, 0);\n this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(r => r.clearTransformsFromQueue());\n return this;\n }\n\n edit(id, newRunner) {\n const index = this.ids.indexOf(id + 1);\n this.ids.splice(index, 1, id + 1);\n this.runners.splice(index, 1, newRunner);\n return this;\n }\n\n getByID(id) {\n return this.runners[this.ids.indexOf(id + 1)];\n }\n\n length() {\n return this.ids.length;\n }\n\n merge() {\n let lastRunner = null;\n\n for (let i = 0; i < this.runners.length; ++i) {\n const runner = this.runners[i];\n const condition = lastRunner && runner.done && lastRunner.done // don't merge runner when persisted on timeline\n && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id)) && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id));\n\n if (condition) {\n // the +1 happens in the function\n this.remove(runner.id);\n const newRunner = runner.mergeWith(lastRunner);\n this.edit(lastRunner.id, newRunner);\n lastRunner = newRunner;\n --i;\n } else {\n lastRunner = runner;\n }\n }\n\n return this;\n }\n\n remove(id) {\n const index = this.ids.indexOf(id + 1);\n this.ids.splice(index, 1);\n this.runners.splice(index, 1);\n return this;\n }\n\n}\nregisterMethods({\n Element: {\n animate(duration, delay, when) {\n const o = Runner.sanitise(duration, delay, when);\n const timeline = this.timeline();\n return new Runner(o.duration).loop(o).element(this).timeline(timeline.play()).schedule(o.delay, o.when);\n },\n\n delay(by, when) {\n return this.animate(0, by, when);\n },\n\n // this function searches for all runners on the element and deletes the ones\n // which run before the current one. This is because absolute transformations\n // overwfrite anything anyway so there is no need to waste time computing\n // other runners\n _clearTransformRunnersBefore(currentRunner) {\n this._transformationRunners.clearBefore(currentRunner.id);\n },\n\n _currentTransform(current) {\n return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations\n // on the same runner which execute before the current transformation are\n // taken into account\n .filter(runner => runner.id <= current.id).map(getRunnerTransform).reduce(lmultiply, new Matrix());\n },\n\n _addRunner(runner) {\n this._transformationRunners.add(runner); // Make sure that the runner merge is executed at the very end of\n // all Animator functions. Thats why we use immediate here to execute\n // the merge right after all frames are run\n\n\n Animator.cancelImmediate(this._frameId);\n this._frameId = Animator.immediate(mergeTransforms.bind(this));\n },\n\n _prepareRunner() {\n if (this._frameId == null) {\n this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));\n }\n }\n\n }\n}); // Will output the elements from array A that are not in the array B\n\nconst difference = (a, b) => a.filter(x => !b.includes(x));\n\nextend(Runner, {\n attr(a, v) {\n return this.styleAttr('attr', a, v);\n },\n\n // Add animatable styles\n css(s, v) {\n return this.styleAttr('css', s, v);\n },\n\n styleAttr(type, nameOrAttrs, val) {\n if (typeof nameOrAttrs === 'string') {\n return this.styleAttr(type, {\n [nameOrAttrs]: val\n });\n }\n\n let attrs = nameOrAttrs;\n if (this._tryRetarget(type, attrs)) return this;\n let morpher = new Morphable(this._stepper).to(attrs);\n let keys = Object.keys(attrs);\n this.queue(function () {\n morpher = morpher.from(this.element()[type](keys));\n }, function (pos) {\n this.element()[type](morpher.at(pos).valueOf());\n return morpher.done();\n }, function (newToAttrs) {\n // Check if any new keys were added\n const newKeys = Object.keys(newToAttrs);\n const differences = difference(newKeys, keys); // If their are new keys, initialize them and add them to morpher\n\n if (differences.length) {\n // Get the values\n const addedFromAttrs = this.element()[type](differences); // Get the already initialized values\n\n const oldFromAttrs = new ObjectBag(morpher.from()).valueOf(); // Merge old and new\n\n Object.assign(oldFromAttrs, addedFromAttrs);\n morpher.from(oldFromAttrs);\n } // Get the object from the morpher\n\n\n const oldToAttrs = new ObjectBag(morpher.to()).valueOf(); // Merge in new attributes\n\n Object.assign(oldToAttrs, newToAttrs); // Change morpher target\n\n morpher.to(oldToAttrs); // Make sure that we save the work we did so we don't need it to do again\n\n keys = newKeys;\n attrs = newToAttrs;\n });\n\n this._rememberMorpher(type, morpher);\n\n return this;\n },\n\n zoom(level, point) {\n if (this._tryRetarget('zoom', level, point)) return this;\n let morpher = new Morphable(this._stepper).to(new SVGNumber(level));\n this.queue(function () {\n morpher = morpher.from(this.element().zoom());\n }, function (pos) {\n this.element().zoom(morpher.at(pos), point);\n return morpher.done();\n }, function (newLevel, newPoint) {\n point = newPoint;\n morpher.to(newLevel);\n });\n\n this._rememberMorpher('zoom', morpher);\n\n return this;\n },\n\n /**\r\n ** absolute transformations\r\n **/\n //\n // M v -----|-----(D M v = F v)------|-----> T v\n //\n // 1. define the final state (T) and decompose it (once)\n // t = [tx, ty, the, lam, sy, sx]\n // 2. on every frame: pull the current state of all previous transforms\n // (M - m can change)\n // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]\n // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)\n // - Note F(0) = M\n // - Note F(1) = T\n // 4. Now you get the delta matrix as a result: D = F * inv(M)\n transform(transforms, relative, affine) {\n // If we have a declarative function, we should retarget it if possible\n relative = transforms.relative || relative;\n\n if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {\n return this;\n } // Parse the parameters\n\n\n const isMatrix = Matrix.isMatrixLike(transforms);\n affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morepher and set its type\n\n const morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);\n let origin;\n let element;\n let current;\n let currentAngle;\n let startTransform;\n\n function setup() {\n // make sure element and origin is defined\n element = element || this.element();\n origin = origin || getOrigin(transforms, element);\n startTransform = new Matrix(relative ? undefined : element); // add the runner to the element so it can merge transformations\n\n element._addRunner(this); // Deactivate all transforms that have run so far if we are absolute\n\n\n if (!relative) {\n element._clearTransformRunnersBefore(this);\n }\n }\n\n function run(pos) {\n // clear all other transforms before this in case something is saved\n // on this runner. We are absolute. We dont need these!\n if (!relative) this.clearTransform();\n const {\n x,\n y\n } = new Point(origin).transform(element._currentTransform(this));\n let target = new Matrix({ ...transforms,\n origin: [x, y]\n });\n let start = this._isDeclarative && current ? current : startTransform;\n\n if (affine) {\n target = target.decompose(x, y);\n start = start.decompose(x, y); // Get the current and target angle as it was set\n\n const rTarget = target.rotate;\n const rCurrent = start.rotate; // Figure out the shortest path to rotate directly\n\n const possibilities = [rTarget - 360, rTarget, rTarget + 360];\n const distances = possibilities.map(a => Math.abs(a - rCurrent));\n const shortest = Math.min(...distances);\n const index = distances.indexOf(shortest);\n target.rotate = possibilities[index];\n }\n\n if (relative) {\n // we have to be careful here not to overwrite the rotation\n // with the rotate method of Matrix\n if (!isMatrix) {\n target.rotate = transforms.rotate || 0;\n }\n\n if (this._isDeclarative && currentAngle) {\n start.rotate = currentAngle;\n }\n }\n\n morpher.from(start);\n morpher.to(target);\n const affineParameters = morpher.at(pos);\n currentAngle = affineParameters.rotate;\n current = new Matrix(affineParameters);\n this.addTransform(current);\n\n element._addRunner(this);\n\n return morpher.done();\n }\n\n function retarget(newTransforms) {\n // only get a new origin if it changed since the last call\n if ((newTransforms.origin || 'center').toString() !== (transforms.origin || 'center').toString()) {\n origin = getOrigin(newTransforms, element);\n } // overwrite the old transformations with the new ones\n\n\n transforms = { ...newTransforms,\n origin\n };\n }\n\n this.queue(setup, run, retarget, true);\n this._isDeclarative && this._rememberMorpher('transform', morpher);\n return this;\n },\n\n // Animatable x-axis\n x(x, relative) {\n return this._queueNumber('x', x);\n },\n\n // Animatable y-axis\n y(y) {\n return this._queueNumber('y', y);\n },\n\n dx(x = 0) {\n return this._queueNumberDelta('x', x);\n },\n\n dy(y = 0) {\n return this._queueNumberDelta('y', y);\n },\n\n dmove(x, y) {\n return this.dx(x).dy(y);\n },\n\n _queueNumberDelta(method, to) {\n to = new SVGNumber(to); // Try to change the target if we have this method already registerd\n\n if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation\n\n const morpher = new Morphable(this._stepper).to(to);\n let from = null;\n this.queue(function () {\n from = this.element()[method]();\n morpher.from(from);\n morpher.to(from + to);\n }, function (pos) {\n this.element()[method](morpher.at(pos));\n return morpher.done();\n }, function (newTo) {\n morpher.to(from + new SVGNumber(newTo));\n }); // Register the morpher so that if it is changed again, we can retarget it\n\n this._rememberMorpher(method, morpher);\n\n return this;\n },\n\n _queueObject(method, to) {\n // Try to change the target if we have this method already registerd\n if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation\n\n const morpher = new Morphable(this._stepper).to(to);\n this.queue(function () {\n morpher.from(this.element()[method]());\n }, function (pos) {\n this.element()[method](morpher.at(pos));\n return morpher.done();\n }); // Register the morpher so that if it is changed again, we can retarget it\n\n this._rememberMorpher(method, morpher);\n\n return this;\n },\n\n _queueNumber(method, value) {\n return this._queueObject(method, new SVGNumber(value));\n },\n\n // Animatable center x-axis\n cx(x) {\n return this._queueNumber('cx', x);\n },\n\n // Animatable center y-axis\n cy(y) {\n return this._queueNumber('cy', y);\n },\n\n // Add animatable move\n move(x, y) {\n return this.x(x).y(y);\n },\n\n // Add animatable center\n center(x, y) {\n return this.cx(x).cy(y);\n },\n\n // Add animatable size\n size(width, height) {\n // animate bbox based size for all other elements\n let box;\n\n if (!width || !height) {\n box = this._element.bbox();\n }\n\n if (!width) {\n width = box.width / box.height * height;\n }\n\n if (!height) {\n height = box.height / box.width * width;\n }\n\n return this.width(width).height(height);\n },\n\n // Add animatable width\n width(width) {\n return this._queueNumber('width', width);\n },\n\n // Add animatable height\n height(height) {\n return this._queueNumber('height', height);\n },\n\n // Add animatable plot\n plot(a, b, c, d) {\n // Lines can be plotted with 4 arguments\n if (arguments.length === 4) {\n return this.plot([a, b, c, d]);\n }\n\n if (this._tryRetarget('plot', a)) return this;\n const morpher = new Morphable(this._stepper).type(this._element.MorphArray).to(a);\n this.queue(function () {\n morpher.from(this._element.array());\n }, function (pos) {\n this._element.plot(morpher.at(pos));\n\n return morpher.done();\n });\n\n this._rememberMorpher('plot', morpher);\n\n return this;\n },\n\n // Add leading method\n leading(value) {\n return this._queueNumber('leading', value);\n },\n\n // Add animatable viewbox\n viewbox(x, y, width, height) {\n return this._queueObject('viewbox', new Box(x, y, width, height));\n },\n\n update(o) {\n if (typeof o !== 'object') {\n return this.update({\n offset: arguments[0],\n color: arguments[1],\n opacity: arguments[2]\n });\n }\n\n if (o.opacity != null) this.attr('stop-opacity', o.opacity);\n if (o.color != null) this.attr('stop-color', o.color);\n if (o.offset != null) this.attr('offset', o.offset);\n return this;\n }\n\n});\nextend(Runner, {\n rx,\n ry,\n from,\n to\n});\nregister(Runner, 'Runner');\n\nclass Svg extends Container {\n constructor(node, attrs = node) {\n super(nodeOrNew('svg', node), attrs);\n this.namespace();\n } // Creates and returns defs element\n\n\n defs() {\n if (!this.isRoot()) return this.root().defs();\n return adopt(this.node.querySelector('defs')) || this.put(new Defs());\n }\n\n isRoot() {\n return !this.node.parentNode || !(this.node.parentNode instanceof globals.window.SVGElement) && this.node.parentNode.nodeName !== '#document-fragment';\n } // Add namespaces\n\n\n namespace() {\n if (!this.isRoot()) return this.root().namespace();\n return this.attr({\n xmlns: svg,\n version: '1.1'\n }).attr('xmlns:xlink', xlink, xmlns).attr('xmlns:svgjs', svgjs, xmlns);\n }\n\n removeNamespace() {\n return this.attr({\n xmlns: null,\n version: null\n }).attr('xmlns:xlink', null, xmlns).attr('xmlns:svgjs', null, xmlns);\n } // Check if this is a root svg\n // If not, call root() from this element\n\n\n root() {\n if (this.isRoot()) return this;\n return super.root();\n }\n\n}\nregisterMethods({\n Container: {\n // Create nested svg document\n nested: wrapWithAttrCheck(function () {\n return this.put(new Svg());\n })\n }\n});\nregister(Svg, 'Svg', true);\n\nclass Symbol extends Container {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('symbol', node), attrs);\n }\n\n}\nregisterMethods({\n Container: {\n symbol: wrapWithAttrCheck(function () {\n return this.put(new Symbol());\n })\n }\n});\nregister(Symbol, 'Symbol');\n\nfunction plain(text) {\n // clear if build mode is disabled\n if (this._build === false) {\n this.clear();\n } // create text node\n\n\n this.node.appendChild(globals.document.createTextNode(text));\n return this;\n} // Get length of text element\n\nfunction length() {\n return this.node.getComputedTextLength();\n} // Move over x-axis\n// Text is moved by its bounding box\n// text-anchor does NOT matter\n\nfunction x$1(x, box = this.bbox()) {\n if (x == null) {\n return box.x;\n }\n\n return this.attr('x', this.attr('x') + x - box.x);\n} // Move over y-axis\n\nfunction y$1(y, box = this.bbox()) {\n if (y == null) {\n return box.y;\n }\n\n return this.attr('y', this.attr('y') + y - box.y);\n}\nfunction move$1(x, y, box = this.bbox()) {\n return this.x(x, box).y(y, box);\n} // Move center over x-axis\n\nfunction cx(x, box = this.bbox()) {\n if (x == null) {\n return box.cx;\n }\n\n return this.attr('x', this.attr('x') + x - box.cx);\n} // Move center over y-axis\n\nfunction cy(y, box = this.bbox()) {\n if (y == null) {\n return box.cy;\n }\n\n return this.attr('y', this.attr('y') + y - box.cy);\n}\nfunction center(x, y, box = this.bbox()) {\n return this.cx(x, box).cy(y, box);\n}\nfunction ax(x) {\n return this.attr('x', x);\n}\nfunction ay(y) {\n return this.attr('y', y);\n}\nfunction amove(x, y) {\n return this.ax(x).ay(y);\n} // Enable / disable build mode\n\nfunction build(build) {\n this._build = !!build;\n return this;\n}\n\nvar textable = {\n __proto__: null,\n plain: plain,\n length: length,\n x: x$1,\n y: y$1,\n move: move$1,\n cx: cx,\n cy: cy,\n center: center,\n ax: ax,\n ay: ay,\n amove: amove,\n build: build\n};\n\nclass Text extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('text', node), attrs);\n this.dom.leading = new SVGNumber(1.3); // store leading value for rebuilding\n\n this._rebuild = true; // enable automatic updating of dy values\n\n this._build = false; // disable build mode for adding multiple lines\n } // Set / get leading\n\n\n leading(value) {\n // act as getter\n if (value == null) {\n return this.dom.leading;\n } // act as setter\n\n\n this.dom.leading = new SVGNumber(value);\n return this.rebuild();\n } // Rebuild appearance type\n\n\n rebuild(rebuild) {\n // store new rebuild flag if given\n if (typeof rebuild === 'boolean') {\n this._rebuild = rebuild;\n } // define position of all lines\n\n\n if (this._rebuild) {\n const self = this;\n let blankLineOffset = 0;\n const leading = this.dom.leading;\n this.each(function (i) {\n const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');\n const dy = leading * new SVGNumber(fontSize);\n\n if (this.dom.newLined) {\n this.attr('x', self.attr('x'));\n\n if (this.text() === '\\n') {\n blankLineOffset += dy;\n } else {\n this.attr('dy', i ? dy + blankLineOffset : 0);\n blankLineOffset = 0;\n }\n }\n });\n this.fire('rebuild');\n }\n\n return this;\n } // overwrite method from parent to set data properly\n\n\n setData(o) {\n this.dom = o;\n this.dom.leading = new SVGNumber(o.leading || 1.3);\n return this;\n } // Set the text content\n\n\n text(text) {\n // act as getter\n if (text === undefined) {\n const children = this.node.childNodes;\n let firstLine = 0;\n text = '';\n\n for (let i = 0, len = children.length; i < len; ++i) {\n // skip textPaths - they are no lines\n if (children[i].nodeName === 'textPath') {\n if (i === 0) firstLine = 1;\n continue;\n } // add newline if its not the first child and newLined is set to true\n\n\n if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {\n text += '\\n';\n } // add content of this node\n\n\n text += children[i].textContent;\n }\n\n return text;\n } // remove existing content\n\n\n this.clear().build(true);\n\n if (typeof text === 'function') {\n // call block\n text.call(this, this);\n } else {\n // store text and make sure text is not blank\n text = (text + '').split('\\n'); // build new lines\n\n for (let j = 0, jl = text.length; j < jl; j++) {\n this.newLine(text[j]);\n }\n } // disable build mode and rebuild lines\n\n\n return this.build(false).rebuild();\n }\n\n}\nextend(Text, textable);\nregisterMethods({\n Container: {\n // Create text element\n text: wrapWithAttrCheck(function (text = '') {\n return this.put(new Text()).text(text);\n }),\n // Create plain text element\n plain: wrapWithAttrCheck(function (text = '') {\n return this.put(new Text()).plain(text);\n })\n }\n});\nregister(Text, 'Text');\n\nclass Tspan extends Shape {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('tspan', node), attrs);\n this._build = false; // disable build mode for adding multiple lines\n } // Shortcut dx\n\n\n dx(dx) {\n return this.attr('dx', dx);\n } // Shortcut dy\n\n\n dy(dy) {\n return this.attr('dy', dy);\n } // Create new line\n\n\n newLine() {\n // mark new line\n this.dom.newLined = true; // fetch parent\n\n const text = this.parent(); // early return in case we are not in a text element\n\n if (!(text instanceof Text)) {\n return this;\n }\n\n const i = text.index(this);\n const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');\n const dy = text.dom.leading * new SVGNumber(fontSize); // apply new position\n\n return this.dy(i ? dy : 0).attr('x', text.x());\n } // Set text content\n\n\n text(text) {\n if (text == null) return this.node.textContent + (this.dom.newLined ? '\\n' : '');\n\n if (typeof text === 'function') {\n this.clear().build(true);\n text.call(this, this);\n this.build(false);\n } else {\n this.plain(text);\n }\n\n return this;\n }\n\n}\nextend(Tspan, textable);\nregisterMethods({\n Tspan: {\n tspan: wrapWithAttrCheck(function (text = '') {\n const tspan = new Tspan(); // clear if build mode is disabled\n\n if (!this._build) {\n this.clear();\n } // add new tspan\n\n\n return this.put(tspan).text(text);\n })\n },\n Text: {\n newLine: function (text = '') {\n return this.tspan(text).newLine();\n }\n }\n});\nregister(Tspan, 'Tspan');\n\nclass Circle extends Shape {\n constructor(node, attrs = node) {\n super(nodeOrNew('circle', node), attrs);\n }\n\n radius(r) {\n return this.attr('r', r);\n } // Radius x value\n\n\n rx(rx) {\n return this.attr('r', rx);\n } // Alias radius x value\n\n\n ry(ry) {\n return this.rx(ry);\n }\n\n size(size) {\n return this.radius(new SVGNumber(size).divide(2));\n }\n\n}\nextend(Circle, {\n x: x$3,\n y: y$3,\n cx: cx$1,\n cy: cy$1,\n width: width$2,\n height: height$2\n});\nregisterMethods({\n Container: {\n // Create circle element\n circle: wrapWithAttrCheck(function (size = 0) {\n return this.put(new Circle()).size(size).move(0, 0);\n })\n }\n});\nregister(Circle, 'Circle');\n\nclass ClipPath extends Container {\n constructor(node, attrs = node) {\n super(nodeOrNew('clipPath', node), attrs);\n } // Unclip all clipped elements and remove itself\n\n\n remove() {\n // unclip all targets\n this.targets().forEach(function (el) {\n el.unclip();\n }); // remove clipPath from parent\n\n return super.remove();\n }\n\n targets() {\n return baseFind('svg [clip-path*=\"' + this.id() + '\"]');\n }\n\n}\nregisterMethods({\n Container: {\n // Create clipping element\n clip: wrapWithAttrCheck(function () {\n return this.defs().put(new ClipPath());\n })\n },\n Element: {\n // Distribute clipPath to svg element\n clipper() {\n return this.reference('clip-path');\n },\n\n clipWith(element) {\n // use given clip or create a new one\n const clipper = element instanceof ClipPath ? element : this.parent().clip().add(element); // apply mask\n\n return this.attr('clip-path', 'url(\"#' + clipper.id() + '\")');\n },\n\n // Unclip element\n unclip() {\n return this.attr('clip-path', null);\n }\n\n }\n});\nregister(ClipPath, 'ClipPath');\n\nclass ForeignObject extends Element {\n constructor(node, attrs = node) {\n super(nodeOrNew('foreignObject', node), attrs);\n }\n\n}\nregisterMethods({\n Container: {\n foreignObject: wrapWithAttrCheck(function (width, height) {\n return this.put(new ForeignObject()).size(width, height);\n })\n }\n});\nregister(ForeignObject, 'ForeignObject');\n\nfunction dmove(dx, dy) {\n this.children().forEach((child, i) => {\n let bbox; // We have to wrap this for elements that dont have a bbox\n // e.g. title and other descriptive elements\n\n try {\n // Get the childs bbox\n bbox = child.bbox();\n } catch (e) {\n return;\n } // Get childs matrix\n\n\n const m = new Matrix(child); // Translate childs matrix by amount and\n // transform it back into parents space\n\n const matrix = m.translate(dx, dy).transform(m.inverse()); // Calculate new x and y from old box\n\n const p = new Point(bbox.x, bbox.y).transform(matrix); // Move element\n\n child.move(p.x, p.y);\n });\n return this;\n}\nfunction dx(dx) {\n return this.dmove(dx, 0);\n}\nfunction dy(dy) {\n return this.dmove(0, dy);\n}\nfunction height(height, box = this.bbox()) {\n if (height == null) return box.height;\n return this.size(box.width, height, box);\n}\nfunction move(x = 0, y = 0, box = this.bbox()) {\n const dx = x - box.x;\n const dy = y - box.y;\n return this.dmove(dx, dy);\n}\nfunction size(width, height, box = this.bbox()) {\n const p = proportionalSize(this, width, height, box);\n const scaleX = p.width / box.width;\n const scaleY = p.height / box.height;\n this.children().forEach((child, i) => {\n const o = new Point(box).transform(new Matrix(child).inverse());\n child.scale(scaleX, scaleY, o.x, o.y);\n });\n return this;\n}\nfunction width(width, box = this.bbox()) {\n if (width == null) return box.width;\n return this.size(width, box.height, box);\n}\nfunction x(x, box = this.bbox()) {\n if (x == null) return box.x;\n return this.move(x, box.y, box);\n}\nfunction y(y, box = this.bbox()) {\n if (y == null) return box.y;\n return this.move(box.x, y, box);\n}\n\nvar containerGeometry = {\n __proto__: null,\n dmove: dmove,\n dx: dx,\n dy: dy,\n height: height,\n move: move,\n size: size,\n width: width,\n x: x,\n y: y\n};\n\nclass G extends Container {\n constructor(node, attrs = node) {\n super(nodeOrNew('g', node), attrs);\n }\n\n}\nextend(G, containerGeometry);\nregisterMethods({\n Container: {\n // Create a group element\n group: wrapWithAttrCheck(function () {\n return this.put(new G());\n })\n }\n});\nregister(G, 'G');\n\nclass A extends Container {\n constructor(node, attrs = node) {\n super(nodeOrNew('a', node), attrs);\n } // Link target attribute\n\n\n target(target) {\n return this.attr('target', target);\n } // Link url\n\n\n to(url) {\n return this.attr('href', url, xlink);\n }\n\n}\nextend(A, containerGeometry);\nregisterMethods({\n Container: {\n // Create a hyperlink element\n link: wrapWithAttrCheck(function (url) {\n return this.put(new A()).to(url);\n })\n },\n Element: {\n unlink() {\n const link = this.linker();\n if (!link) return this;\n const parent = link.parent();\n\n if (!parent) {\n return this.remove();\n }\n\n const index = parent.index(link);\n parent.add(this, index);\n link.remove();\n return this;\n },\n\n linkTo(url) {\n // reuse old link if possible\n let link = this.linker();\n\n if (!link) {\n link = new A();\n this.wrap(link);\n }\n\n if (typeof url === 'function') {\n url.call(link, link);\n } else {\n link.to(url);\n }\n\n return this;\n },\n\n linker() {\n const link = this.parent();\n\n if (link && link.node.nodeName.toLowerCase() === 'a') {\n return link;\n }\n\n return null;\n }\n\n }\n});\nregister(A, 'A');\n\nclass Mask extends Container {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('mask', node), attrs);\n } // Unmask all masked elements and remove itself\n\n\n remove() {\n // unmask all targets\n this.targets().forEach(function (el) {\n el.unmask();\n }); // remove mask from parent\n\n return super.remove();\n }\n\n targets() {\n return baseFind('svg [mask*=\"' + this.id() + '\"]');\n }\n\n}\nregisterMethods({\n Container: {\n mask: wrapWithAttrCheck(function () {\n return this.defs().put(new Mask());\n })\n },\n Element: {\n // Distribute mask to svg element\n masker() {\n return this.reference('mask');\n },\n\n maskWith(element) {\n // use given mask or create a new one\n const masker = element instanceof Mask ? element : this.parent().mask().add(element); // apply mask\n\n return this.attr('mask', 'url(\"#' + masker.id() + '\")');\n },\n\n // Unmask element\n unmask() {\n return this.attr('mask', null);\n }\n\n }\n});\nregister(Mask, 'Mask');\n\nclass Stop extends Element {\n constructor(node, attrs = node) {\n super(nodeOrNew('stop', node), attrs);\n } // add color stops\n\n\n update(o) {\n if (typeof o === 'number' || o instanceof SVGNumber) {\n o = {\n offset: arguments[0],\n color: arguments[1],\n opacity: arguments[2]\n };\n } // set attributes\n\n\n if (o.opacity != null) this.attr('stop-opacity', o.opacity);\n if (o.color != null) this.attr('stop-color', o.color);\n if (o.offset != null) this.attr('offset', new SVGNumber(o.offset));\n return this;\n }\n\n}\nregisterMethods({\n Gradient: {\n // Add a color stop\n stop: function (offset, color, opacity) {\n return this.put(new Stop()).update(offset, color, opacity);\n }\n }\n});\nregister(Stop, 'Stop');\n\nfunction cssRule(selector, rule) {\n if (!selector) return '';\n if (!rule) return selector;\n let ret = selector + '{';\n\n for (const i in rule) {\n ret += unCamelCase(i) + ':' + rule[i] + ';';\n }\n\n ret += '}';\n return ret;\n}\n\nclass Style extends Element {\n constructor(node, attrs = node) {\n super(nodeOrNew('style', node), attrs);\n }\n\n addText(w = '') {\n this.node.textContent += w;\n return this;\n }\n\n font(name, src, params = {}) {\n return this.rule('@font-face', {\n fontFamily: name,\n src: src,\n ...params\n });\n }\n\n rule(selector, obj) {\n return this.addText(cssRule(selector, obj));\n }\n\n}\nregisterMethods('Dom', {\n style(selector, obj) {\n return this.put(new Style()).rule(selector, obj);\n },\n\n fontface(name, src, params) {\n return this.put(new Style()).font(name, src, params);\n }\n\n});\nregister(Style, 'Style');\n\nclass TextPath extends Text {\n // Initialize node\n constructor(node, attrs = node) {\n super(nodeOrNew('textPath', node), attrs);\n } // return the array of the path track element\n\n\n array() {\n const track = this.track();\n return track ? track.array() : null;\n } // Plot path if any\n\n\n plot(d) {\n const track = this.track();\n let pathArray = null;\n\n if (track) {\n pathArray = track.plot(d);\n }\n\n return d == null ? pathArray : this;\n } // Get the path element\n\n\n track() {\n return this.reference('href');\n }\n\n}\nregisterMethods({\n Container: {\n textPath: wrapWithAttrCheck(function (text, path) {\n // Convert text to instance if needed\n if (!(text instanceof Text)) {\n text = this.text(text);\n }\n\n return text.path(path);\n })\n },\n Text: {\n // Create path for text to run on\n path: wrapWithAttrCheck(function (track, importNodes = true) {\n const textPath = new TextPath(); // if track is a path, reuse it\n\n if (!(track instanceof Path)) {\n // create path element\n track = this.defs().path(track);\n } // link textPath to path and add content\n\n\n textPath.attr('href', '#' + track, xlink); // Transplant all nodes from text to textPath\n\n let node;\n\n if (importNodes) {\n while (node = this.node.firstChild) {\n textPath.node.appendChild(node);\n }\n } // add textPath element as child node and return textPath\n\n\n return this.put(textPath);\n }),\n\n // Get the textPath children\n textPath() {\n return this.findOne('textPath');\n }\n\n },\n Path: {\n // creates a textPath from this path\n text: wrapWithAttrCheck(function (text) {\n // Convert text to instance if needed\n if (!(text instanceof Text)) {\n text = new Text().addTo(this.parent()).text(text);\n } // Create textPath from text and path and return\n\n\n return text.path(this);\n }),\n\n targets() {\n return baseFind('svg textPath').filter(node => {\n return (node.attr('href') || '').includes(this.id());\n }); // Does not work in IE11. Use when IE support is dropped\n // return baseFind('svg textPath[*|href*=\"' + this.id() + '\"]')\n }\n\n }\n});\nTextPath.prototype.MorphArray = PathArray;\nregister(TextPath, 'TextPath');\n\nclass Use extends Shape {\n constructor(node, attrs = node) {\n super(nodeOrNew('use', node), attrs);\n } // Use element as a reference\n\n\n use(element, file) {\n // Set lined element\n return this.attr('href', (file || '') + '#' + element, xlink);\n }\n\n}\nregisterMethods({\n Container: {\n // Create a use element\n use: wrapWithAttrCheck(function (element, file) {\n return this.put(new Use()).use(element, file);\n })\n }\n});\nregister(Use, 'Use');\n\n/* Optional Modules */\nconst SVG = makeInstance;\nextend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox'));\nextend([Line, Polyline, Polygon, Path], getMethodsFor('marker'));\nextend(Text, getMethodsFor('Text'));\nextend(Path, getMethodsFor('Path'));\nextend(Defs, getMethodsFor('Defs'));\nextend([Text, Tspan], getMethodsFor('Tspan'));\nextend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'));\nextend(EventTarget, getMethodsFor('EventTarget'));\nextend(Dom, getMethodsFor('Dom'));\nextend(Element, getMethodsFor('Element'));\nextend(Shape, getMethodsFor('Shape'));\nextend([Container, Fragment], getMethodsFor('Container'));\nextend(Gradient, getMethodsFor('Gradient'));\nextend(Runner, getMethodsFor('Runner'));\nList.extend(getMethodNames());\nregisterMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray]);\nmakeMorphable();\n\nexport { A, Animator, SVGArray as Array, Box, Circle, ClipPath, Color, Container, Controller, Defs, Dom, Ease, Element, Ellipse, EventTarget, ForeignObject, Fragment, G, Gradient, Image, Line, List, Marker, Mask, Matrix, Morphable, NonMorphable, SVGNumber as Number, ObjectBag, PID, Path, PathArray, Pattern, Point, PointArray, Polygon, Polyline, Queue, Rect, Runner, SVG, Shape, Spring, Stop, Style, Svg, Symbol, Text, TextPath, Timeline, TransformBag, Tspan, Use, adopt, assignNewId, clearEvents, create, defaults, dispatch, easing, eid, extend, baseFind as find, getClass, getEventTarget, getEvents, getWindow, makeInstance, makeMorphable, mockAdopt, namespaces, nodeOrNew, off, on, parser, regex, register, registerMorphableType, registerWindow, restoreWindow, root, saveWindow, utils, windowEvents, withWindow, wrapWithAttrCheck };\n//# sourceMappingURL=svg.esm.js.map\n","import { SVG } from '@svgdotjs/svg.js';\n\nexport function getPaper() {\n // eslint-disable-next-line new-cap\n return SVG();\n}\n","import { getPaper } from './getPaper';\n\nexport function appendResiduesPosition(data, options = {}) {\n const residues = data.residues;\n const {\n leftRightBorders = 20,\n spaceBetweenResidues = 20,\n width = 600,\n } = options;\n\n let xPos = leftRightBorders;\n let xOld = xPos;\n\n let line = 0;\n // we create a temporary paper in order to get the width of the text blocs\n\n const paper = getPaper();\n\n for (let i = 0; i < residues.all.length; i++) {\n let residue = residues.all[i];\n let textWidth = getTextWidth(paper, residue.label, options);\n xPos += textWidth;\n if (xPos > width - leftRightBorders) {\n xOld = leftRightBorders;\n xPos = leftRightBorders + textWidth;\n line++;\n }\n setPaper(residue, xOld, xPos, line);\n xPos += spaceBetweenResidues;\n xOld = xPos;\n }\n\n residues.nbLines = line;\n}\n\nfunction setPaper(residue, xFrom, xTo, line) {\n residue.paper = {\n xFrom,\n xTo,\n line,\n usedSlots: [],\n topPosition: 0,\n bottomPosition: 0,\n };\n}\n\nfunction getTextWidth(paper, label, options = {}) {\n const { labelFontFamily = 'Verdana', labelSize = 12 } = options;\n let text = paper.text(label);\n text.font({\n family: labelFontFamily,\n size: labelSize,\n weight: 'bold',\n fill: '#888',\n });\n let textWidth = text.length() || text.bbox().width;\n text.remove();\n return textWidth;\n}\n","export function appendResults(data, analysisResult, options = {}) {\n const numberResidues = data.residues.residues.length;\n const { merge = {}, filter = {} } = options;\n\n let results = JSON.parse(JSON.stringify(analysisResult));\n results = results.filter((result) => !result.type.match(/^-B[0-9]$/));\n // we calculate all the lines based on the results\n for (let result of results) {\n let parts = result.type.split(/:|(?=[a-z])/); // we may have ':' but not mandatory\n if (parts.length === 2) {\n result.internal = true;\n if (parts[1].match(/^[abcd][1-9]/)) {\n [parts[0], parts[1]] = [parts[1], parts[0]];\n }\n result.to = getNumber(parts[0]) - 1;\n result.from = numberResidues - getNumber(parts[1]);\n } else {\n if (parts[0].match(/^[abcd][1-9]/)) {\n result.fromBegin = true;\n result.position = getNumber(parts[0]) - 1;\n }\n if (parts[0].match(/^[wxyz][1-9]/)) {\n result.fromEnd = true;\n result.position = numberResidues - 1 - getNumber(parts[0]);\n }\n }\n\n if (result.fromEnd) result.color = 'red';\n if (result.fromBegin) result.color = 'blue';\n if (result.internal) {\n switch (result.type.substring(0, 1)) {\n case 'a':\n result.color = 'green';\n break;\n case 'b':\n result.color = 'orange';\n break;\n case 'c':\n result.color = 'cyan';\n break;\n default:\n result.color = 'green';\n }\n }\n }\n\n if (merge.charge) {\n const unique = {};\n for (let result of results) {\n if (!unique[result.type]) {\n unique[result.type] = [];\n }\n unique[result.type].push(result);\n }\n results = [];\n for (let key in unique) {\n let current = unique[key][0];\n current.similarity = unique[key].reduce(\n (previous, item) => previous + item.similarity,\n 0,\n );\n current.similarity = current.similarity / unique[key].length;\n results.push(current);\n current.charge = '';\n }\n }\n\n for (let result of results) {\n if (result.similarity > 0.95) {\n result.textColor = 'black';\n } else if (result.similarity > 0.9) {\n result.textColor = '#333';\n } else if (result.similariy > 0.8) {\n result.textColor = '#666';\n } else {\n result.textColor = '#999';\n }\n }\n\n results = filterResults(results, filter);\n\n // sort by residue length\n results.sort((a, b) => a.length - b.length);\n data.results = results;\n}\n\nfunction getNumber(text) {\n return Number(text.replace(/^.([0-9]+).*$/, '$1'));\n}\n\nfunction filterResults(results, filter) {\n if (!filter) return;\n let {\n minRelativeQuantity = 0,\n minSimilarity = 0,\n minQuantity = 0,\n showInternals = true,\n } = filter;\n\n if (minRelativeQuantity) {\n minQuantity =\n Math.max(...results.map((entry) => entry.quantity)) * minRelativeQuantity;\n }\n\n if (minSimilarity) {\n results = results.filter(\n (result) => !result.similarity || result.similarity >= minSimilarity,\n );\n }\n if (minQuantity) {\n results = results.filter(\n (result) => !result.quantity || result.quantity >= minQuantity,\n );\n }\n\n if (!showInternals) {\n results = results.filter((result) => !result.internal);\n }\n return results;\n}\n","export function appendRows(data) {\n let allResidues = data.residues.all.sort((a, b) => a.line - b.line);\n data.rows = [];\n for (let residue of allResidues) {\n let line = residue.paper.line;\n if (!data.rows[line]) {\n data.rows[line] = {\n residues: [],\n };\n }\n data.rows[line].residues.push(residue);\n }\n for (let row of data.rows) {\n row.info = {};\n }\n}\n","/* eslint-disable no-loop-func */\n\n/**\n * For each row we calculate internals, label over and label under\n * @param {*} data\n */\nexport function appendRowsInformation(data) {\n for (let row of data.rows) {\n let filtered = row.residues.filter(\n (entry) => entry.fromBegin !== undefined,\n );\n if (filtered.length > 0) {\n row.info.firstResidue = filtered[0].fromBegin;\n row.info.lastResidue = filtered[filtered.length - 1].fromBegin;\n }\n row.internals = [];\n }\n\n for (let result of data.results) {\n if (result.internal) {\n let fromResidue = data.residues.residues[result.from];\n let from = fromResidue.fromBegin;\n let toResidue = data.residues.residues[result.to];\n let to = toResidue.fromBegin;\n for (let row of data.rows) {\n if (from <= row.info.lastResidue && to >= row.info.firstResidue) {\n result = JSON.parse(JSON.stringify(result));\n result.fromResidue = fromResidue;\n if (from < row.info.firstResidue) {\n result.firstIndex = true;\n } else {\n row.residues.forEach((residue, index) => {\n if (residue.fromBegin === from) {\n result.firstIndex = index;\n }\n });\n }\n result.toResidue = toResidue;\n if (to > row.info.lastResidue) {\n result.lastIndex = true;\n } else {\n row.residues.forEach((residue, index) => {\n if (residue.fromBegin === to) {\n result.lastIndex = index;\n }\n });\n }\n row.internals.push(result);\n }\n }\n } else if (\n result.position !== undefined &&\n data.residues.residues[result.position]\n ) {\n let residue = data.residues.residues[result.position];\n if (result.fromEnd) {\n residue.info.nbOver++;\n residue.results.end.push(result);\n }\n if (result.fromBegin) {\n residue.info.nbUnder++;\n residue.results.begin.push(result);\n }\n }\n }\n\n for (let row of data.rows) {\n let maxNbOver = 0;\n let maxNbUnder = 0;\n for (let residue of row.residues) {\n if (residue.info.nbOver > maxNbOver) maxNbOver = residue.info.nbOver;\n if (residue.info.nbUnder > maxNbUnder) maxNbUnder = residue.info.nbUnder;\n }\n row.info.nbOver = maxNbOver;\n row.info.nbUnder = maxNbUnder;\n }\n}\n","export function addCSS(paper) {\n let cssCode = `\n .highlight {\n stroke: yellow;\n stroke-width: 5px;\n fill: red;\n }\n .highlightText {\n fill: red;\n }\n `;\n let style = paper.element('style');\n style.words(cssCode);\n}\n","export function addScript(paper) {\n let scriptCode = ` // =from && residueNumber<=to) {\n child.setAttribute('class','highlightText');\n }\n }\n }\n }\n function mouseOut(evt) {\n let children=evt.target.parentNode.children;\n for (let child of children) {\n if (child.id === evt.target.id) {\n child.setAttribute('class','');\n }\n if (child.nodeName === 'text' && child.id.startsWith(\"residue\")) {\n child.setAttribute('class','');\n }\n }\n }\n // ]]>\n `;\n let script = paper.element('script');\n script.attr({\n type: 'application/ecmascript',\n });\n script.words(scriptCode);\n}\n","export function drawLabel(paper, result, x, y, options) {\n let label = result.type;\n let similarity = String(Math.round(result.similarity * 100));\n let charge = result.charge > 0 ? `+${result.charge}` : result.charge;\n let text = paper.plain(label);\n text.font({\n fill: result.textColor,\n family: options.labelFontFamily,\n weight: 'bold',\n size: options.labelSize,\n anchor: 'end',\n });\n text.attr({\n x,\n y,\n });\n let textWidth = 0;\n text = paper.plain(charge);\n text.font({\n fill: result.textColor,\n family: options.labelFontFamily,\n size: options.labelSize / 2,\n });\n text.attr({ x: x + textWidth, y: y - options.labelSize / 2 });\n text = paper.plain(similarity);\n text.font({\n fill: result.textColor,\n family: options.labelFontFamily,\n size: options.labelSize / 2,\n });\n text.attr({ x: x + textWidth, y });\n}\n","import { drawLabel } from './drawLabel';\n\nexport function drawInternals(paper, row, options) {\n let fromX = 0;\n let toX = 0;\n for (const internal of row.internals) {\n options.verticalPosition += options.spaceBetweenInternalLines;\n if (internal.firstIndex === true) {\n fromX = 0;\n } else {\n fromX =\n internal.fromResidue.paper.xFrom - options.spaceBetweenResidues / 2;\n }\n if (internal.lastIndex === true) {\n toX = options.width - 1;\n } else {\n toX = internal.toResidue.paper.xTo + options.spaceBetweenResidues / 2;\n }\n\n let y = options.verticalPosition;\n\n let drawLine = paper.line(fromX, y, toX, y);\n drawLine.attr({\n onmouseover: 'mouseOver(evt)',\n onmouseout: 'mouseOut(evt)',\n id: `line${internal.fromResidue.fromBegin}-${internal.toResidue.fromBegin}`,\n });\n drawLine.stroke({\n color: internal.color,\n width: options.strokeWidth,\n });\n\n let center =\n (fromX + toX + (internal.type.length * options.labelSize * 2) / 3) / 2;\n drawLabel(paper, internal, center, y - 2, options);\n }\n options.verticalPosition += options.spaceBetweenInternalLines * 2;\n}\n","export function drawReplacements(paper, data, options) {\n let replacements = data.residues.replacements;\n replacements = Object.keys(replacements).map((key) => {\n return { key, ...replacements[key] };\n });\n\n for (let replacement of replacements) {\n options.verticalPosition += options.spaceBetweenInternalLines;\n let text = paper.plain(`${replacement.label} = ${replacement.key}`);\n text.font({\n fill: 'darkviolet',\n family: options.labelFontFamily,\n weight: 'bold',\n size: 10,\n });\n text.attr({\n x: options.leftRightBorders,\n y: options.verticalPosition,\n });\n }\n options.verticalPosition += options.spaceBetweenInternalLines;\n}\n","import { drawLabel } from './drawLabel';\n\nexport function drawTerminals(paper, row, options) {\n for (let residue of row.residues) {\n if (!residue.results) continue;\n for (let result of residue.results.begin) {\n let line = paper.line(\n residue.paper.xTo + options.spaceBetweenResidues / 2,\n residue.paper.y,\n residue.paper.xTo + options.spaceBetweenResidues / 2,\n residue.paper.y - 8,\n );\n line.stroke({\n color: result.color,\n width: options.strokeWidth,\n linecap: 'round',\n });\n line = paper.line(\n residue.paper.xTo + options.spaceBetweenResidues / 2,\n residue.paper.y,\n residue.paper.xTo + options.spaceBetweenResidues / 2 - 5,\n residue.paper.y + 5,\n );\n line.stroke({\n color: result.color,\n width: options.strokeWidth,\n linecap: 'round',\n });\n drawLabel(\n paper,\n result,\n residue.paper.xTo + options.spaceBetweenResidues / 2,\n residue.paper.y +\n options.labelSize +\n 6 +\n residue.paper.bottomPosition * options.labelSize,\n options,\n );\n residue.paper.bottomPosition++;\n }\n for (let result of residue.results.end) {\n let line = paper.line(\n residue.paper.xTo +\n options.spaceBetweenResidues / 2 +\n options.strokeWidth,\n residue.paper.y,\n residue.paper.xTo +\n options.spaceBetweenResidues / 2 +\n options.strokeWidth,\n residue.paper.y - 8,\n );\n line.stroke({\n color: result.color,\n width: options.strokeWidth,\n linecap: 'round',\n });\n line = paper.line(\n residue.paper.xTo +\n options.spaceBetweenResidues / 2 +\n options.strokeWidth,\n residue.paper.y - 8,\n residue.paper.xTo +\n options.spaceBetweenResidues / 2 +\n 5 +\n options.strokeWidth,\n residue.paper.y - 13,\n );\n line.stroke({\n color: result.color,\n width: options.strokeWidth,\n linecap: 'round',\n });\n drawLabel(\n paper,\n result,\n residue.paper.xTo + options.spaceBetweenResidues,\n residue.paper.y - 17 - residue.paper.topPosition * options.labelSize,\n options,\n );\n residue.paper.topPosition++;\n }\n }\n}\n","import { drawTerminals } from './drawTerminals';\n\nexport function drawSequence(paper, row, options) {\n // need to plan some space for the OVER\n options.verticalPosition += row.info.nbOver * (options.labelSize + 1);\n\n for (const residue of row.residues) {\n residue.paper.y = options.verticalPosition;\n\n let text = paper.plain(residue.label);\n\n let textColor = residue.replaced\n ? 'darkviolet'\n : residue.kind === 'residue'\n ? '#555'\n : '#CCC';\n\n text.font({\n family: options.labelFontFamily,\n size: 12,\n weight: 'bold',\n fill: textColor,\n });\n text.attr({\n x: residue.paper.xFrom,\n y: residue.paper.y,\n });\n text.attr({ id: `residue-${residue.fromBegin}` });\n }\n\n drawTerminals(paper, row, options);\n\n // need to plan some space for the UNDER\n options.verticalPosition += row.info.nbUnder * (options.labelSize + 1);\n\n options.verticalPosition += options.spaceBetweenInternalLines * 2;\n}\n","import { appendInternals } from './appendInternals';\nimport { appendResidues } from './appendResidues';\nimport { appendResiduesPosition } from './appendResiduesPosition';\nimport { appendResults } from './appendResults';\nimport { appendRows } from './appendRows';\nimport { appendRowsInformation } from './appendRowsInformation';\nimport { addCSS } from './draw/addCSS';\nimport { addScript } from './draw/addScript';\nimport { drawInternals } from './draw/drawInternals';\nimport { drawReplacements } from './draw/drawReplacements';\nimport { drawSequence } from './draw/drawSequence';\nimport { getPaper } from './getPaper';\n\n/**\n *\n * @param {string} sequence\n * @param {array} analysisResults\n * @param {object} [options={}]\n * @param {number} [options.leftRightBorders=50]\n * @param {number} [options.width=600]\n * @param {number} [options.spaceBetweenResidues=30]\n * @param {number} [options.spaceBetweenInternalLines=12]\n * @param {number} [options.strokeWidth=2]\n * @param {string} [options.labelFontFamily='Verdana']\n * @param {number} [options.labelSize=8]\n * @param {number} [options.parsing] Sequence parsing options\n * @param {object} [options.merge={}]\n * @param {boolean} [options.merge.charge] Merge results if only differs by charge\n * @param {object} [options.filter={}] define some filters\n * @param {number} [options.filter.minSimilarity=0] minimal similarity\n * @param {number} [options.filter.minQuantity=0] minimal quantity\n * @param {number} [options.filter.minRelativeQuantity=0] minimal relative quantity. This value should be between 0 and 1 and supersede minQuantity.\n * @param {boolean} [options.filter.showInternals=true] show the internal fragments\n *\n */\nexport function sequenceSVG(sequence, analysisResults, options = {}) {\n const {\n width = 600,\n leftRightBorders = 50,\n spaceBetweenResidues = 30,\n spaceBetweenInternalLines = 12,\n strokeWidth = 2,\n labelFontFamily = 'Verdana',\n labelSize = 8,\n parsing,\n merge,\n filter,\n } = options;\n const drawOptions = {\n spaceBetweenResidues,\n leftRightBorders,\n spaceBetweenInternalLines,\n strokeWidth,\n labelSize,\n labelFontFamily,\n verticalPosition: spaceBetweenInternalLines,\n width,\n };\n\n let data = {};\n appendResidues(data, sequence, parsing);\n appendResults(data, analysisResults, {\n merge,\n filter,\n });\n appendResiduesPosition(data, {\n leftRightBorders,\n spaceBetweenResidues,\n labelFontFamily,\n labelSize,\n width,\n });\n appendRows(data);\n appendInternals(data);\n appendRowsInformation(data);\n\n // We start to create the SVG and create the paper\n const paper = getPaper();\n addCSS(paper);\n addScript(paper);\n\n for (let row of data.rows) {\n drawInternals(paper, row, drawOptions);\n drawSequence(paper, row, drawOptions);\n }\n drawReplacements(paper, data, drawOptions);\n paper.size(width, drawOptions.verticalPosition);\n\n let svg = paper.svg();\n paper.clear();\n return svg;\n}\n","import { Kind, MF, parse } from 'mf-parser';\n\nexport function preprocessEARanges(ranges, targetEA) {\n ranges = JSON.parse(JSON.stringify(ranges));\n if (typeof ranges === 'string') {\n // need to convert to ranges\n let parsed = parse(ranges.replace(/[\\r\\n\\t ]/g, ''));\n let newRanges = [];\n let current = {\n mf: '',\n min: 1,\n max: 1,\n };\n\n // example ClBr2(CH2)0-2NO\n // the idea is that has long as we don't have a range we don't really care\n // there is a limitation is that the range has to be first level of parenthesis\n let parenthesisLevel = 0;\n let currentMF = ''; // start at an atom first level or a parenthesis\n for (let item of parsed) {\n switch (item.kind) {\n case Kind.ATOM:\n if (parenthesisLevel === 0 && currentMF) {\n current.mf += currentMF;\n currentMF = '';\n }\n currentMF += item.value;\n break;\n case Kind.ISOTOPE:\n if (parenthesisLevel === 0 && currentMF) {\n current.mf += currentMF;\n currentMF = '';\n }\n currentMF += `[${item.value.isotope}${item.value.atom}]`;\n break;\n case Kind.MULTIPLIER:\n if (parenthesisLevel === 0 && currentMF) {\n current.mf += currentMF + item.value;\n currentMF = '';\n } else {\n currentMF += item.value;\n }\n break;\n case Kind.MULTIPLIER_RANGE:\n if (parenthesisLevel !== 0) {\n throw new Error('Range multiplier can only be at the first level');\n }\n newRanges.push({\n mf: currentMF,\n min: item.value.from,\n max: item.value.to,\n });\n currentMF = '';\n break;\n case Kind.OPENING_PARENTHESIS:\n parenthesisLevel++;\n currentMF += '(';\n break;\n case Kind.CLOSING_PARENTHESIS:\n parenthesisLevel--;\n currentMF += ')';\n break;\n default:\n throw Error(`can not preprocess ${ranges}`);\n }\n }\n if (currentMF) {\n current.mf += currentMF;\n }\n if (current.mf) {\n newRanges.push(current);\n }\n ranges = newRanges;\n }\n\n let possibilities = [];\n for (let i = 0; i < ranges.length; i++) {\n let range = ranges[i];\n let min = range.min === undefined ? 0 : range.min;\n let max = range.max === undefined ? 1 : range.max;\n let possibility = {\n mf: range.mf,\n minCount: min, // value defined by the user\n maxCount: max, // value defined by the user\n targetEA: targetEA[range.mf],\n mw: 0, // mw till this level\n aw: 0, // atomic weight (mass * currentCount)\n maxRatio: 0, // maximum ratio possible if all next elements are 0\n currentValue: 0,\n currentCount: min - 1,\n currentUnsaturation: 0,\n initialOrder: i,\n };\n possibilities.push(possibility);\n let info = new MF(range.mf).getInfo();\n possibility.mass = info.mass;\n possibility.unsaturation =\n range.unsaturation === undefined\n ? (info.unsaturation - 1) * 2\n : range.unsaturation;\n if (possibility.mf !== info.mf) possibility.isGroup = true;\n }\n possibilities = possibilities.filter(\n (r) => r.minCount !== 0 || r.maxCount !== 0,\n );\n\n possibilities.sort((a, b) => {\n return b.targetEA - a.targetEA;\n });\n\n return possibilities;\n}\n","import { atomSorter } from 'atom-sorter';\n\nimport { preprocessEARanges } from './preprocessEARanges';\n\n/**\n * Returns possible combinations\n * @param {object} [targetEA]\n * @param {object} [options={}]\n * @param {string} [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search\n * @param {number} [options.maxElementError=0.003]\n * @param {number} [options.maxTotalError=0.01]\n * @param {number} [options.minMW=0] minimal molecular weight\n * @param {number} [options.maxMW=+Infinity] maximal molecular weight\n * @param {object} [options.unsaturation={}]\n * @param {number} [options.unsaturation.min=-Infinity] Minimal unsaturation\n * @param {number} [options.unsaturation.max=+Infinity] Maximal unsaturation\n * @param {boolean} [options.unsaturation.onlyInteger=false] Integer unsaturation\n * @param {boolean} [options.unsaturation.onlyNonInteger=false] Non integer unsaturation\n * @return {Array