diff --git a/package.json b/package.json
index 3ccc126..d5eb4ee 100644
--- a/package.json
+++ b/package.json
@@ -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"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b892009..9cb7bc0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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==}
diff --git a/src/lib/Sidebar.svelte b/src/lib/Sidebar.svelte
index 68d5ab0..44bd824 100644
--- a/src/lib/Sidebar.svelte
+++ b/src/lib/Sidebar.svelte
@@ -1,15 +1,16 @@
diff --git a/src/lib/rendering/BarRenderer.ts b/src/lib/rendering/BarRenderer.ts
index 669675c..1e2d5b0 100644
--- a/src/lib/rendering/BarRenderer.ts
+++ b/src/lib/rendering/BarRenderer.ts
@@ -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 {
- 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 {
...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 {
)
);
+ 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 {
}
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 {
diff --git a/src/lib/rendering/GraphRenderer.ts b/src/lib/rendering/GraphRenderer.ts
index fd6d9fa..c70cd0f 100644
--- a/src/lib/rendering/GraphRenderer.ts
+++ b/src/lib/rendering/GraphRenderer.ts
@@ -1,4 +1,4 @@
-export abstract class GraphRenderer {
+export abstract class GraphRenderer {
constructor(public scene: THREE.Scene, public camera: THREE.Camera) {}
abstract destroy(): void;
@@ -8,4 +8,6 @@ export abstract class GraphRenderer {
* @param data
*/
abstract updateWithData(data: T): void;
+
+ abstract getIntersections(raycaster: THREE.Raycaster): THREE.Intersection[];
}
diff --git a/src/lib/rendering/PlainRenderer.ts b/src/lib/rendering/PlainRenderer.ts
new file mode 100644
index 0000000..76b2e99
--- /dev/null
+++ b/src/lib/rendering/PlainRenderer.ts
@@ -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 {
+ 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 = {}
+ ) {
+ 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;
+ }
+}
diff --git a/src/lib/rendering/geometry/DataPlaneGeometry.ts b/src/lib/rendering/geometry/DataPlaneGeometry.ts
new file mode 100644
index 0000000..722681d
--- /dev/null
+++ b/src/lib/rendering/geometry/DataPlaneGeometry.ts
@@ -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();
+ }
+}
diff --git a/src/lib/rendering/materials/DataPlaneMaterial.ts b/src/lib/rendering/materials/DataPlaneMaterial.ts
new file mode 100644
index 0000000..b3c0dc7
--- /dev/null
+++ b/src/lib/rendering/materials/DataPlaneMaterial.ts
@@ -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 = {}
+ ) {
+ 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);
+ // };
+ }
+}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 4844ce8..8ee4da0 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,29 +1,94 @@