diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9fc624b3..e3b39f29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,9 +12,11 @@ "@geoapify/react-geocoder-autocomplete": "^1.5.0", "axios": "^1.5.0", "chart.js": "^4.4.0", + "chartjs-plugin-zoom": "^2.0.1", "d3": "^7.8.5", "leaflet": "^1.9.4", "react": "^18.2.0", + "react-burger-menu": "^3.0.9", "react-chartjs-2": "^5.2.0", "react-datepicker": "^4.19.0", "react-dom": "^18.2.0", @@ -38,6 +40,7 @@ "@types/leaflet": "^1.9.4", "@types/node": "^20.6.2", "@types/react": "^18.2.21", + "@types/react-burger-menu": "^2.8.5", "@types/react-datepicker": "^4.15.1", "@types/react-dom": "^18.2.7", "@types/react-router-dom": "^5.3.3", @@ -4148,6 +4151,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-burger-menu": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@types/react-burger-menu/-/react-burger-menu-2.8.5.tgz", + "integrity": "sha512-2VCnDyg4JGzaTByh17qwu2rVnjylF+L5Lzw9CwiiDmcYBMlW9XE8+q0ZChV+9qiBW6SNjBiz/KH+bOyv28THAQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-datepicker": { "version": "4.15.1", "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.15.1.tgz", @@ -5041,6 +5053,15 @@ "ajv": "^6.9.1" } }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "optional": true, + "engines": { + "node": ">=0.4.2" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5291,6 +5312,76 @@ "util": "0.10.3" } }, + "node_modules/ast-transform": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz", + "integrity": "sha512-e/JfLiSoakfmL4wmTGPjv0HpTICVmxwXgYOB8x+mzozHL8v+dSfCbrJ8J8hJ0YBP0XcYu1aLZ6b/3TnxNK3P2A==", + "dependencies": { + "escodegen": "~1.2.0", + "esprima": "~1.0.4", + "through": "~2.3.4" + } + }, + "node_modules/ast-transform/node_modules/escodegen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", + "integrity": "sha512-yLy3Cc+zAC0WSmoT2fig3J87TpQ8UaZGx8ahCAs9FL8qNbyV7CVyPKS74DG4bsHiL5ew9sxdYx131OkBQMFnvA==", + "dependencies": { + "esprima": "~1.0.4", + "estraverse": "~1.5.0", + "esutils": "~1.0.0" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.4.0" + }, + "optionalDependencies": { + "source-map": "~0.1.30" + } + }, + "node_modules/ast-transform/node_modules/esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha512-rp5dMKN8zEs9dfi9g0X1ClLmV//WRyk/R15mppFNICIFRG5P92VP7Z04p8pk++gABo9W2tY+kHyu6P1mEHgmTA==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ast-transform/node_modules/estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ast-transform/node_modules/esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-transform/node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -5872,6 +5963,37 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "node_modules/browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dependencies": { + "resolve": "1.1.7" + } + }, + "node_modules/browser-resolve/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==" + }, + "node_modules/browserify-optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz", + "integrity": "sha512-VrhjbZ+Ba5mDiSYEuPelekQMfTbhcA2DhLk2VQWqdcCROWeFqlTcXZ7yfRkXCIl8E+g4gINJYJiRB7WEtfomAQ==", + "dependencies": { + "ast-transform": "0.0.0", + "ast-types": "^0.7.0", + "browser-resolve": "^1.8.1" + } + }, + "node_modules/browserify-optional/node_modules/ast-types": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz", + "integrity": "sha512-RIOpVnVlltB6PcBJ5BMLx+H+6JJ/zjDGU0t7f0L6c2M1dqcK92VQopLBlPQ9R80AVXelfqYgjcPLtHtDbNFg0Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", @@ -6254,6 +6376,17 @@ "pnpm": ">=7" } }, + "node_modules/chartjs-plugin-zoom": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.0.1.tgz", + "integrity": "sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==", + "dependencies": { + "hammerjs": "^2.0.8" + }, + "peerDependencies": { + "chart.js": ">=3.2.0" + } + }, "node_modules/check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -9297,6 +9430,11 @@ "node": ">= 0.6" } }, + "node_modules/eve": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eve/-/eve-0.5.4.tgz", + "integrity": "sha512-aqprQ9MAOh1t66PrHxDFmMXPlgNO6Uv1uqvxmwjprQV50jaQ2RqO7O1neY4PJwC+hMnkyMDphu2AQPOPZdjQog==" + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -10348,6 +10486,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -16141,6 +16287,25 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-burger-menu": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/react-burger-menu/-/react-burger-menu-3.0.9.tgz", + "integrity": "sha512-Qy15hkCxwxNEKfqdAv43F+8ZSl+/c6KkqrBwGP0CesFYJ02onHtiUFUbuhSWCMtBH8/n0HhfekFlp/NyCdKYzQ==", + "dependencies": { + "browserify-optional": "^1.0.0", + "classnames": "^2.2.6", + "eve": "~0.5.1", + "prop-types": "^15.7.2", + "snapsvg-cjs": "0.0.6" + }, + "engines": { + "node": ">=4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0", + "react-dom": ">=0.14.0" + } + }, "node_modules/react-chartjs-2": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", @@ -18796,6 +18961,25 @@ "node": ">=8" } }, + "node_modules/snapsvg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/snapsvg/-/snapsvg-0.5.1.tgz", + "integrity": "sha512-CjwWYsL7+CCk1vCk9BBKGYS4WJVDfJAOMWU+Zhzf8wf6pAm/xT34wnpaMPAgcgCNkxuU6OkQPPd8wGuRCY9aNw==", + "dependencies": { + "eve": "~0.5.1" + } + }, + "node_modules/snapsvg-cjs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/snapsvg-cjs/-/snapsvg-cjs-0.0.6.tgz", + "integrity": "sha512-7NNvoGrc3BQvWz5rWK1DsD5/Vni4STswz5B3JrBADboQWcN8OBVGjYVJFPT5JkUXb2iVnEflZANhufEpEcTHXw==", + "dependencies": { + "snapsvg": "0.5.1" + }, + "peerDependencies": { + "eve": "~0.5.1" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -19576,6 +19760,11 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index a86a7c21..2da0d9ef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "@geoapify/react-geocoder-autocomplete": "^1.5.0", "axios": "^1.5.0", "chart.js": "^4.4.0", + "chartjs-plugin-zoom": "^2.0.1", "d3": "^7.8.5", "leaflet": "^1.9.4", "react": "^18.2.0", @@ -21,7 +22,8 @@ "react-slider": "^2.0.6", "react-split": "^2.0.14", "typescript": "^4.9.5", - "web-vitals": "^3.4.0" + "web-vitals": "^3.4.0", + "chartjs-plugin-zoom": "^2.0.1" }, "scripts": { "start": "react-scripts start", diff --git a/frontend/src/Components/RoadDetails/ConditionsGraph.tsx b/frontend/src/Components/RoadDetails/ConditionsGraph.tsx index 51ede9fc..82a962c5 100644 --- a/frontend/src/Components/RoadDetails/ConditionsGraph.tsx +++ b/frontend/src/Components/RoadDetails/ConditionsGraph.tsx @@ -10,13 +10,13 @@ import { Legend, LinearScale, LineElement, - Plugin, PointElement, Title, Tooltip, } from 'chart.js'; import 'chart.js/auto'; import { Scatter } from 'react-chartjs-2'; +import zoomPlugin from 'chartjs-plugin-zoom'; Chart.register( CategoryScale, @@ -26,9 +26,10 @@ Chart.register( Title, Tooltip, Legend, + zoomPlugin, ); -const options = (): ChartOptions<'scatter'> => ({ +const options = (minAndMax: number[]): ChartOptions<'scatter'> => ({ responsive: true, maintainAspectRatio: false, plugins: { @@ -36,6 +37,32 @@ const options = (): ChartOptions<'scatter'> => ({ position: 'top' as const, labels: { color: 'white' }, }, + zoom: { + pan: { + enabled: true, + mode: 'x', + //to enable in drag to zoom option + // modifierKey: 'ctrl', + }, + limits: { + x: { min: 0, max: 100, minRange: 20 }, + y: { min: 0, max: 10 }, + }, + zoom: { + // vv drag to zoom option could be a solution vv + // drag: { + // enabled: true, + // }, + // ^^ here ^^ + pinch: { + enabled: true, // Enable pinch zooming + }, + wheel: { + enabled: true, // Enable wheel zooming + }, + mode: 'x', + }, + }, }, scales: { x: { @@ -54,6 +81,8 @@ const options = (): ChartOptions<'scatter'> => ({ type: 'linear', position: 'left', display: 'auto', + min: Math.floor(minAndMax[0]), + max: Math.ceil(minAndMax[1]), title: { display: true, text: 'KPI', @@ -63,6 +92,8 @@ const options = (): ChartOptions<'scatter'> => ({ type: 'linear', position: 'right', display: 'auto', + min: Math.floor(minAndMax[2]), + max: Math.ceil(minAndMax[3]), title: { display: true, text: 'DI', @@ -73,9 +104,10 @@ const options = (): ChartOptions<'scatter'> => ({ interface Props { data: ChartData<'scatter', number[], number> | undefined; + minAndMax: number[]; } -const ConditionsGraph: FC = ({ data }) => { +const ConditionsGraph: FC = ({ data, minAndMax }) => { const ref = useRef>(null); useEffect(() => { @@ -87,7 +119,7 @@ const ConditionsGraph: FC = ({ data }) => { // attach events to the graph options const graphOptions: ChartOptions<'scatter'> = useMemo( () => ({ - ...options(), + ...options(minAndMax), onClick: ( event: ChartEvent, elts: ActiveElement[], @@ -99,25 +131,12 @@ const ConditionsGraph: FC = ({ data }) => { console.log(pointIndex, event, elts); }, }), - [], + [minAndMax], ); - const plugins: Plugin<'scatter'>[] = [ - { - id: 'id', - }, - ]; - return (
- {data && ( - - )} + {data && }
); }; diff --git a/frontend/src/css/road_details.css b/frontend/src/css/road_details.css index cd96b9f9..96b1c175 100644 --- a/frontend/src/css/road_details.css +++ b/frontend/src/css/road_details.css @@ -83,8 +83,10 @@ .road-conditions-graph { position: relative; - width: 100vw; - display: flex; + left: 2vw; + right: 2vw; + width: 96vw; + /*display: flex;*/ justify-content: center; align-items: center; } diff --git a/frontend/src/pages/RoadDetails.tsx b/frontend/src/pages/RoadDetails.tsx index 2573f18c..3f4dd849 100644 --- a/frontend/src/pages/RoadDetails.tsx +++ b/frontend/src/pages/RoadDetails.tsx @@ -14,11 +14,25 @@ const RoadDetails = () => { const [showMapImageMode, setShowRoadImageMode] = useState(false); const [wayData, setWayData] = useState>(); + const [minAndMax, setMinAndMax] = useState([1, 1, 0, 0]); const [triggerUpdate, setTriggerUpdate] = useState(0); useEffect(() => { getConditionsWay('0cba0666-d75e-45bd-9da6-62ef0fe9544c', (wc) => { console.log(wc); + + // Extract 'KPI' and 'DI' data arrays + const KPIData = wc.map((p) => p.KPI); + const DIData = wc.map((p) => p.DI); + + // Find max and min values for 'KPI' and 'DI' + const minKPI = Math.min(...KPIData); + const maxKPI = Math.max(...KPIData); + const maxDI = Math.max(...DIData); + const minDI = Math.min(...DIData); + + setMinAndMax([minKPI, maxKPI, minDI, maxDI]); + setWayData({ labels: wc.map((p) => p.way_dist * 100), datasets: [ @@ -29,7 +43,6 @@ const RoadDetails = () => { borderColor: 'rgb(255, 99, 132)', borderWidth: 2, fill: false, - tension: 0.2, data: wc.map((p) => p.KPI), yAxisID: 'KPI', }, @@ -40,7 +53,6 @@ const RoadDetails = () => { borderColor: 'rgb(120, 245, 23)', borderWidth: 2, fill: false, - tension: 0.2, data: wc.map((p) => p.DI), yAxisID: 'DI', }, @@ -56,8 +68,8 @@ const RoadDetails = () => { className="split" direction="vertical" sizes={[45, 55]} - minSize={150} - snapOffset={10} + minSize={0} + snapOffset={50} onDragEnd={() => { setTriggerUpdate((prev) => prev + 1); }} @@ -69,9 +81,8 @@ const RoadDetails = () => { )} - + - , ); };