mirror of
https://github.com/gosticks/partition-filter-visualization.git
synced 2026-08-12 04:40:23 +00:00
wip: add plane renderer
This commit is contained in:
+3
-1
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@duckdb/duckdb-wasm": "^1.27.0",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/three": "^0.152.1",
|
||||
@@ -45,6 +46,7 @@
|
||||
"svelte-feather-icons": "^4.0.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"three": "^0.154.0",
|
||||
"three.meshline": "^1.4.0"
|
||||
"three.meshline": "^1.4.0",
|
||||
"web-worker": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+196
-1
@@ -5,6 +5,9 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@duckdb/duckdb-wasm':
|
||||
specifier: ^1.27.0
|
||||
version: 1.27.0
|
||||
'@sveltejs/adapter-static':
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(@sveltejs/kit@1.20.5)
|
||||
@@ -44,6 +47,9 @@ dependencies:
|
||||
three.meshline:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
web-worker:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
@@ -102,6 +108,12 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/@duckdb/duckdb-wasm@1.27.0:
|
||||
resolution: {integrity: sha512-qx0OjbS1NWAWZSU6hJb6bbyhN48LQn5kO54XsGeBdXbtJYDpPWGfTksP6meWsH8DKCUJIwAXLLImuGvIcY0l0A==}
|
||||
dependencies:
|
||||
apache-arrow: 11.0.0
|
||||
dev: false
|
||||
|
||||
/@esbuild/android-arm64@0.17.19:
|
||||
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -488,16 +500,36 @@ packages:
|
||||
resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
|
||||
dev: true
|
||||
|
||||
/@types/command-line-args@5.2.0:
|
||||
resolution: {integrity: sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA==}
|
||||
dev: false
|
||||
|
||||
/@types/command-line-usage@5.0.2:
|
||||
resolution: {integrity: sha512-n7RlEEJ+4x4TS7ZQddTmNSxP+zziEG0TNsMfiRIxcIVXt71ENJ9ojeXmGO3wPoTdn7pJcU2xc3CJYMktNT6DPg==}
|
||||
dev: false
|
||||
|
||||
/@types/cookie@0.5.1:
|
||||
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
|
||||
|
||||
/@types/flatbuffers@1.10.0:
|
||||
resolution: {integrity: sha512-7btbphLrKvo5yl/5CC2OCxUSMx1wV1wvGT1qDXkSt7yi00/YW7E8k6qzXqJHsp+WU0eoG7r6MTQQXI9lIvd0qA==}
|
||||
dev: false
|
||||
|
||||
/@types/json-schema@7.0.12:
|
||||
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
||||
dev: true
|
||||
|
||||
/@types/node@18.7.23:
|
||||
resolution: {integrity: sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==}
|
||||
dev: false
|
||||
|
||||
/@types/node@20.3.1:
|
||||
resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==}
|
||||
|
||||
/@types/pad-left@2.1.1:
|
||||
resolution: {integrity: sha512-Xd22WCRBydkGSApl5Bw0PhAOHKSVjNL3E3AwzKaps96IMraPqy5BvZIsBVK6JLwdybUzjHnuWVwpDd0JjTfHXA==}
|
||||
dev: false
|
||||
|
||||
/@types/papaparse@5.3.7:
|
||||
resolution: {integrity: sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==}
|
||||
dependencies:
|
||||
@@ -693,6 +725,13 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/ansi-styles@3.2.1:
|
||||
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
color-convert: 1.9.3
|
||||
dev: false
|
||||
|
||||
/ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -711,6 +750,23 @@ packages:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
/apache-arrow@11.0.0:
|
||||
resolution: {integrity: sha512-M8J4y+DimIyS44w2KOmVfzNHbTroR1oDpBKK6BYnlu8xVB41lxTz0yLmapo8/WJVAt5XcinAxMm14M771dm/rA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/command-line-args': 5.2.0
|
||||
'@types/command-line-usage': 5.0.2
|
||||
'@types/flatbuffers': 1.10.0
|
||||
'@types/node': 18.7.23
|
||||
'@types/pad-left': 2.1.1
|
||||
command-line-args: 5.2.1
|
||||
command-line-usage: 6.1.3
|
||||
flatbuffers: 2.0.4
|
||||
json-bignum: 0.0.3
|
||||
pad-left: 2.1.0
|
||||
tslib: 2.5.3
|
||||
dev: false
|
||||
|
||||
/arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
dev: false
|
||||
@@ -719,6 +775,16 @@ packages:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: true
|
||||
|
||||
/array-back@3.1.0:
|
||||
resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/array-back@4.0.2:
|
||||
resolution: {integrity: sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/array-union@2.1.0:
|
||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -811,6 +877,15 @@ packages:
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/chalk@2.4.2:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
escape-string-regexp: 1.0.5
|
||||
supports-color: 5.5.0
|
||||
dev: false
|
||||
|
||||
/chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -841,6 +916,12 @@ packages:
|
||||
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
|
||||
dev: false
|
||||
|
||||
/color-convert@1.9.3:
|
||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
dependencies:
|
||||
color-name: 1.1.3
|
||||
dev: false
|
||||
|
||||
/color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@@ -848,10 +929,34 @@ packages:
|
||||
color-name: 1.1.4
|
||||
dev: true
|
||||
|
||||
/color-name@1.1.3:
|
||||
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
|
||||
dev: false
|
||||
|
||||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: true
|
||||
|
||||
/command-line-args@5.2.1:
|
||||
resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
array-back: 3.1.0
|
||||
find-replace: 3.0.0
|
||||
lodash.camelcase: 4.3.0
|
||||
typical: 4.0.0
|
||||
dev: false
|
||||
|
||||
/command-line-usage@6.1.3:
|
||||
resolution: {integrity: sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
array-back: 4.0.2
|
||||
chalk: 2.4.2
|
||||
table-layout: 1.0.2
|
||||
typical: 5.2.0
|
||||
dev: false
|
||||
|
||||
/commander@4.1.1:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -902,6 +1007,11 @@ packages:
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dev: false
|
||||
|
||||
/deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
@@ -982,6 +1092,11 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/escape-string-regexp@1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
dev: false
|
||||
|
||||
/escape-string-regexp@4.0.0:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1167,6 +1282,13 @@ packages:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
/find-replace@3.0.0:
|
||||
resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
array-back: 3.1.0
|
||||
dev: false
|
||||
|
||||
/find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1183,6 +1305,10 @@ packages:
|
||||
rimraf: 3.0.2
|
||||
dev: true
|
||||
|
||||
/flatbuffers@2.0.4:
|
||||
resolution: {integrity: sha512-4rUFVDPjSoP0tOII34oQf+72NKU7E088U5oX7kwICahft0UB2kOQ9wUzzCp+OHxByERIfxRDCgX5mP8Pjkfl0g==}
|
||||
dev: false
|
||||
|
||||
/flatted@3.2.7:
|
||||
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
||||
dev: true
|
||||
@@ -1274,6 +1400,11 @@ packages:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
dev: true
|
||||
|
||||
/has-flag@3.0.0:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1367,6 +1498,11 @@ packages:
|
||||
argparse: 2.0.1
|
||||
dev: true
|
||||
|
||||
/json-bignum@0.0.3:
|
||||
resolution: {integrity: sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==}
|
||||
engines: {node: '>=0.8'}
|
||||
dev: false
|
||||
|
||||
/json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
dev: true
|
||||
@@ -1412,6 +1548,10 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash.camelcase@4.3.0:
|
||||
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
@@ -1565,6 +1705,13 @@ packages:
|
||||
p-limit: 3.1.0
|
||||
dev: true
|
||||
|
||||
/pad-left@2.1.0:
|
||||
resolution: {integrity: sha512-HJxs9K9AztdIQIAIa/OIazRAUW/L6B9hbQDxO4X07roW3eo9XqZc2ur9bn1StH9CnbbI9EgvejHQX7CBpCF1QA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
repeat-string: 1.6.1
|
||||
dev: false
|
||||
|
||||
/papaparse@5.4.1:
|
||||
resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==}
|
||||
dev: false
|
||||
@@ -1736,6 +1883,16 @@ packages:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
/reduce-flatten@2.0.0:
|
||||
resolution: {integrity: sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/repeat-string@1.6.1:
|
||||
resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
|
||||
engines: {node: '>=0.10'}
|
||||
dev: false
|
||||
|
||||
/resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1906,6 +2063,13 @@ packages:
|
||||
ts-interface-checker: 0.1.13
|
||||
dev: false
|
||||
|
||||
/supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
dev: false
|
||||
|
||||
/supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2012,6 +2176,16 @@ packages:
|
||||
resolution: {integrity: sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
/table-layout@1.0.2:
|
||||
resolution: {integrity: sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
array-back: 4.0.2
|
||||
deep-extend: 0.6.0
|
||||
typical: 5.2.0
|
||||
wordwrapjs: 4.0.1
|
||||
dev: false
|
||||
|
||||
/tailwindcss@3.3.2:
|
||||
resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -2103,7 +2277,6 @@ packages:
|
||||
|
||||
/tslib@2.5.3:
|
||||
resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==}
|
||||
dev: true
|
||||
|
||||
/tsutils@3.21.0(typescript@5.1.3):
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
@@ -2138,6 +2311,16 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typical@4.0.0:
|
||||
resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/typical@5.2.0:
|
||||
resolution: {integrity: sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/undici@5.22.1:
|
||||
resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==}
|
||||
engines: {node: '>=14.0'}
|
||||
@@ -2253,6 +2436,10 @@ packages:
|
||||
- terser
|
||||
dev: true
|
||||
|
||||
/web-worker@1.2.0:
|
||||
resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==}
|
||||
dev: false
|
||||
|
||||
/which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -2266,6 +2453,14 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/wordwrapjs@4.0.1:
|
||||
resolution: {integrity: sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
reduce-flatten: 2.0.0
|
||||
typical: 5.2.0
|
||||
dev: false
|
||||
|
||||
/wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { Theme, appSettingsStore, toggleThemeMode, updateTheme } from '../store/SettingsStore';
|
||||
import { Theme, toggleThemeMode, useSettingsStore } from '../store/SettingsStore';
|
||||
|
||||
let className: string | undefined = '';
|
||||
export { className as class };
|
||||
|
||||
let theme: Theme;
|
||||
|
||||
let store = useSettingsStore();
|
||||
|
||||
onMount(() => {
|
||||
appSettingsStore.subscribe((value) => {
|
||||
store.subscribe((value) => {
|
||||
theme = value.theme;
|
||||
});
|
||||
});
|
||||
@@ -29,7 +30,7 @@
|
||||
<span class="relative inline-block w-10 h-6 transition bg-gray-300 rounded-full">
|
||||
<span
|
||||
class="absolute top-0 left-0 w-6 h-6 transition transform bg-white rounded-full shadow-md"
|
||||
style="transform: translateX({$appSettingsStore.theme === Theme.Dark
|
||||
style="transform: translateX({$store.theme === Theme.Dark
|
||||
? 'calc(100% - 0.75rem)'
|
||||
: '0'})"
|
||||
/>
|
||||
|
||||
@@ -6,40 +6,31 @@
|
||||
Scene,
|
||||
PerspectiveCamera,
|
||||
WebGLRenderer,
|
||||
BoxGeometry,
|
||||
MeshBasicMaterial,
|
||||
Mesh,
|
||||
Color,
|
||||
AmbientLight,
|
||||
MeshLambertMaterial,
|
||||
MeshPhongMaterial,
|
||||
DirectionalLight,
|
||||
Group,
|
||||
Light,
|
||||
PointLight,
|
||||
Vector2,
|
||||
Vector3,
|
||||
OrthographicCamera,
|
||||
Camera,
|
||||
MeshStandardMaterial,
|
||||
Object3D,
|
||||
LineBasicMaterial,
|
||||
BufferGeometry,
|
||||
BufferAttribute,
|
||||
Line,
|
||||
DoubleSide,
|
||||
Raycaster,
|
||||
ShaderMaterial,
|
||||
type Intersection
|
||||
Raycaster
|
||||
} from 'three';
|
||||
import * as THREE from 'three';
|
||||
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
|
||||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
|
||||
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
|
||||
import { MeshLine, MeshLineMaterial } from 'three.meshline';
|
||||
import { AxisRenderer } from '$lib/rendering/AxisRenderer';
|
||||
import { BarRenderer } from '$lib/rendering/BarRenderer';
|
||||
import { PlainRenderer } from '$lib/rendering/PlainRenderer';
|
||||
|
||||
export let data: Array<Array<number>> = [[]];
|
||||
export let onHover: (position: Vector2, object?: Object3D) => void = () => {};
|
||||
|
||||
export let data: Array<Array<Array<number>>> = [[[]]];
|
||||
|
||||
// let displatFilter: ;
|
||||
|
||||
let containerElement: HTMLDivElement;
|
||||
let statsElement: HTMLDivElement;
|
||||
@@ -53,13 +44,14 @@
|
||||
let composer: EffectComposer;
|
||||
let stats: Stats;
|
||||
|
||||
let barGroup: Group | undefined = undefined;
|
||||
let dataRenderer: PlainRenderer;
|
||||
let axisRenderer: AxisRenderer;
|
||||
|
||||
function setupControls() {
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.rotateSpeed = 0.4;
|
||||
controls.zoomSpeed = 0.3;
|
||||
controls.panSpeed = 0;
|
||||
controls.rotateSpeed = 1;
|
||||
controls.zoomSpeed = 1;
|
||||
controls.panSpeed = 1;
|
||||
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.1;
|
||||
@@ -71,16 +63,18 @@
|
||||
|
||||
scene.add(new AmbientLight(0xffffff, 0.5));
|
||||
|
||||
const cameraField = Math.max(containerElement.clientWidth, containerElement.clientHeight) * 2;
|
||||
|
||||
camera = new OrthographicCamera(
|
||||
containerElement.clientWidth / -2,
|
||||
containerElement.clientWidth / 2,
|
||||
containerElement.clientHeight / 2,
|
||||
containerElement.clientHeight / -2,
|
||||
-1000,
|
||||
1000
|
||||
-cameraField,
|
||||
cameraField
|
||||
);
|
||||
|
||||
camera.position.z = 500;
|
||||
camera.position.z = 1000;
|
||||
|
||||
// Add directional light pointing from camera
|
||||
const light = new DirectionalLight(0xffffff, 1);
|
||||
@@ -92,10 +86,27 @@
|
||||
scene.add(camera);
|
||||
}
|
||||
|
||||
function windowResizeHandler(evt: UIEvent) {
|
||||
if (camera instanceof OrthographicCamera) {
|
||||
camera.left = containerElement.clientWidth / -2;
|
||||
camera.right = containerElement.clientWidth / 2;
|
||||
camera.top = containerElement.clientHeight / 2;
|
||||
camera.bottom = containerElement.clientHeight / -2;
|
||||
camera.updateProjectionMatrix();
|
||||
} else if (camera instanceof PerspectiveCamera) {
|
||||
camera.aspect = containerElement.clientWidth / containerElement.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
renderer.setSize(containerElement.clientWidth, containerElement.clientHeight);
|
||||
composer.setSize(containerElement.clientWidth, containerElement.clientHeight);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
setupScene();
|
||||
|
||||
renderer = new WebGLRenderer({ antialias: true, alpha: true });
|
||||
@@ -104,6 +115,9 @@
|
||||
renderer.setSize(containerElement.clientWidth, containerElement.clientHeight);
|
||||
containerElement.appendChild(renderer.domElement);
|
||||
|
||||
const width = containerElement.clientWidth;
|
||||
const height = containerElement.clientHeight;
|
||||
|
||||
composer = new EffectComposer(renderer);
|
||||
|
||||
// Add default render pass
|
||||
@@ -126,21 +140,29 @@
|
||||
|
||||
setupControls();
|
||||
|
||||
raycaster = new Raycaster();
|
||||
dataRenderer = new PlainRenderer(scene, camera);
|
||||
dataRenderer.setScale(new Vector3(width, width, width));
|
||||
dataRenderer.updateWithData({ data });
|
||||
axisRenderer = new AxisRenderer(scene, {
|
||||
size: new Vector3(width, height, width),
|
||||
labelScale: width / 25
|
||||
});
|
||||
axisRenderer.render();
|
||||
|
||||
createBarChart();
|
||||
raycaster = new Raycaster();
|
||||
|
||||
stats = new Stats();
|
||||
stats.showPanel(1);
|
||||
statsElement.appendChild(stats.dom);
|
||||
|
||||
const size = width * 2;
|
||||
const divisions = (width * 2) / 100;
|
||||
|
||||
const gridHelper = new THREE.GridHelper(size, divisions);
|
||||
scene.add(gridHelper);
|
||||
|
||||
// Animation loop
|
||||
const animate = () => {
|
||||
if (!barGroup) {
|
||||
createBarChart();
|
||||
return;
|
||||
}
|
||||
|
||||
controls.update();
|
||||
stats.begin();
|
||||
|
||||
@@ -148,12 +170,14 @@
|
||||
// Handle selection
|
||||
raycaster.setFromCamera(mousePosition, camera);
|
||||
|
||||
const intersections = raycaster.intersectObjects(barGroup.children, true);
|
||||
const intersections = dataRenderer.getIntersections(raycaster);
|
||||
|
||||
if (intersections.length === 0) {
|
||||
outlinePass.selectedObjects = [];
|
||||
onHover(mouseClientPosition, undefined);
|
||||
} else {
|
||||
outlinePass.selectedObjects = [intersections[0].object];
|
||||
onHover(mouseClientPosition, intersections[0].object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,20 +186,36 @@
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// Setup resize handler
|
||||
window.addEventListener('resize', windowResizeHandler);
|
||||
|
||||
animate();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', windowResizeHandler);
|
||||
});
|
||||
|
||||
beforeUpdate(() => {
|
||||
console.log('beforeUpdate');
|
||||
// Clear the scene before updating
|
||||
// clearScene();
|
||||
clearScene();
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
console.log('afterUpdate');
|
||||
|
||||
console.log('data', data);
|
||||
|
||||
// Re-create the bar chart when data is updated
|
||||
createBarChart();
|
||||
dataRenderer.updateWithData({
|
||||
data: data
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -189,10 +229,15 @@
|
||||
});
|
||||
|
||||
let mousePosition: Vector2 = new Vector2(0, 0);
|
||||
let mouseClientPosition: Vector2 = new Vector2(0, 0);
|
||||
|
||||
function onHover(event: MouseEvent) {
|
||||
mousePosition.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
|
||||
mousePosition.y = -(event.clientY / renderer.domElement.clientHeight) * 2 + 1.0;
|
||||
function handleHover(event: MouseEvent) {
|
||||
const bounds = containerElement.getBoundingClientRect();
|
||||
mouseClientPosition.x = event.clientX - bounds.left;
|
||||
mouseClientPosition.y = event.clientY - bounds.top;
|
||||
|
||||
mousePosition.x = (mouseClientPosition.x / bounds.width) * 2 - 1;
|
||||
mousePosition.y = -(mouseClientPosition.y / bounds.height) * 2 + 1.0;
|
||||
}
|
||||
|
||||
function clearScene() {
|
||||
@@ -200,154 +245,14 @@
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
if (barGroup) {
|
||||
barGroup.clear();
|
||||
scene.remove(barGroup);
|
||||
barGroup = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const barGap = 15;
|
||||
|
||||
// Define your colors
|
||||
let color1 = new Color('#F0F624');
|
||||
let color2 = new Color('#C5407D');
|
||||
let color3 = new Color('#15078A');
|
||||
|
||||
function createBarChart() {
|
||||
// Remove the previous bar chart
|
||||
clearScene();
|
||||
|
||||
let maxBarHeight = 0;
|
||||
|
||||
let barWidth = (containerElement.clientWidth * 0.4) / data.length - barGap * 2;
|
||||
let barHeightScale = 10;
|
||||
|
||||
const group = new Group();
|
||||
let positionZ = 0;
|
||||
let positionX = 0;
|
||||
|
||||
// compute max values for scaling
|
||||
for (let x = 0; x < data.length; x++) {
|
||||
maxBarHeight = Math.max(
|
||||
maxBarHeight,
|
||||
data[x].reduce((a, b) => a + b, 0)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: adjust data for now only mock
|
||||
for (let z = 0; z < data.length; z++) {
|
||||
positionX = 0;
|
||||
|
||||
for (let x = 0; x < data.length; x++) {
|
||||
let currentBarHeight = 0;
|
||||
const xProgress = x / data.length;
|
||||
const baseColor = color1.clone().lerp(color2, xProgress);
|
||||
|
||||
for (let y = 0; y < data[x].length; y++) {
|
||||
const barHeight = data[x][y] * barHeightScale;
|
||||
const geometry = new BoxGeometry(barWidth, barHeight, barWidth);
|
||||
const material = new MeshLambertMaterial({
|
||||
color: baseColor.clone().lerp(color3, currentBarHeight / maxBarHeight),
|
||||
transparent: true
|
||||
});
|
||||
if (currentBarHeight > 100) {
|
||||
material.opacity = 0.05;
|
||||
}
|
||||
const bar = new Mesh(geometry, material);
|
||||
|
||||
bar.userData = {
|
||||
data: data[x][y],
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
};
|
||||
|
||||
bar.position.x = positionX;
|
||||
bar.position.y = currentBarHeight + barHeight / 2 + 2;
|
||||
bar.position.z = positionZ;
|
||||
currentBarHeight += barHeight;
|
||||
|
||||
group.add(bar);
|
||||
}
|
||||
|
||||
maxBarHeight = Math.max(maxBarHeight, currentBarHeight);
|
||||
positionX += barWidth + barGap;
|
||||
}
|
||||
|
||||
positionZ += barWidth + barGap;
|
||||
}
|
||||
// Move group to center
|
||||
group.position.x = -positionX / 2;
|
||||
group.position.y = -maxBarHeight / 2;
|
||||
group.position.z = -positionZ / 2;
|
||||
|
||||
scene.add(group);
|
||||
|
||||
barGroup = group;
|
||||
|
||||
// Draw axis indicator
|
||||
const axisIndicator = createAxisIndicator(Math.max(positionX, positionZ) * 1.5);
|
||||
axisIndicator.position.set(
|
||||
group.position.x - barWidth / 2 - 5 / 2,
|
||||
group.position.y,
|
||||
group.position.z - barWidth / 2 - 5 / 2
|
||||
);
|
||||
scene.add(axisIndicator);
|
||||
}
|
||||
|
||||
// Create the axis indicator
|
||||
function createAxisIndicator(size: number, lineWidth: number = 5) {
|
||||
const xAxisGeometry = new BufferGeometry().setFromPoints([
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(size, 0, 0)
|
||||
]);
|
||||
|
||||
const yAxisGeometry = new BufferGeometry().setFromPoints([
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(0, size, 0)
|
||||
]);
|
||||
const zAxisGeometry = new BufferGeometry().setFromPoints([
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(0, 0, size)
|
||||
]);
|
||||
|
||||
const xAxisMaterial = new MeshLineMaterial({ color: 0xeeeeee, lineWidth });
|
||||
const yAxisMaterial = new MeshLineMaterial({
|
||||
color: 0xeeeeee,
|
||||
lineWidth
|
||||
});
|
||||
const zAxisMaterial = new MeshLineMaterial({
|
||||
color: 0xeeeeee,
|
||||
lineWidth
|
||||
});
|
||||
|
||||
const xAxis = new MeshLine();
|
||||
xAxis.setGeometry(xAxisGeometry);
|
||||
const xAxisMesh = new Mesh(xAxis.geometry, xAxisMaterial);
|
||||
|
||||
const yAxis = new MeshLine();
|
||||
yAxis.setGeometry(yAxisGeometry);
|
||||
const yAxisMesh = new Mesh(yAxis.geometry, yAxisMaterial);
|
||||
|
||||
const zAxis = new MeshLine();
|
||||
zAxis.setGeometry(zAxisGeometry);
|
||||
const zAxisMesh = new Mesh(zAxis.geometry, zAxisMaterial);
|
||||
|
||||
const axisIndicator = new Object3D();
|
||||
axisIndicator.add(xAxisMesh);
|
||||
axisIndicator.add(yAxisMesh);
|
||||
axisIndicator.add(zAxisMesh);
|
||||
|
||||
return axisIndicator;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative w-full">
|
||||
<div
|
||||
bind:this={containerElement}
|
||||
on:mousemove={onHover}
|
||||
class="bar-chart-container w-full aspect-square isolate"
|
||||
on:mousemove={handleHover}
|
||||
class="bar-chart-container w-full aspect-square overflow-hidden isolate"
|
||||
/>
|
||||
<div class="stats absolute isolate top-0 left-0" bind:this={statsElement} />
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,19 @@ interface BarRendererOptions {
|
||||
barWidth: number;
|
||||
barDepth: number;
|
||||
barGap: number;
|
||||
|
||||
// TODO: think about naming
|
||||
cols: number;
|
||||
rows: number;
|
||||
}
|
||||
|
||||
const defaultBarRendererOptions: BarRendererOptions = {
|
||||
barWidth: 1,
|
||||
barDepth: 1,
|
||||
barGap: 0.1
|
||||
barWidth: 0.15,
|
||||
barDepth: 0.15,
|
||||
barGap: 0.05,
|
||||
|
||||
cols: 10,
|
||||
rows: 10
|
||||
};
|
||||
|
||||
// Define your colors
|
||||
@@ -23,16 +30,21 @@ const color1 = new THREE.Color('#F0F624');
|
||||
const color2 = new THREE.Color('#C5407D');
|
||||
const color3 = new THREE.Color('#15078A');
|
||||
|
||||
const dataColorizer = (value: number, dataIndex: Vector3, axisProgress: Vector3): THREE.Color => {
|
||||
const dataColorizer = (dataIndex: Vector3, axisProgress: Vector3): THREE.Color => {
|
||||
const xColor = color1.clone().lerp(color2, axisProgress.x);
|
||||
return xColor.lerp(color3, axisProgress.y);
|
||||
};
|
||||
|
||||
export class BarRenderer extends GraphRenderer<BarData> {
|
||||
private barGroup: THREE.Group;
|
||||
private barGroup?: THREE.Group;
|
||||
private options: BarRendererOptions;
|
||||
private size: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
|
||||
|
||||
// Getter for all bar blocks managed by the renderer
|
||||
get bars(): THREE.Object3D[] {
|
||||
return this.barGroup?.children ?? [];
|
||||
}
|
||||
|
||||
constructor(
|
||||
public scene: THREE.Scene,
|
||||
public camera: THREE.Camera,
|
||||
@@ -44,23 +56,28 @@ export class BarRenderer extends GraphRenderer<BarData> {
|
||||
...defaultBarRendererOptions,
|
||||
...options
|
||||
};
|
||||
|
||||
this.barGroup = new THREE.Group();
|
||||
this.scene.add(this.barGroup);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.scene.remove(this.barGroup);
|
||||
if (this.barGroup) {
|
||||
this.scene.remove(this.barGroup);
|
||||
}
|
||||
}
|
||||
|
||||
setScale(scale: THREE.Vector3): void {
|
||||
this.size = scale;
|
||||
this.barGroup.scale.copy(scale);
|
||||
this.barGroup?.scale.copy(scale);
|
||||
}
|
||||
|
||||
getIntersections(raycaster: THREE.Raycaster): THREE.Intersection[] {
|
||||
return raycaster.intersectObjects(this.bars, true);
|
||||
}
|
||||
|
||||
updateWithData(data: BarData) {
|
||||
// Clear group
|
||||
this.barGroup.children = [];
|
||||
if (this.barGroup) {
|
||||
this.scene.remove(this.barGroup);
|
||||
}
|
||||
const barGroup = new THREE.Group();
|
||||
|
||||
// Compute min and max values
|
||||
let maxBarHeight = -Infinity;
|
||||
@@ -76,10 +93,23 @@ export class BarRenderer extends GraphRenderer<BarData> {
|
||||
)
|
||||
);
|
||||
|
||||
console.log(maxBarHeight);
|
||||
|
||||
const relBarSize = Math.min(
|
||||
(1 - this.options.barGap) / this.options.cols,
|
||||
(1 - this.options.barGap) / this.options.rows
|
||||
);
|
||||
|
||||
const relGapSize = Math.min(
|
||||
this.options.barGap / this.options.cols,
|
||||
this.options.barGap / this.options.rows
|
||||
);
|
||||
|
||||
// Iterate over input data and create bars
|
||||
for (let z = 0; z < data.data.length; z++) {
|
||||
for (let x = 0; x < data.data[z].length; x++) {
|
||||
let currentBarHeight = 0;
|
||||
|
||||
for (let y = 0; y < data.data[z][x].length; y++) {
|
||||
const value = data.data[z][x][y];
|
||||
|
||||
@@ -89,45 +119,43 @@ export class BarRenderer extends GraphRenderer<BarData> {
|
||||
}
|
||||
|
||||
const color = dataColorizer(
|
||||
value,
|
||||
new THREE.Vector3(x, y, z),
|
||||
new THREE.Vector3(
|
||||
x / data.data[z][x].length,
|
||||
y / data.data[z].length,
|
||||
z / data.data.length
|
||||
)
|
||||
new THREE.Vector3(x / this.options.rows, value / maxBarHeight, z / this.options.cols)
|
||||
);
|
||||
|
||||
const barSegmentHeight = value / maxBarHeight;
|
||||
|
||||
// Create bar
|
||||
const bar = this.createBarSegment(
|
||||
relBarSize,
|
||||
// Scale bar to 0 to 1 range
|
||||
barSegmentHeight,
|
||||
this.options.barWidth,
|
||||
this.options.barDepth,
|
||||
relBarSize,
|
||||
color
|
||||
);
|
||||
|
||||
// Position bar
|
||||
bar.position.set(
|
||||
this.options.barWidth / 2 + x * (this.options.barWidth + this.options.barGap),
|
||||
relBarSize / 2 + x * (relBarSize + relGapSize),
|
||||
currentBarHeight + barSegmentHeight / 2,
|
||||
this.options.barDepth / 2 + z * (this.options.barWidth + this.options.barGap)
|
||||
relBarSize / 2 + z * (relBarSize + relGapSize)
|
||||
);
|
||||
|
||||
currentBarHeight += barSegmentHeight;
|
||||
|
||||
// Add bar to group
|
||||
this.barGroup.add(bar);
|
||||
barGroup.add(bar);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.barGroup = barGroup;
|
||||
this.barGroup.scale.copy(this.size);
|
||||
this.scene.add(barGroup);
|
||||
}
|
||||
|
||||
private createBarSegment(
|
||||
height: number,
|
||||
width: number,
|
||||
height: number,
|
||||
depth: number,
|
||||
color: THREE.Color
|
||||
): THREE.Mesh {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export abstract class GraphRenderer<T> {
|
||||
export abstract class GraphRenderer<T = unknown> {
|
||||
constructor(public scene: THREE.Scene, public camera: THREE.Camera) {}
|
||||
|
||||
abstract destroy(): void;
|
||||
@@ -8,4 +8,6 @@ export abstract class GraphRenderer<T> {
|
||||
* @param data
|
||||
*/
|
||||
abstract updateWithData(data: T): void;
|
||||
|
||||
abstract getIntersections(raycaster: THREE.Raycaster): THREE.Intersection[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import * as THREE from 'three';
|
||||
import { GraphRenderer } from './GraphRenderer';
|
||||
import { DataPlaneShapeMaterial } from './materials/DataPlaneMaterial';
|
||||
import { DataPlaneShapeGeometry } from './geometry/DataPlaneGeometry';
|
||||
|
||||
interface PlainData {
|
||||
data: number[][][];
|
||||
}
|
||||
|
||||
interface PlainRendererOptions {
|
||||
// TODO: think about naming
|
||||
cols: number;
|
||||
rows: number;
|
||||
}
|
||||
|
||||
const defaultRendererOptions: PlainRendererOptions = {
|
||||
cols: 10,
|
||||
rows: 10
|
||||
};
|
||||
|
||||
export class PlainRenderer extends GraphRenderer<PlainData> {
|
||||
private group?: THREE.Group;
|
||||
private options: PlainRendererOptions;
|
||||
private size: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
|
||||
|
||||
// Getter for all bar blocks managed by the renderer
|
||||
get bars(): THREE.Object3D[] {
|
||||
return this.group?.children ?? [];
|
||||
}
|
||||
|
||||
constructor(
|
||||
public scene: THREE.Scene,
|
||||
public camera: THREE.Camera,
|
||||
options: Partial<PlainRendererOptions> = {}
|
||||
) {
|
||||
super(scene, camera);
|
||||
|
||||
this.options = {
|
||||
...defaultRendererOptions,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.group) {
|
||||
this.scene.remove(this.group);
|
||||
}
|
||||
}
|
||||
|
||||
setScale(scale: THREE.Vector3): void {
|
||||
this.size = scale;
|
||||
this.group?.scale.copy(scale);
|
||||
}
|
||||
|
||||
getIntersections(raycaster: THREE.Raycaster): THREE.Intersection[] {
|
||||
return raycaster.intersectObjects(this.bars, true);
|
||||
}
|
||||
|
||||
updateWithData(data: PlainData) {
|
||||
if (this.group) {
|
||||
this.scene.remove(this.group);
|
||||
}
|
||||
const group = new THREE.Group();
|
||||
|
||||
// Construct a list of all highest values
|
||||
const highestValues = Array.from({ length: data.data.length }, () =>
|
||||
Array.from({ length: data.data.length > 0 ? data.data[0].length : 0 }, () => 0)
|
||||
);
|
||||
|
||||
let maxValue = -0.0;
|
||||
|
||||
// Compute maxBar height to correctly scale bars
|
||||
data.data.forEach((z, zIdx) =>
|
||||
z.forEach((x, xIdx) => {
|
||||
// FIXME: this is really slow, just use for initial rendering setup
|
||||
let height = Math.max(...x);
|
||||
if (!Number.isFinite(height)) {
|
||||
height = 0;
|
||||
}
|
||||
highestValues[zIdx][xIdx] = height;
|
||||
maxValue = Math.max(maxValue, height);
|
||||
})
|
||||
);
|
||||
|
||||
const mockupData = highestValues.map((row, z) => row.map((_, x) => highestValues[z][x] * 0.5));
|
||||
|
||||
const geometry = new DataPlaneShapeGeometry(highestValues, undefined);
|
||||
const geometry2 = new DataPlaneShapeGeometry(mockupData, undefined, true);
|
||||
|
||||
// Create a material with the custom fragment shader
|
||||
const material = new DataPlaneShapeMaterial(new THREE.Color(0xff00ff));
|
||||
const material2 = new DataPlaneShapeMaterial(new THREE.Color(0x00ffff));
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
const mesh2 = new THREE.Mesh(geometry2, material2);
|
||||
group.add(mesh);
|
||||
// group.add(mesh2);
|
||||
|
||||
this.group = group;
|
||||
this.group.position.z = -0.5;
|
||||
this.group.scale.copy(this.size).multiplyScalar(0.5);
|
||||
this.scene.add(group);
|
||||
}
|
||||
|
||||
private createBarSegment(
|
||||
width: number,
|
||||
height: number,
|
||||
depth: number,
|
||||
color: THREE.Color
|
||||
): THREE.Mesh {
|
||||
const geometry = new THREE.BoxGeometry(width, height, depth);
|
||||
const material = new THREE.MeshPhongMaterial({ color, transparent: true });
|
||||
|
||||
const bar = new THREE.Mesh(geometry, material);
|
||||
return bar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class DataPlaneShapeGeometry extends THREE.BufferGeometry {
|
||||
static readonly pointComponentSize = 3;
|
||||
|
||||
private normalizedData: number[][];
|
||||
private previousNormalizedData: number[][] | undefined = undefined;
|
||||
private width: number;
|
||||
private depth: number;
|
||||
|
||||
private indexBuffer: Uint32Array | undefined = undefined;
|
||||
private pointBuffer: Float32Array | undefined = undefined;
|
||||
|
||||
private get pointsPerPlane() {
|
||||
return this.width * this.depth;
|
||||
}
|
||||
|
||||
private get numPolygonsPerPlane() {
|
||||
return (this.width - 1) * (this.depth - 1) * 2;
|
||||
}
|
||||
|
||||
constructor(
|
||||
data: number[][],
|
||||
previousData: number[][] | undefined = undefined,
|
||||
normalized = false
|
||||
) {
|
||||
super();
|
||||
|
||||
if (normalized) {
|
||||
this.normalizedData = data;
|
||||
} else {
|
||||
// Find max value and normalize all values to be between 0 and 1
|
||||
let maxValue = -Infinity;
|
||||
|
||||
data.forEach((row) => {
|
||||
row.forEach((value) => {
|
||||
maxValue = Math.max(maxValue, value);
|
||||
});
|
||||
});
|
||||
|
||||
this.normalizedData = data.map((row) => row.map((value) => value / maxValue));
|
||||
|
||||
// If previous data is provided, normalize it as well
|
||||
if (previousData) {
|
||||
this.previousNormalizedData = previousData.map((row) =>
|
||||
row.map((value) => value / maxValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.width = this.normalizedData[0].length;
|
||||
this.depth = this.normalizedData.length;
|
||||
|
||||
if (previousData) {
|
||||
if (previousData.length !== this.depth || previousData[0].length !== this.width) {
|
||||
throw new Error('Previous data has different dimensions');
|
||||
}
|
||||
}
|
||||
|
||||
this.computeGeometry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup buffers for the geometry
|
||||
* - we are using indexed geometry to avoid duplicating vertices
|
||||
*/
|
||||
private setupBuffers() {
|
||||
const { pointsPerPlane, width, depth } = this;
|
||||
// Total number of floats in the buffer
|
||||
// - Each point has DataPlaneShapeGeometry.pointComponentSize components
|
||||
// - Each plane has pointsPerPlane points and we have 2 planes
|
||||
const bufferElementSize = pointsPerPlane * 2 * DataPlaneShapeGeometry.pointComponentSize;
|
||||
|
||||
if (!this.pointBuffer) {
|
||||
this.pointBuffer = new Float32Array(bufferElementSize);
|
||||
} else if (this.pointBuffer.length !== pointsPerPlane * bufferElementSize * 2) {
|
||||
// Recreate buffer if size changed
|
||||
delete this.pointBuffer;
|
||||
this.pointBuffer = new Float32Array(bufferElementSize);
|
||||
}
|
||||
// All indices for geometry sides (front,back,left,right)
|
||||
const numEdgeIndices = (width - 1 + (depth - 1)) * 2 * 3 * 2;
|
||||
// All indices for geometry top and bottom (top, bottom)
|
||||
const numPlaneIndices = this.numPolygonsPerPlane * 3 * 2;
|
||||
const numIndices = numEdgeIndices + numPlaneIndices;
|
||||
if (!this.indexBuffer) {
|
||||
this.indexBuffer = new Uint32Array(numIndices);
|
||||
} else if (this.indexBuffer.length !== numIndices) {
|
||||
// Recreate buffer if size changed
|
||||
delete this.indexBuffer;
|
||||
this.indexBuffer = new Uint32Array(numIndices);
|
||||
}
|
||||
}
|
||||
|
||||
private computeGeometry() {
|
||||
const { normalizedData, width, depth, pointsPerPlane } = this;
|
||||
|
||||
this.setupBuffers();
|
||||
|
||||
// Sanity check
|
||||
if (this.pointBuffer === undefined || this.indexBuffer === undefined) {
|
||||
throw new Error('Buffers not initialized');
|
||||
}
|
||||
|
||||
const vertices = this.pointBuffer;
|
||||
const indices = this.indexBuffer;
|
||||
|
||||
const bufferElementSize = 3;
|
||||
const numPolygons = (width - 1) * (depth - 1) * 2;
|
||||
|
||||
const hasBottomLayer = this.previousNormalizedData !== undefined;
|
||||
|
||||
// Add all points to the geometry
|
||||
for (let z = 0; z < depth; z++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const pointIdx = z * width + x;
|
||||
const vertexIdx = pointIdx * bufferElementSize;
|
||||
const indexIdx = (z * width + x) * 6;
|
||||
|
||||
// Add top plane coordinates
|
||||
vertices[vertexIdx] = (x / width) * 2.0 - 1.0; //x
|
||||
vertices[vertexIdx + 1] = normalizedData[z][x]; //y
|
||||
vertices[vertexIdx + 2] = (z / depth) * 2.0 - 1.0; //z
|
||||
|
||||
// Add bottom plane coordinates
|
||||
vertices[vertexIdx + pointsPerPlane * bufferElementSize] = (x / width) * 2.0 - 1.0; //x
|
||||
vertices[vertexIdx + pointsPerPlane * bufferElementSize + 1] = hasBottomLayer
|
||||
? this.previousNormalizedData?.[z][x] ?? 0 // syntax enforced by strict null checks (should never happen)
|
||||
: 0;
|
||||
vertices[vertexIdx + pointsPerPlane * bufferElementSize + 2] = (z / depth) * 2.0 - 1.0; //z
|
||||
|
||||
if (z === depth - 1 || x === width - 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if value is 0
|
||||
// if (
|
||||
// highestValues[z][x] === 0 &&
|
||||
// highestValues[z][x + 1] === 0 &&
|
||||
// highestValues[z + 1][x] === 0 &&
|
||||
// highestValues[z + 1][x + 1] === 0
|
||||
// ) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// Add top plane indices
|
||||
indices[indexIdx] = pointIdx + width;
|
||||
indices[indexIdx + 1] = pointIdx + 1;
|
||||
indices[indexIdx + 2] = pointIdx;
|
||||
|
||||
indices[indexIdx + 3] = pointIdx + width;
|
||||
indices[indexIdx + 4] = pointIdx + width + 1;
|
||||
indices[indexIdx + 5] = pointIdx + 1;
|
||||
|
||||
// Add bottom plane indices
|
||||
indices[indexIdx + numPolygons * 3] = pointIdx + pointsPerPlane;
|
||||
indices[indexIdx + numPolygons * 3 + 1] = pointIdx + 1 + pointsPerPlane;
|
||||
indices[indexIdx + numPolygons * 3 + 2] = pointIdx + width + pointsPerPlane;
|
||||
|
||||
indices[indexIdx + numPolygons * 3 + 3] = pointIdx + 1 + pointsPerPlane;
|
||||
indices[indexIdx + numPolygons * 3 + 4] = pointIdx + width + 1 + pointsPerPlane;
|
||||
indices[indexIdx + numPolygons * 3 + 5] = pointIdx + width + pointsPerPlane;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw walls after top and bottom planes
|
||||
const wallIdxOffset = numPolygons * 3 * 2;
|
||||
|
||||
// Draw front & back polygons
|
||||
for (let x = 0; x < width - 1; x++) {
|
||||
const indexIdx = wallIdxOffset + x * 6;
|
||||
|
||||
// Draw back wall
|
||||
indices[indexIdx] = x;
|
||||
indices[indexIdx + 1] = x + 1;
|
||||
indices[indexIdx + 2] = pointsPerPlane + x;
|
||||
|
||||
indices[indexIdx + 3] = x + 1;
|
||||
indices[indexIdx + 4] = pointsPerPlane + x + 1;
|
||||
indices[indexIdx + 5] = pointsPerPlane + x;
|
||||
|
||||
const frontWallIdx = wallIdxOffset + (width - 1 + x) * 6;
|
||||
|
||||
// Draw front wall
|
||||
indices[frontWallIdx] = pointsPerPlane * 2 - width + x;
|
||||
indices[frontWallIdx + 1] = x + 1 + pointsPerPlane - width;
|
||||
indices[frontWallIdx + 2] = x + pointsPerPlane - width;
|
||||
|
||||
indices[frontWallIdx + 3] = pointsPerPlane * 2 - width + x;
|
||||
indices[frontWallIdx + 4] = pointsPerPlane * 2 - width + x + 1;
|
||||
indices[frontWallIdx + 5] = x + 1 + pointsPerPlane - width;
|
||||
}
|
||||
|
||||
const sideWallIdxOffset = wallIdxOffset + (width - 1) * 6 * 2;
|
||||
|
||||
// Draw side walls
|
||||
for (let z = 0; z < depth - 1; z++) {
|
||||
const indexIdx = sideWallIdxOffset + z * 6;
|
||||
|
||||
// Draw left wall
|
||||
indices[indexIdx] = pointsPerPlane + z * width;
|
||||
indices[indexIdx + 1] = z * width + width;
|
||||
indices[indexIdx * 2] = z * width;
|
||||
|
||||
console.log({ x: indices[indexIdx], y: indices[indexIdx + 1], z: indices[indexIdx + 2] });
|
||||
|
||||
// indices[indexIdx + 3] = z * width + width;
|
||||
// indices[indexIdx + 4] = pointsPerPlane + z * width + width;
|
||||
// indices[indexIdx + 5] = pointsPerPlane + z * width;
|
||||
|
||||
// const rightWallIdx = sideWallIdxOffset + (depth - 1 + z) * 6;
|
||||
// indices[rightWallIdx] = z * width + width - 1;
|
||||
// indices[rightWallIdx + 1] = z * width + width - 1 + width;
|
||||
// indices[rightWallIdx + 2] = pointsPerPlane + z * width + width - 1;
|
||||
|
||||
// indices[rightWallIdx + 3] = z * width + width - 1 + width;
|
||||
// indices[rightWallIdx + 4] = pointsPerPlane + z * width + width - 1 + width;
|
||||
// indices[rightWallIdx + 5] = pointsPerPlane + z * width + width - 1;
|
||||
}
|
||||
|
||||
this.setAttribute(
|
||||
'position',
|
||||
new THREE.BufferAttribute(vertices, DataPlaneShapeGeometry.pointComponentSize)
|
||||
);
|
||||
this.setIndex(new THREE.Uint32BufferAttribute(indices, 1));
|
||||
this.computeVertexNormals();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
// Define your colors
|
||||
const color1 = new THREE.Color('#F0F624');
|
||||
const color2 = new THREE.Color('#C5407D');
|
||||
const color3 = new THREE.Color('#15078A');
|
||||
|
||||
export class DataPlaneShapeMaterial extends THREE.ShaderMaterial {
|
||||
static vertexShader = `
|
||||
varying float y;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
#if NUM_DIR_LIGHTS > 0
|
||||
|
||||
struct DirectionalLight {
|
||||
vec3 direction;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];
|
||||
|
||||
void applyDirectionalLight(int index, vec3 normal, vec3 viewDir) {
|
||||
DirectionalLight light = directionalLights[ index ];
|
||||
// ... apply lighting based on light direction and color
|
||||
}
|
||||
|
||||
#endif
|
||||
void main() {
|
||||
y = position.y;
|
||||
vNormal = normalize( normalMatrix * normal );
|
||||
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
||||
vViewPosition = -mvPosition.xyz;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`;
|
||||
|
||||
// Define the fragment shader that uses the y-coordinate to color
|
||||
static fragmentShader = `
|
||||
uniform float time;
|
||||
uniform vec3 colorA;
|
||||
uniform vec3 colorB;
|
||||
uniform vec3 lightPosition;
|
||||
|
||||
varying vec3 vNormal;
|
||||
varying float y;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
void main() {
|
||||
// Use the y-coordinate to determine the color
|
||||
vec3 color = mix(colorA, colorB, y);
|
||||
|
||||
// // ambient
|
||||
// float ambientStrength = 0.95;
|
||||
// vec3 ambient = ambientStrength * color;
|
||||
|
||||
// // diffuse
|
||||
// vec3 norm = normalize(vNormal);
|
||||
// vec3 lightDir = normalize(lightPosition - vViewPosition);
|
||||
// float diff = max(dot(norm, lightDir), 0.0);
|
||||
// vec3 diffuse = diff * color;
|
||||
|
||||
// // specular
|
||||
// float specularStrength = 0.05;
|
||||
// vec3 viewDir = normalize(-vViewPosition);
|
||||
// vec3 reflectDir = reflect(-lightDir, norm);
|
||||
// float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
|
||||
// vec3 specular = specularStrength * spec * color;
|
||||
|
||||
// vec3 result = (ambient + diffuse + specular);
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}`;
|
||||
|
||||
constructor(
|
||||
colorA: THREE.Color = color2,
|
||||
colorB: THREE.Color = color3,
|
||||
options: Omit<THREE.ShaderMaterialParameters, 'lights'> = {}
|
||||
) {
|
||||
super({
|
||||
...options,
|
||||
vertexShader: DataPlaneShapeMaterial.vertexShader,
|
||||
fragmentShader: DataPlaneShapeMaterial.fragmentShader,
|
||||
// lights: true,
|
||||
// wireframe: true,
|
||||
side: THREE.DoubleSide,
|
||||
uniforms: THREE.UniformsUtils.merge([
|
||||
THREE.UniformsLib['common'],
|
||||
THREE.UniformsLib['lights'],
|
||||
|
||||
{
|
||||
colorA: { value: colorA },
|
||||
colorB: { value: colorB },
|
||||
time: { value: 0 }
|
||||
}
|
||||
])
|
||||
});
|
||||
// super.onBeforeCompile = function (shader) {
|
||||
// // Add custom uniforms if needed
|
||||
// shader.uniforms.customColor1 = { value: new THREE.Color(0xff0000) }; // Red
|
||||
// shader.uniforms.customColor2 = { value: new THREE.Color(0x0000ff) }; // Blue
|
||||
|
||||
// // Replace gl_FragColor with your own color logic
|
||||
// shader.fragmentShader = shader.fragmentShader.replace(
|
||||
// 'gl_FragColor = vec4( outgoingLight, diffuseColor.a );',
|
||||
// [
|
||||
// 'float gradientFactor = (normal.y + 1.0) / 2.0;', // Make it range from 0 to 1
|
||||
// 'vec3 gradientColor = mix(customColor1, customColor2, gradientFactor);',
|
||||
// 'gl_FragColor = vec4( gradientColor, diffuseColor.a );'
|
||||
// ].join('\n')
|
||||
// );
|
||||
|
||||
// console.log(shader.fragmentShader);
|
||||
// };
|
||||
}
|
||||
}
|
||||
+214
-17
@@ -1,29 +1,94 @@
|
||||
<script lang="ts">
|
||||
import Papa from 'papaparse';
|
||||
import type { ParseResult } from 'papaparse';
|
||||
import type { PageServerData } from './$types';
|
||||
import type { DataEntry, EntryDefinition, FilterEntry } from './proxy+page.server';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import DropdownSelect from '$lib/components/DropdownSelect.svelte';
|
||||
import BasicGraph from '$lib/components/BasicGraph.svelte';
|
||||
import LoadingOverlay from '$lib/components/LoadingOverlay.svelte';
|
||||
import type { Vector2 } from 'three';
|
||||
import { useDataStore } from '../store/DataStore';
|
||||
import { browser } from '$app/environment';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import type { AsyncDuckDBConnection } from '@duckdb/duckdb-wasm';
|
||||
|
||||
export let isLoading = false;
|
||||
export let isLoading = true;
|
||||
|
||||
export let data: PageServerData;
|
||||
let numberOfElementsOptions: string[] = [];
|
||||
|
||||
// TODO: either generate or manually write types for csv data
|
||||
type CsvData = object;
|
||||
type CsvData = {
|
||||
fpr: number;
|
||||
k: number;
|
||||
size: number;
|
||||
n_threads: number;
|
||||
n_partitions: number;
|
||||
n_elements_build: number;
|
||||
n_elements_lookup: number;
|
||||
shared_elements: number;
|
||||
name: string;
|
||||
fixture: string;
|
||||
s: number;
|
||||
duration: number;
|
||||
split_name: string[];
|
||||
};
|
||||
|
||||
let selectedFilters: FilterEntry[] | undefined = [];
|
||||
let selectedNumberOfElements: string | undefined = undefined;
|
||||
// List of possible test sizes (e.g. 10, 100, 1000, 10000)
|
||||
|
||||
let filterData: number[][][] = [
|
||||
[
|
||||
[2, 3, 4, 5, 6, 7],
|
||||
[20, 2, 3, 4, 50, 6],
|
||||
[20, 2, 3, 4, 5, 6],
|
||||
[3, 4, 5, 6, 7, 8],
|
||||
[20, 2, 3, 4, 5, 6]
|
||||
],
|
||||
[
|
||||
[2, 3, 4, 5, 6, 7],
|
||||
[20, 2, 3, 4, 50, 6],
|
||||
[20, 2, 3, 4, 5, 6],
|
||||
[3, 4, 5, 6, 7, 8],
|
||||
[20, 2, 3, 4, 5, 6]
|
||||
],
|
||||
[[], [20, 2, 3, 4, 5, 6], [20, 2, 3, 4, 5, 6], [3, 4, 5, 6, 7, 8], [20, 2, 3, 4, 5, 6]]
|
||||
];
|
||||
|
||||
let db = useDataStore();
|
||||
let dbConnection: AsyncDuckDBConnection | undefined = undefined;
|
||||
onMount(() => {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.subscribe((dbInstance) => {
|
||||
if (!dbInstance) {
|
||||
dbConnection = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
dbInstance
|
||||
.connect()
|
||||
.then((connection) => {
|
||||
console.log('Connected to DB');
|
||||
dbConnection = connection;
|
||||
isLoading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Failed to connect to DB', e);
|
||||
dbConnection = undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function fetchAndParseCSV(csvUrl: string) {
|
||||
const url = new URL(csvUrl, location.href);
|
||||
|
||||
console.log(url);
|
||||
|
||||
const promise = new Promise<Papa.ParseResult<CsvData>>((resolve, reject) => {
|
||||
Papa.parse<CsvData>(url.href, {
|
||||
download: true,
|
||||
@@ -32,7 +97,30 @@
|
||||
dynamicTyping: true,
|
||||
skipEmptyLines: true,
|
||||
complete: function (results) {
|
||||
console.log('Finished:', results);
|
||||
// TODO: cleanup into a separate parser function
|
||||
if (results.errors.length > 0) {
|
||||
console.error(results.errors);
|
||||
reject(results.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: use generated types
|
||||
results.data.forEach((row: any) => {
|
||||
// console.log(d);
|
||||
row['split_name'] = row['name'].split('/')[0].split('_');
|
||||
|
||||
row['k'] = parseInt(row['split_name'][row['split_name'].length - 1], 10);
|
||||
row['fixture'] = row['name'].split('/')[1];
|
||||
row['s'] = parseFloat(row['name'].split('/')[2]) / 100;
|
||||
row['n_threads'] = parseInt(row['name'].split('/')[3], 10);
|
||||
row['n_partitions'] = parseInt(row['name'].split('/')[4], 10);
|
||||
row['n_elements_build'] = parseInt(row['name'].split('/')[5], 10);
|
||||
row['n_elements_lookup'] = parseInt(row['name'].split('/')[6], 10);
|
||||
row['shared_elements'] = parseFloat(row['name'].split('/')[7]) / 100;
|
||||
row['name'] = row['split_name'].slice(0, row['split_name'].length - 1).join('_');
|
||||
});
|
||||
|
||||
// Analyze data
|
||||
results.errors.length > 0 ? reject(results.errors) : resolve(results);
|
||||
}
|
||||
});
|
||||
@@ -40,7 +128,8 @@
|
||||
return promise;
|
||||
}
|
||||
|
||||
let filterPromise: Promise<Papa.ParseResult<object>[]> | undefined = undefined;
|
||||
let hoverPosition: Vector2 | undefined = undefined;
|
||||
let filterPromise: Promise<Papa.ParseResult<CsvData>[]> | undefined = undefined;
|
||||
// TODO: type info values
|
||||
let infoPromise: Promise<any> | undefined = undefined;
|
||||
|
||||
@@ -59,10 +148,112 @@
|
||||
f.entries.find((e) => e.name.includes(selectedNumberOfElements!))
|
||||
);
|
||||
|
||||
console.log(csvFiles);
|
||||
// console.log(csvFiles);
|
||||
|
||||
// let dbLoadPromise = Promise.all(
|
||||
// csvFiles.map((f) =>
|
||||
// dbConnection?.insertCSVFromPath(new URL(f!.dataUrl, location.href).href, {
|
||||
// name: f!.filterName,
|
||||
// detect: true
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
|
||||
// dbLoadPromise
|
||||
// .then((results) => {
|
||||
// console.debug('DuckDB loaded tables', results);
|
||||
// isLoading = false;
|
||||
|
||||
// dbConnection?.getTableNames('').then((names) => {
|
||||
// console.log('Table names', names);
|
||||
// });
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// console.error('DB load failed:', e);
|
||||
// isLoading = false;
|
||||
// });
|
||||
|
||||
filterPromise = Promise.all(csvFiles.map((f) => fetchAndParseCSV(f!.dataUrl)));
|
||||
filterPromise?.finally(() => (isLoading = false));
|
||||
|
||||
let tempFilterData: any;
|
||||
|
||||
// Prepare data for display when all CSV files are loaded
|
||||
filterPromise.then((csvs) => {
|
||||
console.log(csvs);
|
||||
|
||||
type ValueGroup = { value: number; rows: CsvData[] };
|
||||
// Create a list of all unique sizes and fprs
|
||||
const mbSizeGroup: ValueGroup[] = [];
|
||||
const fprGroup: ValueGroup[] = [];
|
||||
|
||||
let maxFpr: number = 0.0;
|
||||
let maxSize: number = 0;
|
||||
|
||||
const steps = 10;
|
||||
|
||||
// TODO: covnert to single pass
|
||||
// Compute range of values for each size and fpr
|
||||
csvs.forEach((csv) => {
|
||||
csv.data.forEach((row) => {
|
||||
maxFpr = Math.max(maxFpr, row.fpr);
|
||||
maxSize = Math.max(maxSize, row.size);
|
||||
});
|
||||
});
|
||||
|
||||
// Create groups for each size and fpr
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const fpr = i / steps;
|
||||
const size = Math.round((i / steps) * maxSize);
|
||||
|
||||
fprGroup.push({ value: fpr, rows: [] });
|
||||
mbSizeGroup.push({ value: size, rows: [] });
|
||||
}
|
||||
|
||||
const indexForFpr = (fpr: number) => {
|
||||
return Math.round((fpr / maxFpr) * (steps - 1));
|
||||
};
|
||||
|
||||
// Go over all entries and place them into the correct buckets
|
||||
csvs.forEach((csv) => {
|
||||
csv.data.forEach((row) => {
|
||||
const fprIndex = indexForFpr(row.fpr);
|
||||
const sizeIndex = Math.round((row.size / maxSize) * (steps - 1));
|
||||
// console.log(fprIndex, sizeIndex);
|
||||
// fprGroup[fprIndex].rows.push(row);
|
||||
mbSizeGroup[sizeIndex].rows.push(row);
|
||||
});
|
||||
});
|
||||
|
||||
// Construct a datamap sorting all CSV values by size and fpr\
|
||||
console.log(fprGroup, mbSizeGroup);
|
||||
|
||||
tempFilterData = mbSizeGroup.map((size) => {
|
||||
let fprArray: number[][] = [];
|
||||
|
||||
// Populate fprArray
|
||||
for (let i = 0; i < steps; i++) {
|
||||
fprArray.push(
|
||||
size.rows
|
||||
.filter((row) => {
|
||||
const fprIndex = indexForFpr(row.fpr);
|
||||
return fprIndex === i;
|
||||
})
|
||||
.map((row) => (row.n_elements_lookup / row.duration) * 0.0000001)
|
||||
);
|
||||
}
|
||||
|
||||
return fprArray;
|
||||
});
|
||||
|
||||
// csvs.forEach((csv, i) => {
|
||||
// const filter = selectedFilters![i];
|
||||
// const entries = filter.entries.filter((e) => e.name.includes(selectedNumberOfElements!));
|
||||
});
|
||||
|
||||
filterPromise?.finally(() => {
|
||||
isLoading = false;
|
||||
filterData = tempFilterData;
|
||||
});
|
||||
|
||||
// Load info files
|
||||
infoPromise = Promise.all(csvFiles.map((f) => fetch(f!.infoUrl).then((r) => r.json())));
|
||||
@@ -140,15 +331,17 @@
|
||||
{/if} -->
|
||||
<div class="lg:flex w-full">
|
||||
<div class="flex-grow flex-shrink">
|
||||
<BasicGraph
|
||||
data={[
|
||||
[2, 3, 4, 5, 6, 7],
|
||||
[20, 2, 3, 4, 5, 6],
|
||||
[20, 2, 3, 4, 5, 6],
|
||||
[3, 4, 5, 6, 7, 8],
|
||||
[20, 2, 3, 4, 5, 6]
|
||||
]}
|
||||
/>
|
||||
<div>
|
||||
<BasicGraph
|
||||
data={filterData}
|
||||
onHover={(obj, pos) => {
|
||||
// hoverPosition = pos;
|
||||
}}
|
||||
zAxisLabel="Size in Bytes"
|
||||
xAxisLabel="False positive rate"
|
||||
yAxisLabel="Throughput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-96 pt-4 flex-grow-0">
|
||||
<div class="mb-4 p-4 text-left rounded-2xl border-secondary-200 bg-white border shadow-xl">
|
||||
@@ -195,7 +388,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if hoverPosition}
|
||||
<div class="absolute border-t-2 mt-6" style="left: {hoverPosition.x}; top: {hoverPosition.y}">
|
||||
<h2>Hovering over {hoverPosition}</h2>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isLoading}
|
||||
<LoadingOverlay isLoading={true} />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as duckdb from '@duckdb/duckdb-wasm';
|
||||
import duckdbWasm from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm?url';
|
||||
import duckdbWorker from '@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js?worker';
|
||||
|
||||
import type { AsyncDuckDB } from '@duckdb/duckdb-wasm';
|
||||
import { useWritable } from './utils';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
const uniqueStoreName = 'dataStore';
|
||||
|
||||
let db: AsyncDuckDB | null = null;
|
||||
let loading = false;
|
||||
|
||||
export const initDuckDB = async () => {
|
||||
if (db) {
|
||||
return db; // Return existing database, if any
|
||||
}
|
||||
|
||||
// Instantiate worker
|
||||
const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.DEBUG);
|
||||
const worker = new duckdbWorker();
|
||||
|
||||
// and asynchronous database
|
||||
db = new duckdb.AsyncDuckDB(logger, worker);
|
||||
await db.instantiate(duckdbWasm);
|
||||
return db;
|
||||
};
|
||||
|
||||
export const useDataStore = () =>
|
||||
useWritable<AsyncDuckDB | undefined>(uniqueStoreName, undefined, (set) => {
|
||||
if (loading || !browser) {
|
||||
return;
|
||||
}
|
||||
console.debug('useDataStore');
|
||||
loading = true;
|
||||
|
||||
initDuckDB().then((db) => {
|
||||
// FIXME: remove after debug
|
||||
// add db to window object for use in console
|
||||
// @ts-ignore
|
||||
window.db = db;
|
||||
|
||||
console.debug('DuckDB instantiated', db);
|
||||
set(db);
|
||||
});
|
||||
|
||||
return () => {
|
||||
console.debug('useDataStore cleanup');
|
||||
db?.terminate();
|
||||
db = null;
|
||||
};
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { get } from 'svelte/store';
|
||||
import { useWritable } from './utils';
|
||||
|
||||
export enum Theme {
|
||||
Light = 'light',
|
||||
@@ -17,15 +17,17 @@ const initialAppSettings: AppSettings = {
|
||||
theme: Theme.Light
|
||||
};
|
||||
|
||||
export const appSettingsStore: Writable<AppSettings> = writable(initialAppSettings);
|
||||
export const useSettingsStore = () => useWritable('appSettings', initialAppSettings);
|
||||
|
||||
export const updateTheme = (theme: Theme) => {
|
||||
appSettingsStore.update((settings) => {
|
||||
const store = useSettingsStore();
|
||||
store.update((settings) => {
|
||||
return { ...settings, theme };
|
||||
});
|
||||
};
|
||||
|
||||
export const toggleThemeMode = () => {
|
||||
console.log('toggleThemeMode', appSettingsStore);
|
||||
updateTheme(appSettingsStore.theme === Theme.Dark ? Theme.Light : Theme.Dark);
|
||||
const store = useSettingsStore();
|
||||
const value = get(store);
|
||||
updateTheme(value.theme === Theme.Dark ? Theme.Light : Theme.Dark);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { getContext, hasContext, setContext } from 'svelte';
|
||||
import { readable, writable, type StartStopNotifier } from 'svelte/store';
|
||||
|
||||
// Based on context handling suggested by https://dev.to/jdgamble555/the-correct-way-to-use-stores-in-sveltekit-3h6i
|
||||
|
||||
// context for any type of store
|
||||
export const useSharedStore = <T, A>(
|
||||
name: string,
|
||||
fn: (value?: A, startStop?: StartStopNotifier<A>) => T,
|
||||
defaultValue?: A,
|
||||
startStop?: StartStopNotifier<A>
|
||||
) => {
|
||||
if (hasContext(name)) {
|
||||
return getContext<T>(name);
|
||||
}
|
||||
const _value = fn(defaultValue, startStop);
|
||||
setContext(name, _value);
|
||||
return _value;
|
||||
};
|
||||
|
||||
// writable store context
|
||||
export const useWritable = <T>(name: string, value: T, startStop?: StartStopNotifier<T>) =>
|
||||
useSharedStore(name, writable, value, startStop);
|
||||
|
||||
// readable store context
|
||||
export const useReadable = <T>(name: string, value: T, startStop?: StartStopNotifier<T>) =>
|
||||
useSharedStore(name, readable, value, startStop);
|
||||
Reference in New Issue
Block a user