From 7149642adff52d9afdba59190125cd92d028a48f Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Mon, 21 Oct 2024 14:36:19 +0200 Subject: [PATCH 01/46] upgrade packages --- package-lock.json | 286 +++++++++++++++++++++------------------------- 1 file changed, 129 insertions(+), 157 deletions(-) diff --git a/package-lock.json b/package-lock.json index 284ce44..abc13c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,6 +99,12 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/generator": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", @@ -460,22 +466,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@emotion/cache": { "version": "11.13.1", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", @@ -1006,9 +996,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1047,9 +1037,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", - "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1063,9 +1053,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", + "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", "dependencies": { "levn": "^0.4.1" }, @@ -1479,9 +1469,9 @@ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==" }, "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==" + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1540,9 +1530,9 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==" + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz", + "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==" }, "node_modules/@types/lodash.mergewith": { "version": "4.6.9", @@ -1602,12 +1592,6 @@ "meshoptimizer": "~0.18.1" } }, - "node_modules/@types/three/node_modules/@tweenjs/tween.js": { - "version": "23.1.3", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", - "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", - "dev": true - }, "node_modules/@types/webxr": { "version": "0.5.20", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.20.tgz", @@ -1615,16 +1599,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.9.0.tgz", - "integrity": "sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", + "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.9.0", - "@typescript-eslint/type-utils": "8.9.0", - "@typescript-eslint/utils": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0", + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/type-utils": "8.10.0", + "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1648,15 +1632,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.9.0.tgz", - "integrity": "sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", + "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.9.0", - "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/typescript-estree": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0", + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/typescript-estree": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "debug": "^4.3.4" }, "engines": { @@ -1676,13 +1660,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", - "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", + "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0" + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1693,13 +1677,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.9.0.tgz", - "integrity": "sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", + "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.9.0", - "@typescript-eslint/utils": "8.9.0", + "@typescript-eslint/typescript-estree": "8.10.0", + "@typescript-eslint/utils": "8.10.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1717,9 +1701,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", - "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", + "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1730,13 +1714,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", - "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", + "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1794,15 +1778,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.9.0.tgz", - "integrity": "sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.9.0", - "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/typescript-estree": "8.9.0" + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/typescript-estree": "8.10.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1816,12 +1800,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", - "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", + "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/types": "8.10.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1850,9 +1834,9 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", - "integrity": "sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", "dev": true, "dependencies": { "@babel/core": "^7.25.2", @@ -2356,6 +2340,14 @@ "node": ">=4" } }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2380,10 +2372,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/copy-to-clipboard": { "version": "3.3.3", @@ -2408,23 +2399,6 @@ "node": ">=10" } }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cross-fetch": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", @@ -2724,9 +2698,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.40", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.40.tgz", - "integrity": "sha512-LYm78o6if4zTasnYclgQzxEcgMoIcybWOhkATWepN95uwVVWV0/IW10v+2sIeHE+bIYWipLneTftVyQm45UY7g==", + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", "dev": true }, "node_modules/error-ex": { @@ -2935,24 +2909,27 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -3216,9 +3193,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", - "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.13.tgz", + "integrity": "sha512-f1EppwrpJRWmqDTyvAyomFVDYRtrS7iTEqv3nokETnMiMzs2SSTmKRTACce4O2p4jYyowiSMvpdwC/RLcMFhuQ==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -3336,17 +3313,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4611,6 +4577,18 @@ "node": ">=6" } }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4999,15 +4977,20 @@ } }, "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dependencies": { + "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/path-exists": { @@ -5342,9 +5325,9 @@ } }, "node_modules/react-force-graph-3d": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/react-force-graph-3d/-/react-force-graph-3d-1.24.4.tgz", - "integrity": "sha512-7EK1L5DvRk3mqevi3lo6xKXhnFK8FlYgSR2drUuB4hD5xDrco3IyB2TRJuewEdW+lRP5lxcxWX5UQgNASZtkTQ==", + "version": "1.24.5", + "resolved": "https://registry.npmjs.org/react-force-graph-3d/-/react-force-graph-3d-1.24.5.tgz", + "integrity": "sha512-w9uTytRJ7Phqb9HcILtM6j8rkmCZtsVi+YNnZ8GtiLYA6ebXxYOTSBhHCbchaU9O/be9zIWS9jqJcAcPFumxog==", "dependencies": { "3d-force-graph": "^1.73", "prop-types": "15", @@ -5940,17 +5923,6 @@ "node": ">=6.0.0" } }, - "node_modules/standard/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/standard/node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -6346,9 +6318,9 @@ } }, "node_modules/three-render-objects": { - "version": "1.29.5", - "resolved": "https://registry.npmjs.org/three-render-objects/-/three-render-objects-1.29.5.tgz", - "integrity": "sha512-OLtETrjF184NuaaI/vpRlIP9FxVNAgBBCgWYXhGFUDnPdl/2iX8rialUPGA1gEXvOTiKyepArVgm1LUkJw15rQ==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/three-render-objects/-/three-render-objects-1.30.0.tgz", + "integrity": "sha512-46sckKM9VdsDuCe0u1W7BNME5RinDujuwLc2ANviw60f3uCc5pSlAb2fXGZwPz+vb7aq5AAZvHm94bTCkBk+pQ==", "dependencies": { "@tweenjs/tween.js": "18 - 25", "accessor-fn": "1", @@ -6538,14 +6510,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.9.0.tgz", - "integrity": "sha512-AuD/FXGYRQyqyOBCpNLldMlsCGvmDNxptQ3Dp58/NXeB+FqyvTfXmMyba3PYa0Vi9ybnj7G8S/yd/4Cw8y47eA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.10.0.tgz", + "integrity": "sha512-YIu230PeN7z9zpu/EtqCIuRVHPs4iSlqW6TEvjbyDAE3MZsSl2RXBo+5ag+lbABCG8sFM1WVKEXhlQ8Ml8A3Fw==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.9.0", - "@typescript-eslint/parser": "8.9.0", - "@typescript-eslint/utils": "8.9.0" + "@typescript-eslint/eslint-plugin": "8.10.0", + "@typescript-eslint/parser": "8.10.0", + "@typescript-eslint/utils": "8.10.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" From 534b126d0a9360186376415a7dd8428e3693b172 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Mon, 21 Oct 2024 14:36:28 +0200 Subject: [PATCH 02/46] remove node_modues from eslint --- eslint.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 092408a..360139f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,7 +8,7 @@ export default tseslint.config( { ignores: ['dist'] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ['{**/*,*}.{js,ts,jsx,tsx,html,vue},{!node_modules/*}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, From 80ab9e3135a3561b5d9469a26e69c795d162b756 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Mon, 21 Oct 2024 14:36:37 +0200 Subject: [PATCH 03/46] add legend --- src/components/Graph.tsx | 88 +++++++++++++++++++++------------------ src/components/Legend.tsx | 32 ++++++++++++++ 2 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 src/components/Legend.tsx diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 7928116..848b271 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -1,7 +1,9 @@ // Graph.tsx -import React from "react"; +import * as React from "react"; import ForceGraph3D, { GraphData, NodeObject, LinkObject } from "react-force-graph-3d"; import * as THREE from "three"; +import { Box } from "@chakra-ui/react"; +import Legend from "./Legend.tsx"; interface GraphProps { graphData: GraphData; @@ -9,53 +11,57 @@ interface GraphProps { height: number; } +const groupColors: { [key: string]: string } = { + person: "red", + dataset: "blue", + organization: "green", + software: "yellow", + document: "orange", + article: "indigo", + creativeWork: "violet", + service: "cyan", + "": "gray", // Default group +}; + const Graph: React.FC = ({ graphData, width, height }) => { const getGroupColor = (group: string) => { - const colors: { [key: string]: string } = { - person: "red", - dataset: "blue", - organization: "green", - software: "yellow", - document: "orange", - article: "indigo", - creativeWork: "violet", - service: "cyan", - "": "gray", // Default group - }; - return colors[group] || "gray"; // Default to gray if group not found + return groupColors[group] || "gray"; // Default to gray if group not found }; const getNodeById = (id: string) => graphData.nodes.find((node: NodeObject) => node.id === id); return ( - getGroupColor(d.group || "")} - linkAutoColorBy={(d) => { - const sourceNode = - typeof d.source === "object" ? d.source : getNodeById(d.source?.toString() ?? ""); - return getGroupColor(sourceNode?.group || ""); - }} - linkWidth={2} - nodeLabel={(d) => `${(d as NodeObject).label || (d as NodeObject).id}`} // Show the label or fallback to id - linkLabel={(d) => `${(d as LinkObject).label}`} // Show the label for links - linkDirectionalArrowLength={2.5} - linkDirectionalArrowRelPos={1} - linkCurvature={0.2} - nodeThreeObject={({ group }) => { - const geometry = - group === "" ? new THREE.BoxGeometry(8, 8, 8) : new THREE.SphereGeometry(5); - - const material = new THREE.MeshStandardMaterial({ - color: getGroupColor(group), - roughness: 0.7, - }); - - return new THREE.Mesh(geometry, material); - }} - /> + + getGroupColor(d.group || "")} + linkAutoColorBy={(d) => { + const sourceNode = + typeof d.source === "object" ? d.source : getNodeById(d.source?.toString() ?? ""); + return getGroupColor(sourceNode?.group || ""); + }} + linkWidth={2} + nodeLabel={(d) => `${(d as NodeObject).label || (d as NodeObject).id}`} // Show the label or fallback to id + linkLabel={(d) => `${(d as LinkObject).label}`} // Show the label for links + linkDirectionalArrowLength={2.5} + linkDirectionalArrowRelPos={1} + linkCurvature={0.2} + nodeThreeObject={({ group }) => { + const geometry = + group === "" ? new THREE.BoxGeometry(8, 8, 8) : new THREE.SphereGeometry(5); + + const material = new THREE.MeshStandardMaterial({ + color: getGroupColor(group), + roughness: 0.7, + }); + + return new THREE.Mesh(geometry, material); + }} + /> + + ); }; diff --git a/src/components/Legend.tsx b/src/components/Legend.tsx new file mode 100644 index 0000000..0851236 --- /dev/null +++ b/src/components/Legend.tsx @@ -0,0 +1,32 @@ +import { Flex, Box, Text } from "@chakra-ui/react"; +import * as React from "react"; + +interface LegendProps { + groupColors: { [key: string]: string }; +} + +const Legend: React.FC = ({ groupColors }) => { + return ( + + {Object.keys(groupColors).map((group: string) => ( + + + + {group || "Default"} + + + ))} + + ); +}; + +export default Legend; From 5022778708ef21555f89ad3bb09481ed013b371f Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Mon, 21 Oct 2024 14:36:54 +0200 Subject: [PATCH 04/46] sytling --- src/graph.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graph.d.ts b/src/graph.d.ts index 825943b..d0e6083 100644 --- a/src/graph.d.ts +++ b/src/graph.d.ts @@ -1,12 +1,12 @@ // Type definitions for the graph data type NodeType = { - id: string; + id: string; label?: string; group?: string; -} +}; type LinkType = { source: string; target: string; label?: string; -} +}; From bb606fb44a2cfcbdadc036e46eb93daa5e5bcd82 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Mon, 21 Oct 2024 17:18:18 +0200 Subject: [PATCH 05/46] filter by relevant properties --- src/utils.ts | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 9575ac0..7877739 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,6 +19,7 @@ const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): const nodesMap = new Map>(); const edges: LinkObject[] = []; + // create the element if not exists const safeUpdateElement = (id: string, label?: string, group?: string): void => { if (!nodesMap.has(id)) { // create the node @@ -43,19 +44,42 @@ const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): const pred = statement.predicate.value; const obj = statement.object.value; + // Filter out unwanted annotation properties and irrelevant predicates + // List of relevant properties you care about + const relevantProperties = new Set([ + "http://schema.org/name", + "http://schema.org/text", + "http://schema.org/affiliation", + "http://schema.org/author", + "http://schema.org/creator", + "http://schema.org/license", + "http://schema.org/keywords", + "http://schema.org/provider", + "http://schema.org/publisher", + "http://schema.org/dateModified", + "http://schema.org/datePublished", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + ]); + + // If the predicate is in the ignored list, skip this statement + if (!relevantProperties.has(pred)) { + return; // Skip this statement + } + // set node type if (pred === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") { const group = typeToGroup(obj); + if (group === "") { + return; + } + safeUpdateElement(subj, undefined, group); } else if (pred === "http://schema.org/name" || pred === "http://schema.org/text") { // Check for schema:name, foaf:name, rdfs:label, etc. const label = statement.object.value; safeUpdateElement(subj, label); - } else { - safeUpdateElement(subj); - safeUpdateElement(obj); } // Create links for relevant relationships @@ -63,7 +87,6 @@ const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): "provider", "license", "keywords", - // "identifier", "dataPublished", "dateModified", "creator", @@ -82,6 +105,9 @@ const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): if (!nodesMap.has(subj)) { nodesMap.set(subj, { id: subj, label: subj, group: "" }); } + if (!nodesMap.has(obj)) { + nodesMap.set(obj, { id: obj, label: obj, group: "" }); + } edges.push({ source: subj, target: obj, label: pred }); } }); From 0582abfa15097c26729117143d6a3904e2d883df Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 23 Oct 2024 12:58:55 +0200 Subject: [PATCH 06/46] implement filter switches --- src/components/FilterSwitch.tsx | 30 ++++++++++++++++++++++++++++++ src/components/Graph.tsx | 24 +++++++----------------- src/components/Selections.tsx | 31 ++++++++++++++++++++++--------- src/components/utils.tsx | 19 +++++++++++++++++++ 4 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 src/components/FilterSwitch.tsx create mode 100644 src/components/utils.tsx diff --git a/src/components/FilterSwitch.tsx b/src/components/FilterSwitch.tsx new file mode 100644 index 0000000..3faae1b --- /dev/null +++ b/src/components/FilterSwitch.tsx @@ -0,0 +1,30 @@ +import { FormControl, FormLabel, Switch } from "@chakra-ui/react"; +import * as React from "react"; + +interface FilterSwitchProps { + name: string; + filters: Set; + setFilters: React.Dispatch>>; +} + +const FilterSwitch = ({name,filters,setFilters}:FilterSwitchProps) => { + const isChecked = filters.has(name); + + const handleChange = (event: React.ChangeEvent) => { + if (event.target.checked) { + setFilters(new Set(filters).add(name)); + } else { + filters.delete(name); + setFilters(new Set(filters)); + } + } + + return ( + + {name} + + + ) +} + +export default FilterSwitch; \ No newline at end of file diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 848b271..0b157bf 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -4,6 +4,7 @@ import ForceGraph3D, { GraphData, NodeObject, LinkObject } from "react-force-gra import * as THREE from "three"; import { Box } from "@chakra-ui/react"; import Legend from "./Legend.tsx"; +import { groupColors, getGroupColor } from "./utils"; interface GraphProps { graphData: GraphData; @@ -11,22 +12,10 @@ interface GraphProps { height: number; } -const groupColors: { [key: string]: string } = { - person: "red", - dataset: "blue", - organization: "green", - software: "yellow", - document: "orange", - article: "indigo", - creativeWork: "violet", - service: "cyan", - "": "gray", // Default group -}; + const Graph: React.FC = ({ graphData, width, height }) => { - const getGroupColor = (group: string) => { - return groupColors[group] || "gray"; // Default to gray if group not found - }; + const getNodeById = (id: string) => graphData.nodes.find((node: NodeObject) => node.id === id); @@ -42,12 +31,13 @@ const Graph: React.FC = ({ graphData, width, height }) => { typeof d.source === "object" ? d.source : getNodeById(d.source?.toString() ?? ""); return getGroupColor(sourceNode?.group || ""); }} - linkWidth={2} nodeLabel={(d) => `${(d as NodeObject).label || (d as NodeObject).id}`} // Show the label or fallback to id linkLabel={(d) => `${(d as LinkObject).label}`} // Show the label for links - linkDirectionalArrowLength={2.5} + linkDirectionalArrowLength={7} linkDirectionalArrowRelPos={1} - linkCurvature={0.2} + linkCurvature={0.3} + linkOpacity={0.5} + linkWidth={5} nodeThreeObject={({ group }) => { const geometry = group === "" ? new THREE.BoxGeometry(8, 8, 8) : new THREE.SphereGeometry(5); diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 48bcd18..6ae90b1 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -1,7 +1,11 @@ -import { Box, Button, Checkbox, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react"; -import React, { useState, Dispatch, SetStateAction } from "react"; +import { Box, Checkbox, FormControl, FormLabel, Input, VStack,Heading,HStack } from "@chakra-ui/react"; +import * as React from "react"; +import { useState, Dispatch, SetStateAction,useEffect } from "react"; import { createGraph, rdfGraphToNodes } from "../utils"; import { GraphData } from "react-force-graph-3d"; +import {groups} from "./utils.tsx"; +import FilterSwitch from "./FilterSwitch.tsx"; + const parseRDF = async ( rdfData: string, @@ -19,7 +23,8 @@ interface SelectionsProps { const Selections: React.FC = ({ setGraphData }: SelectionsProps) => { const [file, setFile] = useState(null); - const [isChecked, setIsChecked] = useState(false); + const [isChecked, setIsChecked] = useState(false); + const [filters, setFilters] = useState>(new Set()); const handleFileChange = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0] || null; @@ -30,7 +35,7 @@ const Selections: React.FC = ({ setGraphData }: SelectionsProps setIsChecked(event.target.checked); }; - const handleSubmit = () => { + useEffect(() => { if (!file) { console.log("No file selected."); return; @@ -56,7 +61,11 @@ const Selections: React.FC = ({ setGraphData }: SelectionsProps console.log("Reading file:", file.name); reader.readAsText(file); // Read the file as text - }; + }, [file, isChecked, setGraphData]); + + + + return ( @@ -72,10 +81,14 @@ const Selections: React.FC = ({ setGraphData }: SelectionsProps - - + Filter by group + + {groups.map((group) => ( + + ))} + + + ); }; diff --git a/src/components/utils.tsx b/src/components/utils.tsx new file mode 100644 index 0000000..210173c --- /dev/null +++ b/src/components/utils.tsx @@ -0,0 +1,19 @@ +const groupColors: { [key: string]: string } = { + person: "red", + dataset: "blue", + organization: "green", + software: "yellow", + document: "orange", + article: "indigo", + creativeWork: "violet", + service: "cyan", + "": "gray", // Default group +}; + +const groups = Object.keys(groupColors).filter((group) => group !== ""); // Remove the default group + +const getGroupColor = (group: string) => { + return groupColors[group] || "gray"; // Default to gray if group not found +}; + +export { groups,getGroupColor, groupColors }; \ No newline at end of file From bca21e6619b3ef721441e327ee015edaf04b13dd Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Wed, 23 Oct 2024 22:16:04 +0200 Subject: [PATCH 07/46] add filter --- src/components/Content.tsx | 9 +++- src/components/FilterSwitch.tsx | 12 ++--- src/components/Graph.tsx | 4 -- src/components/Selections.tsx | 85 +++++++++++++++++++++++---------- src/components/utils.tsx | 2 +- src/utils.ts | 19 +++++--- 6 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 73a0653..8508d09 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -7,6 +7,7 @@ import useResizeObserver from "@react-hook/resize-observer"; // Import the hook import { GraphData } from "react-force-graph-3d"; const Content = () => { + const [filteredGraphData, setFilteredGraphData] = useState({ nodes: [], links: [] }); const [graphData, setGraphData] = useState({ nodes: [], links: [] }); const boxRef = useRef(null); // Create a ref for the Box const [size, setSize] = useState({ width: 0, height: 0 }); // Initialize size state @@ -22,10 +23,14 @@ const Content = () => { return ( - + - + ); diff --git a/src/components/FilterSwitch.tsx b/src/components/FilterSwitch.tsx index 3faae1b..8e856be 100644 --- a/src/components/FilterSwitch.tsx +++ b/src/components/FilterSwitch.tsx @@ -1,4 +1,4 @@ -import { FormControl, FormLabel, Switch } from "@chakra-ui/react"; +import { FormControl, FormLabel, Switch } from "@chakra-ui/react"; import * as React from "react"; interface FilterSwitchProps { @@ -7,7 +7,7 @@ interface FilterSwitchProps { setFilters: React.Dispatch>>; } -const FilterSwitch = ({name,filters,setFilters}:FilterSwitchProps) => { +const FilterSwitch = ({ name, filters, setFilters }: FilterSwitchProps) => { const isChecked = filters.has(name); const handleChange = (event: React.ChangeEvent) => { @@ -17,14 +17,14 @@ const FilterSwitch = ({name,filters,setFilters}:FilterSwitchProps) => { filters.delete(name); setFilters(new Set(filters)); } - } + }; return ( {name} - ) -} + ); +}; -export default FilterSwitch; \ No newline at end of file +export default FilterSwitch; diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 0b157bf..8dc0fd6 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -12,11 +12,7 @@ interface GraphProps { height: number; } - - const Graph: React.FC = ({ graphData, width, height }) => { - - const getNodeById = (id: string) => graphData.nodes.find((node: NodeObject) => node.id === id); return ( diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 6ae90b1..8967cac 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -1,27 +1,36 @@ -import { Box, Checkbox, FormControl, FormLabel, Input, VStack,Heading,HStack } from "@chakra-ui/react"; +import { + Box, + Checkbox, + FormControl, + FormLabel, + Input, + VStack, + Heading, + HStack, +} from "@chakra-ui/react"; import * as React from "react"; -import { useState, Dispatch, SetStateAction,useEffect } from "react"; -import { createGraph, rdfGraphToNodes } from "../utils"; -import { GraphData } from "react-force-graph-3d"; -import {groups} from "./utils.tsx"; +import { useState, Dispatch, SetStateAction, useEffect } from "react"; +import { createGraph, rdfGraphToNodes, removeNonConnectedNodes } from "../utils"; +import { groups } from "./utils.tsx"; import FilterSwitch from "./FilterSwitch.tsx"; +import { GraphData, LinkObject } from "react-force-graph-3d"; - -const parseRDF = async ( - rdfData: string, - removeUnconnectedNodes: boolean, - callback: (data: GraphData) => void -) => { +const parseRDF = (rdfData: string): GraphData => { const store = createGraph(rdfData, "http://schema.org/"); - const graphData = rdfGraphToNodes(store, removeUnconnectedNodes); - callback(graphData); + return rdfGraphToNodes(store); }; interface SelectionsProps { + graphData: GraphData; setGraphData: Dispatch>; + setFilteredGraphData: Dispatch>; } -const Selections: React.FC = ({ setGraphData }: SelectionsProps) => { +const Selections: React.FC = ({ + graphData, + setGraphData, + setFilteredGraphData, +}: SelectionsProps) => { const [file, setFile] = useState(null); const [isChecked, setIsChecked] = useState(false); const [filters, setFilters] = useState>(new Set()); @@ -48,7 +57,9 @@ const Selections: React.FC = ({ setGraphData }: SelectionsProps const fileContent = reader.result; if (typeof fileContent === "string") { // Ensure the content is a string - parseRDF(fileContent, isChecked, setGraphData); + const data = parseRDF(fileContent); + setGraphData(data); + setFilteredGraphData(data); } else { console.error("File content is not a string."); } @@ -61,15 +72,39 @@ const Selections: React.FC = ({ setGraphData }: SelectionsProps console.log("Reading file:", file.name); reader.readAsText(file); // Read the file as text - }, [file, isChecked, setGraphData]); - - - + }, [file]); + useEffect(() => { + // first, filter + const filteredNodes = graphData.nodes.filter((node) => { + // if no filters are selected, show all nodes + if (filters.size === 0) { + return true; + } + // otherwise, dont show nodes that match the selected filters + return !filters.has(node.group); + }); + const filteredLinks = graphData.links.filter((link: LinkObject) => { + // link should be shown if both source or target is in the filtered nodes + return ( + filteredNodes.some((node) => node.id === link.source?.id) && + filteredNodes.some((node) => node.id === link.target?.id) + ); + }); + const filteredGraph = { nodes: filteredNodes, links: filteredLinks }; + + if (isChecked) { + const connectedNodes = removeNonConnectedNodes(filteredGraph); + const unconnectedGraph = { nodes: connectedNodes, links: filteredGraph.links }; + setFilteredGraphData(unconnectedGraph); + } else { + setFilteredGraphData(filteredGraph); + } + }, [isChecked, filters]); return ( - + Upload a file @@ -81,14 +116,16 @@ const Selections: React.FC = ({ setGraphData }: SelectionsProps - Filter by group + + Filter by group + + {groups.map((group) => ( - + ))} - - + ); }; diff --git a/src/components/utils.tsx b/src/components/utils.tsx index 210173c..7ab96bb 100644 --- a/src/components/utils.tsx +++ b/src/components/utils.tsx @@ -16,4 +16,4 @@ const getGroupColor = (group: string) => { return groupColors[group] || "gray"; // Default to gray if group not found }; -export { groups,getGroupColor, groupColors }; \ No newline at end of file +export { groups, getGroupColor, groupColors }; diff --git a/src/utils.ts b/src/utils.ts index 7877739..0ea9a19 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,7 +15,7 @@ const createGraph = (rdfData: string, baseUrl: string): rdflib.Store => { return store; }; -const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): GraphData => { +const rdfGraphToNodes = (store: rdflib.Store): GraphData => { const nodesMap = new Map>(); const edges: LinkObject[] = []; @@ -114,18 +114,23 @@ const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): const nodes = Array.from(nodesMap.values()); const graphData: GraphData = { nodes, links: edges }; - const connectedNodes = removeNonConnectedNodes(graphData); - if (removeUnconnectedNodes) return { nodes: connectedNodes, links: edges }; - else return graphData; + return graphData; }; const removeNonConnectedNodes = (graphData: GraphData): NodeObject[] => { + // Collect all connected node ids from the links const connectedNodeIds = new Set( - graphData.links.flatMap(({ source, target }: NodeObject) => [source, target] as LinkObject) + graphData.links.flatMap(({ source, target }) => { + const sourceId = typeof source === "string" ? source : source.id; + const targetId = typeof target === "string" ? target : target.id; + return [sourceId, targetId]; + }) ); // Keep only nodes that are connected by edges - return graphData.nodes.filter((node: NodeObject) => connectedNodeIds.has(node.id)); + return graphData.nodes.filter((node: NodeObject) => + connectedNodeIds.has(node?.id?.toString() ?? "") + ); }; const typeToGroup = (type: string): string => { @@ -153,4 +158,4 @@ const typeToGroup = (type: string): string => { return ""; // return a default value if no match is found }; -export { createGraph, rdfGraphToNodes }; +export { createGraph, rdfGraphToNodes, removeNonConnectedNodes }; From 0a8a5d3816c760ffb73d3de51d8160ac65574f17 Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Wed, 23 Oct 2024 23:17:25 +0200 Subject: [PATCH 08/46] fix build errors --- src/components/Selections.tsx | 13 +++++++++---- src/utils.ts | 11 ++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 8967cac..767e28a 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -86,10 +86,15 @@ const Selections: React.FC = ({ }); const filteredLinks = graphData.links.filter((link: LinkObject) => { // link should be shown if both source or target is in the filtered nodes - return ( - filteredNodes.some((node) => node.id === link.source?.id) && - filteredNodes.some((node) => node.id === link.target?.id) - ); + const linkExists = + filteredNodes.some( + (node) => node.id === (typeof link.source === "object" ? link.source?.id : link.source) + ) && + filteredNodes.some( + (node) => node.id === (typeof link.target === "object" ? link.target?.id : link.target) + ); + + return linkExists; }); const filteredGraph = { nodes: filteredNodes, links: filteredLinks }; diff --git a/src/utils.ts b/src/utils.ts index 0ea9a19..da8ef63 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -121,9 +121,14 @@ const removeNonConnectedNodes = (graphData: GraphData): NodeObject[] => { // Collect all connected node ids from the links const connectedNodeIds = new Set( graphData.links.flatMap(({ source, target }) => { - const sourceId = typeof source === "string" ? source : source.id; - const targetId = typeof target === "string" ? target : target.id; - return [sourceId, targetId]; + const sourceId = + typeof source === "string" || typeof source === "number" ? source : source?.id; // Handle cases where source could be undefined or an object + + const targetId = + typeof target === "string" || typeof target === "number" ? target : target?.id; // Handle cases where target could be undefined or an object + + // Only return IDs if sourceId and targetId are defined + return [sourceId, targetId].filter((id) => id !== undefined); }) ); From 3c2071765f2a054e37671aacbc3cec5fbbcf1509 Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Tue, 29 Oct 2024 10:52:23 +0100 Subject: [PATCH 09/46] add fullscreen button and feature --- package-lock.json | 9 +++++++++ package.json | 1 + src/components/Content.tsx | 14 ++++++-------- src/components/FullScreenButton.tsx | 23 ++++++++++++++++++++++ src/components/Graph.tsx | 30 +++++++++++++++++++++++++---- 5 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 src/components/FullScreenButton.tsx diff --git a/package-lock.json b/package-lock.json index abc13c9..50ec29f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-force-graph-3d": "^1.24.4", + "react-icons": "^5.3.0", "three": "^0.169.0" }, "devDependencies": { @@ -5340,6 +5341,14 @@ "react": "*" } }, + "node_modules/react-icons": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", + "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index d13d685..0d4ed5f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-force-graph-3d": "^1.24.4", + "react-icons": "^5.3.0", "three": "^0.169.0" }, "devDependencies": { diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 8508d09..e357736 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -1,27 +1,25 @@ -// Content.tsx import { Box } from "@chakra-ui/react"; import Graph from "./Graph"; import Selections from "./Selections"; import { useState, useRef } from "react"; -import useResizeObserver from "@react-hook/resize-observer"; // Import the hook directly +import useResizeObserver from "@react-hook/resize-observer"; import { GraphData } from "react-force-graph-3d"; const Content = () => { const [filteredGraphData, setFilteredGraphData] = useState({ nodes: [], links: [] }); const [graphData, setGraphData] = useState({ nodes: [], links: [] }); - const boxRef = useRef(null); // Create a ref for the Box - const [size, setSize] = useState({ width: 0, height: 0 }); // Initialize size state + const boxRef = useRef(null); + const [size, setSize] = useState({ width: 0, height: 0 }); - // Use useResizeObserver to monitor size changes useResizeObserver(boxRef, (entry) => { if (entry) { - const { width, height } = entry.contentRect; // Get the new width and height - setSize({ width, height }); // Update the size state + const { width, height } = entry.contentRect; + setSize({ width, height }); } }); return ( - + void; +} + +const FullScreenButton: React.FC = ({ isFullScreen, setIsFullScreen }) => { + return ( + + : } + onClick={() => setIsFullScreen(!isFullScreen)} + /> + + ); +}; + +export default FullScreenButton; diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 8dc0fd6..c33dc0f 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -1,10 +1,10 @@ -// Graph.tsx import * as React from "react"; import ForceGraph3D, { GraphData, NodeObject, LinkObject } from "react-force-graph-3d"; import * as THREE from "three"; import { Box } from "@chakra-ui/react"; import Legend from "./Legend.tsx"; import { groupColors, getGroupColor } from "./utils"; +import FullScreenButton from "./FullScreenButton.tsx"; interface GraphProps { graphData: GraphData; @@ -13,14 +13,35 @@ interface GraphProps { } const Graph: React.FC = ({ graphData, width, height }) => { + const [isFullScreen, setIsFullScreen] = React.useState(false); + const [graphWidth, setGraphWidth] = React.useState(width); + const [graphHeight, setGraphHeight] = React.useState(height); const getNodeById = (id: string) => graphData.nodes.find((node: NodeObject) => node.id === id); + // Update graph dimensions when full screen is toggled + React.useEffect(() => { + if (isFullScreen) { + setGraphWidth(window.innerWidth); + setGraphHeight(window.innerHeight); + } else { + setGraphWidth(width); + setGraphHeight(height); + } + }, [isFullScreen, width, height]); + return ( - + getGroupColor(d.group || "")} linkAutoColorBy={(d) => { const sourceNode = @@ -46,6 +67,7 @@ const Graph: React.FC = ({ graphData, width, height }) => { return new THREE.Mesh(geometry, material); }} /> + ); From 202520b27de1bd5457369ffe62e0f1d71b206bbf Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Tue, 29 Oct 2024 13:45:24 +0100 Subject: [PATCH 10/46] add favicon, zoom on click and loading spinner on file load --- index.html | 2 +- public/favicon.ico | Bin 0 -> 7406 bytes src/components/Graph.tsx | 39 ++++++++++++++++++---------- src/components/LoadingSpinner.tsx | 22 ++++++++++++++++ src/components/Selections.tsx | 31 +++++++++++++++++----- src/components/utils.tsx | 19 -------------- src/utils.ts | 41 ++++++++++++++++++++++++++---- vite.config.ts | 9 ------- 8 files changed, 110 insertions(+), 53 deletions(-) create mode 100644 public/favicon.ico create mode 100644 src/components/LoadingSpinner.tsx delete mode 100644 src/components/utils.tsx diff --git a/index.html b/index.html index a910011..604d93e 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + RDF Graph Visualization diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f6d3491d7532cb76223ba396f8a481e61593070e GIT binary patch literal 7406 zcmeHLX>b%p6n?pr-6aW-kWENPNG8kX+6`9%AwZar5JJKR2r#f50pt*YAKs-_Szf8q zDyzinhgFuPIPJ#% z!?d=lNT6Ih3F7KvkbhqV^07}q`feIX-%J2<@NG~o?FHq+4j@0j4C1<>peh8h{ekRw z98~E#NZS{Kv~@1}Oh=!I=<^Qx>_eZO=<^Etl!5$53;M|ze+~LQfqplD>}>}5Kv$Zb$qGkk4*L%pJtIKt8n+ zRPhQ(+ZH1J97x|z1?9@G=;Hx$;4SnUgMO{(Hxk6Pr5OKN^cB(9jrjAR;!i&MF(}th zfl6+G_(eYYHlXiv^!pLWo|izmydTIfuLAk$1yEH5#5DsEdlRt*px(I%()Tk!KJp>v z@i>q}?;-vGko|9hd}b5kRm2Yhvhyj#7lQoT=jd|@q#x#ka`Oyg4}!Fz3gb6}a()|P z2O}19KyHK7QiDE4Ag-@KpUap}9LvqKnA0Jc(+#G@3^hX;xVH=lq3fZ}Kw7SO_^vdw z)Iqy$*|;Ts^-RkQn_udnWt&mxnZ8&zWTbelqg=Yw!QJIt>UFzQGBlGbCfwne7UPpT zfG3pJJKSzZcr}Y@b53JY%FGU+RMk)ujsUmYGpm7$8Sx$}Io#9xQL)`MJ05KmkjQJc zft;rB4hU-p7KBN&aOgB#d4+CWT&s2H?J-tY8TZ&I4c@I!e?uo!CebP22up_gm8>-{ z&qEE19%&qJwzwj#EQ~ilwa%okTQ)~i+p~w8)Y!BYmuZ$l)5<|+mZp3(qTTw}O&!8| z_AYhmE4N_cFb4N5EHZbhcN7=R931tfzLo1uSIO=6_UxrY0`E-|SUAUsY)$XfIvg}O z5ayTozRY3tl)J@u55RXdp2=;-lN;oGg&^}O2tlqFPP-te{eTY1`hJ^h+HV>9eybYe z&#tfK-_Ij;o3WpB+V5vUn4PXY9*geoc@cz}j|D!D>Ds>O?0FP~PM&w;{i2W4xNGl| zTHikG@_mAj2fnZ78}Yh)U(uBd>U}5(LB4N|se~F2W(Mx<)D-giuQEV+Y8L(~@MrMU zNEGzT#8W9pmA1|S`S1szTs^}0snjwS$i6oiKP9`LLteBI#FcrV-oC(isdD4A#!Xvl z86Q>7ZDCwgTs?sCP-$Zo9TNCDKImoBKWSo-h!~T(1lGbI6Q&K(| zxDsLcgN_!|wFBCL_<=N{j4VdHpD=OdVLw*}1YQCO~|no#U$A#hNO^QH@gC zI0Fzl9u@YQcwNzNGzO?nNcG8m>dQHMQJhUT^ZexQVc1<>CevglqB&`z%WvlS#5sEc5c_=4+xC!@yF-ekFQo6U%s!;$Z$HpF~9>~Fo@u>T+`zN?H9bqvnyzwwE85p--*K!T)AIuTj??cj zt1YkRS$&w*muY?bG}g>_J%hFN23EJG^>})AA-kW)?|UPwlhc|xts#pm^I3ge`npa# zqhOt$)}(2jo7Rr$d4-;hSbZAn_w=kp4!w(*V_1KFjh%1@e)n(ES5I?SuELc934+LEo^ZA|90*E26`|cEC2ui literal 0 HcmV?d00001 diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index c33dc0f..d115f2b 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -3,8 +3,9 @@ import ForceGraph3D, { GraphData, NodeObject, LinkObject } from "react-force-gra import * as THREE from "three"; import { Box } from "@chakra-ui/react"; import Legend from "./Legend.tsx"; -import { groupColors, getGroupColor } from "./utils"; +import { groupColors, getGroupColor } from "../utils"; import FullScreenButton from "./FullScreenButton.tsx"; +import { useCallback, useRef, useState } from "react"; interface GraphProps { graphData: GraphData; @@ -13,10 +14,26 @@ interface GraphProps { } const Graph: React.FC = ({ graphData, width, height }) => { - const [isFullScreen, setIsFullScreen] = React.useState(false); - const [graphWidth, setGraphWidth] = React.useState(width); - const [graphHeight, setGraphHeight] = React.useState(height); - const getNodeById = (id: string) => graphData.nodes.find((node: NodeObject) => node.id === id); + const [isFullScreen, setIsFullScreen] = useState(false); + const [graphWidth, setGraphWidth] = useState(width); + const [graphHeight, setGraphHeight] = useState(height); + + const fgRef = useRef(); + + const handleClick = useCallback( + (node) => { + // Aim at node from outside it + const distance = 80; + const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z); + + fgRef.current.cameraPosition( + { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position + node, // lookAt ({ x, y, z }) + 2000 // ms transition duration + ); + }, + [fgRef] + ); // Update graph dimensions when full screen is toggled React.useEffect(() => { @@ -39,17 +56,12 @@ const Graph: React.FC = ({ graphData, width, height }) => { zIndex={isFullScreen ? 9999 : "auto"} // Ensure it covers everything in full screen > getGroupColor(d.group || "")} - linkAutoColorBy={(d) => { - const sourceNode = - typeof d.source === "object" ? d.source : getNodeById(d.source?.toString() ?? ""); - return getGroupColor(sourceNode?.group || ""); - }} - nodeLabel={(d) => `${(d as NodeObject).label || (d as NodeObject).id}`} // Show the label or fallback to id - linkLabel={(d) => `${(d as LinkObject).label}`} // Show the label for links + nodeLabel={(node: NodeObject) => `${node.label || node.id}`} + linkLabel={(d: LinkObject) => `${d.label}`} linkDirectionalArrowLength={7} linkDirectionalArrowRelPos={1} linkCurvature={0.3} @@ -66,6 +78,7 @@ const Graph: React.FC = ({ graphData, width, height }) => { return new THREE.Mesh(geometry, material); }} + onNodeClick={handleClick} /> diff --git a/src/components/LoadingSpinner.tsx b/src/components/LoadingSpinner.tsx new file mode 100644 index 0000000..c52c56f --- /dev/null +++ b/src/components/LoadingSpinner.tsx @@ -0,0 +1,22 @@ +import { Box, Spinner } from "@chakra-ui/react"; + +function LoadingSpinner() { + return ( + + + + ); +} + +export default LoadingSpinner; diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 767e28a..2e6c63a 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -10,10 +10,10 @@ import { } from "@chakra-ui/react"; import * as React from "react"; import { useState, Dispatch, SetStateAction, useEffect } from "react"; -import { createGraph, rdfGraphToNodes, removeNonConnectedNodes } from "../utils"; -import { groups } from "./utils.tsx"; +import { createGraph, rdfGraphToNodes, removeNonConnectedNodes, groups } from "../utils"; import FilterSwitch from "./FilterSwitch.tsx"; import { GraphData, LinkObject } from "react-force-graph-3d"; +import LoadingSpinner from "./LoadingSpinner.tsx"; const parseRDF = (rdfData: string): GraphData => { const store = createGraph(rdfData, "http://schema.org/"); @@ -34,6 +34,7 @@ const Selections: React.FC = ({ const [file, setFile] = useState(null); const [isChecked, setIsChecked] = useState(false); const [filters, setFilters] = useState>(new Set()); + const [loading, setLoading] = useState(false); const handleFileChange = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0] || null; @@ -60,9 +61,15 @@ const Selections: React.FC = ({ const data = parseRDF(fileContent); setGraphData(data); setFilteredGraphData(data); + setIsChecked(false); + setFilters(new Set()); + console.log( + `Loaded file: ${file.name} with ${data.nodes.length} nodes and ${data.links.length} links.` + ); } else { console.error("File content is not a string."); } + setLoading(false); }; // Add error event listener for debugging @@ -71,10 +78,12 @@ const Selections: React.FC = ({ }; console.log("Reading file:", file.name); + setLoading(true); reader.readAsText(file); // Read the file as text }, [file]); useEffect(() => { + setLoading(true); // first, filter const filteredNodes = graphData.nodes.filter((node) => { // if no filters are selected, show all nodes @@ -86,15 +95,14 @@ const Selections: React.FC = ({ }); const filteredLinks = graphData.links.filter((link: LinkObject) => { // link should be shown if both source or target is in the filtered nodes - const linkExists = + return ( filteredNodes.some( (node) => node.id === (typeof link.source === "object" ? link.source?.id : link.source) ) && filteredNodes.some( (node) => node.id === (typeof link.target === "object" ? link.target?.id : link.target) - ); - - return linkExists; + ) + ); }); const filteredGraph = { nodes: filteredNodes, links: filteredLinks }; @@ -102,11 +110,22 @@ const Selections: React.FC = ({ const connectedNodes = removeNonConnectedNodes(filteredGraph); const unconnectedGraph = { nodes: connectedNodes, links: filteredGraph.links }; setFilteredGraphData(unconnectedGraph); + console.log( + `Filtered graph with ${connectedNodes.length} nodes and ${filteredGraph.links.length} links.` + ); } else { setFilteredGraphData(filteredGraph); + console.log( + `Filtered graph with ${filteredGraph.nodes.length} nodes and ${filteredGraph.links.length} links.` + ); } + setLoading(false); }, [isChecked, filters]); + if (loading) { + return ; + } + return ( diff --git a/src/components/utils.tsx b/src/components/utils.tsx deleted file mode 100644 index 7ab96bb..0000000 --- a/src/components/utils.tsx +++ /dev/null @@ -1,19 +0,0 @@ -const groupColors: { [key: string]: string } = { - person: "red", - dataset: "blue", - organization: "green", - software: "yellow", - document: "orange", - article: "indigo", - creativeWork: "violet", - service: "cyan", - "": "gray", // Default group -}; - -const groups = Object.keys(groupColors).filter((group) => group !== ""); // Remove the default group - -const getGroupColor = (group: string) => { - return groupColors[group] || "gray"; // Default to gray if group not found -}; - -export { groups, getGroupColor, groupColors }; diff --git a/src/utils.ts b/src/utils.ts index da8ef63..baed702 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,22 @@ import * as rdflib from "rdflib"; import { GraphData, LinkObject, NodeObject } from "react-force-graph-3d"; +const groupColors: { [key: string]: string } = { + person: "red", + dataset: "blue", + organization: "green", + software: "yellow", + document: "orange", + article: "indigo", + creativeWork: "violet", + service: "cyan", + "": "gray", // Default group +}; + +const getGroupColor = (group: string) => { + return groupColors[group] || "gray"; // Default to gray if group not found +}; + const createGraph = (rdfData: string, baseUrl: string): rdflib.Store => { const store = rdflib.graph(); const base = rdflib.sym(baseUrl); @@ -21,9 +37,12 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { // create the element if not exists const safeUpdateElement = (id: string, label?: string, group?: string): void => { + const safeGroup = group ?? ""; + const safeLabel = label ?? id; + const color = group ? getGroupColor(safeGroup) : "gray"; if (!nodesMap.has(id)) { // create the node - const newNode = { id: id, label: label ?? id, group: group ?? "" } as NodeType; + const newNode = { id: id, label: safeLabel, group: safeGroup, color } as NodeType; nodesMap.set(id, newNode); } else { @@ -34,6 +53,7 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { label: label ?? node?.label, group: group ?? node?.group, }; + updatedNode.color = updatedNode.group ? getGroupColor(updatedNode.group) : "gray"; nodesMap.set(id, updatedNode); } @@ -108,13 +128,14 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { if (!nodesMap.has(obj)) { nodesMap.set(obj, { id: obj, label: obj, group: "" }); } - edges.push({ source: subj, target: obj, label: pred }); + // set as source color, if not set target color, if not set default color gray + const linkColor = nodesMap.get(subj)?.color ?? nodesMap.get(obj)?.color ?? "gray"; + edges.push({ source: subj, target: obj, label: pred, color: linkColor }); } }); const nodes = Array.from(nodesMap.values()); - const graphData: GraphData = { nodes, links: edges }; - return graphData; + return { nodes, links: edges } as GraphData; }; const removeNonConnectedNodes = (graphData: GraphData): NodeObject[] => { @@ -163,4 +184,14 @@ const typeToGroup = (type: string): string => { return ""; // return a default value if no match is found }; -export { createGraph, rdfGraphToNodes, removeNonConnectedNodes }; + +const groups = Object.keys(groupColors).filter((group) => group !== ""); // Remove the default group + +export { + createGraph, + rdfGraphToNodes, + removeNonConnectedNodes, + groupColors, + groups, + getGroupColor, +}; diff --git a/vite.config.ts b/vite.config.ts index 85ddbb0..9cc50ea 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,13 +4,4 @@ import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - server: { - proxy: { - "/data": { - target: "http://localhost:8000", - changeOrigin: true, - rewrite: (path) => path.replace(/^\/data/, ""), - }, - }, - }, }); From e2d592fec5e0da6cb164e2f042b8726c720099f2 Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Tue, 29 Oct 2024 14:49:18 +0100 Subject: [PATCH 11/46] fix build errors --- src/components/Graph.tsx | 9 ++++++++- src/utils.ts | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index d115f2b..5fc70e4 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -21,11 +21,18 @@ const Graph: React.FC = ({ graphData, width, height }) => { const fgRef = useRef(); const handleClick = useCallback( - (node) => { + (node: NodeObject) => { + // return if node is undefined or node's x,y,z are undefined + if (!node || node.x === undefined || node.y === undefined || node.z === undefined) { + return; + } + if (!fgRef.current) return; + // Aim at node from outside it const distance = 80; const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z); + // @ts-ignore fgRef.current.cameraPosition( { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position node, // lookAt ({ x, y, z }) diff --git a/src/utils.ts b/src/utils.ts index baed702..177b560 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -52,6 +52,7 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { id: id, label: label ?? node?.label, group: group ?? node?.group, + color: node?.color, }; updatedNode.color = updatedNode.group ? getGroupColor(updatedNode.group) : "gray"; From 687c2d2cb20bcab49dc9834f489b3040e3501ba1 Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Tue, 29 Oct 2024 15:12:56 +0100 Subject: [PATCH 12/46] add aniomation --- src/components/Content.tsx | 10 +++++- src/components/Graph.tsx | 58 +++++++++++++++++++++++++++-------- src/components/Selections.tsx | 7 +++++ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/components/Content.tsx b/src/components/Content.tsx index e357736..53f2b69 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -10,6 +10,7 @@ const Content = () => { const [graphData, setGraphData] = useState({ nodes: [], links: [] }); const boxRef = useRef(null); const [size, setSize] = useState({ width: 0, height: 0 }); + const [isAnimating, setIsAnimating] = useState(false); // For orbit animation useResizeObserver(boxRef, (entry) => { if (entry) { @@ -25,10 +26,17 @@ const Content = () => { setGraphData={setGraphData} graphData={graphData} setFilteredGraphData={setFilteredGraphData} + isAnimating={isAnimating} + setIsAnimating={setIsAnimating} /> - + ); diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 5fc70e4..cdf450e 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -5,45 +5,45 @@ import { Box } from "@chakra-ui/react"; import Legend from "./Legend.tsx"; import { groupColors, getGroupColor } from "../utils"; import FullScreenButton from "./FullScreenButton.tsx"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; interface GraphProps { graphData: GraphData; width: number; height: number; + isAnimating: boolean; } -const Graph: React.FC = ({ graphData, width, height }) => { +const Graph: React.FC = ({ graphData, width, height, isAnimating }) => { const [isFullScreen, setIsFullScreen] = useState(false); const [graphWidth, setGraphWidth] = useState(width); const [graphHeight, setGraphHeight] = useState(height); + const fgRef = useRef(null); - const fgRef = useRef(); + // For orbit animation + const animationId = useRef(); + const angleRef = useRef(0); // Track the angle for orbiting const handleClick = useCallback( (node: NodeObject) => { - // return if node is undefined or node's x,y,z are undefined if (!node || node.x === undefined || node.y === undefined || node.z === undefined) { return; } if (!fgRef.current) return; - // Aim at node from outside it const distance = 80; const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z); - // @ts-ignore fgRef.current.cameraPosition( - { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position - node, // lookAt ({ x, y, z }) - 2000 // ms transition duration + { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, + node, + 2000 ); }, [fgRef] ); - // Update graph dimensions when full screen is toggled - React.useEffect(() => { + useEffect(() => { if (isFullScreen) { setGraphWidth(window.innerWidth); setGraphHeight(window.innerHeight); @@ -53,6 +53,40 @@ const Graph: React.FC = ({ graphData, width, height }) => { } }, [isFullScreen, width, height]); + // Orbit animation function + const animateOrbit = useCallback(() => { + if (!fgRef.current) return; + + angleRef.current += 0.01; // Increment angle for smooth orbit + const distance = 300; + + // Calculate camera position based on angle + const x = distance * Math.sin(angleRef.current); + const z = distance * Math.cos(angleRef.current); + const y = 100; // Optional: adjust for a slight elevation + + fgRef.current.cameraPosition( + { x, y, z }, // New camera position + { x: 0, y: 0, z: 0 }, // Look at center of graph + 0 + ); + + // Request the next frame for smooth animation + animationId.current = requestAnimationFrame(animateOrbit); + }, []); + + // Toggle animation on/off + useEffect(() => { + if (isAnimating) { + animateOrbit(); + } else if (animationId.current) { + cancelAnimationFrame(animationId.current); + } + return () => { + if (animationId.current) cancelAnimationFrame(animationId.current); + }; + }, [isAnimating, animateOrbit]); + return ( = ({ graphData, width, height }) => { position={isFullScreen ? "fixed" : "relative"} top={isFullScreen ? 0 : "unset"} left={isFullScreen ? 0 : "unset"} - zIndex={isFullScreen ? 9999 : "auto"} // Ensure it covers everything in full screen + zIndex={isFullScreen ? 9999 : "auto"} > >; setFilteredGraphData: Dispatch>; + isAnimating: boolean; + setIsAnimating: Dispatch>; } const Selections: React.FC = ({ graphData, setGraphData, setFilteredGraphData, + isAnimating, + setIsAnimating, }: SelectionsProps) => { const [file, setFile] = useState(null); const [isChecked, setIsChecked] = useState(false); @@ -138,6 +142,9 @@ const Selections: React.FC = ({ Remove nodes that are not linked + setIsAnimating(e.target.checked)}> + Animate graph + From 2c4ecde8917a34548914247ff094c3474857d758 Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Tue, 29 Oct 2024 15:23:17 +0100 Subject: [PATCH 13/46] better animation --- src/components/Graph.tsx | 6 +++++- src/components/Selections.tsx | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index cdf450e..b7d383a 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -58,7 +58,11 @@ const Graph: React.FC = ({ graphData, width, height, isAnimating }) if (!fgRef.current) return; angleRef.current += 0.01; // Increment angle for smooth orbit - const distance = 300; + const baseDistance = 1500; + const distanceMultiplier = [1, 0.75, 0.5, 0.33]; + const currentMultiplier = + distanceMultiplier[Math.floor(angleRef.current / (2 * Math.PI)) % distanceMultiplier.length]; + const distance = baseDistance * currentMultiplier; // Calculate camera position based on angle const x = distance * Math.sin(angleRef.current); diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 0fe2f18..c9b1560 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -138,14 +138,19 @@ const Selections: React.FC = ({ - - - Remove nodes that are not linked - - setIsAnimating(e.target.checked)}> - Animate graph - - + + + + Remove nodes that are not linked + + + + + setIsAnimating(e.target.checked)}> + Animate graph + + + Filter by group From 71f5f04c01b031ddf59adb12620b2458565a05f7 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Thu, 31 Oct 2024 15:11:15 +0100 Subject: [PATCH 14/46] add 3 animations --- src/components/Content.tsx | 16 ++- src/components/Graph.tsx | 191 +++++++++++++++++++++++++++------- src/components/Selections.tsx | 97 +++++++++++++---- src/components/animation.ts | 89 ++++++++++++++++ 4 files changed, 331 insertions(+), 62 deletions(-) create mode 100644 src/components/animation.ts diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 53f2b69..b5058c0 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -10,7 +10,9 @@ const Content = () => { const [graphData, setGraphData] = useState({ nodes: [], links: [] }); const boxRef = useRef(null); const [size, setSize] = useState({ width: 0, height: 0 }); - const [isAnimating, setIsAnimating] = useState(false); // For orbit animation + const [isAnimating1, setIsAnimating1] = useState(false); // For orbit animation + const [isAnimating2, setIsAnimating2] = useState(false); + const [isAnimating3, setIsAnimating3] = useState(false); useResizeObserver(boxRef, (entry) => { if (entry) { @@ -26,8 +28,12 @@ const Content = () => { setGraphData={setGraphData} graphData={graphData} setFilteredGraphData={setFilteredGraphData} - isAnimating={isAnimating} - setIsAnimating={setIsAnimating} + isAnimating1={isAnimating1} + setIsAnimating1={setIsAnimating1} + isAnimating2={isAnimating2} + setIsAnimating2={setIsAnimating2} + isAnimating3={isAnimating3} + setIsAnimating3={setIsAnimating3} /> @@ -35,7 +41,9 @@ const Content = () => { graphData={filteredGraphData} width={size.width} height={size.height} - isAnimating={isAnimating} + isAnimating1={isAnimating1} + isAnimating2={isAnimating2} + isAnimating3={isAnimating3} /> diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index b7d383a..becf18f 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -6,29 +6,42 @@ import Legend from "./Legend.tsx"; import { groupColors, getGroupColor } from "../utils"; import FullScreenButton from "./FullScreenButton.tsx"; import { useCallback, useEffect, useRef, useState } from "react"; +import { + AnimationStageType, + zoomedOutedRotationAnimation, + humanZoomedInRotationAnimation, + miningZoomedInRotationAnimation, +} from "./animation"; interface GraphProps { graphData: GraphData; width: number; height: number; - isAnimating: boolean; + isAnimating1: boolean; + isAnimating2: boolean; + isAnimating3: boolean; } -const Graph: React.FC = ({ graphData, width, height, isAnimating }) => { +const Graph: React.FC = ({ + graphData, + width, + height, + isAnimating1, + isAnimating2, + isAnimating3, +}) => { const [isFullScreen, setIsFullScreen] = useState(false); const [graphWidth, setGraphWidth] = useState(width); const [graphHeight, setGraphHeight] = useState(height); - const fgRef = useRef(null); + const [stageIndex, setStageIndex] = useState(0); + const [stages, setStages] = useState([]); - // For orbit animation + const fgRef = useRef(null); const animationId = useRef(); - const angleRef = useRef(0); // Track the angle for orbiting const handleClick = useCallback( (node: NodeObject) => { - if (!node || node.x === undefined || node.y === undefined || node.z === undefined) { - return; - } + if (!node || node.x === undefined || node.y === undefined || node.z === undefined) return; if (!fgRef.current) return; const distance = 80; @@ -53,43 +66,141 @@ const Graph: React.FC = ({ graphData, width, height, isAnimating }) } }, [isFullScreen, width, height]); - // Orbit animation function - const animateOrbit = useCallback(() => { - if (!fgRef.current) return; - - angleRef.current += 0.01; // Increment angle for smooth orbit - const baseDistance = 1500; - const distanceMultiplier = [1, 0.75, 0.5, 0.33]; - const currentMultiplier = - distanceMultiplier[Math.floor(angleRef.current / (2 * Math.PI)) % distanceMultiplier.length]; - const distance = baseDistance * currentMultiplier; - - // Calculate camera position based on angle - const x = distance * Math.sin(angleRef.current); - const z = distance * Math.cos(angleRef.current); - const y = 100; // Optional: adjust for a slight elevation - - fgRef.current.cameraPosition( - { x, y, z }, // New camera position - { x: 0, y: 0, z: 0 }, // Look at center of graph - 0 - ); - - // Request the next frame for smooth animation - animationId.current = requestAnimationFrame(animateOrbit); - }, []); - - // Toggle animation on/off + const animateStage = useCallback( + (stage: AnimationStageType) => { + if (!fgRef.current) return; + + const cameraPosition = ( + position: { x: number; y: number; z: number }, + lookAt: { x: number; y: number; z: number }, + duration: number + ) => { + fgRef.current.cameraPosition(position, lookAt, duration * 1000); + }; + + if (stage.type === "zoomIn") { + let position = { x: 0, y: 0, z: 0 }; + if (stage.nodeId) { + const targetNode = graphData.nodes.find((node) => node.id === stage.nodeId); + if ( + targetNode && + targetNode.x !== undefined && + targetNode.y !== undefined && + targetNode.z !== undefined + ) { + position = { x: targetNode.x , y: targetNode.y , z: targetNode.z }; + } + } else if (stage.position) { + position = stage.position; + } + // set camera like rotate but dont rotate ofc + const distance = stage.distance || 300; + cameraPosition({x:position.x+distance, y:position.y,z:position.z}, position, stage.duration ?? 0); + } else if (stage.type === "rotate") { + let rotateAround = { x: 0, y: 0, z: 0 }; + if (stage.nodeId) { + const targetNode = graphData.nodes.find((node) => node.id === stage.nodeId); + if (targetNode) rotateAround = { x: targetNode.x, y: targetNode.y, z: targetNode.z }; + } else if (stage.position) { + rotateAround = stage.position; + } + + const radius = stage.distance || 300; + const duration = stage.duration * 1000; + const startTime = Date.now(); + const rotationAngle = Math.PI / 4; + + const rotate = () => { + const elapsedTime = Date.now() - startTime; + if (elapsedTime >= duration) return; + + const angle = (rotationAngle * elapsedTime) / duration; + const x = rotateAround.x + radius * Math.cos(angle); + const z = rotateAround.z + radius * Math.sin(angle); + + fgRef.current.cameraPosition( + { x, y: rotateAround.y, z }, + { x: rotateAround.x, y: rotateAround.y, z: rotateAround.z }, + 0 + ); + + animationId.current = requestAnimationFrame(rotate); + }; + rotate(); + } + }, + [graphData] + ); + + useEffect(() => { + // Select the appropriate animation stages based on the active isAnimating flag + if (isAnimating1) { + setStages(zoomedOutedRotationAnimation); + setStageIndex(0); + } else if (isAnimating2) { + setStages(humanZoomedInRotationAnimation); + setStageIndex(0); + } else if (isAnimating3) { + setStages(miningZoomedInRotationAnimation); + setStageIndex(0); + } else { + setStages([]); // Reset stages if no animation flag is active + } + }, [isAnimating1, isAnimating2, isAnimating3]); + + // useEffect(() => { + // if (stages.length > 0 && stageIndex < stages.length) { + // animateStage(stages[stageIndex]); + // const { duration } = stages[stageIndex]; + // const timeoutId = setTimeout(() => { + // setStageIndex((prevStageIndex) => prevStageIndex + 1); + // }, duration * 1000); + // + // return () => clearTimeout(timeoutId); + // } else if (stageIndex >= stages.length) { + // setStageIndex(0); + // } + // return () => { + // if (animationId.current) cancelAnimationFrame(animationId.current); + // }; + // }, [stages, stageIndex, animateStage]); useEffect(() => { - if (isAnimating) { - animateOrbit(); - } else if (animationId.current) { - cancelAnimationFrame(animationId.current); + if (stages.length > 0) { + const animateNextStage = (currentStageIndex: number) => { + if (currentStageIndex >= stages.length) { + setStageIndex(0); // Reset to the beginning or stop if desired + return; + } + + // Animate the current stage + animateStage(stages[currentStageIndex]); + + const duration = stages[currentStageIndex].duration * 1000; // Stage duration in milliseconds + const startTime = Date.now(); + + // Transition loop for smooth movement + const transition = () => { + const elapsed = Date.now() - startTime; + + if (elapsed < duration) { + animationId.current = requestAnimationFrame(transition); + } else { + // Move to the next stage after current stage duration has passed + setStageIndex((index) => index + 1); + } + }; + + animationId.current = requestAnimationFrame(transition); + }; + + animateNextStage(stageIndex); } + + // Cleanup return () => { if (animationId.current) cancelAnimationFrame(animationId.current); }; - }, [isAnimating, animateOrbit]); + }, [stageIndex, stages, animateStage]); return ( >; setFilteredGraphData: Dispatch>; - isAnimating: boolean; - setIsAnimating: Dispatch>; + isAnimating1: boolean; + setIsAnimating1: Dispatch>; + isAnimating2: boolean; + setIsAnimating2: Dispatch>; + isAnimating3: boolean; + setIsAnimating3: Dispatch>; } const Selections: React.FC = ({ graphData, setGraphData, setFilteredGraphData, - isAnimating, - setIsAnimating, + isAnimating1, + setIsAnimating1, + isAnimating2, + setIsAnimating2, + isAnimating3, + setIsAnimating3, }: SelectionsProps) => { const [file, setFile] = useState(null); const [isChecked, setIsChecked] = useState(false); const [filters, setFilters] = useState>(new Set()); const [loading, setLoading] = useState(false); + const [animationOn, setAnimationOn] = useState(false); const handleFileChange = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0] || null; @@ -60,18 +72,23 @@ const Selections: React.FC = ({ // Add the load event listener reader.onload = () => { const fileContent = reader.result; - if (typeof fileContent === "string") { - // Ensure the content is a string - const data = parseRDF(fileContent); - setGraphData(data); - setFilteredGraphData(data); - setIsChecked(false); - setFilters(new Set()); - console.log( - `Loaded file: ${file.name} with ${data.nodes.length} nodes and ${data.links.length} links.` - ); - } else { - console.error("File content is not a string."); + try { + if (typeof fileContent === "string") { + // Ensure the content is a string + const data = parseRDF(fileContent); + setGraphData(data); + setFilteredGraphData(data); + setIsChecked(false); + setFilters(new Set()); + console.log( + `Loaded file: ${file.name} with ${data.nodes.length} nodes and ${data.links.length} links.` + ); + } else { + console.error("File content is not a string."); + } + } catch (error) { + console.error("Error parsing RDF:", error); + window.alert("Error parsing RDF. Please check the file format."); } setLoading(false); }; @@ -130,6 +147,21 @@ const Selections: React.FC = ({ return ; } + const onRadioChange = (value: string) => { + if (value === "zoomOut") { + setIsAnimating1(true); + setIsAnimating2(false); + setIsAnimating3(false); + } else if (value === "personZoomIn") { + setIsAnimating1(false); + setIsAnimating2(true); + setIsAnimating3(false); + } else if (value === "creativeWorkZoomIn") { + setIsAnimating1(false); + setIsAnimating2(false); + setIsAnimating3(true); + } + }; return ( @@ -146,10 +178,39 @@ const Selections: React.FC = ({ - setIsAnimating(e.target.checked)}> - Animate graph + { + setAnimationOn(event.target.checked); + if (!event.target.checked) { + setIsAnimating1(false); + setIsAnimating2(false); + setIsAnimating3(false); + } + }} + > + Animation On + + {animationOn && ( + + Animation Options + + + + Animate zoom out and rotation + + + Animate license zoom-in and rotation + + + Animate creative work zoom-in and rotation + + + + + )} diff --git a/src/components/animation.ts b/src/components/animation.ts new file mode 100644 index 0000000..c2f0b49 --- /dev/null +++ b/src/components/animation.ts @@ -0,0 +1,89 @@ +export type AnimationStageTypeType = "zoomIn" | "rotate"; + +export type PositionType = { + x: number; + y: number; + z: number; +}; + +export type AnimationStageType = { + type: AnimationStageTypeType; + duration: number; //seconds + position?: PositionType; + nodeId?: string; + distance?: number; +}; + +const stages: AnimationStageType[] = [ + { type: "rotate", duration: 6, position: { x: 0, y: 0, z: 0 }, distance: 2000 }, + { + type: "zoomIn", + nodeId: "https://creativecommons.org/licenses/by/3.0", + duration: 3, + distance: 300, + }, + { + type: "rotate", + nodeId: "https://creativecommons.org/licenses/by/3.0", + duration: 5, + distance: 600, + }, + { type: "zoomIn", nodeId: "https://orcid.org/0000-0002-7527-7827", duration: 2, distance: 300 }, + { type: "rotate", nodeId: "https://orcid.org/0000-0002-7527-7827", duration: 5, distance: 600 }, + { + type: "zoomIn", + nodeId: "Global-scale mining polygons (Version 2)", + duration: 2, + distance: 300, + }, + { + type: "rotate", + nodeId: "Global-scale mining polygons (Version 2)", + duration: 5, + distance: 600, + }, + { type: "zoomIn", duration: 1, position: { x: 0, y: 1000, z: 1500 }, distance: 300 }, + { type: "rotate", duration: 4, position: { x: 0, y: 1000, z: 1500 }, distance: 300 }, +]; + +const zoomedOutedRotationAnimation: AnimationStageType[] = [ + { type: "rotate", duration: 10, position: { x: 0, y: 0, z: 0 }, distance: 4000 }, +]; + +const humanZoomedInRotationAnimation: AnimationStageType[] = [ + { type: "zoomIn", duration: 0, position: { x: 0, y: 0, z: 0 }, distance: 4000 }, + { + type: "zoomIn", + nodeId: "https://orcid.org/0000-0002-7527-7827", + duration: 8, + distance: 600, + }, + { + type: "rotate", + nodeId: "https://orcid.org/0000-0002-7527-7827", + duration: 10, + distance: 600, + }, +]; + +const miningZoomedInRotationAnimation: AnimationStageType[] = [ + { type: "zoomIn", duration: 0, position: { x: 0, y: 0, z: 0 }, distance: 4000 }, + { + type: "zoomIn", + nodeId: "Global-scale mining polygons (Version 2)", + duration: 5, + distance: 900, + }, + { + type: "rotate", + nodeId: "Global-scale mining polygons (Version 2)", + duration: 10, + distance: 900, + }, +]; +export { + stages, + zoomedOutedRotationAnimation, + humanZoomedInRotationAnimation, + miningZoomedInRotationAnimation, +}; From 432bc32cb22bb247ae5163dfbaf83a3bf511c982 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Thu, 31 Oct 2024 15:27:30 +0100 Subject: [PATCH 15/46] typing --- src/components/Graph.tsx | 13 +++++++++---- src/components/Selections.tsx | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index becf18f..074bfe4 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -88,19 +88,24 @@ const Graph: React.FC = ({ targetNode.y !== undefined && targetNode.z !== undefined ) { - position = { x: targetNode.x , y: targetNode.y , z: targetNode.z }; + position = { x: targetNode.x, y: targetNode.y, z: targetNode.z }; } } else if (stage.position) { position = stage.position; } // set camera like rotate but dont rotate ofc const distance = stage.distance || 300; - cameraPosition({x:position.x+distance, y:position.y,z:position.z}, position, stage.duration ?? 0); + cameraPosition( + { x: position.x + distance, y: position.y, z: position.z }, + position, + stage.duration ?? 0 + ); } else if (stage.type === "rotate") { let rotateAround = { x: 0, y: 0, z: 0 }; if (stage.nodeId) { const targetNode = graphData.nodes.find((node) => node.id === stage.nodeId); - if (targetNode) rotateAround = { x: targetNode.x, y: targetNode.y, z: targetNode.z }; + if (targetNode) + rotateAround = { x: targetNode?.x ?? 0, y: targetNode?.y ?? 0, z: targetNode?.z ?? 0 }; } else if (stage.position) { rotateAround = stage.position; } @@ -112,7 +117,7 @@ const Graph: React.FC = ({ const rotate = () => { const elapsedTime = Date.now() - startTime; - if (elapsedTime >= duration) return; + if (elapsedTime >= duration) return; const angle = (rotationAngle * elapsedTime) / duration; const x = rotateAround.x + radius * Math.cos(angle); diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 4ddd464..7774ece 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -162,6 +162,7 @@ const Selections: React.FC = ({ setIsAnimating3(true); } }; + return ( From ae5fb5ef0f861313d288030a1e8a404f10ce8534 Mon Sep 17 00:00:00 2001 From: Mustafa SOYLU Date: Thu, 31 Oct 2024 16:44:56 +0100 Subject: [PATCH 16/46] fix texts --- src/components/Selections.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 7774ece..8542d2f 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -203,10 +203,10 @@ const Selections: React.FC = ({ Animate zoom out and rotation - Animate license zoom-in and rotation + Animate creative work zoom-in and rotation - Animate creative work zoom-in and rotation + Animate license zoom-in and rotation From 87b49de99cb4c938e7490af2661ef5d5c7fe173e Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 09:45:50 +0100 Subject: [PATCH 17/46] remove animation options --- src/components/Content.tsx | 18 +--- src/components/Graph.tsx | 164 +--------------------------------- src/components/Selections.tsx | 69 +------------- src/components/animation.ts | 74 --------------- 4 files changed, 4 insertions(+), 321 deletions(-) diff --git a/src/components/Content.tsx b/src/components/Content.tsx index b5058c0..e357736 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -10,9 +10,6 @@ const Content = () => { const [graphData, setGraphData] = useState({ nodes: [], links: [] }); const boxRef = useRef(null); const [size, setSize] = useState({ width: 0, height: 0 }); - const [isAnimating1, setIsAnimating1] = useState(false); // For orbit animation - const [isAnimating2, setIsAnimating2] = useState(false); - const [isAnimating3, setIsAnimating3] = useState(false); useResizeObserver(boxRef, (entry) => { if (entry) { @@ -28,23 +25,10 @@ const Content = () => { setGraphData={setGraphData} graphData={graphData} setFilteredGraphData={setFilteredGraphData} - isAnimating1={isAnimating1} - setIsAnimating1={setIsAnimating1} - isAnimating2={isAnimating2} - setIsAnimating2={setIsAnimating2} - isAnimating3={isAnimating3} - setIsAnimating3={setIsAnimating3} /> - + ); diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 074bfe4..b8238be 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -6,38 +6,19 @@ import Legend from "./Legend.tsx"; import { groupColors, getGroupColor } from "../utils"; import FullScreenButton from "./FullScreenButton.tsx"; import { useCallback, useEffect, useRef, useState } from "react"; -import { - AnimationStageType, - zoomedOutedRotationAnimation, - humanZoomedInRotationAnimation, - miningZoomedInRotationAnimation, -} from "./animation"; interface GraphProps { graphData: GraphData; width: number; height: number; - isAnimating1: boolean; - isAnimating2: boolean; - isAnimating3: boolean; } -const Graph: React.FC = ({ - graphData, - width, - height, - isAnimating1, - isAnimating2, - isAnimating3, -}) => { +const Graph: React.FC = ({ graphData, width, height }) => { const [isFullScreen, setIsFullScreen] = useState(false); const [graphWidth, setGraphWidth] = useState(width); const [graphHeight, setGraphHeight] = useState(height); - const [stageIndex, setStageIndex] = useState(0); - const [stages, setStages] = useState([]); const fgRef = useRef(null); - const animationId = useRef(); const handleClick = useCallback( (node: NodeObject) => { @@ -66,152 +47,11 @@ const Graph: React.FC = ({ } }, [isFullScreen, width, height]); - const animateStage = useCallback( - (stage: AnimationStageType) => { - if (!fgRef.current) return; - - const cameraPosition = ( - position: { x: number; y: number; z: number }, - lookAt: { x: number; y: number; z: number }, - duration: number - ) => { - fgRef.current.cameraPosition(position, lookAt, duration * 1000); - }; - - if (stage.type === "zoomIn") { - let position = { x: 0, y: 0, z: 0 }; - if (stage.nodeId) { - const targetNode = graphData.nodes.find((node) => node.id === stage.nodeId); - if ( - targetNode && - targetNode.x !== undefined && - targetNode.y !== undefined && - targetNode.z !== undefined - ) { - position = { x: targetNode.x, y: targetNode.y, z: targetNode.z }; - } - } else if (stage.position) { - position = stage.position; - } - // set camera like rotate but dont rotate ofc - const distance = stage.distance || 300; - cameraPosition( - { x: position.x + distance, y: position.y, z: position.z }, - position, - stage.duration ?? 0 - ); - } else if (stage.type === "rotate") { - let rotateAround = { x: 0, y: 0, z: 0 }; - if (stage.nodeId) { - const targetNode = graphData.nodes.find((node) => node.id === stage.nodeId); - if (targetNode) - rotateAround = { x: targetNode?.x ?? 0, y: targetNode?.y ?? 0, z: targetNode?.z ?? 0 }; - } else if (stage.position) { - rotateAround = stage.position; - } - - const radius = stage.distance || 300; - const duration = stage.duration * 1000; - const startTime = Date.now(); - const rotationAngle = Math.PI / 4; - - const rotate = () => { - const elapsedTime = Date.now() - startTime; - if (elapsedTime >= duration) return; - - const angle = (rotationAngle * elapsedTime) / duration; - const x = rotateAround.x + radius * Math.cos(angle); - const z = rotateAround.z + radius * Math.sin(angle); - - fgRef.current.cameraPosition( - { x, y: rotateAround.y, z }, - { x: rotateAround.x, y: rotateAround.y, z: rotateAround.z }, - 0 - ); - - animationId.current = requestAnimationFrame(rotate); - }; - rotate(); - } - }, - [graphData] - ); - - useEffect(() => { - // Select the appropriate animation stages based on the active isAnimating flag - if (isAnimating1) { - setStages(zoomedOutedRotationAnimation); - setStageIndex(0); - } else if (isAnimating2) { - setStages(humanZoomedInRotationAnimation); - setStageIndex(0); - } else if (isAnimating3) { - setStages(miningZoomedInRotationAnimation); - setStageIndex(0); - } else { - setStages([]); // Reset stages if no animation flag is active - } - }, [isAnimating1, isAnimating2, isAnimating3]); - - // useEffect(() => { - // if (stages.length > 0 && stageIndex < stages.length) { - // animateStage(stages[stageIndex]); - // const { duration } = stages[stageIndex]; - // const timeoutId = setTimeout(() => { - // setStageIndex((prevStageIndex) => prevStageIndex + 1); - // }, duration * 1000); - // - // return () => clearTimeout(timeoutId); - // } else if (stageIndex >= stages.length) { - // setStageIndex(0); - // } - // return () => { - // if (animationId.current) cancelAnimationFrame(animationId.current); - // }; - // }, [stages, stageIndex, animateStage]); - useEffect(() => { - if (stages.length > 0) { - const animateNextStage = (currentStageIndex: number) => { - if (currentStageIndex >= stages.length) { - setStageIndex(0); // Reset to the beginning or stop if desired - return; - } - - // Animate the current stage - animateStage(stages[currentStageIndex]); - - const duration = stages[currentStageIndex].duration * 1000; // Stage duration in milliseconds - const startTime = Date.now(); - - // Transition loop for smooth movement - const transition = () => { - const elapsed = Date.now() - startTime; - - if (elapsed < duration) { - animationId.current = requestAnimationFrame(transition); - } else { - // Move to the next stage after current stage duration has passed - setStageIndex((index) => index + 1); - } - }; - - animationId.current = requestAnimationFrame(transition); - }; - - animateNextStage(stageIndex); - } - - // Cleanup - return () => { - if (animationId.current) cancelAnimationFrame(animationId.current); - }; - }, [stageIndex, stages, animateStage]); - return ( >; setFilteredGraphData: Dispatch>; - isAnimating1: boolean; - setIsAnimating1: Dispatch>; - isAnimating2: boolean; - setIsAnimating2: Dispatch>; - isAnimating3: boolean; - setIsAnimating3: Dispatch>; } const Selections: React.FC = ({ graphData, setGraphData, setFilteredGraphData, - isAnimating1, - setIsAnimating1, - isAnimating2, - setIsAnimating2, - isAnimating3, - setIsAnimating3, }: SelectionsProps) => { const [file, setFile] = useState(null); const [isChecked, setIsChecked] = useState(false); const [filters, setFilters] = useState>(new Set()); const [loading, setLoading] = useState(false); - const [animationOn, setAnimationOn] = useState(false); const handleFileChange = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0] || null; @@ -147,22 +131,6 @@ const Selections: React.FC = ({ return ; } - const onRadioChange = (value: string) => { - if (value === "zoomOut") { - setIsAnimating1(true); - setIsAnimating2(false); - setIsAnimating3(false); - } else if (value === "personZoomIn") { - setIsAnimating1(false); - setIsAnimating2(true); - setIsAnimating3(false); - } else if (value === "creativeWorkZoomIn") { - setIsAnimating1(false); - setIsAnimating2(false); - setIsAnimating3(true); - } - }; - return ( @@ -177,41 +145,6 @@ const Selections: React.FC = ({ Remove nodes that are not linked - - - { - setAnimationOn(event.target.checked); - if (!event.target.checked) { - setIsAnimating1(false); - setIsAnimating2(false); - setIsAnimating3(false); - } - }} - > - Animation On - - - - {animationOn && ( - - Animation Options - - - - Animate zoom out and rotation - - - Animate creative work zoom-in and rotation - - - Animate license zoom-in and rotation - - - - - )} @@ -220,7 +153,7 @@ const Selections: React.FC = ({ {groups.map((group) => ( - + ))} diff --git a/src/components/animation.ts b/src/components/animation.ts index c2f0b49..6a08c85 100644 --- a/src/components/animation.ts +++ b/src/components/animation.ts @@ -13,77 +13,3 @@ export type AnimationStageType = { nodeId?: string; distance?: number; }; - -const stages: AnimationStageType[] = [ - { type: "rotate", duration: 6, position: { x: 0, y: 0, z: 0 }, distance: 2000 }, - { - type: "zoomIn", - nodeId: "https://creativecommons.org/licenses/by/3.0", - duration: 3, - distance: 300, - }, - { - type: "rotate", - nodeId: "https://creativecommons.org/licenses/by/3.0", - duration: 5, - distance: 600, - }, - { type: "zoomIn", nodeId: "https://orcid.org/0000-0002-7527-7827", duration: 2, distance: 300 }, - { type: "rotate", nodeId: "https://orcid.org/0000-0002-7527-7827", duration: 5, distance: 600 }, - { - type: "zoomIn", - nodeId: "Global-scale mining polygons (Version 2)", - duration: 2, - distance: 300, - }, - { - type: "rotate", - nodeId: "Global-scale mining polygons (Version 2)", - duration: 5, - distance: 600, - }, - { type: "zoomIn", duration: 1, position: { x: 0, y: 1000, z: 1500 }, distance: 300 }, - { type: "rotate", duration: 4, position: { x: 0, y: 1000, z: 1500 }, distance: 300 }, -]; - -const zoomedOutedRotationAnimation: AnimationStageType[] = [ - { type: "rotate", duration: 10, position: { x: 0, y: 0, z: 0 }, distance: 4000 }, -]; - -const humanZoomedInRotationAnimation: AnimationStageType[] = [ - { type: "zoomIn", duration: 0, position: { x: 0, y: 0, z: 0 }, distance: 4000 }, - { - type: "zoomIn", - nodeId: "https://orcid.org/0000-0002-7527-7827", - duration: 8, - distance: 600, - }, - { - type: "rotate", - nodeId: "https://orcid.org/0000-0002-7527-7827", - duration: 10, - distance: 600, - }, -]; - -const miningZoomedInRotationAnimation: AnimationStageType[] = [ - { type: "zoomIn", duration: 0, position: { x: 0, y: 0, z: 0 }, distance: 4000 }, - { - type: "zoomIn", - nodeId: "Global-scale mining polygons (Version 2)", - duration: 5, - distance: 900, - }, - { - type: "rotate", - nodeId: "Global-scale mining polygons (Version 2)", - duration: 10, - distance: 900, - }, -]; -export { - stages, - zoomedOutedRotationAnimation, - humanZoomedInRotationAnimation, - miningZoomedInRotationAnimation, -}; From 4f9e2dfffad72942a3a082082f9c2b439ee1c482 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 09:46:11 +0100 Subject: [PATCH 18/46] install csstype lib for type errors --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 50ec29f..9d5a097 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@react-hook/resize-observer": "^2.0.2", + "csstype": "^3.1.3", "framer-motion": "^11.11.9", "rdflib": "^2.2.35", "react": "^18.3.1", diff --git a/package.json b/package.json index 0d4ed5f..bbaa1c6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@react-hook/resize-observer": "^2.0.2", + "csstype": "^3.1.3", "framer-motion": "^11.11.9", "rdflib": "^2.2.35", "react": "^18.3.1", From 3ec465ceb33846cd0dfb1a14ad402671e3239d3b Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 10:02:25 +0100 Subject: [PATCH 19/46] base url as input --- src/components/Content.tsx | 4 ++-- src/components/Graph.tsx | 2 +- src/components/Selections.tsx | 27 ++++++++++++++++++++------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/Content.tsx b/src/components/Content.tsx index e357736..77efa6a 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -19,7 +19,7 @@ const Content = () => { }); return ( - + { setFilteredGraphData={setFilteredGraphData} /> - + diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index b8238be..6f4b405 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -51,7 +51,7 @@ const Graph: React.FC = ({ graphData, width, height }) => { { - const store = createGraph(rdfData, "http://schema.org/"); +const parseRDF = (rdfData: string, baseUrl: string): GraphData => { + const store = createGraph(rdfData, baseUrl); return rdfGraphToNodes(store); }; @@ -35,6 +35,7 @@ const Selections: React.FC = ({ const [isChecked, setIsChecked] = useState(false); const [filters, setFilters] = useState>(new Set()); const [loading, setLoading] = useState(false); + const [baseUrl, setBaseUrl] = useState("http://schema.org/"); const handleFileChange = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0] || null; @@ -59,7 +60,7 @@ const Selections: React.FC = ({ try { if (typeof fileContent === "string") { // Ensure the content is a string - const data = parseRDF(fileContent); + const data = parseRDF(fileContent, baseUrl); setGraphData(data); setFilteredGraphData(data); setIsChecked(false); @@ -134,10 +135,22 @@ const Selections: React.FC = ({ return ( - - Upload a file - - + + + RDF Base URL + setBaseUrl(e.target.value)} + /> + + + Upload a turtle file + + {file && Selected File: {file.name}} + + From 8752fc390314d6d39a07060039bd68b8323e54d1 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 13:05:32 +0100 Subject: [PATCH 20/46] configuration from a file --- .gitignore | 3 + package-lock.json | 577 ++-------------------------------- package.json | 3 + src/components/Graph.tsx | 4 +- src/components/Legend.tsx | 21 +- src/components/Selections.tsx | 23 +- src/config.d.ts | 13 + src/config.ts | 10 + src/utils.ts | 116 +++---- vite.config.ts | 1 + 10 files changed, 127 insertions(+), 644 deletions(-) create mode 100644 src/config.d.ts create mode 100644 src/config.ts diff --git a/.gitignore b/.gitignore index a547bf3..aa4e8e1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# project configuration +config.yml \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9d5a097..f5daa35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@react-hook/resize-observer": "^2.0.2", "csstype": "^3.1.3", "framer-motion": "^11.11.9", + "js-yaml": "^4.1.0", "rdflib": "^2.2.35", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -23,6 +24,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.9.0", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@types/three": "^0.169.0", @@ -583,262 +586,6 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", @@ -855,102 +602,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1257,149 +908,6 @@ "react": ">=18" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", @@ -1426,45 +934,6 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1521,6 +990,12 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1544,6 +1019,15 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.8" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -3629,25 +3113,16 @@ } ] }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6567,6 +6042,12 @@ "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/package.json b/package.json index bbaa1c6..27573e0 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@react-hook/resize-observer": "^2.0.2", "csstype": "^3.1.3", "framer-motion": "^11.11.9", + "js-yaml": "^4.1.0", "rdflib": "^2.2.35", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -25,6 +26,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.9.0", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@types/three": "^0.169.0", diff --git a/src/components/Graph.tsx b/src/components/Graph.tsx index 6f4b405..65badd4 100644 --- a/src/components/Graph.tsx +++ b/src/components/Graph.tsx @@ -3,7 +3,7 @@ import ForceGraph3D, { GraphData, NodeObject, LinkObject } from "react-force-gra import * as THREE from "three"; import { Box } from "@chakra-ui/react"; import Legend from "./Legend.tsx"; -import { groupColors, getGroupColor } from "../utils"; +import { getGroupColor } from "../utils"; import FullScreenButton from "./FullScreenButton.tsx"; import { useCallback, useEffect, useRef, useState } from "react"; @@ -82,7 +82,7 @@ const Graph: React.FC = ({ graphData, width, height }) => { onNodeClick={handleClick} /> - + ); }; diff --git a/src/components/Legend.tsx b/src/components/Legend.tsx index 0851236..e26ee6d 100644 --- a/src/components/Legend.tsx +++ b/src/components/Legend.tsx @@ -1,11 +1,8 @@ import { Flex, Box, Text } from "@chakra-ui/react"; import * as React from "react"; +import CONFIG from "../config.ts"; -interface LegendProps { - groupColors: { [key: string]: string }; -} - -const Legend: React.FC = ({ groupColors }) => { +const Legend: React.FC = () => { return ( = ({ groupColors }) => { boxShadow="md" flexDirection="column" > - {Object.keys(groupColors).map((group: string) => ( - - + {CONFIG.groups.map((group) => ( + + - {group || "Default"} + {group.name} ))} + + + + Default + + ); }; diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 8bd4701..8731019 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -6,7 +6,7 @@ import { Input, VStack, Heading, - HStack, + HStack,Button,Text } from "@chakra-ui/react"; import * as React from "react"; import { useState, Dispatch, SetStateAction, useEffect } from "react"; @@ -147,8 +147,23 @@ const Selections: React.FC = ({ Upload a turtle file - - {file && Selected File: {file.name}} + + + {file && ( + + {file.name} + + )} @@ -166,7 +181,7 @@ const Selections: React.FC = ({ {groups.map((group) => ( - + ))} diff --git a/src/config.d.ts b/src/config.d.ts new file mode 100644 index 0000000..3524c56 --- /dev/null +++ b/src/config.d.ts @@ -0,0 +1,13 @@ +export type GroupType = { + name: string; + types: string[]; + properties?: string[]; + color: string; +}; + +export type ConfigType = { + relevantProperties: string[]; + relationProperties: string[]; + labelProperties: string[]; + groups: Group[]; +}; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..83e8117 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,10 @@ +import configFile from "/config.yml?raw"; +import * as yaml from "js-yaml"; +import { ConfigType } from "./config.d.ts"; + +const config = yaml.load(configFile) as ConfigType; + +// relevant properties should be a Set of strings +config.relevantProperties = new Set(config.relevantProperties); + +export default config; diff --git a/src/utils.ts b/src/utils.ts index 177b560..3fd4e26 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,20 +1,14 @@ import * as rdflib from "rdflib"; import { GraphData, LinkObject, NodeObject } from "react-force-graph-3d"; - -const groupColors: { [key: string]: string } = { - person: "red", - dataset: "blue", - organization: "green", - software: "yellow", - document: "orange", - article: "indigo", - creativeWork: "violet", - service: "cyan", - "": "gray", // Default group -}; +import CONFIG from "./config.ts"; const getGroupColor = (group: string) => { - return groupColors[group] || "gray"; // Default to gray if group not found + for (const g of CONFIG.groups) { + if (g.name === group) { + return g.color; + } + } + return "gray"; }; const createGraph = (rdfData: string, baseUrl: string): rdflib.Store => { @@ -39,7 +33,7 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { const safeUpdateElement = (id: string, label?: string, group?: string): void => { const safeGroup = group ?? ""; const safeLabel = label ?? id; - const color = group ? getGroupColor(safeGroup) : "gray"; + const color = getGroupColor(safeGroup); if (!nodesMap.has(id)) { // create the node const newNode = { id: id, label: safeLabel, group: safeGroup, color } as NodeType; @@ -54,7 +48,7 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { group: group ?? node?.group, color: node?.color, }; - updatedNode.color = updatedNode.group ? getGroupColor(updatedNode.group) : "gray"; + updatedNode.color = getGroupColor(updatedNode.group ?? ""); nodesMap.set(id, updatedNode); } @@ -65,25 +59,13 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { const pred = statement.predicate.value; const obj = statement.object.value; - // Filter out unwanted annotation properties and irrelevant predicates - // List of relevant properties you care about - const relevantProperties = new Set([ - "http://schema.org/name", - "http://schema.org/text", - "http://schema.org/affiliation", - "http://schema.org/author", - "http://schema.org/creator", - "http://schema.org/license", - "http://schema.org/keywords", - "http://schema.org/provider", - "http://schema.org/publisher", - "http://schema.org/dateModified", - "http://schema.org/datePublished", - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", - ]); - // If the predicate is in the ignored list, skip this statement - if (!relevantProperties.has(pred)) { + if ( + !( + CONFIG.relevantProperties.has(pred) || + pred === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" + ) + ) { return; // Skip this statement } @@ -96,32 +78,18 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { } safeUpdateElement(subj, undefined, group); - } else if (pred === "http://schema.org/name" || pred === "http://schema.org/text") { - // Check for schema:name, foaf:name, rdfs:label, etc. + } else if (pred in CONFIG.labelProperties) { const label = statement.object.value; safeUpdateElement(subj, label); } // Create links for relevant relationships - const relationProperties = [ - "provider", - "license", - "keywords", - "dataPublished", - "dateModified", - "creator", - "author", - "publisher", - "affiliation", - ]; - const includesElement = relationProperties.some((item) => pred.includes(item)); + const includesElement = CONFIG.relationProperties.some((item) => pred.includes(item)); if (includesElement) { - if (pred.includes("affiliation")) { - safeUpdateElement(obj, undefined, "organization"); - } - if (pred.includes("author") || pred.includes("creator")) { - safeUpdateElement(obj, undefined, "person"); + const group = predToGroup(pred); + if (group !== "") { + safeUpdateElement(obj, undefined, group); } if (!nodesMap.has(subj)) { nodesMap.set(subj, { id: subj, label: subj, group: "" }); @@ -161,38 +129,24 @@ const removeNonConnectedNodes = (graphData: GraphData): NodeObject[] => { }; const typeToGroup = (type: string): string => { - const typeMap: Map = new Map(); - //schema:Person, schema:Organization, schema:Dataset, schema:SoftwareSourceCode, schema:Document, - // schema:Article, schema:creativeWork, schema:Service - - // set types - typeMap.set("person", "Person"); - typeMap.set("dataset", "Dataset"); - typeMap.set("organization", "Organization"); - typeMap.set("software", "SoftwareSourceCode"); - typeMap.set("document", "Document"); - typeMap.set("article", "Article"); - typeMap.set("creativeWork", "CreativeWork"); - typeMap.set("service", "Service"); - - // iterate over the entries of the typeMap - for (const [key, value] of typeMap.entries()) { - // check if type input contains any of the elements in the values array - if (type.toLowerCase().includes(value.toLowerCase())) { - return key; // return the map key if a match is found + for (const group of CONFIG.groups) { + if (group.types.includes(type)) { + return group.name; } } + return ""; +}; - return ""; // return a default value if no match is found +const predToGroup = (pred: string): string => { + for (const group of CONFIG.groups) { + if (group.properties && group.properties.includes(pred)) { + return group.name; + } + } + return ""; }; -const groups = Object.keys(groupColors).filter((group) => group !== ""); // Remove the default group +// config groups keys +const groups = CONFIG.groups.map((group) => group.name); -export { - createGraph, - rdfGraphToNodes, - removeNonConnectedNodes, - groupColors, - groups, - getGroupColor, -}; +export { createGraph, rdfGraphToNodes, removeNonConnectedNodes, groups, getGroupColor }; diff --git a/vite.config.ts b/vite.config.ts index 9cc50ea..630ef81 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,5 +3,6 @@ import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ + assetsInclude: ["./config.yml"], plugins: [react()], }); From 4d274ee90b65c6bb79facf95cca47f1e3870c310 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 13:17:18 +0100 Subject: [PATCH 21/46] fix build errors --- src/components/Legend.tsx | 3 ++- src/components/Selections.tsx | 10 ++++++---- src/config.d.ts | 13 ------------- src/config.ts | 4 ++-- src/utils.ts | 5 +++-- src/vite-env.d.ts | 13 +++++++++++++ 6 files changed, 26 insertions(+), 22 deletions(-) delete mode 100644 src/config.d.ts diff --git a/src/components/Legend.tsx b/src/components/Legend.tsx index e26ee6d..fcf7c6c 100644 --- a/src/components/Legend.tsx +++ b/src/components/Legend.tsx @@ -1,6 +1,7 @@ import { Flex, Box, Text } from "@chakra-ui/react"; import * as React from "react"; import CONFIG from "../config.ts"; +import { GroupType } from "../vite-env"; const Legend: React.FC = () => { return ( @@ -14,7 +15,7 @@ const Legend: React.FC = () => { boxShadow="md" flexDirection="column" > - {CONFIG.groups.map((group) => ( + {CONFIG.groups.map((group: GroupType) => ( diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 8731019..ba54a1d 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -6,7 +6,9 @@ import { Input, VStack, Heading, - HStack,Button,Text + HStack, + Button, + Text, } from "@chakra-ui/react"; import * as React from "react"; import { useState, Dispatch, SetStateAction, useEffect } from "react"; @@ -151,12 +153,12 @@ const Selections: React.FC = ({ id="file-upload" type="file" accept=".rdf, .ttl" - display="none" // Hide the default file input + display="none" // Hide the default file input onChange={handleFileChange} /> {file && ( @@ -180,7 +182,7 @@ const Selections: React.FC = ({ - {groups.map((group) => ( + {groups.map((group: string) => ( ))} diff --git a/src/config.d.ts b/src/config.d.ts deleted file mode 100644 index 3524c56..0000000 --- a/src/config.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type GroupType = { - name: string; - types: string[]; - properties?: string[]; - color: string; -}; - -export type ConfigType = { - relevantProperties: string[]; - relationProperties: string[]; - labelProperties: string[]; - groups: Group[]; -}; diff --git a/src/config.ts b/src/config.ts index 83e8117..3ed46e0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,8 @@ import configFile from "/config.yml?raw"; import * as yaml from "js-yaml"; -import { ConfigType } from "./config.d.ts"; +import { ConfigType } from "./vite-env"; -const config = yaml.load(configFile) as ConfigType; +const config: ConfigType = yaml.load(configFile) as ConfigType; // relevant properties should be a Set of strings config.relevantProperties = new Set(config.relevantProperties); diff --git a/src/utils.ts b/src/utils.ts index 3fd4e26..5ae618b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import * as rdflib from "rdflib"; import { GraphData, LinkObject, NodeObject } from "react-force-graph-3d"; import CONFIG from "./config.ts"; +import { GroupType } from "./vite-env"; const getGroupColor = (group: string) => { for (const g of CONFIG.groups) { @@ -85,7 +86,7 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { } // Create links for relevant relationships - const includesElement = CONFIG.relationProperties.some((item) => pred.includes(item)); + const includesElement = CONFIG.relationProperties.some((item:string) => pred.includes(item)); if (includesElement) { const group = predToGroup(pred); if (group !== "") { @@ -147,6 +148,6 @@ const predToGroup = (pred: string): string => { }; // config groups keys -const groups = CONFIG.groups.map((group) => group.name); +const groups = CONFIG.groups.map((group:GroupType) => group.name); export { createGraph, rdfGraphToNodes, removeNonConnectedNodes, groups, getGroupColor }; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..2e223a5 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,14 @@ /// +export type GroupType = { + name: string; + types: string[]; + properties?: string[]; + color: string; +}; + +export type ConfigType = { + relevantProperties: Set; + relationProperties: string[]; + labelProperties: string[]; + groups: GroupType[]; +}; From 0c874b3403641344cc0200da6167c20d1c396190 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 13:44:42 +0100 Subject: [PATCH 22/46] fomratting --- src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 5ae618b..fb6c308 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -86,7 +86,7 @@ const rdfGraphToNodes = (store: rdflib.Store): GraphData => { } // Create links for relevant relationships - const includesElement = CONFIG.relationProperties.some((item:string) => pred.includes(item)); + const includesElement = CONFIG.relationProperties.some((item: string) => pred.includes(item)); if (includesElement) { const group = predToGroup(pred); if (group !== "") { @@ -148,6 +148,6 @@ const predToGroup = (pred: string): string => { }; // config groups keys -const groups = CONFIG.groups.map((group:GroupType) => group.name); +const groups = CONFIG.groups.map((group: GroupType) => group.name); export { createGraph, rdfGraphToNodes, removeNonConnectedNodes, groups, getGroupColor }; From c8d06d06ba6a48e73e86c95cbaaafb2f886c74c2 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 13:44:54 +0100 Subject: [PATCH 23/46] better metadata --- .somesy.toml | 41 ++++++++++++++--------------- CITATION.cff | 34 ++++++++++++++++++++++++ codemeta.json | 66 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 10 +++---- package.json | 53 ++++++++++++++++++++++++++++++++++--- 5 files changed, 173 insertions(+), 31 deletions(-) create mode 100644 CITATION.cff create mode 100644 codemeta.json diff --git a/.somesy.toml b/.somesy.toml index 8bf901d..2b92a17 100644 --- a/.somesy.toml +++ b/.somesy.toml @@ -1,34 +1,31 @@ [project] -name = "beamer-gui" -version = "0.0.1" -description = "A GUI for creating a Beamer presentation using LaTeX." -keywords = ["beamer", "latex"] +name = "rdf-graph-visualization" +version = "0.1.0" +description = "Visualization of RDF graphs in Turtle format in 3D on the web." +keywords = ["rdf", "graph", "turtle", "visualization"] license = "MIT" -repository = "https://github.com/Materials-Data-Science-and-Informatics/beamer-gui" +repository = "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization" +homepage = "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization" [[project.people]] family-names = "Soylu" given-names = "Mustafa" email = "m.soylu@fz-juelich.de" orcid = "https://orcid.org/0000-0003-2637-0432" - -contribution = "Main developer and project manager." -contribution_types = ["maintenance", "code", "test", "review", "doc"] - +contribution = "The main author, maintainer and tester." +contribution_begin = "2023-03-01" +contribution_types = ["ideas", "code", "design", "test"] author = true maintainer = true [[project.people]] -family-names = "D'Mello" -given-names = "Fiona" -orcid = "https://orcid.org/0000-0002-0465-1009" -email = "f.dmello@fz-juelich.de" - -contribution = "Main developer." -contribution_types = ["ideas", "code", "test", "review", "doc"] - -author = true +family-names = "Fathalla" +given-names = "Said" +email = "s.fathalla@fz-juelich.de" +orcid = "https://orcid.org/0000-0002-2818-5890" +contribution_begin = "2023-03-01" +contribution_types = ["ideas", "userTesting"] [[project.people]] family-names = "Hofmann" @@ -36,6 +33,10 @@ given-names = "Volker" email = "v.hofmann@fz-juelich.de" orcid = "https://orcid.org/0000-0002-5149-603X" +contribution = "Discussions and suggestions concerning tool scope and usability." +contribution_begin = "2023-06-01" +contribution_end = "2023-06-30" +contribution_types = ["ideas", "userTesting"] publication_author = true [[project.people]] @@ -44,7 +45,5 @@ given-names = "Stefan" email = "s.sandfeld@fz-juelich.de" orcid = "https://orcid.org/0000-0001-9560-4728" -contribution_types = ["ideas", "fundingFinding"] - +contribution_types = ["fundingFinding"] publication_author = true - diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..5e9ae95 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,34 @@ +cff-version: 1.2.0 +message: If you use this software, please cite it using these metadata. +type: software +title: rdf-graph-visualization +abstract: Visualization of RDF graphs in Turtle format in 3D on the web. +version: 0.1.0 +keywords: +- rdf +- graph +- turtle +- visualization +authors: +- family-names: Soylu + given-names: Mustafa + orcid: https://orcid.org/0000-0003-2637-0432 + email: m.soylu@fz-juelich.de +- family-names: Hofmann + given-names: Volker + orcid: https://orcid.org/0000-0002-5149-603X + email: v.hofmann@fz-juelich.de +- family-names: Sandfeld + given-names: Stefan + orcid: https://orcid.org/0000-0001-9560-4728 + email: s.sandfeld@fz-juelich.de +contact: +- family-names: Soylu + given-names: Mustafa + orcid: https://orcid.org/0000-0003-2637-0432 + email: m.soylu@fz-juelich.de +license: MIT +repository-code: + https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization +url: + https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization diff --git a/codemeta.json b/codemeta.json new file mode 100644 index 0000000..c733586 --- /dev/null +++ b/codemeta.json @@ -0,0 +1,66 @@ +{ + "@context": [ + "https://doi.org/10.5063/schema/codemeta-2.0", + "https://w3id.org/software-iodata", + "https://raw.githubusercontent.com/jantman/repostatus.org/master/badges/latest/ontology.jsonld", + "https://schema.org", + "https://w3id.org/software-types" + ], + "@type": "SoftwareSourceCode", + "author": [ + { + "@type": "Person", + "givenName": "Mustafa", + "familyName": "Soylu", + "email": "m.soylu@fz-juelich.de", + "@id": "https://orcid.org/0000-0003-2637-0432" + } + ], + "name": "rdf-graph-visualization", + "description": "Visualization of RDF graphs in Turtle format in 3D on the web.", + "version": "0.1.0", + "keywords": [ + "rdf", + "graph", + "turtle", + "visualization" + ], + "maintainer": [ + { + "@type": "Person", + "givenName": "Mustafa", + "familyName": "Soylu", + "email": "m.soylu@fz-juelich.de", + "@id": "https://orcid.org/0000-0003-2637-0432" + } + ], + "license": [ + "https://spdx.org/licenses/MIT" + ], + "softwareHelp": "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization", + "codeRepository": "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization", + "contributor": [ + { + "@type": "Person", + "givenName": "Said", + "familyName": "Fathalla", + "email": "s.fathalla@fz-juelich.de", + "@id": "https://orcid.org/0000-0002-2818-5890" + }, + { + "@type": "Person", + "givenName": "Volker", + "familyName": "Hofmann", + "email": "v.hofmann@fz-juelich.de", + "@id": "https://orcid.org/0000-0002-5149-603X" + }, + { + "@type": "Person", + "givenName": "Stefan", + "familyName": "Sandfeld", + "email": "s.sandfeld@fz-juelich.de", + "@id": "https://orcid.org/0000-0001-9560-4728" + } + ], + "url": "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f5daa35..cc231ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "rdf-graph-visualization", - "version": "0.0.0", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rdf-graph-visualization", - "version": "0.0.0", + "version": "0.1.0", + "license": "MIT", "dependencies": { "@chakra-ui/react": "^2.10.3", "@emotion/react": "^11.13.3", @@ -3113,11 +3114,6 @@ } ] }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", diff --git a/package.json b/package.json index 27573e0..e0b2672 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rdf-graph-visualization", "private": true, - "version": "0.0.0", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", @@ -40,5 +40,52 @@ "typescript": "^5.5.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" - } -} + }, + "author": { + "name": "Mustafa Soylu", + "email": "m.soylu@fz-juelich.de", + "url": "https://orcid.org/0000-0003-2637-0432" + }, + "description": "Visualization of RDF graphs in Turtle format in 3D on the web.", + "keywords": [ + "rdf", + "graph", + "turtle", + "visualization" + ], + "maintainers": [ + { + "name": "Mustafa Soylu", + "email": "m.soylu@fz-juelich.de", + "url": "https://orcid.org/0000-0003-2637-0432" + } + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization" + }, + "contributors": [ + { + "name": "Mustafa Soylu", + "email": "m.soylu@fz-juelich.de", + "url": "https://orcid.org/0000-0003-2637-0432" + }, + { + "name": "Volker Hofmann", + "email": "v.hofmann@fz-juelich.de", + "url": "https://orcid.org/0000-0002-5149-603X" + }, + { + "name": "Stefan Sandfeld", + "email": "s.sandfeld@fz-juelich.de", + "url": "https://orcid.org/0000-0001-9560-4728" + }, + { + "name": "Said Fathalla", + "email": "s.fathalla@fz-juelich.de", + "url": "https://orcid.org/0000-0002-2818-5890" + } + ], + "homepage": "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization" +} \ No newline at end of file From 92863a885fed137df65220567daf97b044629774 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 13:45:04 +0100 Subject: [PATCH 24/46] update readme --- README.md | 129 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 74872fd..3f0e731 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,99 @@ -# React + TypeScript + Vite +# RDF Graph Visualization -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +## Description +This project is a TypeScript/JavaScript application that processes RDF data to create a graph representation. It uses React for the frontend and Chakra UI for styling. -Currently, two official plugins are available: +We strongly suggest users to use `turtle for`mat for RDF data and `schema.org` vocabulary for the properties. The application is designed to work with the `schema.org` vocabulary, since we developed it for a specific use case. However, it can be easily modified to work with other vocabularies. -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +## Features +- Parses RDF data and filters relevant properties. +- Creates nodes and edges for a graph representation. +- Filters and displays nodes based on user-selected criteria. -## Expanding the ESLint configuration +## Installation +1. Clone the repository. -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +```bash +git clone https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization.git +``` +2. Install dependencies: + +```bash +npm install +``` -- Configure the top-level `parserOptions` property like this: +3. Create the `config.yml` file in the root directory and set it according to next section. +4. You can run the application with: -```js -export default tseslint.config({ - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) +```bash +npm run dev ``` -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` -- Optionally add `...tseslint.configs.stylisticTypeChecked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: - -```js -// eslint.config.js -import react from 'eslint-plugin-react' - -export default tseslint.config({ - // Set the react version - settings: { react: { version: '18.3' } }, - plugins: { - // Add the react plugin - react, - }, - rules: { - // other rules... - // Enable its recommended rules - ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, - }, -}) +5. Or you can build the application with below command and serve it with a web server. + +```bash +npm run build +``` + +## Configuration +Configuration file has the necessary information to show nodes and links in the graph. Since a 3D graph on the browser consumes a lot of resources, it is important to filter the data to show only the necessary information. + +The configuration file is a YAML file that has the following properties: +```yaml +# parsing will skip all properties that are not in the list of relevantProperties +relevantProperties: + - "http://schema.org/name" + - "http://schema.org/affiliation" + - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" + +# links will be created from these properties +relationProperties: + - "http://schema.org/affiliation" + +# labels will be created from these properties +labelProperties: + - "http://schema.org/name" + +# groups will be created from these properties +groups: + - name: 'Organization' + types: + - 'http://schema.org/Organization' + properties: + - 'http://schema.org/affiliation' + color: '#FF0000' ``` + +- `relevantProperties`: List of properties that will be used to filter the data. +- `relationProperties`: List of properties that will be used to create links between nodes. +- `labelProperties`: List of properties that will be used to create labels for nodes. +- `groups`: List of groups that will be created based on the properties. Each group has the following properties: + - `name`: Name of the group. + - `types`: List of types that will be included in the group. + - `properties`: List of properties that will be used to filter the data. **Can be empty.** + - `color`: Color of the group. Please use HEX color codes. + +## How to Cite + +If you want to cite this project in your scientific work, +please use the [citation file](https://citation-file-format.github.io/) +in the [repository](https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization/blob/main/CITATION.cff). + + + + +## Acknowledgements + +We kindly thank all authors and contributors. + +
+HMC Logo +   +FZJ Logo +
+
+ +This project was developed at the Institute for Materials Data Science and Informatics +(IAS-9) of the Jülich Research Center and funded by the Helmholtz Metadata Collaboration +(HMC), an incubator-platform of the Helmholtz Association within the framework of the +Information and Data Science strategic initiative. \ No newline at end of file From 9b9802dbf15a0fa61a77a07e3fc67ca317039a54 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 14:03:33 +0100 Subject: [PATCH 25/46] reverse filter showing --- src/components/FilterSwitch.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FilterSwitch.tsx b/src/components/FilterSwitch.tsx index 8e856be..d15021a 100644 --- a/src/components/FilterSwitch.tsx +++ b/src/components/FilterSwitch.tsx @@ -8,10 +8,10 @@ interface FilterSwitchProps { } const FilterSwitch = ({ name, filters, setFilters }: FilterSwitchProps) => { - const isChecked = filters.has(name); + const isChecked = !filters.has(name); const handleChange = (event: React.ChangeEvent) => { - if (event.target.checked) { + if (!event.target.checked) { setFilters(new Set(filters).add(name)); } else { filters.delete(name); From ac5a5bc43eb3b6d7a658a6971210855a52f7a28c Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 14:04:43 +0100 Subject: [PATCH 26/46] Filter for default too --- src/components/Selections.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index ba54a1d..50eda2b 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -98,8 +98,15 @@ const Selections: React.FC = ({ if (filters.size === 0) { return true; } + // replace `Default` with `` + const tempFilters = new Set(filters); + if (filters.has("Default")) { + tempFilters.delete("Default"); + tempFilters.add(""); + } + // otherwise, dont show nodes that match the selected filters - return !filters.has(node.group); + return !tempFilters.has(node.group); }); const filteredLinks = graphData.links.filter((link: LinkObject) => { // link should be shown if both source or target is in the filtered nodes @@ -185,6 +192,9 @@ const Selections: React.FC = ({ {groups.map((group: string) => ( ))} + {groups.length && ( + + )}
From 74a9ce603a4d0505d7fc589cd972b59b77e0cee9 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 14:23:35 +0100 Subject: [PATCH 27/46] collapsible configuration (selections) --- src/components/Content.tsx | 37 +++++++++++++++++++++++++++-------- src/components/Selections.tsx | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 77efa6a..0b50aaf 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -1,15 +1,17 @@ -import { Box } from "@chakra-ui/react"; +import { Box, Collapse, useDisclosure, Text, HStack, IconButton } from "@chakra-ui/react"; import Graph from "./Graph"; import Selections from "./Selections"; import { useState, useRef } from "react"; import useResizeObserver from "@react-hook/resize-observer"; import { GraphData } from "react-force-graph-3d"; +import { CiSquareChevUp, CiSquareChevDown } from "react-icons/ci"; const Content = () => { const [filteredGraphData, setFilteredGraphData] = useState({ nodes: [], links: [] }); const [graphData, setGraphData] = useState({ nodes: [], links: [] }); const boxRef = useRef(null); const [size, setSize] = useState({ width: 0, height: 0 }); + const { isOpen, onToggle } = useDisclosure(); useResizeObserver(boxRef, (entry) => { if (entry) { @@ -20,14 +22,33 @@ const Content = () => { return ( - - + + + + Configuration + + + {/* Button to toggle collapse */} + : } + aria-label="toogle configuration" + /> + + {/* Collapsible content */} + + + + + - + diff --git a/src/components/Selections.tsx b/src/components/Selections.tsx index 50eda2b..179f119 100644 --- a/src/components/Selections.tsx +++ b/src/components/Selections.tsx @@ -142,7 +142,7 @@ const Selections: React.FC = ({ } return ( - + From 345c8e25ffe094e11ad64073f0841f085fac473a Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 14:48:45 +0100 Subject: [PATCH 28/46] style navbar --- package-lock.json | 10 ++++++++++ package.json | 3 ++- src/components/Navbar.tsx | 26 ++++++++++++++++++++++---- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc231ba..8a9e47c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { + "@chakra-ui/icons": "^2.2.4", "@chakra-ui/react": "^2.10.3", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", @@ -370,6 +371,15 @@ "react": ">=18" } }, + "node_modules/@chakra-ui/icons": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.2.4.tgz", + "integrity": "sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==", + "peerDependencies": { + "@chakra-ui/react": ">=2.0.0", + "react": ">=18" + } + }, "node_modules/@chakra-ui/react": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.10.3.tgz", diff --git a/package.json b/package.json index e0b2672..04c18f5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@chakra-ui/icons": "^2.2.4", "@chakra-ui/react": "^2.10.3", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", @@ -88,4 +89,4 @@ } ], "homepage": "https://github.com/Materials-Data-Science-and-Informatics/rdf-graph-visualization" -} \ No newline at end of file +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 6b5235d..7d167e3 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,14 +1,32 @@ -import { Box, Flex, Text } from "@chakra-ui/react"; +import { Box, Flex, Text, Image, Link } from "@chakra-ui/react"; +import { ExternalLinkIcon } from '@chakra-ui/icons' const Navbar = () => { return ( - - - + + + Logo window.location.reload()} + /> + RDF Graph Visualization + + HMC + ); }; + export default Navbar; From 69214ad147cfc82d9bb83fbfe390cee2a857a6a1 Mon Sep 17 00:00:00 2001 From: Mustafa Soylu Date: Wed, 6 Nov 2024 15:10:52 +0100 Subject: [PATCH 29/46] add footer --- src/components/Content.tsx | 2 +- src/components/Footer.tsx | 67 ++++++++++++++++++++++++++++++++++++++ src/components/Layout.tsx | 19 +++++++++-- 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/components/Footer.tsx diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 0b50aaf..26da40f 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -48,7 +48,7 @@ const Content = () => { - + diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..36c433f --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Box, Flex, Link, Text, UnorderedList, ListItem } from '@chakra-ui/react'; +import { FaLinkedin, FaMastodon, FaTwitter, FaEnvelope, FaGithub } from 'react-icons/fa'; + +const Footer = () => { + const contact = [ + { type: 'linkedin', link: 'https://www.linkedin.com/company/helmholtz-metadata-collaboration-hmc/' }, + { type: 'mastodon', link: 'https://helmholtz.social/@helmholtz_hmc' }, + { type: 'x', link: 'https://x.com/helmholtz_hmc' }, + { type: 'mattermost', link: 'https://mattermost.hzdr.de/hmc-public' }, + { type: 'mail', link: 'mailto:m.soylu@fz-juelich.de' }, + ]; + + const iconComponents = { + linkedin: , + mastodon: , + x: , + mattermost: , + mail: , + }; + + return ( + + + + + + + + + Research for grand challenges. + + + + + {contact.map((item, index) => ( + + + {iconComponents[item.type]} + + + ))} + + + + + + + + Imprint + + + + Contact + + + © {new Date().getFullYear()} Forschungszentrum Jülich + + + + ); +}; + +export default Footer; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index c385341..3f3276a 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,6 +1,7 @@ import { Box, Flex, useBreakpointValue } from "@chakra-ui/react"; import { ReactNode } from "react"; import Navbar from "./Navbar"; +import Footer from "./Footer"; interface LayoutProps { children: ReactNode; @@ -14,12 +15,24 @@ export default function Layout({ children }: LayoutProps) { }); return ( - + - + {children} - {/*