Yassine Mhirsi
commited on
Commit
·
75137c7
1
Parent(s):
c04cfa4
Add analysis charts and utilities
Browse files- Introduced `StanceDistributionChart`, `TimeSeriesChart`, and `TopicFrequencyChart` components for visualizing analysis results.
- Implemented utility functions for calculating stance statistics, topic frequency, and time-based statistics.
- Updated `AnalysisPage` to include new charts and display user analysis statistics.
- Added `recharts` library for charting capabilities and updated dependencies in `package.json` and `package-lock.json`.
- package-lock.json +400 -0
- package.json +1 -0
- src/app/components/analysis/StanceDistributionChart.tsx +55 -0
- src/app/components/analysis/TimeSeriesChart.tsx +45 -0
- src/app/components/analysis/TopicFrequencyChart.tsx +48 -0
- src/app/pages/AnalysisPage.tsx +186 -12
- src/app/utils/analysis.utils.ts +139 -0
- src/app/utils/index.ts +3 -0
package-lock.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
| 17 |
"react-dom": "^19.1.0",
|
| 18 |
"react-router-dom": "^7.10.1",
|
| 19 |
"react-scripts": "5.0.1",
|
|
|
|
| 20 |
"web-vitals": "^2.1.4"
|
| 21 |
},
|
| 22 |
"devDependencies": {
|
|
@@ -3005,6 +3006,42 @@
|
|
| 3005 |
}
|
| 3006 |
}
|
| 3007 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3008 |
"node_modules/@rollup/plugin-babel": {
|
| 3009 |
"version": "5.3.1",
|
| 3010 |
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
|
@@ -3120,6 +3157,18 @@
|
|
| 3120 |
"@sinonjs/commons": "^1.7.0"
|
| 3121 |
}
|
| 3122 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3123 |
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
| 3124 |
"version": "2.2.3",
|
| 3125 |
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
|
@@ -3575,6 +3624,69 @@
|
|
| 3575 |
"@types/node": "*"
|
| 3576 |
}
|
| 3577 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3578 |
"node_modules/@types/eslint": {
|
| 3579 |
"version": "8.56.12",
|
| 3580 |
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
|
|
@@ -3838,6 +3950,12 @@
|
|
| 3838 |
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
| 3839 |
"license": "MIT"
|
| 3840 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3841 |
"node_modules/@types/ws": {
|
| 3842 |
"version": "8.18.1",
|
| 3843 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
@@ -5616,6 +5734,15 @@
|
|
| 5616 |
"wrap-ansi": "^7.0.0"
|
| 5617 |
}
|
| 5618 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5619 |
"node_modules/co": {
|
| 5620 |
"version": "4.6.0",
|
| 5621 |
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
|
@@ -6354,6 +6481,127 @@
|
|
| 6354 |
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
| 6355 |
"license": "MIT"
|
| 6356 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6357 |
"node_modules/damerau-levenshtein": {
|
| 6358 |
"version": "1.0.8",
|
| 6359 |
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
|
@@ -6448,6 +6696,12 @@
|
|
| 6448 |
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
| 6449 |
"license": "MIT"
|
| 6450 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6451 |
"node_modules/dedent": {
|
| 6452 |
"version": "0.7.0",
|
| 6453 |
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
|
@@ -7097,6 +7351,16 @@
|
|
| 7097 |
"url": "https://github.com/sponsors/ljharb"
|
| 7098 |
}
|
| 7099 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7100 |
"node_modules/escalade": {
|
| 7101 |
"version": "3.2.0",
|
| 7102 |
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
|
@@ -9240,6 +9504,15 @@
|
|
| 9240 |
"node": ">= 0.4"
|
| 9241 |
}
|
| 9242 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9243 |
"node_modules/ipaddr.js": {
|
| 9244 |
"version": "2.3.0",
|
| 9245 |
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
|
|
@@ -13790,6 +14063,29 @@
|
|
| 13790 |
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
| 13791 |
"license": "MIT"
|
| 13792 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13793 |
"node_modules/react-refresh": {
|
| 13794 |
"version": "0.11.0",
|
| 13795 |
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
|
@@ -13958,6 +14254,52 @@
|
|
| 13958 |
"node": ">=8.10.0"
|
| 13959 |
}
|
| 13960 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13961 |
"node_modules/recursive-readdir": {
|
| 13962 |
"version": "2.2.3",
|
| 13963 |
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
|
|
@@ -13983,6 +14325,21 @@
|
|
| 13983 |
"node": ">=8"
|
| 13984 |
}
|
| 13985 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13986 |
"node_modules/reflect.getprototypeof": {
|
| 13987 |
"version": "1.0.10",
|
| 13988 |
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
|
@@ -14136,6 +14493,12 @@
|
|
| 14136 |
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
| 14137 |
"license": "MIT"
|
| 14138 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14139 |
"node_modules/resolve": {
|
| 14140 |
"version": "1.22.11",
|
| 14141 |
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
|
@@ -16080,6 +16443,12 @@
|
|
| 16080 |
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
|
| 16081 |
"license": "MIT"
|
| 16082 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16083 |
"node_modules/tinyglobby": {
|
| 16084 |
"version": "0.2.15",
|
| 16085 |
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
|
@@ -16635,6 +17004,15 @@
|
|
| 16635 |
"requires-port": "^1.0.0"
|
| 16636 |
}
|
| 16637 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16638 |
"node_modules/util-deprecate": {
|
| 16639 |
"version": "1.0.2",
|
| 16640 |
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
@@ -16717,6 +17095,28 @@
|
|
| 16717 |
"node": ">= 0.8"
|
| 16718 |
}
|
| 16719 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16720 |
"node_modules/w3c-hr-time": {
|
| 16721 |
"version": "1.0.2",
|
| 16722 |
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
|
|
|
| 17 |
"react-dom": "^19.1.0",
|
| 18 |
"react-router-dom": "^7.10.1",
|
| 19 |
"react-scripts": "5.0.1",
|
| 20 |
+
"recharts": "^3.5.1",
|
| 21 |
"web-vitals": "^2.1.4"
|
| 22 |
},
|
| 23 |
"devDependencies": {
|
|
|
|
| 3006 |
}
|
| 3007 |
}
|
| 3008 |
},
|
| 3009 |
+
"node_modules/@reduxjs/toolkit": {
|
| 3010 |
+
"version": "2.11.1",
|
| 3011 |
+
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.1.tgz",
|
| 3012 |
+
"integrity": "sha512-HjhlEREguAyBTGNzRlGNiDHGQ2EjLSPWwdhhpoEqHYy8hWak3Dp6/fU72OfqVsiMb8S6rbfPsWUF24fxpilrVA==",
|
| 3013 |
+
"license": "MIT",
|
| 3014 |
+
"dependencies": {
|
| 3015 |
+
"@standard-schema/spec": "^1.0.0",
|
| 3016 |
+
"@standard-schema/utils": "^0.3.0",
|
| 3017 |
+
"immer": "^11.0.0",
|
| 3018 |
+
"redux": "^5.0.1",
|
| 3019 |
+
"redux-thunk": "^3.1.0",
|
| 3020 |
+
"reselect": "^5.1.0"
|
| 3021 |
+
},
|
| 3022 |
+
"peerDependencies": {
|
| 3023 |
+
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
| 3024 |
+
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
| 3025 |
+
},
|
| 3026 |
+
"peerDependenciesMeta": {
|
| 3027 |
+
"react": {
|
| 3028 |
+
"optional": true
|
| 3029 |
+
},
|
| 3030 |
+
"react-redux": {
|
| 3031 |
+
"optional": true
|
| 3032 |
+
}
|
| 3033 |
+
}
|
| 3034 |
+
},
|
| 3035 |
+
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
| 3036 |
+
"version": "11.0.1",
|
| 3037 |
+
"resolved": "https://registry.npmjs.org/immer/-/immer-11.0.1.tgz",
|
| 3038 |
+
"integrity": "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==",
|
| 3039 |
+
"license": "MIT",
|
| 3040 |
+
"funding": {
|
| 3041 |
+
"type": "opencollective",
|
| 3042 |
+
"url": "https://opencollective.com/immer"
|
| 3043 |
+
}
|
| 3044 |
+
},
|
| 3045 |
"node_modules/@rollup/plugin-babel": {
|
| 3046 |
"version": "5.3.1",
|
| 3047 |
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
|
|
|
| 3157 |
"@sinonjs/commons": "^1.7.0"
|
| 3158 |
}
|
| 3159 |
},
|
| 3160 |
+
"node_modules/@standard-schema/spec": {
|
| 3161 |
+
"version": "1.0.0",
|
| 3162 |
+
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
| 3163 |
+
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
| 3164 |
+
"license": "MIT"
|
| 3165 |
+
},
|
| 3166 |
+
"node_modules/@standard-schema/utils": {
|
| 3167 |
+
"version": "0.3.0",
|
| 3168 |
+
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
| 3169 |
+
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
| 3170 |
+
"license": "MIT"
|
| 3171 |
+
},
|
| 3172 |
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
| 3173 |
"version": "2.2.3",
|
| 3174 |
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
|
|
|
| 3624 |
"@types/node": "*"
|
| 3625 |
}
|
| 3626 |
},
|
| 3627 |
+
"node_modules/@types/d3-array": {
|
| 3628 |
+
"version": "3.2.2",
|
| 3629 |
+
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
| 3630 |
+
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
| 3631 |
+
"license": "MIT"
|
| 3632 |
+
},
|
| 3633 |
+
"node_modules/@types/d3-color": {
|
| 3634 |
+
"version": "3.1.3",
|
| 3635 |
+
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
| 3636 |
+
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
| 3637 |
+
"license": "MIT"
|
| 3638 |
+
},
|
| 3639 |
+
"node_modules/@types/d3-ease": {
|
| 3640 |
+
"version": "3.0.2",
|
| 3641 |
+
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
| 3642 |
+
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
| 3643 |
+
"license": "MIT"
|
| 3644 |
+
},
|
| 3645 |
+
"node_modules/@types/d3-interpolate": {
|
| 3646 |
+
"version": "3.0.4",
|
| 3647 |
+
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
| 3648 |
+
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
| 3649 |
+
"license": "MIT",
|
| 3650 |
+
"dependencies": {
|
| 3651 |
+
"@types/d3-color": "*"
|
| 3652 |
+
}
|
| 3653 |
+
},
|
| 3654 |
+
"node_modules/@types/d3-path": {
|
| 3655 |
+
"version": "3.1.1",
|
| 3656 |
+
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
| 3657 |
+
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
| 3658 |
+
"license": "MIT"
|
| 3659 |
+
},
|
| 3660 |
+
"node_modules/@types/d3-scale": {
|
| 3661 |
+
"version": "4.0.9",
|
| 3662 |
+
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
| 3663 |
+
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
| 3664 |
+
"license": "MIT",
|
| 3665 |
+
"dependencies": {
|
| 3666 |
+
"@types/d3-time": "*"
|
| 3667 |
+
}
|
| 3668 |
+
},
|
| 3669 |
+
"node_modules/@types/d3-shape": {
|
| 3670 |
+
"version": "3.1.7",
|
| 3671 |
+
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
| 3672 |
+
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
| 3673 |
+
"license": "MIT",
|
| 3674 |
+
"dependencies": {
|
| 3675 |
+
"@types/d3-path": "*"
|
| 3676 |
+
}
|
| 3677 |
+
},
|
| 3678 |
+
"node_modules/@types/d3-time": {
|
| 3679 |
+
"version": "3.0.4",
|
| 3680 |
+
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
| 3681 |
+
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
| 3682 |
+
"license": "MIT"
|
| 3683 |
+
},
|
| 3684 |
+
"node_modules/@types/d3-timer": {
|
| 3685 |
+
"version": "3.0.2",
|
| 3686 |
+
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
| 3687 |
+
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
| 3688 |
+
"license": "MIT"
|
| 3689 |
+
},
|
| 3690 |
"node_modules/@types/eslint": {
|
| 3691 |
"version": "8.56.12",
|
| 3692 |
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
|
|
|
|
| 3950 |
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
| 3951 |
"license": "MIT"
|
| 3952 |
},
|
| 3953 |
+
"node_modules/@types/use-sync-external-store": {
|
| 3954 |
+
"version": "0.0.6",
|
| 3955 |
+
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
| 3956 |
+
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
| 3957 |
+
"license": "MIT"
|
| 3958 |
+
},
|
| 3959 |
"node_modules/@types/ws": {
|
| 3960 |
"version": "8.18.1",
|
| 3961 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
|
|
| 5734 |
"wrap-ansi": "^7.0.0"
|
| 5735 |
}
|
| 5736 |
},
|
| 5737 |
+
"node_modules/clsx": {
|
| 5738 |
+
"version": "2.1.1",
|
| 5739 |
+
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
| 5740 |
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
| 5741 |
+
"license": "MIT",
|
| 5742 |
+
"engines": {
|
| 5743 |
+
"node": ">=6"
|
| 5744 |
+
}
|
| 5745 |
+
},
|
| 5746 |
"node_modules/co": {
|
| 5747 |
"version": "4.6.0",
|
| 5748 |
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
|
|
|
| 6481 |
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
| 6482 |
"license": "MIT"
|
| 6483 |
},
|
| 6484 |
+
"node_modules/d3-array": {
|
| 6485 |
+
"version": "3.2.4",
|
| 6486 |
+
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
| 6487 |
+
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
| 6488 |
+
"license": "ISC",
|
| 6489 |
+
"dependencies": {
|
| 6490 |
+
"internmap": "1 - 2"
|
| 6491 |
+
},
|
| 6492 |
+
"engines": {
|
| 6493 |
+
"node": ">=12"
|
| 6494 |
+
}
|
| 6495 |
+
},
|
| 6496 |
+
"node_modules/d3-color": {
|
| 6497 |
+
"version": "3.1.0",
|
| 6498 |
+
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
| 6499 |
+
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
| 6500 |
+
"license": "ISC",
|
| 6501 |
+
"engines": {
|
| 6502 |
+
"node": ">=12"
|
| 6503 |
+
}
|
| 6504 |
+
},
|
| 6505 |
+
"node_modules/d3-ease": {
|
| 6506 |
+
"version": "3.0.1",
|
| 6507 |
+
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
| 6508 |
+
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
| 6509 |
+
"license": "BSD-3-Clause",
|
| 6510 |
+
"engines": {
|
| 6511 |
+
"node": ">=12"
|
| 6512 |
+
}
|
| 6513 |
+
},
|
| 6514 |
+
"node_modules/d3-format": {
|
| 6515 |
+
"version": "3.1.0",
|
| 6516 |
+
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
| 6517 |
+
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
| 6518 |
+
"license": "ISC",
|
| 6519 |
+
"engines": {
|
| 6520 |
+
"node": ">=12"
|
| 6521 |
+
}
|
| 6522 |
+
},
|
| 6523 |
+
"node_modules/d3-interpolate": {
|
| 6524 |
+
"version": "3.0.1",
|
| 6525 |
+
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
| 6526 |
+
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
| 6527 |
+
"license": "ISC",
|
| 6528 |
+
"dependencies": {
|
| 6529 |
+
"d3-color": "1 - 3"
|
| 6530 |
+
},
|
| 6531 |
+
"engines": {
|
| 6532 |
+
"node": ">=12"
|
| 6533 |
+
}
|
| 6534 |
+
},
|
| 6535 |
+
"node_modules/d3-path": {
|
| 6536 |
+
"version": "3.1.0",
|
| 6537 |
+
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
| 6538 |
+
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
| 6539 |
+
"license": "ISC",
|
| 6540 |
+
"engines": {
|
| 6541 |
+
"node": ">=12"
|
| 6542 |
+
}
|
| 6543 |
+
},
|
| 6544 |
+
"node_modules/d3-scale": {
|
| 6545 |
+
"version": "4.0.2",
|
| 6546 |
+
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
| 6547 |
+
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
| 6548 |
+
"license": "ISC",
|
| 6549 |
+
"dependencies": {
|
| 6550 |
+
"d3-array": "2.10.0 - 3",
|
| 6551 |
+
"d3-format": "1 - 3",
|
| 6552 |
+
"d3-interpolate": "1.2.0 - 3",
|
| 6553 |
+
"d3-time": "2.1.1 - 3",
|
| 6554 |
+
"d3-time-format": "2 - 4"
|
| 6555 |
+
},
|
| 6556 |
+
"engines": {
|
| 6557 |
+
"node": ">=12"
|
| 6558 |
+
}
|
| 6559 |
+
},
|
| 6560 |
+
"node_modules/d3-shape": {
|
| 6561 |
+
"version": "3.2.0",
|
| 6562 |
+
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
| 6563 |
+
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
| 6564 |
+
"license": "ISC",
|
| 6565 |
+
"dependencies": {
|
| 6566 |
+
"d3-path": "^3.1.0"
|
| 6567 |
+
},
|
| 6568 |
+
"engines": {
|
| 6569 |
+
"node": ">=12"
|
| 6570 |
+
}
|
| 6571 |
+
},
|
| 6572 |
+
"node_modules/d3-time": {
|
| 6573 |
+
"version": "3.1.0",
|
| 6574 |
+
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
| 6575 |
+
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
| 6576 |
+
"license": "ISC",
|
| 6577 |
+
"dependencies": {
|
| 6578 |
+
"d3-array": "2 - 3"
|
| 6579 |
+
},
|
| 6580 |
+
"engines": {
|
| 6581 |
+
"node": ">=12"
|
| 6582 |
+
}
|
| 6583 |
+
},
|
| 6584 |
+
"node_modules/d3-time-format": {
|
| 6585 |
+
"version": "4.1.0",
|
| 6586 |
+
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
| 6587 |
+
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
| 6588 |
+
"license": "ISC",
|
| 6589 |
+
"dependencies": {
|
| 6590 |
+
"d3-time": "1 - 3"
|
| 6591 |
+
},
|
| 6592 |
+
"engines": {
|
| 6593 |
+
"node": ">=12"
|
| 6594 |
+
}
|
| 6595 |
+
},
|
| 6596 |
+
"node_modules/d3-timer": {
|
| 6597 |
+
"version": "3.0.1",
|
| 6598 |
+
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
| 6599 |
+
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
| 6600 |
+
"license": "ISC",
|
| 6601 |
+
"engines": {
|
| 6602 |
+
"node": ">=12"
|
| 6603 |
+
}
|
| 6604 |
+
},
|
| 6605 |
"node_modules/damerau-levenshtein": {
|
| 6606 |
"version": "1.0.8",
|
| 6607 |
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
|
|
|
| 6696 |
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
| 6697 |
"license": "MIT"
|
| 6698 |
},
|
| 6699 |
+
"node_modules/decimal.js-light": {
|
| 6700 |
+
"version": "2.5.1",
|
| 6701 |
+
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
| 6702 |
+
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
| 6703 |
+
"license": "MIT"
|
| 6704 |
+
},
|
| 6705 |
"node_modules/dedent": {
|
| 6706 |
"version": "0.7.0",
|
| 6707 |
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
|
|
|
| 7351 |
"url": "https://github.com/sponsors/ljharb"
|
| 7352 |
}
|
| 7353 |
},
|
| 7354 |
+
"node_modules/es-toolkit": {
|
| 7355 |
+
"version": "1.43.0",
|
| 7356 |
+
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz",
|
| 7357 |
+
"integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==",
|
| 7358 |
+
"license": "MIT",
|
| 7359 |
+
"workspaces": [
|
| 7360 |
+
"docs",
|
| 7361 |
+
"benchmarks"
|
| 7362 |
+
]
|
| 7363 |
+
},
|
| 7364 |
"node_modules/escalade": {
|
| 7365 |
"version": "3.2.0",
|
| 7366 |
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
|
|
|
| 9504 |
"node": ">= 0.4"
|
| 9505 |
}
|
| 9506 |
},
|
| 9507 |
+
"node_modules/internmap": {
|
| 9508 |
+
"version": "2.0.3",
|
| 9509 |
+
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
| 9510 |
+
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
| 9511 |
+
"license": "ISC",
|
| 9512 |
+
"engines": {
|
| 9513 |
+
"node": ">=12"
|
| 9514 |
+
}
|
| 9515 |
+
},
|
| 9516 |
"node_modules/ipaddr.js": {
|
| 9517 |
"version": "2.3.0",
|
| 9518 |
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
|
|
|
|
| 14063 |
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
| 14064 |
"license": "MIT"
|
| 14065 |
},
|
| 14066 |
+
"node_modules/react-redux": {
|
| 14067 |
+
"version": "9.2.0",
|
| 14068 |
+
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
| 14069 |
+
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
| 14070 |
+
"license": "MIT",
|
| 14071 |
+
"dependencies": {
|
| 14072 |
+
"@types/use-sync-external-store": "^0.0.6",
|
| 14073 |
+
"use-sync-external-store": "^1.4.0"
|
| 14074 |
+
},
|
| 14075 |
+
"peerDependencies": {
|
| 14076 |
+
"@types/react": "^18.2.25 || ^19",
|
| 14077 |
+
"react": "^18.0 || ^19",
|
| 14078 |
+
"redux": "^5.0.0"
|
| 14079 |
+
},
|
| 14080 |
+
"peerDependenciesMeta": {
|
| 14081 |
+
"@types/react": {
|
| 14082 |
+
"optional": true
|
| 14083 |
+
},
|
| 14084 |
+
"redux": {
|
| 14085 |
+
"optional": true
|
| 14086 |
+
}
|
| 14087 |
+
}
|
| 14088 |
+
},
|
| 14089 |
"node_modules/react-refresh": {
|
| 14090 |
"version": "0.11.0",
|
| 14091 |
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
|
|
|
| 14254 |
"node": ">=8.10.0"
|
| 14255 |
}
|
| 14256 |
},
|
| 14257 |
+
"node_modules/recharts": {
|
| 14258 |
+
"version": "3.5.1",
|
| 14259 |
+
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.5.1.tgz",
|
| 14260 |
+
"integrity": "sha512-+v+HJojK7gnEgG6h+b2u7k8HH7FhyFUzAc4+cPrsjL4Otdgqr/ecXzAnHciqlzV1ko064eNcsdzrYOM78kankA==",
|
| 14261 |
+
"license": "MIT",
|
| 14262 |
+
"workspaces": [
|
| 14263 |
+
"www"
|
| 14264 |
+
],
|
| 14265 |
+
"dependencies": {
|
| 14266 |
+
"@reduxjs/toolkit": "1.x.x || 2.x.x",
|
| 14267 |
+
"clsx": "^2.1.1",
|
| 14268 |
+
"decimal.js-light": "^2.5.1",
|
| 14269 |
+
"es-toolkit": "^1.39.3",
|
| 14270 |
+
"eventemitter3": "^5.0.1",
|
| 14271 |
+
"immer": "^10.1.1",
|
| 14272 |
+
"react-redux": "8.x.x || 9.x.x",
|
| 14273 |
+
"reselect": "5.1.1",
|
| 14274 |
+
"tiny-invariant": "^1.3.3",
|
| 14275 |
+
"use-sync-external-store": "^1.2.2",
|
| 14276 |
+
"victory-vendor": "^37.0.2"
|
| 14277 |
+
},
|
| 14278 |
+
"engines": {
|
| 14279 |
+
"node": ">=18"
|
| 14280 |
+
},
|
| 14281 |
+
"peerDependencies": {
|
| 14282 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 14283 |
+
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 14284 |
+
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 14285 |
+
}
|
| 14286 |
+
},
|
| 14287 |
+
"node_modules/recharts/node_modules/eventemitter3": {
|
| 14288 |
+
"version": "5.0.1",
|
| 14289 |
+
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
| 14290 |
+
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
| 14291 |
+
"license": "MIT"
|
| 14292 |
+
},
|
| 14293 |
+
"node_modules/recharts/node_modules/immer": {
|
| 14294 |
+
"version": "10.2.0",
|
| 14295 |
+
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
| 14296 |
+
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
| 14297 |
+
"license": "MIT",
|
| 14298 |
+
"funding": {
|
| 14299 |
+
"type": "opencollective",
|
| 14300 |
+
"url": "https://opencollective.com/immer"
|
| 14301 |
+
}
|
| 14302 |
+
},
|
| 14303 |
"node_modules/recursive-readdir": {
|
| 14304 |
"version": "2.2.3",
|
| 14305 |
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
|
|
|
|
| 14325 |
"node": ">=8"
|
| 14326 |
}
|
| 14327 |
},
|
| 14328 |
+
"node_modules/redux": {
|
| 14329 |
+
"version": "5.0.1",
|
| 14330 |
+
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
| 14331 |
+
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
| 14332 |
+
"license": "MIT"
|
| 14333 |
+
},
|
| 14334 |
+
"node_modules/redux-thunk": {
|
| 14335 |
+
"version": "3.1.0",
|
| 14336 |
+
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
| 14337 |
+
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
| 14338 |
+
"license": "MIT",
|
| 14339 |
+
"peerDependencies": {
|
| 14340 |
+
"redux": "^5.0.0"
|
| 14341 |
+
}
|
| 14342 |
+
},
|
| 14343 |
"node_modules/reflect.getprototypeof": {
|
| 14344 |
"version": "1.0.10",
|
| 14345 |
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
|
|
|
| 14493 |
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
| 14494 |
"license": "MIT"
|
| 14495 |
},
|
| 14496 |
+
"node_modules/reselect": {
|
| 14497 |
+
"version": "5.1.1",
|
| 14498 |
+
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
| 14499 |
+
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
| 14500 |
+
"license": "MIT"
|
| 14501 |
+
},
|
| 14502 |
"node_modules/resolve": {
|
| 14503 |
"version": "1.22.11",
|
| 14504 |
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
|
|
|
| 16443 |
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
|
| 16444 |
"license": "MIT"
|
| 16445 |
},
|
| 16446 |
+
"node_modules/tiny-invariant": {
|
| 16447 |
+
"version": "1.3.3",
|
| 16448 |
+
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
| 16449 |
+
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
| 16450 |
+
"license": "MIT"
|
| 16451 |
+
},
|
| 16452 |
"node_modules/tinyglobby": {
|
| 16453 |
"version": "0.2.15",
|
| 16454 |
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
|
|
|
| 17004 |
"requires-port": "^1.0.0"
|
| 17005 |
}
|
| 17006 |
},
|
| 17007 |
+
"node_modules/use-sync-external-store": {
|
| 17008 |
+
"version": "1.6.0",
|
| 17009 |
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 17010 |
+
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 17011 |
+
"license": "MIT",
|
| 17012 |
+
"peerDependencies": {
|
| 17013 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 17014 |
+
}
|
| 17015 |
+
},
|
| 17016 |
"node_modules/util-deprecate": {
|
| 17017 |
"version": "1.0.2",
|
| 17018 |
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
|
|
| 17095 |
"node": ">= 0.8"
|
| 17096 |
}
|
| 17097 |
},
|
| 17098 |
+
"node_modules/victory-vendor": {
|
| 17099 |
+
"version": "37.3.6",
|
| 17100 |
+
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
| 17101 |
+
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
| 17102 |
+
"license": "MIT AND ISC",
|
| 17103 |
+
"dependencies": {
|
| 17104 |
+
"@types/d3-array": "^3.0.3",
|
| 17105 |
+
"@types/d3-ease": "^3.0.0",
|
| 17106 |
+
"@types/d3-interpolate": "^3.0.1",
|
| 17107 |
+
"@types/d3-scale": "^4.0.2",
|
| 17108 |
+
"@types/d3-shape": "^3.1.0",
|
| 17109 |
+
"@types/d3-time": "^3.0.0",
|
| 17110 |
+
"@types/d3-timer": "^3.0.0",
|
| 17111 |
+
"d3-array": "^3.1.6",
|
| 17112 |
+
"d3-ease": "^3.0.1",
|
| 17113 |
+
"d3-interpolate": "^3.0.1",
|
| 17114 |
+
"d3-scale": "^4.0.2",
|
| 17115 |
+
"d3-shape": "^3.1.0",
|
| 17116 |
+
"d3-time": "^3.0.0",
|
| 17117 |
+
"d3-timer": "^3.0.1"
|
| 17118 |
+
}
|
| 17119 |
+
},
|
| 17120 |
"node_modules/w3c-hr-time": {
|
| 17121 |
"version": "1.0.2",
|
| 17122 |
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
| 12 |
"react-dom": "^19.1.0",
|
| 13 |
"react-router-dom": "^7.10.1",
|
| 14 |
"react-scripts": "5.0.1",
|
|
|
|
| 15 |
"web-vitals": "^2.1.4"
|
| 16 |
},
|
| 17 |
"devDependencies": {
|
|
|
|
| 12 |
"react-dom": "^19.1.0",
|
| 13 |
"react-router-dom": "^7.10.1",
|
| 14 |
"react-scripts": "5.0.1",
|
| 15 |
+
"recharts": "^3.5.1",
|
| 16 |
"web-vitals": "^2.1.4"
|
| 17 |
},
|
| 18 |
"devDependencies": {
|
src/app/components/analysis/StanceDistributionChart.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts';
|
| 3 |
+
import type { StanceStats } from '../../utils/analysis.utils.ts';
|
| 4 |
+
|
| 5 |
+
type StanceDistributionChartProps = {
|
| 6 |
+
stats: StanceStats;
|
| 7 |
+
};
|
| 8 |
+
|
| 9 |
+
const COLORS = {
|
| 10 |
+
PRO: '#10b981', // green-500
|
| 11 |
+
CON: '#ef4444', // red-500
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
const StanceDistributionChart: React.FC<StanceDistributionChartProps> = ({ stats }) => {
|
| 15 |
+
const data = [
|
| 16 |
+
{ name: 'PRO', value: stats.pro, percentage: stats.proPercentage },
|
| 17 |
+
{ name: 'CON', value: stats.con, percentage: stats.conPercentage },
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
return (
|
| 21 |
+
<div className="h-64 w-full">
|
| 22 |
+
<ResponsiveContainer width="100%" height="100%">
|
| 23 |
+
<PieChart>
|
| 24 |
+
<Pie
|
| 25 |
+
data={data}
|
| 26 |
+
cx="50%"
|
| 27 |
+
cy="50%"
|
| 28 |
+
labelLine={false}
|
| 29 |
+
label={({ name, percentage }) => `${name}: ${percentage.toFixed(1)}%`}
|
| 30 |
+
outerRadius={80}
|
| 31 |
+
fill="#8884d8"
|
| 32 |
+
dataKey="value"
|
| 33 |
+
>
|
| 34 |
+
{data.map((entry, index) => (
|
| 35 |
+
<Cell
|
| 36 |
+
key={`cell-${index}`}
|
| 37 |
+
fill={entry.name === 'PRO' ? COLORS.PRO : COLORS.CON}
|
| 38 |
+
/>
|
| 39 |
+
))}
|
| 40 |
+
</Pie>
|
| 41 |
+
<Tooltip
|
| 42 |
+
formatter={(value: number, name: string, props: any) => [
|
| 43 |
+
`${value} (${props.payload.percentage.toFixed(1)}%)`,
|
| 44 |
+
name,
|
| 45 |
+
]}
|
| 46 |
+
/>
|
| 47 |
+
<Legend />
|
| 48 |
+
</PieChart>
|
| 49 |
+
</ResponsiveContainer>
|
| 50 |
+
</div>
|
| 51 |
+
);
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
export default StanceDistributionChart;
|
| 55 |
+
|
src/app/components/analysis/TimeSeriesChart.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
| 3 |
+
import type { TimeStats } from '../../utils/analysis.utils.ts';
|
| 4 |
+
|
| 5 |
+
type TimeSeriesChartProps = {
|
| 6 |
+
data: TimeStats[];
|
| 7 |
+
};
|
| 8 |
+
|
| 9 |
+
const TimeSeriesChart: React.FC<TimeSeriesChartProps> = ({ data }) => {
|
| 10 |
+
// Format date for display
|
| 11 |
+
const chartData = data.map((item) => ({
|
| 12 |
+
...item,
|
| 13 |
+
dateDisplay: new Date(item.date).toLocaleDateString('en-US', {
|
| 14 |
+
month: 'short',
|
| 15 |
+
day: 'numeric',
|
| 16 |
+
}),
|
| 17 |
+
}));
|
| 18 |
+
|
| 19 |
+
return (
|
| 20 |
+
<div className="h-64 w-full">
|
| 21 |
+
<ResponsiveContainer width="100%" height="100%">
|
| 22 |
+
<LineChart
|
| 23 |
+
data={chartData}
|
| 24 |
+
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
| 25 |
+
>
|
| 26 |
+
<CartesianGrid strokeDasharray="3 3" />
|
| 27 |
+
<XAxis dataKey="dateDisplay" />
|
| 28 |
+
<YAxis />
|
| 29 |
+
<Tooltip />
|
| 30 |
+
<Legend />
|
| 31 |
+
<Line
|
| 32 |
+
type="monotone"
|
| 33 |
+
dataKey="count"
|
| 34 |
+
stroke="#3b82f6"
|
| 35 |
+
strokeWidth={2}
|
| 36 |
+
name="Arguments Analyzed"
|
| 37 |
+
/>
|
| 38 |
+
</LineChart>
|
| 39 |
+
</ResponsiveContainer>
|
| 40 |
+
</div>
|
| 41 |
+
);
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
export default TimeSeriesChart;
|
| 45 |
+
|
src/app/components/analysis/TopicFrequencyChart.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
| 3 |
+
import type { TopicFrequency } from '../../utils/analysis.utils.ts';
|
| 4 |
+
|
| 5 |
+
type TopicFrequencyChartProps = {
|
| 6 |
+
data: TopicFrequency[];
|
| 7 |
+
};
|
| 8 |
+
|
| 9 |
+
const TopicFrequencyChart: React.FC<TopicFrequencyChartProps> = ({ data }) => {
|
| 10 |
+
// Truncate long topic names for display
|
| 11 |
+
const chartData = data.map((item) => ({
|
| 12 |
+
...item,
|
| 13 |
+
topicDisplay: item.topic.length > 40 ? `${item.topic.substring(0, 40)}...` : item.topic,
|
| 14 |
+
}));
|
| 15 |
+
|
| 16 |
+
return (
|
| 17 |
+
<div className="h-96 w-full">
|
| 18 |
+
<ResponsiveContainer width="100%" height="100%">
|
| 19 |
+
<BarChart
|
| 20 |
+
layout="vertical"
|
| 21 |
+
data={chartData}
|
| 22 |
+
margin={{ top: 5, right: 30, left: 150, bottom: 5 }}
|
| 23 |
+
>
|
| 24 |
+
<CartesianGrid strokeDasharray="3 3" />
|
| 25 |
+
<XAxis type="number" />
|
| 26 |
+
<YAxis
|
| 27 |
+
type="category"
|
| 28 |
+
dataKey="topicDisplay"
|
| 29 |
+
width={140}
|
| 30 |
+
style={{ fontSize: '12px' }}
|
| 31 |
+
/>
|
| 32 |
+
<Tooltip
|
| 33 |
+
formatter={(value: number, name: string) => [value, name === 'proCount' ? 'PRO' : name === 'conCount' ? 'CON' : 'Total']}
|
| 34 |
+
contentStyle={{ fontSize: '12px' }}
|
| 35 |
+
/>
|
| 36 |
+
<Legend
|
| 37 |
+
formatter={(value) => (value === 'proCount' ? 'PRO' : value === 'conCount' ? 'CON' : 'Total')}
|
| 38 |
+
/>
|
| 39 |
+
<Bar dataKey="proCount" fill="#10b981" name="proCount" />
|
| 40 |
+
<Bar dataKey="conCount" fill="#ef4444" name="conCount" />
|
| 41 |
+
</BarChart>
|
| 42 |
+
</ResponsiveContainer>
|
| 43 |
+
</div>
|
| 44 |
+
);
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
export default TopicFrequencyChart;
|
| 48 |
+
|
src/app/pages/AnalysisPage.tsx
CHANGED
|
@@ -1,8 +1,21 @@
|
|
| 1 |
-
import React, { useMemo, useState } from 'react';
|
| 2 |
import { parseCsv, formatError } from '../utils/index.ts';
|
| 3 |
import { CsvRow } from '../types/index.ts';
|
| 4 |
import { AnalysisResult } from '../types/analysis.types.ts';
|
| 5 |
-
import { analyzeArgumentsFromCsv } from '../services/analysis.service.ts';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
const AnalysisPage: React.FC = () => {
|
| 8 |
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
@@ -11,12 +24,65 @@ const AnalysisPage: React.FC = () => {
|
|
| 11 |
const [error, setError] = useState<string | null>(null);
|
| 12 |
const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false);
|
| 13 |
const [analysisResults, setAnalysisResults] = useState<AnalysisResult[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
const fileName = useMemo(
|
| 16 |
() => selectedFile?.name ?? 'Select a CSV file with arguments',
|
| 17 |
[selectedFile]
|
| 18 |
);
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
| 21 |
const file = event.target.files?.[0] ?? null;
|
| 22 |
setSelectedFile(file);
|
|
@@ -62,6 +128,9 @@ const AnalysisPage: React.FC = () => {
|
|
| 62 |
try {
|
| 63 |
const response = await analyzeArgumentsFromCsv(selectedFile);
|
| 64 |
setAnalysisResults(response.results);
|
|
|
|
|
|
|
|
|
|
| 65 |
} catch (err) {
|
| 66 |
setError(formatError(err));
|
| 67 |
} finally {
|
|
@@ -71,17 +140,121 @@ const AnalysisPage: React.FC = () => {
|
|
| 71 |
|
| 72 |
return (
|
| 73 |
<div className="min-h-screen bg-slate-50 px-4 py-10">
|
| 74 |
-
<div className="mx-auto max-w-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
<div className="flex flex-col gap-3">
|
| 86 |
<label className="text-sm font-medium text-slate-700">
|
| 87 |
Upload a CSV file
|
|
@@ -219,6 +392,7 @@ const AnalysisPage: React.FC = () => {
|
|
| 219 |
</div>
|
| 220 |
</div>
|
| 221 |
)}
|
|
|
|
| 222 |
</div>
|
| 223 |
</div>
|
| 224 |
</div>
|
|
|
|
| 1 |
+
import React, { useMemo, useState, useEffect } from 'react';
|
| 2 |
import { parseCsv, formatError } from '../utils/index.ts';
|
| 3 |
import { CsvRow } from '../types/index.ts';
|
| 4 |
import { AnalysisResult } from '../types/analysis.types.ts';
|
| 5 |
+
import { analyzeArgumentsFromCsv, getAnalysisResults } from '../services/analysis.service.ts';
|
| 6 |
+
import {
|
| 7 |
+
calculateStanceStats,
|
| 8 |
+
calculateTopicFrequency,
|
| 9 |
+
calculateTimeStats,
|
| 10 |
+
getUniqueTopicsCount,
|
| 11 |
+
getAverageArgumentLength,
|
| 12 |
+
getMostRecentAnalysisDate,
|
| 13 |
+
getOldestAnalysisDate,
|
| 14 |
+
} from '../utils/analysis.utils.ts';
|
| 15 |
+
import StanceDistributionChart from '../components/analysis/StanceDistributionChart.tsx';
|
| 16 |
+
import TopicFrequencyChart from '../components/analysis/TopicFrequencyChart.tsx';
|
| 17 |
+
import TimeSeriesChart from '../components/analysis/TimeSeriesChart.tsx';
|
| 18 |
+
import Loading from '../components/common/Loading.tsx';
|
| 19 |
|
| 20 |
const AnalysisPage: React.FC = () => {
|
| 21 |
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
|
|
| 24 |
const [error, setError] = useState<string | null>(null);
|
| 25 |
const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false);
|
| 26 |
const [analysisResults, setAnalysisResults] = useState<AnalysisResult[]>([]);
|
| 27 |
+
|
| 28 |
+
// User's historical analysis data
|
| 29 |
+
const [userAnalysisData, setUserAnalysisData] = useState<AnalysisResult[]>([]);
|
| 30 |
+
const [isLoadingStats, setIsLoadingStats] = useState<boolean>(true);
|
| 31 |
+
const [statsError, setStatsError] = useState<string | null>(null);
|
| 32 |
|
| 33 |
const fileName = useMemo(
|
| 34 |
() => selectedFile?.name ?? 'Select a CSV file with arguments',
|
| 35 |
[selectedFile]
|
| 36 |
);
|
| 37 |
|
| 38 |
+
// Fetch user's analysis data on mount
|
| 39 |
+
useEffect(() => {
|
| 40 |
+
const fetchUserAnalysis = async () => {
|
| 41 |
+
setIsLoadingStats(true);
|
| 42 |
+
setStatsError(null);
|
| 43 |
+
try {
|
| 44 |
+
const response = await getAnalysisResults(1000, 0);
|
| 45 |
+
setUserAnalysisData(response.results);
|
| 46 |
+
} catch (err) {
|
| 47 |
+
setStatsError(formatError(err));
|
| 48 |
+
} finally {
|
| 49 |
+
setIsLoadingStats(false);
|
| 50 |
+
}
|
| 51 |
+
};
|
| 52 |
+
|
| 53 |
+
fetchUserAnalysis();
|
| 54 |
+
}, []);
|
| 55 |
+
|
| 56 |
+
// Calculate statistics from user's analysis data
|
| 57 |
+
const stanceStats = useMemo(
|
| 58 |
+
() => calculateStanceStats(userAnalysisData),
|
| 59 |
+
[userAnalysisData]
|
| 60 |
+
);
|
| 61 |
+
const topicFrequency = useMemo(
|
| 62 |
+
() => calculateTopicFrequency(userAnalysisData, 10),
|
| 63 |
+
[userAnalysisData]
|
| 64 |
+
);
|
| 65 |
+
const timeStats = useMemo(
|
| 66 |
+
() => calculateTimeStats(userAnalysisData),
|
| 67 |
+
[userAnalysisData]
|
| 68 |
+
);
|
| 69 |
+
const uniqueTopicsCount = useMemo(
|
| 70 |
+
() => getUniqueTopicsCount(userAnalysisData),
|
| 71 |
+
[userAnalysisData]
|
| 72 |
+
);
|
| 73 |
+
const avgArgumentLength = useMemo(
|
| 74 |
+
() => getAverageArgumentLength(userAnalysisData),
|
| 75 |
+
[userAnalysisData]
|
| 76 |
+
);
|
| 77 |
+
const mostRecentDate = useMemo(
|
| 78 |
+
() => getMostRecentAnalysisDate(userAnalysisData),
|
| 79 |
+
[userAnalysisData]
|
| 80 |
+
);
|
| 81 |
+
const oldestDate = useMemo(
|
| 82 |
+
() => getOldestAnalysisDate(userAnalysisData),
|
| 83 |
+
[userAnalysisData]
|
| 84 |
+
);
|
| 85 |
+
|
| 86 |
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
| 87 |
const file = event.target.files?.[0] ?? null;
|
| 88 |
setSelectedFile(file);
|
|
|
|
| 128 |
try {
|
| 129 |
const response = await analyzeArgumentsFromCsv(selectedFile);
|
| 130 |
setAnalysisResults(response.results);
|
| 131 |
+
// Refresh user analysis data after successful analysis
|
| 132 |
+
const updatedResponse = await getAnalysisResults(1000, 0);
|
| 133 |
+
setUserAnalysisData(updatedResponse.results);
|
| 134 |
} catch (err) {
|
| 135 |
setError(formatError(err));
|
| 136 |
} finally {
|
|
|
|
| 140 |
|
| 141 |
return (
|
| 142 |
<div className="min-h-screen bg-slate-50 px-4 py-10">
|
| 143 |
+
<div className="mx-auto max-w-7xl space-y-6">
|
| 144 |
+
{/* Statistics Section */}
|
| 145 |
+
{!isLoadingStats && userAnalysisData.length > 0 && (
|
| 146 |
+
<div className="rounded-lg border border-slate-200 bg-white shadow-sm">
|
| 147 |
+
<div className="border-b border-slate-200 px-6 py-4">
|
| 148 |
+
<h2 className="text-lg font-semibold text-slate-800">
|
| 149 |
+
Your Analysis Statistics
|
| 150 |
+
</h2>
|
| 151 |
+
<p className="text-sm text-slate-500">
|
| 152 |
+
Overview of all your analyzed arguments
|
| 153 |
+
</p>
|
| 154 |
+
</div>
|
| 155 |
+
|
| 156 |
+
{isLoadingStats ? (
|
| 157 |
+
<div className="flex items-center justify-center py-12">
|
| 158 |
+
<Loading />
|
| 159 |
+
</div>
|
| 160 |
+
) : statsError ? (
|
| 161 |
+
<div className="px-6 py-4">
|
| 162 |
+
<p className="text-sm text-red-600">{statsError}</p>
|
| 163 |
+
</div>
|
| 164 |
+
) : (
|
| 165 |
+
<div className="p-6 space-y-8">
|
| 166 |
+
{/* Summary Statistics */}
|
| 167 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 168 |
+
<div className="bg-slate-50 rounded-lg p-4">
|
| 169 |
+
<div className="text-2xl font-bold text-slate-800">
|
| 170 |
+
{stanceStats.total}
|
| 171 |
+
</div>
|
| 172 |
+
<div className="text-sm text-slate-600">Total Arguments</div>
|
| 173 |
+
</div>
|
| 174 |
+
<div className="bg-slate-50 rounded-lg p-4">
|
| 175 |
+
<div className="text-2xl font-bold text-slate-800">
|
| 176 |
+
{uniqueTopicsCount}
|
| 177 |
+
</div>
|
| 178 |
+
<div className="text-sm text-slate-600">Unique Topics</div>
|
| 179 |
+
</div>
|
| 180 |
+
<div className="bg-slate-50 rounded-lg p-4">
|
| 181 |
+
<div className="text-2xl font-bold text-slate-800">
|
| 182 |
+
{avgArgumentLength}
|
| 183 |
+
</div>
|
| 184 |
+
<div className="text-sm text-slate-600">Avg Length (chars)</div>
|
| 185 |
+
</div>
|
| 186 |
+
<div className="bg-slate-50 rounded-lg p-4">
|
| 187 |
+
<div className="text-2xl font-bold text-slate-800">
|
| 188 |
+
{stanceStats.pro}
|
| 189 |
+
</div>
|
| 190 |
+
<div className="text-sm text-slate-600">PRO Arguments</div>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
|
| 194 |
+
{/* Charts Grid */}
|
| 195 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
| 196 |
+
{/* Stance Distribution */}
|
| 197 |
+
<div className="rounded-lg border border-slate-200 p-4">
|
| 198 |
+
<h3 className="text-sm font-semibold text-slate-700 mb-4">
|
| 199 |
+
Stance Distribution
|
| 200 |
+
</h3>
|
| 201 |
+
<StanceDistributionChart stats={stanceStats} />
|
| 202 |
+
<div className="mt-4 flex justify-center gap-6 text-sm">
|
| 203 |
+
<div>
|
| 204 |
+
<span className="font-semibold text-green-700">
|
| 205 |
+
PRO: {stanceStats.pro} ({stanceStats.proPercentage.toFixed(1)}%)
|
| 206 |
+
</span>
|
| 207 |
+
</div>
|
| 208 |
+
<div>
|
| 209 |
+
<span className="font-semibold text-red-700">
|
| 210 |
+
CON: {stanceStats.con} ({stanceStats.conPercentage.toFixed(1)}%)
|
| 211 |
+
</span>
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
|
| 216 |
+
{/* Time Series */}
|
| 217 |
+
{timeStats.length > 0 && (
|
| 218 |
+
<div className="rounded-lg border border-slate-200 p-4">
|
| 219 |
+
<h3 className="text-sm font-semibold text-slate-700 mb-4">
|
| 220 |
+
Analysis Timeline
|
| 221 |
+
</h3>
|
| 222 |
+
<TimeSeriesChart data={timeStats} />
|
| 223 |
+
{oldestDate && mostRecentDate && (
|
| 224 |
+
<div className="mt-4 text-center text-xs text-slate-500">
|
| 225 |
+
From {oldestDate} to {mostRecentDate}
|
| 226 |
+
</div>
|
| 227 |
+
)}
|
| 228 |
+
</div>
|
| 229 |
+
)}
|
| 230 |
+
</div>
|
| 231 |
|
| 232 |
+
{/* Topic Frequency */}
|
| 233 |
+
{topicFrequency.length > 0 && (
|
| 234 |
+
<div className="rounded-lg border border-slate-200 p-4">
|
| 235 |
+
<h3 className="text-sm font-semibold text-slate-700 mb-4">
|
| 236 |
+
Most Discussed Topics (Top 10)
|
| 237 |
+
</h3>
|
| 238 |
+
<TopicFrequencyChart data={topicFrequency} />
|
| 239 |
+
</div>
|
| 240 |
+
)}
|
| 241 |
+
</div>
|
| 242 |
+
)}
|
| 243 |
+
</div>
|
| 244 |
+
)}
|
| 245 |
+
|
| 246 |
+
{/* CSV Upload and Analysis Section */}
|
| 247 |
+
<div className="rounded-lg border border-slate-200 bg-white shadow-sm">
|
| 248 |
+
<div className="border-b border-slate-200 px-6 py-4">
|
| 249 |
+
<h1 className="text-lg font-semibold text-slate-800">
|
| 250 |
+
Analyze New Arguments
|
| 251 |
+
</h1>
|
| 252 |
+
<p className="text-sm text-slate-500">
|
| 253 |
+
Upload a CSV file containing arguments to analyze them.
|
| 254 |
+
</p>
|
| 255 |
+
</div>
|
| 256 |
+
|
| 257 |
+
<div className="space-y-6 p-6">
|
| 258 |
<div className="flex flex-col gap-3">
|
| 259 |
<label className="text-sm font-medium text-slate-700">
|
| 260 |
Upload a CSV file
|
|
|
|
| 392 |
</div>
|
| 393 |
</div>
|
| 394 |
)}
|
| 395 |
+
</div>
|
| 396 |
</div>
|
| 397 |
</div>
|
| 398 |
</div>
|
src/app/utils/analysis.utils.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Utility functions for analyzing analysis results and calculating statistics
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
import type { AnalysisResult } from '../types/analysis.types.ts';
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* Statistics about stance distribution
|
| 9 |
+
*/
|
| 10 |
+
export type StanceStats = {
|
| 11 |
+
total: number;
|
| 12 |
+
pro: number;
|
| 13 |
+
con: number;
|
| 14 |
+
proPercentage: number;
|
| 15 |
+
conPercentage: number;
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Topic frequency data
|
| 20 |
+
*/
|
| 21 |
+
export type TopicFrequency = {
|
| 22 |
+
topic: string;
|
| 23 |
+
count: number;
|
| 24 |
+
proCount: number;
|
| 25 |
+
conCount: number;
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
/**
|
| 29 |
+
* Time-based statistics
|
| 30 |
+
*/
|
| 31 |
+
export type TimeStats = {
|
| 32 |
+
date: string;
|
| 33 |
+
count: number;
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
/**
|
| 37 |
+
* Calculate stance distribution statistics
|
| 38 |
+
*/
|
| 39 |
+
export function calculateStanceStats(results: AnalysisResult[]): StanceStats {
|
| 40 |
+
const total = results.length;
|
| 41 |
+
const pro = results.filter((r) => r.predicted_stance === 'PRO').length;
|
| 42 |
+
const con = results.filter((r) => r.predicted_stance === 'CON').length;
|
| 43 |
+
|
| 44 |
+
return {
|
| 45 |
+
total,
|
| 46 |
+
pro,
|
| 47 |
+
con,
|
| 48 |
+
proPercentage: total > 0 ? (pro / total) * 100 : 0,
|
| 49 |
+
conPercentage: total > 0 ? (con / total) * 100 : 0,
|
| 50 |
+
};
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/**
|
| 54 |
+
* Calculate topic frequency statistics
|
| 55 |
+
*/
|
| 56 |
+
export function calculateTopicFrequency(
|
| 57 |
+
results: AnalysisResult[],
|
| 58 |
+
limit: number = 10
|
| 59 |
+
): TopicFrequency[] {
|
| 60 |
+
const topicMap = new Map<string, { count: number; proCount: number; conCount: number }>();
|
| 61 |
+
|
| 62 |
+
results.forEach((result) => {
|
| 63 |
+
const topic = result.topic.trim();
|
| 64 |
+
if (!topic) return;
|
| 65 |
+
|
| 66 |
+
const existing = topicMap.get(topic) || { count: 0, proCount: 0, conCount: 0 };
|
| 67 |
+
existing.count += 1;
|
| 68 |
+
if (result.predicted_stance === 'PRO') {
|
| 69 |
+
existing.proCount += 1;
|
| 70 |
+
} else {
|
| 71 |
+
existing.conCount += 1;
|
| 72 |
+
}
|
| 73 |
+
topicMap.set(topic, existing);
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
return Array.from(topicMap.entries())
|
| 77 |
+
.map(([topic, stats]) => ({
|
| 78 |
+
topic,
|
| 79 |
+
count: stats.count,
|
| 80 |
+
proCount: stats.proCount,
|
| 81 |
+
conCount: stats.conCount,
|
| 82 |
+
}))
|
| 83 |
+
.sort((a, b) => b.count - a.count)
|
| 84 |
+
.slice(0, limit);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/**
|
| 88 |
+
* Calculate time-based statistics (grouped by date)
|
| 89 |
+
*/
|
| 90 |
+
export function calculateTimeStats(results: AnalysisResult[]): TimeStats[] {
|
| 91 |
+
const dateMap = new Map<string, number>();
|
| 92 |
+
|
| 93 |
+
results.forEach((result) => {
|
| 94 |
+
const date = new Date(result.created_at).toISOString().split('T')[0];
|
| 95 |
+
dateMap.set(date, (dateMap.get(date) || 0) + 1);
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
return Array.from(dateMap.entries())
|
| 99 |
+
.map(([date, count]) => ({ date, count }))
|
| 100 |
+
.sort((a, b) => a.date.localeCompare(b.date));
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/**
|
| 104 |
+
* Get unique topics count
|
| 105 |
+
*/
|
| 106 |
+
export function getUniqueTopicsCount(results: AnalysisResult[]): number {
|
| 107 |
+
const uniqueTopics = new Set(results.map((r) => r.topic.trim()).filter((t) => t));
|
| 108 |
+
return uniqueTopics.size;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
/**
|
| 112 |
+
* Get average argument length (in characters)
|
| 113 |
+
*/
|
| 114 |
+
export function getAverageArgumentLength(results: AnalysisResult[]): number {
|
| 115 |
+
if (results.length === 0) return 0;
|
| 116 |
+
const totalLength = results.reduce((sum, r) => sum + r.argument.length, 0);
|
| 117 |
+
return Math.round(totalLength / results.length);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/**
|
| 121 |
+
* Get most recent analysis date
|
| 122 |
+
*/
|
| 123 |
+
export function getMostRecentAnalysisDate(results: AnalysisResult[]): string | null {
|
| 124 |
+
if (results.length === 0) return null;
|
| 125 |
+
const dates = results.map((r) => new Date(r.created_at).getTime());
|
| 126 |
+
const mostRecent = new Date(Math.max(...dates));
|
| 127 |
+
return mostRecent.toISOString().split('T')[0];
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
/**
|
| 131 |
+
* Get oldest analysis date
|
| 132 |
+
*/
|
| 133 |
+
export function getOldestAnalysisDate(results: AnalysisResult[]): string | null {
|
| 134 |
+
if (results.length === 0) return null;
|
| 135 |
+
const dates = results.map((r) => new Date(r.created_at).getTime());
|
| 136 |
+
const oldest = new Date(Math.min(...dates));
|
| 137 |
+
return oldest.toISOString().split('T')[0];
|
| 138 |
+
}
|
| 139 |
+
|
src/app/utils/index.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { CsvParseResult, CsvRow } from '../types/index.ts';
|
|
| 7 |
// Export user utilities
|
| 8 |
export * from './user.utils.ts';
|
| 9 |
|
|
|
|
|
|
|
|
|
|
| 10 |
/**
|
| 11 |
* Debounce function - delays execution until after wait time
|
| 12 |
*/
|
|
|
|
| 7 |
// Export user utilities
|
| 8 |
export * from './user.utils.ts';
|
| 9 |
|
| 10 |
+
// Export analysis utilities
|
| 11 |
+
export * from './analysis.utils.ts';
|
| 12 |
+
|
| 13 |
/**
|
| 14 |
* Debounce function - delays execution until after wait time
|
| 15 |
*/
|