Add plots for statistical significance of measurements.

* This is useful when comparing benchmark runs.
* Inspired by [Statistics for Hackers](https://youtu.be/Iq9DzN6mvYA?t=800)

Change-Id: I4f2bfdf174263e1fa1542e115c0caabcafdb53b3
Test: `npm run-script dev`
diff --git a/development/plot-benchmarks/package-lock.json b/development/plot-benchmarks/package-lock.json
index 5ee7330..451e5aa 100644
--- a/development/plot-benchmarks/package-lock.json
+++ b/development/plot-benchmarks/package-lock.json
@@ -8,16 +8,17 @@
       "name": "plot-benchmarks",
       "version": "0.1.0",
       "dependencies": {
-        "chart.js": "^4.3.0"
+        "chart.js": "^4.3.1",
+        "comlink": "4.4.1"
       },
       "devDependencies": {
-        "@sveltejs/vite-plugin-svelte": "^2.4.1",
-        "@tsconfig/svelte": "^4.0.1",
-        "svelte": "^4.0.0",
-        "svelte-check": "^3.4.3",
-        "tslib": "^2.5.0",
-        "typescript": "^5.0.0",
-        "vite": "^4.3.9"
+        "@sveltejs/vite-plugin-svelte": "^2.4.3",
+        "@tsconfig/svelte": "^5.0.0",
+        "svelte": "^4.1.1",
+        "svelte-check": "^3.4.6",
+        "tslib": "^2.6.1",
+        "typescript": "^5.1.6",
+        "vite": "^4.4.7"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -34,9 +35,9 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
-      "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.16.tgz",
+      "integrity": "sha512-gCHjjQmA8L0soklKbLKA6pgsLk1byULuHe94lkZDzcO3/Ta+bbeewJioEn1Fr7kgy9NWNFy/C+MrBwC6I/WCug==",
       "cpu": [
         "arm"
       ],
@@ -50,9 +51,9 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
-      "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.16.tgz",
+      "integrity": "sha512-wsCqSPqLz+6Ov+OM4EthU43DyYVVyfn15S4j1bJzylDpc1r1jZFFfJQNfDuT8SlgwuqpmpJXK4uPlHGw6ve7eA==",
       "cpu": [
         "arm64"
       ],
@@ -66,9 +67,9 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
-      "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.16.tgz",
+      "integrity": "sha512-ldsTXolyA3eTQ1//4DS+E15xl0H/3DTRJaRL0/0PgkqDsI0fV/FlOtD+h0u/AUJr+eOTlZv4aC9gvfppo3C4sw==",
       "cpu": [
         "x64"
       ],
@@ -82,9 +83,9 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
-      "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.16.tgz",
+      "integrity": "sha512-aBxruWCII+OtluORR/KvisEw0ALuw/qDQWvkoosA+c/ngC/Kwk0lLaZ+B++LLS481/VdydB2u6tYpWxUfnLAIw==",
       "cpu": [
         "arm64"
       ],
@@ -98,9 +99,9 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
-      "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.16.tgz",
+      "integrity": "sha512-6w4Dbue280+rp3LnkgmriS1icOUZDyPuZo/9VsuMUTns7SYEiOaJ7Ca1cbhu9KVObAWfmdjUl4gwy9TIgiO5eA==",
       "cpu": [
         "x64"
       ],
@@ -114,9 +115,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
-      "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.16.tgz",
+      "integrity": "sha512-x35fCebhe9s979DGKbVAwXUOcTmCIE32AIqB9CB1GralMIvxdnMLAw5CnID17ipEw9/3MvDsusj/cspYt2ZLNQ==",
       "cpu": [
         "arm64"
       ],
@@ -130,9 +131,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
-      "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.16.tgz",
+      "integrity": "sha512-YM98f+PeNXF3GbxIJlUsj+McUWG1irguBHkszCIwfr3BXtXZsXo0vqybjUDFfu9a8Wr7uUD/YSmHib+EeGAFlg==",
       "cpu": [
         "x64"
       ],
@@ -146,9 +147,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
-      "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.16.tgz",
+      "integrity": "sha512-b5ABb+5Ha2C9JkeZXV+b+OruR1tJ33ePmv9ZwMeETSEKlmu/WJ45XTTG+l6a2KDsQtJJ66qo/hbSGBtk0XVLHw==",
       "cpu": [
         "arm"
       ],
@@ -162,9 +163,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
-      "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.16.tgz",
+      "integrity": "sha512-XIqhNUxJiuy+zsR77+H5Z2f7s4YRlriSJKtvx99nJuG5ATuJPjmZ9n0ANgnGlPCpXGSReFpgcJ7O3SMtzIFeiQ==",
       "cpu": [
         "arm64"
       ],
@@ -178,9 +179,9 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
-      "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.16.tgz",
+      "integrity": "sha512-no+pfEpwnRvIyH+txbBAWtjxPU9grslmTBfsmDndj7bnBmr55rOo/PfQmRfz7Qg9isswt1FP5hBbWb23fRWnow==",
       "cpu": [
         "ia32"
       ],
@@ -194,9 +195,9 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
-      "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.16.tgz",
+      "integrity": "sha512-Zbnczs9ZXjmo0oZSS0zbNlJbcwKXa/fcNhYQjahDs4Xg18UumpXG/lwM2lcSvHS3mTrRyCYZvJbmzYc4laRI1g==",
       "cpu": [
         "loong64"
       ],
@@ -210,9 +211,9 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
-      "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.16.tgz",
+      "integrity": "sha512-YMF7hih1HVR/hQVa/ot4UVffc5ZlrzEb3k2ip0nZr1w6fnYypll9td2qcoMLvd3o8j3y6EbJM3MyIcXIVzXvQQ==",
       "cpu": [
         "mips64el"
       ],
@@ -226,9 +227,9 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
-      "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.16.tgz",
+      "integrity": "sha512-Wkz++LZ29lDwUyTSEnzDaaP5OveOgTU69q9IyIw9WqLRxM4BjTBjz9un4G6TOvehWpf/J3gYVFN96TjGHrbcNQ==",
       "cpu": [
         "ppc64"
       ],
@@ -242,9 +243,9 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
-      "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.16.tgz",
+      "integrity": "sha512-LFMKZ30tk78/mUv1ygvIP+568bwf4oN6reG/uczXnz6SvFn4e2QUFpUpZY9iSJT6Qpgstrhef/nMykIXZtZWGQ==",
       "cpu": [
         "riscv64"
       ],
@@ -258,9 +259,9 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
-      "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.16.tgz",
+      "integrity": "sha512-3ZC0BgyYHYKfZo3AV2/66TD/I9tlSBaW7eWTEIkrQQKfJIifKMMttXl9FrAg+UT0SGYsCRLI35Gwdmm96vlOjg==",
       "cpu": [
         "s390x"
       ],
@@ -274,9 +275,9 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
-      "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.16.tgz",
+      "integrity": "sha512-xu86B3647DihHJHv/wx3NCz2Dg1gjQ8bbf9cVYZzWKY+gsvxYmn/lnVlqDRazObc3UMwoHpUhNYaZset4X8IPA==",
       "cpu": [
         "x64"
       ],
@@ -290,9 +291,9 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
-      "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.16.tgz",
+      "integrity": "sha512-uVAgpimx9Ffw3xowtg/7qQPwHFx94yCje+DoBx+LNm2ePDpQXHrzE+Sb0Si2VBObYz+LcRps15cq+95YM7gkUw==",
       "cpu": [
         "x64"
       ],
@@ -306,9 +307,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
-      "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.16.tgz",
+      "integrity": "sha512-6OjCQM9wf7z8/MBi6BOWaTL2AS/SZudsZtBziXMtNI8r/U41AxS9x7jn0ATOwVy08OotwkPqGRMkpPR2wcTJXA==",
       "cpu": [
         "x64"
       ],
@@ -322,9 +323,9 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
-      "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.16.tgz",
+      "integrity": "sha512-ZoNkruFYJp9d1LbUYCh8awgQDvB9uOMZqlQ+gGEZR7v6C+N6u7vPr86c+Chih8niBR81Q/bHOSKGBK3brJyvkQ==",
       "cpu": [
         "x64"
       ],
@@ -338,9 +339,9 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
-      "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.16.tgz",
+      "integrity": "sha512-+j4anzQ9hrs+iqO+/wa8UE6TVkKua1pXUb0XWFOx0FiAj6R9INJ+WE//1/Xo6FG1vB5EpH3ko+XcgwiDXTxcdw==",
       "cpu": [
         "arm64"
       ],
@@ -354,9 +355,9 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
-      "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.16.tgz",
+      "integrity": "sha512-5PFPmq3sSKTp9cT9dzvI67WNfRZGvEVctcZa1KGjDDu4n3H8k59Inbk0du1fz0KrAbKKNpJbdFXQMDUz7BG4rQ==",
       "cpu": [
         "ia32"
       ],
@@ -370,9 +371,9 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
-      "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.16.tgz",
+      "integrity": "sha512-sCIVrrtcWN5Ua7jYXNG1xD199IalrbfV2+0k/2Zf2OyV2FtnQnMgdzgpRAbi4AWlKJj1jkX+M+fEGPQj6BQB4w==",
       "cpu": [
         "x64"
       ],
@@ -480,31 +481,31 @@
       }
     },
     "node_modules/@sveltejs/vite-plugin-svelte": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.1.tgz",
-      "integrity": "sha512-bNNKvoRY89ptY7udeBSCmTdCVwkjmMcZ0j/z9J5MuedT8jPjq0zrknAo/jF1sToAza4NVaAgR9AkZoD9oJJmnA==",
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.3.tgz",
+      "integrity": "sha512-NY2h+B54KHZO3kDURTdARqthn6D4YSIebtfW75NvZ/fwyk4G+AJw3V/i0OBjyN4406Ht9yZcnNWMuRUFnDNNiA==",
       "dev": true,
       "dependencies": {
-        "@sveltejs/vite-plugin-svelte-inspector": "^1.0.2",
+        "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3",
         "debug": "^4.3.4",
         "deepmerge": "^4.3.1",
         "kleur": "^4.1.5",
-        "magic-string": "^0.30.0",
-        "svelte-hmr": "^0.15.1",
+        "magic-string": "^0.30.1",
+        "svelte-hmr": "^0.15.2",
         "vitefu": "^0.2.4"
       },
       "engines": {
         "node": "^14.18.0 || >= 16"
       },
       "peerDependencies": {
-        "svelte": "^3.54.0 || ^4.0.0-next.0",
+        "svelte": "^3.54.0 || ^4.0.0",
         "vite": "^4.0.0"
       }
     },
     "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.2.tgz",
-      "integrity": "sha512-Cy1dUMcYCnDVV/hPLXa43YZJ2jGKVW5rA0xuNL9dlmYhT0yoS1g7+FOFSRlgk0BXKk/Oc7grs+8BVA5Iz2fr8A==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz",
+      "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==",
       "dev": true,
       "dependencies": {
         "debug": "^4.3.4"
@@ -514,14 +515,14 @@
       },
       "peerDependencies": {
         "@sveltejs/vite-plugin-svelte": "^2.2.0",
-        "svelte": "^3.54.0 || ^4.0.0-next.0",
+        "svelte": "^3.54.0 || ^4.0.0",
         "vite": "^4.0.0"
       }
     },
     "node_modules/@tsconfig/svelte": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz",
-      "integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.0.tgz",
+      "integrity": "sha512-iu5BqFjU0+OcLTNQp7fHe6Bf6zdNeJ9IZjLZMqWLuGzVFm/xx+lm//Tf6koPyRmxo55/Snm6RRQ990n89cRKFw==",
       "dev": true
     },
     "node_modules/@types/estree": {
@@ -562,9 +563,9 @@
       }
     },
     "node_modules/aria-query": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.2.1.tgz",
-      "integrity": "sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==",
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+      "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
       "dev": true,
       "dependencies": {
         "dequal": "^2.0.3"
@@ -635,9 +636,9 @@
       }
     },
     "node_modules/chart.js": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
-      "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.1.tgz",
+      "integrity": "sha512-QHuISG3hTJ0ftq0I0f5jqH9mNVO9bqG8P+zvMOVslgKajQVvFEX7QAhYNJ+QEmw+uYTwo8XpTimaB82oeTWjxw==",
       "dependencies": {
         "@kurkle/color": "^0.3.0"
       },
@@ -685,6 +686,11 @@
         "periscopic": "^3.1.0"
       }
     },
+    "node_modules/comlink": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz",
+      "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q=="
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -755,9 +761,9 @@
       "dev": true
     },
     "node_modules/esbuild": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
-      "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.16.tgz",
+      "integrity": "sha512-1xLsOXrDqwdHxyXb/x/SOyg59jpf/SH7YMvU5RNSU7z3TInaASNJWNFJ6iRvLvLETZMasF3d1DdZLg7sgRimRQ==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
@@ -767,28 +773,28 @@
         "node": ">=12"
       },
       "optionalDependencies": {
-        "@esbuild/android-arm": "0.17.19",
-        "@esbuild/android-arm64": "0.17.19",
-        "@esbuild/android-x64": "0.17.19",
-        "@esbuild/darwin-arm64": "0.17.19",
-        "@esbuild/darwin-x64": "0.17.19",
-        "@esbuild/freebsd-arm64": "0.17.19",
-        "@esbuild/freebsd-x64": "0.17.19",
-        "@esbuild/linux-arm": "0.17.19",
-        "@esbuild/linux-arm64": "0.17.19",
-        "@esbuild/linux-ia32": "0.17.19",
-        "@esbuild/linux-loong64": "0.17.19",
-        "@esbuild/linux-mips64el": "0.17.19",
-        "@esbuild/linux-ppc64": "0.17.19",
-        "@esbuild/linux-riscv64": "0.17.19",
-        "@esbuild/linux-s390x": "0.17.19",
-        "@esbuild/linux-x64": "0.17.19",
-        "@esbuild/netbsd-x64": "0.17.19",
-        "@esbuild/openbsd-x64": "0.17.19",
-        "@esbuild/sunos-x64": "0.17.19",
-        "@esbuild/win32-arm64": "0.17.19",
-        "@esbuild/win32-ia32": "0.17.19",
-        "@esbuild/win32-x64": "0.17.19"
+        "@esbuild/android-arm": "0.18.16",
+        "@esbuild/android-arm64": "0.18.16",
+        "@esbuild/android-x64": "0.18.16",
+        "@esbuild/darwin-arm64": "0.18.16",
+        "@esbuild/darwin-x64": "0.18.16",
+        "@esbuild/freebsd-arm64": "0.18.16",
+        "@esbuild/freebsd-x64": "0.18.16",
+        "@esbuild/linux-arm": "0.18.16",
+        "@esbuild/linux-arm64": "0.18.16",
+        "@esbuild/linux-ia32": "0.18.16",
+        "@esbuild/linux-loong64": "0.18.16",
+        "@esbuild/linux-mips64el": "0.18.16",
+        "@esbuild/linux-ppc64": "0.18.16",
+        "@esbuild/linux-riscv64": "0.18.16",
+        "@esbuild/linux-s390x": "0.18.16",
+        "@esbuild/linux-x64": "0.18.16",
+        "@esbuild/netbsd-x64": "0.18.16",
+        "@esbuild/openbsd-x64": "0.18.16",
+        "@esbuild/sunos-x64": "0.18.16",
+        "@esbuild/win32-arm64": "0.18.16",
+        "@esbuild/win32-ia32": "0.18.16",
+        "@esbuild/win32-x64": "0.18.16"
       }
     },
     "node_modules/estree-walker": {
@@ -994,12 +1000,12 @@
       "dev": true
     },
     "node_modules/magic-string": {
-      "version": "0.30.0",
-      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
-      "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz",
+      "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==",
       "dev": true,
       "dependencies": {
-        "@jridgewell/sourcemap-codec": "^1.4.13"
+        "@jridgewell/sourcemap-codec": "^1.4.15"
       },
       "engines": {
         "node": ">=12"
@@ -1177,9 +1183,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.24",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
-      "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+      "version": "8.4.27",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
+      "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
       "dev": true,
       "funding": [
         {
@@ -1268,9 +1274,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "3.25.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
-      "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
+      "version": "3.26.3",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz",
+      "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==",
       "dev": true,
       "bin": {
         "rollup": "dist/bin/rollup"
@@ -1367,16 +1373,16 @@
       }
     },
     "node_modules/svelte": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.0.0.tgz",
-      "integrity": "sha512-+yCYu3AEUu9n91dnQNGIbnVp8EmNQtuF/YImW4+FTXRHard7NMo+yTsWzggPAbj3fUEJ1FBJLkql/jkp6YB5pg==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.1.1.tgz",
+      "integrity": "sha512-Enick5fPFISLoVy0MFK45cG+YlQt6upw8skEK9zzTpJnH1DqEv8xOZwizCGSo3Q6HZ7KrZTM0J18poF7aQg5zw==",
       "dev": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.1",
         "@jridgewell/sourcemap-codec": "^1.4.15",
         "@jridgewell/trace-mapping": "^0.3.18",
-        "acorn": "^8.8.2",
-        "aria-query": "^5.2.1",
+        "acorn": "^8.9.0",
+        "aria-query": "^5.3.0",
         "axobject-query": "^3.2.1",
         "code-red": "^1.0.3",
         "css-tree": "^2.3.1",
@@ -1391,9 +1397,9 @@
       }
     },
     "node_modules/svelte-check": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.3.tgz",
-      "integrity": "sha512-O07soQFY3X0VDt+bcGc6D5naz0cLtjwnmNP9JsEBPVyMemFEqUhL2OdLqvkl5H/u8Jwm50EiAU4BPRn5iin/kg==",
+      "version": "3.4.6",
+      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.6.tgz",
+      "integrity": "sha512-OBlY8866Zh1zHQTkBMPS6psPi7o2umTUyj6JWm4SacnIHXpWFm658pG32m3dKvKFL49V4ntAkfFHKo4ztH07og==",
       "dev": true,
       "dependencies": {
         "@jridgewell/trace-mapping": "^0.3.17",
@@ -1402,7 +1408,7 @@
         "import-fresh": "^3.2.1",
         "picocolors": "^1.0.0",
         "sade": "^1.7.4",
-        "svelte-preprocess": "^5.0.3",
+        "svelte-preprocess": "^5.0.4",
         "typescript": "^5.0.3"
       },
       "bin": {
@@ -1511,15 +1517,15 @@
       }
     },
     "node_modules/tslib": {
-      "version": "2.5.3",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
-      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==",
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
+      "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
       "dev": true
     },
     "node_modules/typescript": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
-      "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
+      "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",
@@ -1530,14 +1536,14 @@
       }
     },
     "node_modules/vite": {
-      "version": "4.3.9",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
-      "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
+      "version": "4.4.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz",
+      "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==",
       "dev": true,
       "dependencies": {
-        "esbuild": "^0.17.5",
-        "postcss": "^8.4.23",
-        "rollup": "^3.21.0"
+        "esbuild": "^0.18.10",
+        "postcss": "^8.4.26",
+        "rollup": "^3.25.2"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -1545,12 +1551,16 @@
       "engines": {
         "node": "^14.18.0 || >=16.0.0"
       },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
       "optionalDependencies": {
         "fsevents": "~2.3.2"
       },
       "peerDependencies": {
         "@types/node": ">= 14",
         "less": "*",
+        "lightningcss": "^1.21.0",
         "sass": "*",
         "stylus": "*",
         "sugarss": "*",
@@ -1563,6 +1573,9 @@
         "less": {
           "optional": true
         },
+        "lightningcss": {
+          "optional": true
+        },
         "sass": {
           "optional": true
         },
@@ -1610,156 +1623,156 @@
       }
     },
     "@esbuild/android-arm": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
-      "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.16.tgz",
+      "integrity": "sha512-gCHjjQmA8L0soklKbLKA6pgsLk1byULuHe94lkZDzcO3/Ta+bbeewJioEn1Fr7kgy9NWNFy/C+MrBwC6I/WCug==",
       "dev": true,
       "optional": true
     },
     "@esbuild/android-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
-      "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.16.tgz",
+      "integrity": "sha512-wsCqSPqLz+6Ov+OM4EthU43DyYVVyfn15S4j1bJzylDpc1r1jZFFfJQNfDuT8SlgwuqpmpJXK4uPlHGw6ve7eA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/android-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
-      "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.16.tgz",
+      "integrity": "sha512-ldsTXolyA3eTQ1//4DS+E15xl0H/3DTRJaRL0/0PgkqDsI0fV/FlOtD+h0u/AUJr+eOTlZv4aC9gvfppo3C4sw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/darwin-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
-      "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.16.tgz",
+      "integrity": "sha512-aBxruWCII+OtluORR/KvisEw0ALuw/qDQWvkoosA+c/ngC/Kwk0lLaZ+B++LLS481/VdydB2u6tYpWxUfnLAIw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/darwin-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
-      "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.16.tgz",
+      "integrity": "sha512-6w4Dbue280+rp3LnkgmriS1icOUZDyPuZo/9VsuMUTns7SYEiOaJ7Ca1cbhu9KVObAWfmdjUl4gwy9TIgiO5eA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/freebsd-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
-      "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.16.tgz",
+      "integrity": "sha512-x35fCebhe9s979DGKbVAwXUOcTmCIE32AIqB9CB1GralMIvxdnMLAw5CnID17ipEw9/3MvDsusj/cspYt2ZLNQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/freebsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
-      "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.16.tgz",
+      "integrity": "sha512-YM98f+PeNXF3GbxIJlUsj+McUWG1irguBHkszCIwfr3BXtXZsXo0vqybjUDFfu9a8Wr7uUD/YSmHib+EeGAFlg==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-arm": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
-      "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.16.tgz",
+      "integrity": "sha512-b5ABb+5Ha2C9JkeZXV+b+OruR1tJ33ePmv9ZwMeETSEKlmu/WJ45XTTG+l6a2KDsQtJJ66qo/hbSGBtk0XVLHw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
-      "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.16.tgz",
+      "integrity": "sha512-XIqhNUxJiuy+zsR77+H5Z2f7s4YRlriSJKtvx99nJuG5ATuJPjmZ9n0ANgnGlPCpXGSReFpgcJ7O3SMtzIFeiQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-ia32": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
-      "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.16.tgz",
+      "integrity": "sha512-no+pfEpwnRvIyH+txbBAWtjxPU9grslmTBfsmDndj7bnBmr55rOo/PfQmRfz7Qg9isswt1FP5hBbWb23fRWnow==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-loong64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
-      "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.16.tgz",
+      "integrity": "sha512-Zbnczs9ZXjmo0oZSS0zbNlJbcwKXa/fcNhYQjahDs4Xg18UumpXG/lwM2lcSvHS3mTrRyCYZvJbmzYc4laRI1g==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-mips64el": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
-      "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.16.tgz",
+      "integrity": "sha512-YMF7hih1HVR/hQVa/ot4UVffc5ZlrzEb3k2ip0nZr1w6fnYypll9td2qcoMLvd3o8j3y6EbJM3MyIcXIVzXvQQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-ppc64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
-      "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.16.tgz",
+      "integrity": "sha512-Wkz++LZ29lDwUyTSEnzDaaP5OveOgTU69q9IyIw9WqLRxM4BjTBjz9un4G6TOvehWpf/J3gYVFN96TjGHrbcNQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-riscv64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
-      "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.16.tgz",
+      "integrity": "sha512-LFMKZ30tk78/mUv1ygvIP+568bwf4oN6reG/uczXnz6SvFn4e2QUFpUpZY9iSJT6Qpgstrhef/nMykIXZtZWGQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-s390x": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
-      "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.16.tgz",
+      "integrity": "sha512-3ZC0BgyYHYKfZo3AV2/66TD/I9tlSBaW7eWTEIkrQQKfJIifKMMttXl9FrAg+UT0SGYsCRLI35Gwdmm96vlOjg==",
       "dev": true,
       "optional": true
     },
     "@esbuild/linux-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
-      "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.16.tgz",
+      "integrity": "sha512-xu86B3647DihHJHv/wx3NCz2Dg1gjQ8bbf9cVYZzWKY+gsvxYmn/lnVlqDRazObc3UMwoHpUhNYaZset4X8IPA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/netbsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
-      "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.16.tgz",
+      "integrity": "sha512-uVAgpimx9Ffw3xowtg/7qQPwHFx94yCje+DoBx+LNm2ePDpQXHrzE+Sb0Si2VBObYz+LcRps15cq+95YM7gkUw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/openbsd-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
-      "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.16.tgz",
+      "integrity": "sha512-6OjCQM9wf7z8/MBi6BOWaTL2AS/SZudsZtBziXMtNI8r/U41AxS9x7jn0ATOwVy08OotwkPqGRMkpPR2wcTJXA==",
       "dev": true,
       "optional": true
     },
     "@esbuild/sunos-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
-      "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.16.tgz",
+      "integrity": "sha512-ZoNkruFYJp9d1LbUYCh8awgQDvB9uOMZqlQ+gGEZR7v6C+N6u7vPr86c+Chih8niBR81Q/bHOSKGBK3brJyvkQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/win32-arm64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
-      "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.16.tgz",
+      "integrity": "sha512-+j4anzQ9hrs+iqO+/wa8UE6TVkKua1pXUb0XWFOx0FiAj6R9INJ+WE//1/Xo6FG1vB5EpH3ko+XcgwiDXTxcdw==",
       "dev": true,
       "optional": true
     },
     "@esbuild/win32-ia32": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
-      "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.16.tgz",
+      "integrity": "sha512-5PFPmq3sSKTp9cT9dzvI67WNfRZGvEVctcZa1KGjDDu4n3H8k59Inbk0du1fz0KrAbKKNpJbdFXQMDUz7BG4rQ==",
       "dev": true,
       "optional": true
     },
     "@esbuild/win32-x64": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
-      "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.16.tgz",
+      "integrity": "sha512-sCIVrrtcWN5Ua7jYXNG1xD199IalrbfV2+0k/2Zf2OyV2FtnQnMgdzgpRAbi4AWlKJj1jkX+M+fEGPQj6BQB4w==",
       "dev": true,
       "optional": true
     },
@@ -1842,33 +1855,33 @@
       }
     },
     "@sveltejs/vite-plugin-svelte": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.1.tgz",
-      "integrity": "sha512-bNNKvoRY89ptY7udeBSCmTdCVwkjmMcZ0j/z9J5MuedT8jPjq0zrknAo/jF1sToAza4NVaAgR9AkZoD9oJJmnA==",
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.3.tgz",
+      "integrity": "sha512-NY2h+B54KHZO3kDURTdARqthn6D4YSIebtfW75NvZ/fwyk4G+AJw3V/i0OBjyN4406Ht9yZcnNWMuRUFnDNNiA==",
       "dev": true,
       "requires": {
-        "@sveltejs/vite-plugin-svelte-inspector": "^1.0.2",
+        "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3",
         "debug": "^4.3.4",
         "deepmerge": "^4.3.1",
         "kleur": "^4.1.5",
-        "magic-string": "^0.30.0",
-        "svelte-hmr": "^0.15.1",
+        "magic-string": "^0.30.1",
+        "svelte-hmr": "^0.15.2",
         "vitefu": "^0.2.4"
       }
     },
     "@sveltejs/vite-plugin-svelte-inspector": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.2.tgz",
-      "integrity": "sha512-Cy1dUMcYCnDVV/hPLXa43YZJ2jGKVW5rA0xuNL9dlmYhT0yoS1g7+FOFSRlgk0BXKk/Oc7grs+8BVA5Iz2fr8A==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz",
+      "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==",
       "dev": true,
       "requires": {
         "debug": "^4.3.4"
       }
     },
     "@tsconfig/svelte": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz",
-      "integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.0.tgz",
+      "integrity": "sha512-iu5BqFjU0+OcLTNQp7fHe6Bf6zdNeJ9IZjLZMqWLuGzVFm/xx+lm//Tf6koPyRmxo55/Snm6RRQ990n89cRKFw==",
       "dev": true
     },
     "@types/estree": {
@@ -1900,9 +1913,9 @@
       }
     },
     "aria-query": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.2.1.tgz",
-      "integrity": "sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==",
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+      "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
       "dev": true,
       "requires": {
         "dequal": "^2.0.3"
@@ -1961,9 +1974,9 @@
       "dev": true
     },
     "chart.js": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
-      "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.1.tgz",
+      "integrity": "sha512-QHuISG3hTJ0ftq0I0f5jqH9mNVO9bqG8P+zvMOVslgKajQVvFEX7QAhYNJ+QEmw+uYTwo8XpTimaB82oeTWjxw==",
       "requires": {
         "@kurkle/color": "^0.3.0"
       }
@@ -1997,6 +2010,11 @@
         "periscopic": "^3.1.0"
       }
     },
+    "comlink": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz",
+      "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q=="
+    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2047,33 +2065,33 @@
       "dev": true
     },
     "esbuild": {
-      "version": "0.17.19",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
-      "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+      "version": "0.18.16",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.16.tgz",
+      "integrity": "sha512-1xLsOXrDqwdHxyXb/x/SOyg59jpf/SH7YMvU5RNSU7z3TInaASNJWNFJ6iRvLvLETZMasF3d1DdZLg7sgRimRQ==",
       "dev": true,
       "requires": {
-        "@esbuild/android-arm": "0.17.19",
-        "@esbuild/android-arm64": "0.17.19",
-        "@esbuild/android-x64": "0.17.19",
-        "@esbuild/darwin-arm64": "0.17.19",
-        "@esbuild/darwin-x64": "0.17.19",
-        "@esbuild/freebsd-arm64": "0.17.19",
-        "@esbuild/freebsd-x64": "0.17.19",
-        "@esbuild/linux-arm": "0.17.19",
-        "@esbuild/linux-arm64": "0.17.19",
-        "@esbuild/linux-ia32": "0.17.19",
-        "@esbuild/linux-loong64": "0.17.19",
-        "@esbuild/linux-mips64el": "0.17.19",
-        "@esbuild/linux-ppc64": "0.17.19",
-        "@esbuild/linux-riscv64": "0.17.19",
-        "@esbuild/linux-s390x": "0.17.19",
-        "@esbuild/linux-x64": "0.17.19",
-        "@esbuild/netbsd-x64": "0.17.19",
-        "@esbuild/openbsd-x64": "0.17.19",
-        "@esbuild/sunos-x64": "0.17.19",
-        "@esbuild/win32-arm64": "0.17.19",
-        "@esbuild/win32-ia32": "0.17.19",
-        "@esbuild/win32-x64": "0.17.19"
+        "@esbuild/android-arm": "0.18.16",
+        "@esbuild/android-arm64": "0.18.16",
+        "@esbuild/android-x64": "0.18.16",
+        "@esbuild/darwin-arm64": "0.18.16",
+        "@esbuild/darwin-x64": "0.18.16",
+        "@esbuild/freebsd-arm64": "0.18.16",
+        "@esbuild/freebsd-x64": "0.18.16",
+        "@esbuild/linux-arm": "0.18.16",
+        "@esbuild/linux-arm64": "0.18.16",
+        "@esbuild/linux-ia32": "0.18.16",
+        "@esbuild/linux-loong64": "0.18.16",
+        "@esbuild/linux-mips64el": "0.18.16",
+        "@esbuild/linux-ppc64": "0.18.16",
+        "@esbuild/linux-riscv64": "0.18.16",
+        "@esbuild/linux-s390x": "0.18.16",
+        "@esbuild/linux-x64": "0.18.16",
+        "@esbuild/netbsd-x64": "0.18.16",
+        "@esbuild/openbsd-x64": "0.18.16",
+        "@esbuild/sunos-x64": "0.18.16",
+        "@esbuild/win32-arm64": "0.18.16",
+        "@esbuild/win32-ia32": "0.18.16",
+        "@esbuild/win32-x64": "0.18.16"
       }
     },
     "estree-walker": {
@@ -2236,12 +2254,12 @@
       "dev": true
     },
     "magic-string": {
-      "version": "0.30.0",
-      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
-      "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+      "version": "0.30.1",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz",
+      "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==",
       "dev": true,
       "requires": {
-        "@jridgewell/sourcemap-codec": "^1.4.13"
+        "@jridgewell/sourcemap-codec": "^1.4.15"
       }
     },
     "mdn-data": {
@@ -2368,9 +2386,9 @@
       "dev": true
     },
     "postcss": {
-      "version": "8.4.24",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
-      "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+      "version": "8.4.27",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
+      "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
       "dev": true,
       "requires": {
         "nanoid": "^3.3.6",
@@ -2415,9 +2433,9 @@
       }
     },
     "rollup": {
-      "version": "3.25.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
-      "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
+      "version": "3.26.3",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz",
+      "integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==",
       "dev": true,
       "requires": {
         "fsevents": "~2.3.2"
@@ -2481,16 +2499,16 @@
       }
     },
     "svelte": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.0.0.tgz",
-      "integrity": "sha512-+yCYu3AEUu9n91dnQNGIbnVp8EmNQtuF/YImW4+FTXRHard7NMo+yTsWzggPAbj3fUEJ1FBJLkql/jkp6YB5pg==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.1.1.tgz",
+      "integrity": "sha512-Enick5fPFISLoVy0MFK45cG+YlQt6upw8skEK9zzTpJnH1DqEv8xOZwizCGSo3Q6HZ7KrZTM0J18poF7aQg5zw==",
       "dev": true,
       "requires": {
         "@ampproject/remapping": "^2.2.1",
         "@jridgewell/sourcemap-codec": "^1.4.15",
         "@jridgewell/trace-mapping": "^0.3.18",
-        "acorn": "^8.8.2",
-        "aria-query": "^5.2.1",
+        "acorn": "^8.9.0",
+        "aria-query": "^5.3.0",
         "axobject-query": "^3.2.1",
         "code-red": "^1.0.3",
         "css-tree": "^2.3.1",
@@ -2502,9 +2520,9 @@
       }
     },
     "svelte-check": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.3.tgz",
-      "integrity": "sha512-O07soQFY3X0VDt+bcGc6D5naz0cLtjwnmNP9JsEBPVyMemFEqUhL2OdLqvkl5H/u8Jwm50EiAU4BPRn5iin/kg==",
+      "version": "3.4.6",
+      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.6.tgz",
+      "integrity": "sha512-OBlY8866Zh1zHQTkBMPS6psPi7o2umTUyj6JWm4SacnIHXpWFm658pG32m3dKvKFL49V4ntAkfFHKo4ztH07og==",
       "dev": true,
       "requires": {
         "@jridgewell/trace-mapping": "^0.3.17",
@@ -2513,7 +2531,7 @@
         "import-fresh": "^3.2.1",
         "picocolors": "^1.0.0",
         "sade": "^1.7.4",
-        "svelte-preprocess": "^5.0.3",
+        "svelte-preprocess": "^5.0.4",
         "typescript": "^5.0.3"
       }
     },
@@ -2558,27 +2576,27 @@
       }
     },
     "tslib": {
-      "version": "2.5.3",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
-      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==",
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
+      "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
       "dev": true
     },
     "typescript": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
-      "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
+      "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
       "dev": true
     },
     "vite": {
-      "version": "4.3.9",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
-      "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
+      "version": "4.4.7",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz",
+      "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==",
       "dev": true,
       "requires": {
-        "esbuild": "^0.17.5",
+        "esbuild": "^0.18.10",
         "fsevents": "~2.3.2",
-        "postcss": "^8.4.23",
-        "rollup": "^3.21.0"
+        "postcss": "^8.4.26",
+        "rollup": "^3.25.2"
       }
     },
     "vitefu": {
diff --git a/development/plot-benchmarks/package.json b/development/plot-benchmarks/package.json
index 28ee731..d65f5a2 100644
--- a/development/plot-benchmarks/package.json
+++ b/development/plot-benchmarks/package.json
@@ -10,15 +10,16 @@
     "check": "svelte-check --tsconfig ./tsconfig.json"
   },
   "devDependencies": {
-    "@sveltejs/vite-plugin-svelte": "^2.4.1",
-    "@tsconfig/svelte": "^4.0.1",
-    "svelte": "^4.0.0",
-    "svelte-check": "^3.4.3",
-    "tslib": "^2.5.0",
-    "typescript": "^5.0.0",
-    "vite": "^4.3.9"
+    "@sveltejs/vite-plugin-svelte": "^2.4.3",
+    "@tsconfig/svelte": "^5.0.0",
+    "svelte": "^4.1.1",
+    "svelte-check": "^3.4.6",
+    "tslib": "^2.6.1",
+    "typescript": "^5.1.6",
+    "vite": "^4.4.7"
   },
   "dependencies": {
-    "chart.js": "^4.3.0"
+    "chart.js": "^4.3.1",
+    "comlink": "4.4.1"
   }
 }
\ No newline at end of file
diff --git a/development/plot-benchmarks/src/lib/App.svelte b/development/plot-benchmarks/src/lib/App.svelte
index ffa3e57..f1ace88 100644
--- a/development/plot-benchmarks/src/lib/App.svelte
+++ b/development/plot-benchmarks/src/lib/App.svelte
@@ -3,9 +3,17 @@
   import { writable } from "svelte/store";
   import type { FileMetadata } from "../types/files.js";
   import Session from "./Session.svelte";
+  import { wrap } from "comlink";
+  import { StatService } from "../workers/service.js";
 
   // Stores
   let entries: Writable<FileMetadata[]> = writable([]);
+  const url = new URL("../workers/worker.ts", import.meta.url);
+  const service = wrap<StatService>(
+    new Worker(url, {
+      type: "module",
+    })
+  );
 
   function onFilesChanged(event) {
     const detail: FileMetadata[] = event.detail;
@@ -22,5 +30,5 @@
 </details>
 
 <div class="container">
-  <Session fileEntries={$entries} on:entries={onFilesChanged} />
+  <Session fileEntries={$entries} {service} on:entries={onFilesChanged} />
 </div>
diff --git a/development/plot-benchmarks/src/lib/Chart.svelte b/development/plot-benchmarks/src/lib/Chart.svelte
index 7a063a8..9a224e2 100644
--- a/development/plot-benchmarks/src/lib/Chart.svelte
+++ b/development/plot-benchmarks/src/lib/Chart.svelte
@@ -10,6 +10,7 @@
 
   export let data: Data;
   export let chartType: ChartType = "line";
+  export let isExperimental: boolean = false;
 
   $: {
     if ($chart) {
@@ -27,7 +28,9 @@
   onMount(() => {
     const onUpdate = (chart: Chart) => {
       $chart = chart;
-      $items = chart.options.plugins.legend.labels.generateLabels(chart);
+      // Bad typings.
+      const legend = chart.options.plugins.legend as any;
+      $items = legend.labels.generateLabels(chart);
     };
     const plugins = {
       legend: {
@@ -65,7 +68,14 @@

     </button>
   </div>
-  <canvas id="chart" class="chart" bind:this={element} />
+  <canvas class="chart" bind:this={element} />
+  {#if isExperimental}
+    <footer class="slim">
+      <section class="experimental">
+        <kbd>Experimental</kbd>
+      </section>
+    </footer>
+  {/if}
 </article>
 
 {#if $items}
@@ -89,4 +99,17 @@
     border: none;
     padding: 5px;
   }
+
+  .slim {
+    margin-bottom: 0px;
+    padding: 0;
+  }
+
+  .experimental {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: nowrap;
+    justify-content: center;
+    margin-bottom: 0px;
+  }
 </style>
diff --git a/development/plot-benchmarks/src/lib/Dataset.svelte b/development/plot-benchmarks/src/lib/Dataset.svelte
index a19eff0..6e81cc0 100644
--- a/development/plot-benchmarks/src/lib/Dataset.svelte
+++ b/development/plot-benchmarks/src/lib/Dataset.svelte
@@ -1,14 +1,23 @@
 <script lang="ts">
   import { createEventDispatcher } from "svelte";
   import { Session, type IndexedWrapper } from "../wrappers/session.js";
-  import type { SelectionEvent, Selection } from "../types/events.js";
+  import type {
+    SelectionEvent,
+    Selection,
+    StatEvent,
+    StatInfo,
+    StatType,
+  } from "../types/events.js";
 
   export let name: string;
   export let datasetGroup: IndexedWrapper[];
 
+  // Dispatchers
+  let selectionDispatcher = createEventDispatcher<SelectionEvent>();
+  let statDispatcher = createEventDispatcher<StatEvent>();
   // State
-  let dispatcher = createEventDispatcher<SelectionEvent>();
   let selected: boolean = true;
+  let compute: boolean = false;
   let sources: Set<string>;
   let sampledMetrics: Set<string>;
   let metrics: Set<string>;
@@ -22,7 +31,21 @@
       name: name,
       enabled: selected,
     };
-    dispatcher("selections", [selection]);
+    selectionDispatcher("selections", [selection]);
+  };
+
+  let stat = function (type: StatType) {
+    return function (event: Event) {
+      event.stopPropagation();
+      const target = event.target as HTMLInputElement;
+      compute = target.checked;
+      const stat: StatInfo = {
+        name: name,
+        type: type,
+        enabled: compute
+      };
+      statDispatcher("info", [stat]);
+    };
   };
 
   $: {
@@ -45,16 +68,32 @@
   <hgroup>
     <div class="section">
       <span class="item">{name}</span>
-      <fieldset class="item">
-        <label for="switch">
-          <input
-            type="checkbox"
-            role="switch"
-            checked={selected}
-            on:change={selection}
-          />
-        </label>
-      </fieldset>
+      <div class="item actions">
+        <fieldset>
+          <label for="switch">
+            Show
+            <input
+              type="checkbox"
+              role="switch"
+              checked={selected}
+              on:change={selection}
+            />
+          </label>
+        </fieldset>
+        {#if sources.size > 1}
+          <fieldset>
+            <label for="switch">
+              P
+              <input
+                type="checkbox"
+                role="switch"
+                checked={compute}
+                on:change={stat("p")}
+              />
+            </label>
+          </fieldset>
+        {/if}
+      </div>
     </div>
     <div class="details">
       <div class="sources">
@@ -103,4 +142,14 @@
   .section .item {
     margin: 0px 10px;
   }
+
+  .actions {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-end;
+  }
+
+  .actions fieldset {
+    margin-left: 5px;
+  }
 </style>
diff --git a/development/plot-benchmarks/src/lib/Group.svelte b/development/plot-benchmarks/src/lib/Group.svelte
index e538e68..6dcee76 100644
--- a/development/plot-benchmarks/src/lib/Group.svelte
+++ b/development/plot-benchmarks/src/lib/Group.svelte
@@ -1,18 +1,27 @@
 <script lang="ts">
   import { createEventDispatcher } from "svelte";
-  import type { Selection, SelectionEvent } from "../types/events.js";
+  import type {
+    Selection,
+    SelectionEvent,
+    StatEvent,
+    StatInfo,
+  } from "../types/events.js";
   import { Session, type IndexedWrapper } from "../wrappers/session.js";
   import Dataset from "./Dataset.svelte";
 
   export let className: string;
   export let datasetGroup: IndexedWrapper[];
 
-  let dispatcher = createEventDispatcher<SelectionEvent>();
+  let selectionDispatcher = createEventDispatcher<SelectionEvent>();
+  let statDispatcher = createEventDispatcher<StatEvent>();
   let datasetNames: Set<string>;
 
+  // Forward events.
   let selection = function (event: CustomEvent<Selection[]>) {
-    // Forward events.
-    dispatcher("selections", event.detail);
+    selectionDispatcher("selections", event.detail);
+  };
+  let stat = function (event: CustomEvent<StatInfo[]>) {
+    statDispatcher("info", event.detail);
   };
 
   $: {
@@ -24,7 +33,7 @@
   <summary>{className}</summary>
   <div class="details">
     {#each datasetNames as name (name)}
-      <Dataset {datasetGroup} {name} on:selections={selection} />
+      <Dataset {datasetGroup} {name} on:selections={selection} on:info={stat} />
     {/each}
   </div>
 </details>
diff --git a/development/plot-benchmarks/src/lib/Session.svelte b/development/plot-benchmarks/src/lib/Session.svelte
index b751f67..3a0a062 100644
--- a/development/plot-benchmarks/src/lib/Session.svelte
+++ b/development/plot-benchmarks/src/lib/Session.svelte
@@ -1,19 +1,31 @@
 <script lang="ts">
   import { createEventDispatcher } from "svelte";
-  import { writable, type Writable } from "svelte/store";
+  import {
+    writable,
+    type Readable,
+    type Writable,
+    derived,
+  } from "svelte/store";
   import { readBenchmarks } from "../files.js";
   import { ChartDataTransforms } from "../transforms/data-transforms.js";
   import { Transforms } from "../transforms/metric-transforms.js";
   import { STANDARD_MAPPER } from "../transforms/standard-mappers.js";
   import type { Data, Series } from "../types/chart.js";
   import type { Metrics } from "../types/data.js";
-  import type { FileMetadataEvent, Selection } from "../types/events.js";
+  import type {
+    FileMetadataEvent,
+    Selection,
+    StatInfo,
+  } from "../types/events.js";
   import type { FileMetadata } from "../types/files.js";
   import { Session, type IndexedWrapper } from "../wrappers/session.js";
   import Chart from "./Chart.svelte";
   import Group from "./Group.svelte";
+  import type { StatService } from "../workers/service.js";
+  import type { Remote } from "comlink";
 
   export let fileEntries: FileMetadata[];
+  export let service: Remote<StatService>;
 
   // State
   let eventDispatcher = createEventDispatcher<FileMetadataEvent>();
@@ -23,13 +35,23 @@
   let chartData: Data;
   let classGroups: Record<string, IndexedWrapper[]>;
   let size: number;
+  let activeSeries: Promise<Series[]>;
 
   // Stores
   let activeDragDrop: Writable<boolean> = writable(false);
   let suppressed: Writable<Set<string>> = writable(new Set());
+  let activeStats: Writable<StatInfo[]> = writable([]);
+  let active: Readable<Set<string>> = derived(activeStats, ($activeStats) => {
+    const datasets = [];
+    for (let i = 0; i < $activeStats.length; i += 1) {
+      const activeStat = $activeStats[i];
+      datasets.push(activeStat.name);
+    }
+    return new Set(datasets);
+  });
 
   // Events
-  let handler = function (event: CustomEvent<Selection[]>) {
+  let selectionHandler = function (event: CustomEvent<Selection[]>) {
     const selections: Selection[] = event.detail;
     for (let i = 0; i < selections.length; i += 1) {
       const selection = selections[i];
@@ -42,9 +64,28 @@
     $suppressed = $suppressed;
   };
 
+  let statHandler = function (event: CustomEvent<StatInfo[]>) {
+    const statistics = event.detail;
+    for (let i = 0; i < statistics.length; i += 1) {
+      const statInfo = statistics[i];
+      if (!statInfo.enabled) {
+        const index = $activeStats.findIndex(
+          (entry) => entry.name == statInfo.name && entry.type == statInfo.type
+        );
+        if (index >= 0) {
+          $activeStats.splice(index, 1);
+        }
+      } else {
+        $activeStats.push(statInfo);
+      }
+      $activeStats = $activeStats;
+    }
+  };
+
   $: {
     session = new Session(fileEntries);
     metrics = Transforms.buildMetrics(session, $suppressed);
+    activeSeries = service.pSeries(metrics, $active);
     series = ChartDataTransforms.mapToSeries(metrics, STANDARD_MAPPER);
     chartData = ChartDataTransforms.mapToDataset(series);
     classGroups = session.classGroups;
@@ -115,13 +156,26 @@
   >
     <h5>Benchmarks</h5>
     {#each Object.entries(classGroups) as [className, wrappers]}
-      <Group {className} datasetGroup={wrappers} on:selections={handler} />
+      <Group
+        {className}
+        datasetGroup={wrappers}
+        on:selections={selectionHandler}
+        on:info={statHandler}
+      />
     {/each}
   </article>
 
   {#if series.length > 0}
     <Chart data={chartData} />
   {/if}
+
+  {#await activeSeries}
+    <article aria-busy="true" />
+  {:then chartData}
+    {#if chartData.length > 0}
+      <Chart data={ChartDataTransforms.mapToDataset(chartData)} isExperimental={true} />
+    {/if}
+  {/await}
 {/if}
 
 <style>
diff --git a/development/plot-benchmarks/src/transforms/metric-transforms.ts b/development/plot-benchmarks/src/transforms/metric-transforms.ts
index e35da44..571b4ae 100644
--- a/development/plot-benchmarks/src/transforms/metric-transforms.ts
+++ b/development/plot-benchmarks/src/transforms/metric-transforms.ts
@@ -19,7 +19,6 @@
         const wrapper = wrappers[j];
         const datasetName = wrappers[j].value.datasetName();
         if (suppressed.has(datasetName)) {
-          console.log(`Skipping suppressed dataset name ${datasetName}`, session);
           continue;
         }
         const source = wrapper.source;
diff --git a/development/plot-benchmarks/src/transforms/standard-mappers.ts b/development/plot-benchmarks/src/transforms/standard-mappers.ts
index 8b928cd..6b3646a 100644
--- a/development/plot-benchmarks/src/transforms/standard-mappers.ts
+++ b/development/plot-benchmarks/src/transforms/standard-mappers.ts
@@ -10,7 +10,7 @@
   for (let i = 0; i < entries.length; i += 1) {
     const [source, chartData] = entries[i];
     const label = labelFor(metric, source);
-    const points = histogramPoints(chartData.values);
+    const [points, _, __] = histogramPoints(chartData.values);
     series.push({
       label: label,
       type: "line",
@@ -43,20 +43,51 @@
   return series;
 }
 
-function histogramPoints(runs: number[][], buckets: number = 10): Point[] {
+export function histogramPoints(
+  runs: number[][],
+  buckets: number = 10,
+  target: number | null = null
+): [Point[], Point[] | null, number | null] {
   const flattened = runs.flat();
   // Default comparator coerces types to string !
   flattened.sort((a, b) => a - b); // in-place
   const min = flattened[0];
   const max = flattened[flattened.length - 1];
+  let targetPoints: Point[] | null = null;
+  let pN: number = 0;
+  let maxFreq: number = 0;
   const histogram = new Array(buckets).fill(0);
   const slots = buckets - 1; // The actual number of slots in the histogram
   for (let i = 0; i < flattened.length; i += 1) {
-    let n = normalize(flattened[i], min, max);
-    let index = Math.ceil(n * slots);
+    const value = flattened[i];
+    if (value >= target) {
+      pN += 1;
+    }
+    const n = normalize(value, min, max);
+    const index = Math.ceil(n * slots);
     histogram[index] = histogram[index] + 1;
+    if (maxFreq < histogram[index]) {
+      maxFreq = histogram[index];
+    }
   }
-  return singlePoints(histogram);
+  if (target) {
+    const n = normalize(target, min, max);
+    const index = Math.ceil(n * slots);
+    targetPoints = selectPoints(buckets, index, maxFreq);
+  }
+  return [singlePoints(histogram), targetPoints, (pN / flattened.length)];
+}
+
+function selectPoints(buckets: number, index: number, target: number) {
+  const points: Point[] = [];
+  for (let i = 0; i < buckets; i += 1) {
+    const y = i == index ? target : 0;
+    points.push({
+      x: i + 1, // 1 based index
+      y: y
+    });
+  }
+  return points;
 }
 
 function singlePoints(runs: number[]): Point[] {
@@ -71,6 +102,15 @@
 }
 
 function normalize(n: number, min: number, max: number): number {
+  if (n < min || n > max) {
+    console.warn(`Warning n(${n}) is not in the range of (${min}, ${max})`);
+    if (n < min) {
+      n = min;
+    }
+    if (n > max) {
+      n = max;
+    }
+  }
   return (n - min) / (max - min + 1e-5);
 }
 
@@ -78,7 +118,11 @@
  * Generates a series label.
  */
 function labelFor<T>(metric: Metric<T>, source: string): string {
-  return `${source}[${metric.class} ${metric.benchmark} ${metric.label}]`;
+  return `${source} {${metric.class}${metric.benchmark}} - ${metric.label}`;
+}
+
+export function datasetName(metric: Metric<any>): string {
+  return `${metric.class}_${metric.benchmark}`;
 }
 
 /**
diff --git a/development/plot-benchmarks/src/types/events.ts b/development/plot-benchmarks/src/types/events.ts
index 4dbe7d3b..ae8c3c7 100644
--- a/development/plot-benchmarks/src/types/events.ts
+++ b/development/plot-benchmarks/src/types/events.ts
@@ -1,7 +1,7 @@
 import type { FileMetadata } from "./files.js";
 
 export interface FileMetadataEvent {
-  entries: Array<FileMetadata>;
+  entries: FileMetadata[];
 }
 
 export interface Selection {
@@ -9,5 +9,16 @@
   enabled: boolean;
 }
 export interface SelectionEvent {
-  selections: Array<Selection>;
+  selections: Selection[];
+}
+
+export type StatType = 'p';
+export interface StatInfo {
+  name: string;
+  type: StatType;
+  enabled: boolean;
+}
+
+export interface StatEvent {
+  info: StatInfo[];
 }
diff --git a/development/plot-benchmarks/src/workers/service.ts b/development/plot-benchmarks/src/workers/service.ts
new file mode 100644
index 0000000..9ee37e0
--- /dev/null
+++ b/development/plot-benchmarks/src/workers/service.ts
@@ -0,0 +1,132 @@
+import { datasetName, histogramPoints } from "../transforms/standard-mappers.js";
+import type { Series } from "../types/chart.js";
+import type { ChartData, Metrics } from "../types/data.js";
+
+export class StatService {
+  pSeries(metrics: Metrics<number>, activeDatasets: Set<string>): Series[] {
+    if (activeDatasets.size <= 0) {
+      return [];
+    }
+
+    const series: Series[] = [];
+    const sampled = metrics.sampled;
+    if (sampled) {
+      for (let i = 0; i < sampled.length; i += 1) {
+        const metric = sampled[i];
+        const name = datasetName(metric);
+        if (activeDatasets.has(name)) {
+          const data: Record<string, ChartData<number[]>> = metric.data;
+          const entries = Object.entries(data);
+          const comparables: ChartData<number[]>[] = entries.map(entry => entry[1]);
+          if (comparables.length > 1) {
+            const reference = comparables[0];
+            for (let j = 1; j < comparables.length; j += 1) {
+              const target = comparables[j];
+              const [delta, distribution] = this.buildDistribution(reference, target);
+              const [points, pPlots, p] = histogramPoints([distribution], 20, delta);
+              series.push({
+                label: `${name} { ${metric.label} } - Likelihood`,
+                type: "line",
+                data: points,
+                options: {
+                  tension: 0.3
+                }
+              });
+              if (pPlots && pPlots.length > 0) {
+                series.push({
+                  label: `${name} { ${metric.label} } - { P = ${p} }`,
+                  type: "bar",
+                  data: pPlots,
+                  options: {
+                    tension: 0.01
+                  }
+                });
+              }
+            }
+          }
+        }
+      }
+    }
+    return series;
+  }
+
+  private buildDistribution(
+    reference: ChartData<number[]>,
+    target: ChartData<number[]>,
+    N: number = 1_000
+  ): [number, number[]] {
+    // Compute delta mean
+    const referenceData = reference.values;
+    const targetData = target.values;
+    const referenceMedian = this.arrayMedian(referenceData);
+    const targetMedian = this.arrayMedian(targetData);
+    const deltaMedian = Math.abs(referenceMedian - targetMedian);
+    // Simulate
+    const rs = referenceData.length;
+    const ts = targetData.length;
+    const combined: number[][] = [...referenceData, ...targetData];
+    const medians = [];
+    for (let i = 0; i < N; i += 1) {
+      const [r, t] = this.shuffleSplit(combined, [rs, ts]);
+      const mr = this.arrayMedian(r);
+      const mt = this.arrayMedian(t);
+      medians.push(Math.abs(mr - mt));
+    }
+    return [deltaMedian, medians];
+  }
+
+  private shuffleSplit<T>(data: T[], sizes: number[]): T[][] {
+    const shuffled = this.shuffle(data);
+    const splits: T[][] = [];
+    let index = 0;
+    for (let i = 0; i < sizes.length; i += 1) {
+      const size = sizes[i];
+      let split: T[] = [];
+      for (let j = 0; j < size; j += 1) {
+        const k = index + j;
+        if (k < shuffled.length) {
+          split.push(shuffled[k]);
+        }
+      }
+      index += size;
+      splits.push(split);
+    }
+    return splits;
+  }
+
+  private arrayMedian(data: number[][]): number {
+    // We don't want to compute median of medians here.
+    // This is because while individual runs are correlated
+    // we can still look at the actual metrics in aggregate.
+    return this.median(data.flat());
+  }
+
+  private median(data: number[]): number {
+    const copy = [...data];
+    // Default comparator coerces types to string !
+    copy.sort((a, b) => a - b); // in-place
+    const length = copy.length;
+    const index = Math.trunc(length / 2);
+    return copy[index];
+  }
+
+  private shuffle<T>(data: T[], multiplier: number = 1): T[] {
+    if (data.length <= 0) {
+      return [];
+    }
+
+    let copy = [...data];
+    const count = copy.length * multiplier;
+    const slots = copy.length - 1;
+    for (let i = 0; i < count; i += 1) {
+      const sourceIndex = Math.ceil(Math.random() * slots);
+      const targetIndex = Math.ceil(Math.random() * slots);
+      let source = copy[sourceIndex];
+      let target = copy[targetIndex];
+      copy[sourceIndex] = target;
+      copy[targetIndex] = source;
+    }
+    return copy;
+  }
+
+}
diff --git a/development/plot-benchmarks/src/workers/worker.ts b/development/plot-benchmarks/src/workers/worker.ts
new file mode 100644
index 0000000..367e74c
--- /dev/null
+++ b/development/plot-benchmarks/src/workers/worker.ts
@@ -0,0 +1,10 @@
+/* Stub worker. */
+
+import { expose } from "comlink";
+import { StatService } from "./service.js";
+
+// This is always running in the context of a Web Worker.
+declare var self: Worker;
+
+const service = new StatService();
+expose(service, self);