From b80ddba30fdd7cc354e72ffa59d4498d16cf3670 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 26 Jul 2024 18:04:40 +0000 Subject: [PATCH 01/34] Port sample visual units component code (from nathang15) to current development --- package-lock.json | 693 +++++++++++++++++- package.json | 2 + .../app/components/HeaderButtonsComponent.tsx | 11 +- src/client/app/components/RouteComponent.tsx | 4 +- .../CreateVisualUnitModalMapComponent.tsx | 131 ++++ .../visual-unit/VisualUnitDetailComponent.tsx | 65 ++ src/client/app/translations/data.ts | 3 + 7 files changed, 899 insertions(+), 10 deletions(-) create mode 100644 src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx create mode 100644 src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx diff --git a/package-lock.json b/package-lock.json index 29a99bf36..68bc1cd28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "bootstrap": "~5.3.3", "csv": "~5.3.2", "csv-stringify": "~5.6.5", + "d3": "~7.8.5", "dotenv": "~16.4.5", "escape-html": "~1.0.3", "express": "~4.19.2", @@ -57,6 +58,7 @@ }, "devDependencies": { "@redux-devtools/extension": "~3.2.5", + "@types/d3": "~7.4.3", "@types/lodash": "~4.17.4", "@types/node": "~20.14.10", "@types/plotly.js": "~2.29.2", @@ -976,6 +978,259 @@ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.7", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", @@ -1002,6 +1257,12 @@ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -1633,14 +1894,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/add-dom-event-listener": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", - "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", - "dependencies": { - "object-assign": "4.x" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3018,11 +3271,85 @@ "type": "^1.0.1" } }, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-collection": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", @@ -3036,11 +3363,118 @@ "node": ">=12" } }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-dispatch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-force": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", @@ -3104,11 +3538,84 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-quadtree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-shape": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", @@ -3135,6 +3642,155 @@ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3230,6 +3886,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5234,6 +5898,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -8329,6 +9001,11 @@ "inherits": "^2.0.1" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index f9a48c1bb..fa5f6d8de 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "bootstrap": "~5.3.3", "csv": "~5.3.2", "csv-stringify": "~5.6.5", + "d3": "~7.8.5", "dotenv": "~16.4.5", "escape-html": "~1.0.3", "express": "~4.19.2", @@ -104,6 +105,7 @@ }, "devDependencies": { "@redux-devtools/extension": "~3.2.5", + "@types/d3": "~7.4.3", "@types/lodash": "~4.17.4", "@types/node": "~20.14.10", "@types/plotly.js": "~2.29.2", diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index 5e289288e..6be18cf34 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -60,6 +60,7 @@ export default function HeaderButtonsComponent() { shouldCSVButtonDisabled: true, shouldUnitsButtonDisabled: true, shouldConversionsButtonDisabled: true, + shouldVisualUnitMapButtonDisabled: true, // Translated menu title that depend on whether logged in. menuTitle: '', // link to help page for page choices. Should not see default but use general help URL. @@ -93,7 +94,8 @@ export default function HeaderButtonsComponent() { shouldMapsButtonDisabled: pathname === '/maps', shouldCSVButtonDisabled: pathname === '/csv', shouldUnitsButtonDisabled: pathname === '/units', - shouldConversionsButtonDisabled: pathname === '/conversions' + shouldConversionsButtonDisabled: pathname === '/conversions', + shouldVisualUnitMapButtonDisabled: pathname === '/visual-unit' })); }, [pathname]); @@ -203,6 +205,13 @@ export default function HeaderButtonsComponent() { to="/units"> + + + }, { path: 'units', element: }, { path: 'conversions', element: }, - { path: 'users', element: } + { path: 'users', element: }, + { path: 'visual-unit', element: } ] }, { diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx new file mode 100644 index 000000000..c44de5d99 --- /dev/null +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -0,0 +1,131 @@ +import * as React from 'react'; +import * as d3 from 'd3'; +import { useEffect } from 'react'; +// import { useSelector } from 'react-redux'; +// import { State } from 'types/redux/state'; +// import { noUnitTranslated } from 'utils/input'; +// import { MeterData } from 'types/redux/meters'; +// import { CurrentUserState } from 'types/redux/currentUser'; +// import { UnitData } from 'types/redux/units'; +// import { Dispatch } from 'types/redux/actions'; +// import { useDispatch } from 'react-redux'; +// import { fetchMetersDetailsIfNeeded } from 'actions/meters'; + +interface Node { + id: string; + x?: number; + y?: number; +} + +interface Link { + source: string; + target: string; +} + +interface GraphData { + nodes: Node[]; + links: Link[]; +} + +// interface MeterViewComponentProps { +// meter: MeterData; +// currentUser: CurrentUserState; +// // These two aren't used in this component but are passed to the edit component +// // This is done to avoid having to recalculate the possible units sets in each view component +// possibleMeterUnits: Set; +// possibleGraphicUnits: Set; +// } + +export default function CreateVisualUnitMapModalComponent() { + // const dispatch: Dispatch = useDispatch(); + + // useEffect(() => { + // // Makes async call to Meters API for Meters details if one has not already been made somewhere else, stores Meter ids in state + // dispatch(fetchMetersDetailsIfNeeded()); + // }, []); + + // // Access Redux state directly + // const currentUnitState = useSelector((state: State) => state.units.units); + // const meterDetails = useSelector((state: State) => state.meters); // Replace with your actual slice name + + // useEffect(() => { + // // Check if meterDetails are available + // if (meterDetails && meterDetails.length > 0) { + // meterDetails.forEach((meter: MeterData) => { + // const unitName = (Object.keys(currentUnitState).length === 0 || meter.unitId === -99) ? + // noUnitTranslated().identifier : currentUnitState[meter.unitId].identifier; + + // const graphicName = (Object.keys(currentUnitState).length === 0 || meter.defaultGraphicUnit === -99) ? + // noUnitTranslated().identifier : currentUnitState[meter.defaultGraphicUnit].identifier; + + // console.log('Meter Unit Name:', unitName); + // console.log('Meter Graphic Name:', graphicName); + // console.log('Meter Details:', meter); + // }); + // } + // }, [currentUnitState, meterDetails]); + + useEffect(() => { + const margin = { top: 10, right: 30, bottom: 30, left: 40 }; + const width = 400 - margin.left - margin.right; + const height = 400 - margin.top - margin.bottom; + + const svg = d3.select('#sample') + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', `translate(${margin.left}, ${margin.top})`); + + d3.json('https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_network.json').then(function (data) { + if (!data) { + console.error('Data is undefined or null.'); + return; + } + + // connection link style + const link = svg.selectAll('line') + .data(data.links) + .enter().append('line') + .style('stroke', '#aaa'); + + // node style + const node = svg.selectAll('circle') + .data(data.nodes) + .enter().append('circle') + .attr('r', 20) + .style('fill', '#69b3a2'); + + d3.forceSimulation(data.nodes) + .force('link', d3.forceLink() + .id(function (d) { return (d as Node).id; }) + .links(data.links) + ) + .force('charge', d3.forceManyBody().strength(-400)) + .force('center', d3.forceCenter(width / 2, height / 2)) + .on('tick', ticked); + + function ticked() { + link + .attr('x1', function (d) { return (d.source as d3.SimulationNodeDatum).x ?? 0; }) + .attr('y1', function (d) { return (d.source as d3.SimulationNodeDatum).y ?? 0; }) + .attr('x2', function (d) { return (d.target as d3.SimulationNodeDatum).x ?? 0; }) + .attr('y2', function (d) { return (d.target as d3.SimulationNodeDatum).y ?? 0; }); + + node + .attr('cx', function (d) { return (d.x as number) + 6; }) + .attr('cy', function (d) { return (d.y as number) - 6; }); + } + + }).catch(error => { + console.error('Error loading data:', error); + }); + }, []); // Empty dependency array to run the effect only once + + return ( +
+

Sample Network Graph

+
+
+ ); +} \ No newline at end of file diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx new file mode 100644 index 000000000..1b19f9cd5 --- /dev/null +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { FormattedMessage } from 'react-intl'; +import TooltipHelpComponent from '../TooltipHelpComponent'; +// import TooltipMarkerComponent from '../TooltipMarkerComponent'; +import * as React from 'react'; +// import { useDispatch, useSelector } from 'react-redux'; +// import { useEffect } from 'react'; +// import { State } from '../../types/redux/state'; +// import { fetchUnitsDetailsIfNeeded } from '../../actions/units'; +// import UnitViewComponent from './UnitViewComponent'; +// import CreateUnitModalComponent from './CreateUnitModalComponent'; +// import { UnitData } from 'types/redux/units'; +// import SpinnerComponent from '../../components/SpinnerComponent'; +// import { Dispatch } from 'types/redux/actions'; +import CreateVisualUnitMapModalComponent from './CreateVisualUnitModalMapComponent'; + +/** + * Defines the units page card view + * @returns Units page element + */ +export default function VisualUnitDetailComponent() { + // The route stops you from getting to this page if not an admin. + + // const dispatch: Dispatch = useDispatch(); + + // useEffect(() => { + // // Makes async call to units API for units details if one has not already been made somewhere else, stores unit ids in state + // dispatch(fetchUnitsDetailsIfNeeded()); + // }, []); + + //Units state + // const unitsState = useSelector((state: State) => state.units.units); + + const titleStyle: React.CSSProperties = { + textAlign: 'center' + }; + + // const tooltipStyle = { + // display: 'inline-block', + // fontSize: '50%', + // // For now, only an admin can see the unit page. + // tooltipVisualUnitView: 'help.admin.unitview' + // }; + + return ( +
+ + +
+

+ + {/*
+ +
*/} +

+
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 0b21ce823..29293cc90 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -496,6 +496,7 @@ const LocaleTranslationData = { "uses": "uses", "view.groups": "View Groups", "visit": " or visit our ", + "visual-unit": "Visual Units Graph", "website": "website", "week": "Week", "yes": "yes", @@ -992,6 +993,7 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Visionner les groupes", "visit": " ou visitez notre ", + "visual-unit": "Visual Units Graph.\u{26A1}", "website": "site web", "week": "Semaine", "yes": " yes\u{26A1}", @@ -1487,6 +1489,7 @@ const LocaleTranslationData = { "uses":"usos", "view.groups": "Ver grupos", "visit": " o visite nuestro ", + "visual-unit": "Visual Units Graph.\u{26A1}", "website": "sitio web", "week": "semana", "yes": "sí", From bb54aa3838e132c316bd97498213494926d02608 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 2 Aug 2024 17:54:48 +0000 Subject: [PATCH 02/34] Build graph from unit and conversion data from redux --- .../CreateVisualUnitModalMapComponent.tsx | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index c44de5d99..4c9dad34d 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; +import { useAppSelector } from '../../redux/reduxHooks'; +import { selectAllUnits } from '../../redux/api/unitsApi'; +import { selectConversionsDetails } from '../../redux/api/conversionsApi'; // import { useSelector } from 'react-redux'; // import { State } from 'types/redux/state'; // import { noUnitTranslated } from 'utils/input'; @@ -65,10 +68,31 @@ export default function CreateVisualUnitMapModalComponent() { // } // }, [currentUnitState, meterDetails]); + const jsonData: { nodes: any[], links: any[] } = { + nodes: [], + links: [] + }; + + const unitData = useAppSelector(selectAllUnits); + unitData.map(function (value) { + jsonData.nodes.push({ + 'name': value.name, + 'id': value.id + }); + }); + + const conversionData = useAppSelector(selectConversionsDetails); + conversionData.map(function (value) { + jsonData.links.push({ + 'source': value.sourceId, + 'target': value.destinationId + }); + }); + useEffect(() => { const margin = { top: 10, right: 30, bottom: 30, left: 40 }; - const width = 400 - margin.left - margin.right; - const height = 400 - margin.top - margin.bottom; + const width = 1200 - margin.left - margin.right; + const height = 600 - margin.top - margin.bottom; const svg = d3.select('#sample') .append('svg') @@ -85,23 +109,23 @@ export default function CreateVisualUnitMapModalComponent() { // connection link style const link = svg.selectAll('line') - .data(data.links) + .data(jsonData.links) .enter().append('line') .style('stroke', '#aaa'); // node style const node = svg.selectAll('circle') - .data(data.nodes) + .data(jsonData.nodes) .enter().append('circle') - .attr('r', 20) + .attr('r', 15) .style('fill', '#69b3a2'); - d3.forceSimulation(data.nodes) + d3.forceSimulation(jsonData.nodes) .force('link', d3.forceLink() .id(function (d) { return (d as Node).id; }) - .links(data.links) + .links(jsonData.links) ) - .force('charge', d3.forceManyBody().strength(-400)) + .force('charge', d3.forceManyBody().strength(-50)) .force('center', d3.forceCenter(width / 2, height / 2)) .on('tick', ticked); From 25ad55cd862066b08454cfcb5d159daf76c87590 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 2 Aug 2024 19:25:49 +0000 Subject: [PATCH 03/34] Remove sample data framework and reorganize --- .../CreateVisualUnitModalMapComponent.tsx | 106 +++++++----------- 1 file changed, 43 insertions(+), 63 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 4c9dad34d..a2bbc4fc9 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -14,22 +14,6 @@ import { selectConversionsDetails } from '../../redux/api/conversionsApi'; // import { useDispatch } from 'react-redux'; // import { fetchMetersDetailsIfNeeded } from 'actions/meters'; -interface Node { - id: string; - x?: number; - y?: number; -} - -interface Link { - source: string; - target: string; -} - -interface GraphData { - nodes: Node[]; - links: Link[]; -} - // interface MeterViewComponentProps { // meter: MeterData; // currentUser: CurrentUserState; @@ -68,14 +52,14 @@ export default function CreateVisualUnitMapModalComponent() { // } // }, [currentUnitState, meterDetails]); - const jsonData: { nodes: any[], links: any[] } = { + const data: { nodes: any[], links: any[] } = { nodes: [], links: [] }; const unitData = useAppSelector(selectAllUnits); unitData.map(function (value) { - jsonData.nodes.push({ + data.nodes.push({ 'name': value.name, 'id': value.id }); @@ -83,7 +67,7 @@ export default function CreateVisualUnitMapModalComponent() { const conversionData = useAppSelector(selectConversionsDetails); conversionData.map(function (value) { - jsonData.links.push({ + data.links.push({ 'source': value.sourceId, 'target': value.destinationId }); @@ -91,64 +75,60 @@ export default function CreateVisualUnitMapModalComponent() { useEffect(() => { const margin = { top: 10, right: 30, bottom: 30, left: 40 }; - const width = 1200 - margin.left - margin.right; + const width = 600 - margin.left - margin.right; const height = 600 - margin.top - margin.bottom; + const nodes = data.nodes.map(d => ({...d})); + const links = data.links.map(d => ({...d})); + + const simulation = d3.forceSimulation(nodes) + .force('link', d3.forceLink(links).id((d: any) => d.id)) + .force('charge', d3.forceManyBody().strength(-200)) + .force('x', d3.forceX()) + .force('y', d3.forceY()); + const svg = d3.select('#sample') .append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) + .attr('viewBox', + [-(width + margin.left + margin.right) / 2, -(height + margin.top + margin.bottom) / 2, + width + margin.left + margin.right, height + margin.top + margin.bottom] + ) .append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`); - d3.json('https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_network.json').then(function (data) { - if (!data) { - console.error('Data is undefined or null.'); - return; - } - - // connection link style - const link = svg.selectAll('line') - .data(jsonData.links) - .enter().append('line') - .style('stroke', '#aaa'); - - // node style - const node = svg.selectAll('circle') - .data(jsonData.nodes) - .enter().append('circle') - .attr('r', 15) - .style('fill', '#69b3a2'); - - d3.forceSimulation(jsonData.nodes) - .force('link', d3.forceLink() - .id(function (d) { return (d as Node).id; }) - .links(jsonData.links) - ) - .force('charge', d3.forceManyBody().strength(-50)) - .force('center', d3.forceCenter(width / 2, height / 2)) - .on('tick', ticked); - - function ticked() { - link - .attr('x1', function (d) { return (d.source as d3.SimulationNodeDatum).x ?? 0; }) - .attr('y1', function (d) { return (d.source as d3.SimulationNodeDatum).y ?? 0; }) - .attr('x2', function (d) { return (d.target as d3.SimulationNodeDatum).x ?? 0; }) - .attr('y2', function (d) { return (d.target as d3.SimulationNodeDatum).y ?? 0; }); - - node - .attr('cx', function (d) { return (d.x as number) + 6; }) - .attr('cy', function (d) { return (d.y as number) - 6; }); - } - - }).catch(error => { - console.error('Error loading data:', error); + // connection link style + const link = svg.selectAll('line') + .data(links) + .enter().append('line') + .style('stroke', '#aaa') + .attr('stroke-width', 3); + + // node style + const node = svg.selectAll('.node') + .data(nodes) + .enter().append('circle') + .attr('r', 12) + .style('fill', '#69b3a2') + .text(function(d) { return d.name; }); + + simulation.on('tick', () => { + link + .attr('x1', d => d.source.x) + .attr('y1', d => d.source.y) + .attr('x2', d => d.target.x) + .attr('y2', d => d.target.y); + + node + .attr('cx', d => d.x) + .attr('cy', d => d.y); }); + }, []); // Empty dependency array to run the effect only once return (
-

Sample Network Graph

); From 1aad2d3bea9caf26cbf6b038d28e286e05a7a5d1 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Thu, 8 Aug 2024 21:07:21 +0000 Subject: [PATCH 04/34] Put unit labels on each node --- .../CreateVisualUnitModalMapComponent.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index a2bbc4fc9..58af62417 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -110,8 +110,18 @@ export default function CreateVisualUnitMapModalComponent() { .data(nodes) .enter().append('circle') .attr('r', 12) - .style('fill', '#69b3a2') - .text(function(d) { return d.name; }); + .style('fill', '#69b3a2'); + + // label style + const label = svg.selectAll('.label') + .data(nodes) + .enter() + .append('text') + .text(function (d) { return d.name; }) + .style('text-anchor', 'middle') + .style('fill', '#555') + .style('font-family', 'Arial') + .style('font-size', 12); simulation.on('tick', () => { link @@ -123,6 +133,10 @@ export default function CreateVisualUnitMapModalComponent() { node .attr('cx', d => d.x) .attr('cy', d => d.y); + + label + .attr('x', function(d){ return d.x; }) + .attr('y', function (d) {return d.y - 10; }); }); }, []); // Empty dependency array to run the effect only once From 338cb02c09589617fbda803db6158c826eeec5c0 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 9 Aug 2024 18:20:04 +0000 Subject: [PATCH 05/34] Add arrows to links and removed unused code --- .../CreateVisualUnitModalMapComponent.tsx | 148 +++++++++--------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 58af62417..6431c747a 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -4,124 +4,105 @@ import { useEffect } from 'react'; import { useAppSelector } from '../../redux/reduxHooks'; import { selectAllUnits } from '../../redux/api/unitsApi'; import { selectConversionsDetails } from '../../redux/api/conversionsApi'; -// import { useSelector } from 'react-redux'; -// import { State } from 'types/redux/state'; -// import { noUnitTranslated } from 'utils/input'; -// import { MeterData } from 'types/redux/meters'; -// import { CurrentUserState } from 'types/redux/currentUser'; -// import { UnitData } from 'types/redux/units'; -// import { Dispatch } from 'types/redux/actions'; -// import { useDispatch } from 'react-redux'; -// import { fetchMetersDetailsIfNeeded } from 'actions/meters'; - -// interface MeterViewComponentProps { -// meter: MeterData; -// currentUser: CurrentUserState; -// // These two aren't used in this component but are passed to the edit component -// // This is done to avoid having to recalculate the possible units sets in each view component -// possibleMeterUnits: Set; -// possibleGraphicUnits: Set; -// } export default function CreateVisualUnitMapModalComponent() { - // const dispatch: Dispatch = useDispatch(); - - // useEffect(() => { - // // Makes async call to Meters API for Meters details if one has not already been made somewhere else, stores Meter ids in state - // dispatch(fetchMetersDetailsIfNeeded()); - // }, []); - - // // Access Redux state directly - // const currentUnitState = useSelector((state: State) => state.units.units); - // const meterDetails = useSelector((state: State) => state.meters); // Replace with your actual slice name - - // useEffect(() => { - // // Check if meterDetails are available - // if (meterDetails && meterDetails.length > 0) { - // meterDetails.forEach((meter: MeterData) => { - // const unitName = (Object.keys(currentUnitState).length === 0 || meter.unitId === -99) ? - // noUnitTranslated().identifier : currentUnitState[meter.unitId].identifier; - - // const graphicName = (Object.keys(currentUnitState).length === 0 || meter.defaultGraphicUnit === -99) ? - // noUnitTranslated().identifier : currentUnitState[meter.defaultGraphicUnit].identifier; - - // console.log('Meter Unit Name:', unitName); - // console.log('Meter Graphic Name:', graphicName); - // console.log('Meter Details:', meter); - // }); - // } - // }, [currentUnitState, meterDetails]); + + const unitData = useAppSelector(selectAllUnits); + const conversionData = useAppSelector(selectConversionsDetails); const data: { nodes: any[], links: any[] } = { nodes: [], links: [] }; - - const unitData = useAppSelector(selectAllUnits); unitData.map(function (value) { - data.nodes.push({ - 'name': value.name, + data.nodes.push({'name': value.name, 'id': value.id }); }); - - const conversionData = useAppSelector(selectConversionsDetails); conversionData.map(function (value) { data.links.push({ 'source': value.sourceId, - 'target': value.destinationId + 'target': value.destinationId, + 'bidirectional': value.bidirectional }); }); useEffect(() => { - const margin = { top: 10, right: 30, bottom: 30, left: 40 }; - const width = 600 - margin.left - margin.right; - const height = 600 - margin.top - margin.bottom; + const width = window.innerWidth; + const height = 750; const nodes = data.nodes.map(d => ({...d})); const links = data.links.map(d => ({...d})); const simulation = d3.forceSimulation(nodes) - .force('link', d3.forceLink(links).id((d: any) => d.id)) - .force('charge', d3.forceManyBody().strength(-200)) + .force('link', d3.forceLink(links) + .id((d: any) => d.id) + .distance(60) + ) + .force('charge', d3.forceManyBody() + .strength(-500) + ) .force('x', d3.forceX()) .force('y', d3.forceY()); const svg = d3.select('#sample') .append('svg') - .attr('width', width + margin.left + margin.right) - .attr('height', height + margin.top + margin.bottom) - .attr('viewBox', - [-(width + margin.left + margin.right) / 2, -(height + margin.top + margin.bottom) / 2, - width + margin.left + margin.right, height + margin.top + margin.bottom] - ) - .append('g') - .attr('transform', `translate(${margin.left}, ${margin.top})`); + .attr('width', width) + .attr('height', height) + .attr('viewBox', [-width / 2, -height / 2, width, height]) + .attr('style', 'max-width: 100%; height: auto;') + .append('g'); + + svg.append('defs').append('marker') + .attr('id', 'arrow-end') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 20) + .attr('refY', 0) + .attr('markerWidth', 4) + .attr('markerHeight', 4) + .attr('orient', 'auto') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); + + svg.append('defs').append('marker') + .attr('id', 'arrow-start') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 20) + .attr('refY', 0) + .attr('markerWidth', 4) + .attr('markerHeight', 4) + .attr('orient', 'auto-start-reverse') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); - // connection link style const link = svg.selectAll('line') .data(links) .enter().append('line') .style('stroke', '#aaa') - .attr('stroke-width', 3); + .attr('stroke-width', 3) + .attr('marker-end', 'url(#arrow-end)') + .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); - // node style const node = svg.selectAll('.node') .data(nodes) .enter().append('circle') - .attr('r', 12) + .attr('r', 16) .style('fill', '#69b3a2'); - // label style + node.call(d3.drag() + .on('start', dragstart) + .on('drag', dragged) + .on('end', dragend)); + const label = svg.selectAll('.label') .data(nodes) .enter() .append('text') .text(function (d) { return d.name; }) .style('text-anchor', 'middle') - .style('fill', '#555') + .style('fill', '#000') .style('font-family', 'Arial') - .style('font-size', 12); + .style('font-size', 14); simulation.on('tick', () => { link @@ -136,9 +117,29 @@ export default function CreateVisualUnitMapModalComponent() { label .attr('x', function(d){ return d.x; }) - .attr('y', function (d) {return d.y - 10; }); + .attr('y', function (d) {return d.y + 15; }); }); + // eslint-disable-next-line jsdoc/require-jsdoc + function dragstart(event: any) { + if (!event.active) simulation.alphaTarget(0.3).restart(); + event.subject.fx = event.subject.x; + event.subject.fy = event.subject.y; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragged(event: any) { + event.subject.fx = event.x; + event.subject.fy = event.y; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragend(event: any) { + if (!event.active) simulation.alphaTarget(0); + event.subject.fx = null; + event.subject.fy = null; + } + }, []); // Empty dependency array to run the effect only once return ( @@ -146,4 +147,5 @@ export default function CreateVisualUnitMapModalComponent() {
); + } \ No newline at end of file From 27b55bfd09364a10bc5e241f467be32f79b434c7 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 9 Aug 2024 18:20:54 +0000 Subject: [PATCH 06/34] Add comments --- .../CreateVisualUnitModalMapComponent.tsx | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 6431c747a..09d150844 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -5,11 +5,17 @@ import { useAppSelector } from '../../redux/reduxHooks'; import { selectAllUnits } from '../../redux/api/unitsApi'; import { selectConversionsDetails } from '../../redux/api/conversionsApi'; +/** + * Visual unit-conversion graph component + * @returns D3 force graph visual + */ export default function CreateVisualUnitMapModalComponent() { + /* Get unit and conversion data from redux */ const unitData = useAppSelector(selectAllUnits); const conversionData = useAppSelector(selectConversionsDetails); + /* Create data container to pass to D3 force graph */ const data: { nodes: any[], links: any[] } = { nodes: [], links: [] @@ -23,24 +29,27 @@ export default function CreateVisualUnitMapModalComponent() { data.links.push({ 'source': value.sourceId, 'target': value.destinationId, - 'bidirectional': value.bidirectional + 'bidirectional': value.bidirectional /* boolean value */ }); }); + /* Visuals start here */ useEffect(() => { + /* View-box dimensions */ const width = window.innerWidth; const height = 750; + /* Grab data */ const nodes = data.nodes.map(d => ({...d})); const links = data.links.map(d => ({...d})); const simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links) - .id((d: any) => d.id) - .distance(60) + .id((d: any) => d.id) /* Set all link ids (from data.links) */ + .distance(60) /* This controls how long each link is */ ) - .force('charge', d3.forceManyBody() - .strength(-500) + .force('charge', d3.forceManyBody() /* Create new many-body force */ + .strength(-500) /* This controls the 'repelling' force on each node */ ) .force('x', d3.forceX()) .force('y', d3.forceY()); @@ -53,6 +62,7 @@ export default function CreateVisualUnitMapModalComponent() { .attr('style', 'max-width: 100%; height: auto;') .append('g'); + /* End arrow head */ svg.append('defs').append('marker') .attr('id', 'arrow-end') .attr('viewBox', '0 -5 10 10') @@ -60,10 +70,11 @@ export default function CreateVisualUnitMapModalComponent() { .attr('refY', 0) .attr('markerWidth', 4) .attr('markerHeight', 4) - .attr('orient', 'auto') + .attr('orient', 'auto') /* auto: point towards dest. node */ .append('svg:path') .attr('d', 'M0,-5L10,0L0,5'); + /* Start arrow head (for bidirectional edges) */ svg.append('defs').append('marker') .attr('id', 'arrow-start') .attr('viewBox', '0 -5 10 10') @@ -71,29 +82,33 @@ export default function CreateVisualUnitMapModalComponent() { .attr('refY', 0) .attr('markerWidth', 4) .attr('markerHeight', 4) - .attr('orient', 'auto-start-reverse') + .attr('orient', 'auto-start-reverse') /* auto-start-reverse: point towards src. node */ .append('svg:path') .attr('d', 'M0,-5L10,0L0,5'); + /* Link style */ const link = svg.selectAll('line') .data(links) .enter().append('line') .style('stroke', '#aaa') .attr('stroke-width', 3) .attr('marker-end', 'url(#arrow-end)') - .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); + .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); /* Only draw start arrow head if bidirectional */ + /* Node style */ const node = svg.selectAll('.node') .data(nodes) .enter().append('circle') - .attr('r', 16) + .attr('r', 16) /* Node radius */ .style('fill', '#69b3a2'); + /* Drag behavior */ node.call(d3.drag() .on('start', dragstart) .on('drag', dragged) .on('end', dragend)); + /* Node label style */ const label = svg.selectAll('.label') .data(nodes) .enter() @@ -104,6 +119,7 @@ export default function CreateVisualUnitMapModalComponent() { .style('font-family', 'Arial') .style('font-size', 14); + /* Update element positions when moved */ simulation.on('tick', () => { link .attr('x1', d => d.source.x) From 27d3200c580bda05b63bcad92d57c081d7111d9e Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 9 Aug 2024 18:23:24 +0000 Subject: [PATCH 07/34] Remove unused imports and code and finalize component --- .../visual-unit/VisualUnitDetailComponent.tsx | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 1b19f9cd5..5fc9c77ef 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -1,19 +1,10 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + import { FormattedMessage } from 'react-intl'; import TooltipHelpComponent from '../TooltipHelpComponent'; -// import TooltipMarkerComponent from '../TooltipMarkerComponent'; import * as React from 'react'; -// import { useDispatch, useSelector } from 'react-redux'; -// import { useEffect } from 'react'; -// import { State } from '../../types/redux/state'; -// import { fetchUnitsDetailsIfNeeded } from '../../actions/units'; -// import UnitViewComponent from './UnitViewComponent'; -// import CreateUnitModalComponent from './CreateUnitModalComponent'; -// import { UnitData } from 'types/redux/units'; -// import SpinnerComponent from '../../components/SpinnerComponent'; -// import { Dispatch } from 'types/redux/actions'; import CreateVisualUnitMapModalComponent from './CreateVisualUnitModalMapComponent'; /** @@ -21,17 +12,6 @@ import CreateVisualUnitMapModalComponent from './CreateVisualUnitModalMapCompone * @returns Units page element */ export default function VisualUnitDetailComponent() { - // The route stops you from getting to this page if not an admin. - - // const dispatch: Dispatch = useDispatch(); - - // useEffect(() => { - // // Makes async call to units API for units details if one has not already been made somewhere else, stores unit ids in state - // dispatch(fetchUnitsDetailsIfNeeded()); - // }, []); - - //Units state - // const unitsState = useSelector((state: State) => state.units.units); const titleStyle: React.CSSProperties = { textAlign: 'center' From e0322f3755e61e2841fc5394a924a80b5498047c Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 9 Aug 2024 19:07:14 +0000 Subject: [PATCH 08/34] Tweak nodes and link styles --- .../visual-unit/CreateVisualUnitModalMapComponent.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 09d150844..c9bac41ea 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -46,7 +46,7 @@ export default function CreateVisualUnitMapModalComponent() { const simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links) .id((d: any) => d.id) /* Set all link ids (from data.links) */ - .distance(60) /* This controls how long each link is */ + .distance(90) /* This controls how long each link is */ ) .force('charge', d3.forceManyBody() /* Create new many-body force */ .strength(-500) /* This controls the 'repelling' force on each node */ @@ -66,7 +66,7 @@ export default function CreateVisualUnitMapModalComponent() { svg.append('defs').append('marker') .attr('id', 'arrow-end') .attr('viewBox', '0 -5 10 10') - .attr('refX', 20) + .attr('refX', 25) .attr('refY', 0) .attr('markerWidth', 4) .attr('markerHeight', 4) @@ -78,7 +78,7 @@ export default function CreateVisualUnitMapModalComponent() { svg.append('defs').append('marker') .attr('id', 'arrow-start') .attr('viewBox', '0 -5 10 10') - .attr('refX', 20) + .attr('refX', 25) .attr('refY', 0) .attr('markerWidth', 4) .attr('markerHeight', 4) @@ -99,7 +99,7 @@ export default function CreateVisualUnitMapModalComponent() { const node = svg.selectAll('.node') .data(nodes) .enter().append('circle') - .attr('r', 16) /* Node radius */ + .attr('r', 20) /* Node radius */ .style('fill', '#69b3a2'); /* Drag behavior */ @@ -133,7 +133,7 @@ export default function CreateVisualUnitMapModalComponent() { label .attr('x', function(d){ return d.x; }) - .attr('y', function (d) {return d.y + 15; }); + .attr('y', function (d) {return d.y - 25; }); }); // eslint-disable-next-line jsdoc/require-jsdoc From 87fd6a5969e7732b391f8614d36f89656ad5f5a9 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 9 Aug 2024 20:00:11 +0000 Subject: [PATCH 09/34] Add license header --- .../visual-unit/CreateVisualUnitModalMapComponent.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index c9bac41ea..c4c7c2419 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -1,3 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; From c7a75c9ce8d2d0da9b8615eb019a4da7f977e0c9 Mon Sep 17 00:00:00 2001 From: Nicky Lin Date: Fri, 9 Aug 2024 20:02:26 +0000 Subject: [PATCH 10/34] Import VisualUnitDetailComponent --- src/client/app/components/RouteComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index d5cc9b07b..838f71c34 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -25,6 +25,7 @@ import RoleOutlet from './router/RoleOutlet'; import UnitsDetailComponent from './unit/UnitsDetailComponent'; import ErrorComponent from './router/ErrorComponent'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; +import VisualUnitDetailComponent from './visual-unit/VisualUnitDetailComponent'; /** * @returns the router component Responsible for client side routing. From 8d12b1ffe9323996adf330a6c4bd2dd94abc45f8 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Fri, 11 Oct 2024 17:08:35 -0700 Subject: [PATCH 11/34] added visual component for cik data under previous graph. --- .../CreateCikVisualModalMapComponent.tsx | 181 ++++++++++++++++++ .../visual-unit/VisualUnitDetailComponent.tsx | 14 ++ src/client/app/translations/data.ts | 1 + 3 files changed, 196 insertions(+) create mode 100644 src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx new file mode 100644 index 000000000..f185b0885 --- /dev/null +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -0,0 +1,181 @@ +import * as React from 'react'; +import * as d3 from 'd3'; +import { useEffect } from 'react'; +import { selectCik } from '../../redux/api/conversionsApi'; +import { useAppSelector } from '../../redux/reduxHooks'; +import { selectUnitDataById } from '../../redux/api/unitsApi'; + +/** + * Visual cik graph component + * @returns D3 force graph visual + */ +export default function CreateCikVisualMapComponent() { + /* Get unit and Cik data from redux */ + const cikData = useAppSelector(selectCik); + const units = new Set(); + const unitDataById = useAppSelector(selectUnitDataById); + + /* add all units being used in cik */ + cikData.forEach(unit => ( + units.add(unit.meterUnitId), + units.add(unit.nonMeterUnitId) + )); + + /* Create data container to pass to D3 force graph */ + const data: { nodes: any[], links: any[] } = { + nodes: [], + links: [] + }; + units.forEach(function (value) { + const unit = unitDataById[value]; + data.nodes.push({'name': unit.name, + 'id': unit.id + }); + }); + cikData.map(function (value) { + data.links.push({ + 'source': value.meterUnitId, + 'target': value.nonMeterUnitId, + 'bidirectional': false + }); + }); + + /* Visuals start here */ + useEffect(() => { + /* View-box dimensions */ + const width = window.innerWidth; + const height = 750; + + /* Grab data */ + const nodes = data.nodes.map(d => ({...d})); + const links = data.links.map(d => ({...d})); + + const simulation = d3.forceSimulation(nodes) + .force('link', d3.forceLink(links) + /* Set all link ids (from data.links) */ + .id((d: any) => d.id) + /* This controls how long each link is */ + .distance(90) + ) + /* Create new many-body force */ + .force('charge', d3.forceManyBody() + /* This controls the 'repelling' force on each node */ + .strength(-500) + ) + .force('x', d3.forceX()) + .force('y', d3.forceY()); + + const svg = d3.select('#sample-cik') + .append('svg') + .attr('width', width) + .attr('height', height) + .attr('viewBox', [-width / 2, -height / 2, width, height]) + .attr('style', 'max-width: 100%; height: auto;') + .append('g'); + + /* End arrow head */ + svg.append('defs').append('marker') + .attr('id', 'arrow-end') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 25) + .attr('refY', 0) + .attr('markerWidth', 4) + .attr('markerHeight', 4) + /* auto: point towards dest. node */ + .attr('orient', 'auto') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); + + /* Start arrow head (for bidirectional edges) */ + svg.append('defs').append('marker') + .attr('id', 'arrow-start') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 25) + .attr('refY', 0) + .attr('markerWidth', 4) + .attr('markerHeight', 4) + /* auto-start-reverse: point towards src. node */ + .attr('orient', 'auto-start-reverse') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); + + /* Link style */ + const link = svg.selectAll('line') + .data(links) + .enter().append('line') + .style('stroke', '#aaa') + .attr('stroke-width', 3) + .attr('marker-end', 'url(#arrow-end)') + /* Only draw start arrow head if bidirectional */ + .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); + + /* Node style */ + const node = svg.selectAll('.node') + .data(nodes) + .enter().append('circle') + /* Node radius */ + .attr('r', 20) + .style('fill', '#69b3a2'); + + /* Drag behavior */ + node.call(d3.drag() + .on('start', dragstart) + .on('drag', dragged) + .on('end', dragend)); + + /* Node label style */ + const label = svg.selectAll('.label') + .data(nodes) + .enter() + .append('text') + .text(function (d) { return d.name; }) + .style('text-anchor', 'middle') + .style('fill', '#000') + .style('font-family', 'Arial') + .style('font-size', 14); + + /* Update element positions when moved */ + simulation.on('tick', () => { + link + .attr('x1', d => d.source.x) + .attr('y1', d => d.source.y) + .attr('x2', d => d.target.x) + .attr('y2', d => d.target.y); + + node + .attr('cx', d => d.x) + .attr('cy', d => d.y); + + label + .attr('x', function(d){ return d.x; }) + .attr('y', function (d) {return d.y - 25; }); + }); + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragstart(event: any) { + if (!event.active) simulation.alphaTarget(0.3).restart(); + event.subject.fx = event.subject.x; + event.subject.fy = event.subject.y; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragged(event: any) { + event.subject.fx = event.x; + event.subject.fy = event.y; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragend(event: any) { + if (!event.active) simulation.alphaTarget(0); + event.subject.fx = null; + event.subject.fy = null; + } + + }, []); // Empty dependency array to run the effect only once + + return ( +
+
+
+ ); +} \ No newline at end of file diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 5fc9c77ef..e81b275b3 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -6,6 +6,7 @@ import { FormattedMessage } from 'react-intl'; import TooltipHelpComponent from '../TooltipHelpComponent'; import * as React from 'react'; import CreateVisualUnitMapModalComponent from './CreateVisualUnitModalMapComponent'; +import CreateCikVisualMapComponent from './CreateCikVisualModalMapComponent'; /** * Defines the units page card view @@ -40,6 +41,19 @@ export default function VisualUnitDetailComponent() {
+ +
+

+ + {/*
+ +
*/} +

+
+ +
+ +
); } \ No newline at end of file diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 1476f89ba..cf2a64552 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -509,6 +509,7 @@ const LocaleTranslationData = { "view.groups": "View Groups", "visit": " or visit our ", "visual-unit": "Visual Units Graph", + "cik-visual-unit-temp": "Cik Visual Units Graph(TEMP)", "website": "website", "week": "Week", "yes": "yes", From 3e08250ecc005e874e4c34f037a6a8c34e93de63 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 16 Oct 2024 17:07:44 -0700 Subject: [PATCH 12/34] removed unnecessary bidirectional arrow code, as well reformatted new graph to be more spread out. --- .../CreateCikVisualModalMapComponent.tsx | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index f185b0885..79752aced 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -44,7 +44,7 @@ export default function CreateCikVisualMapComponent() { useEffect(() => { /* View-box dimensions */ const width = window.innerWidth; - const height = 750; + const height = 1000; /* Grab data */ const nodes = data.nodes.map(d => ({...d})); @@ -55,12 +55,12 @@ export default function CreateCikVisualMapComponent() { /* Set all link ids (from data.links) */ .id((d: any) => d.id) /* This controls how long each link is */ - .distance(90) + .distance(120) ) /* Create new many-body force */ .force('charge', d3.forceManyBody() /* This controls the 'repelling' force on each node */ - .strength(-500) + .strength(-800) ) .force('x', d3.forceX()) .force('y', d3.forceY()); @@ -86,19 +86,6 @@ export default function CreateCikVisualMapComponent() { .append('svg:path') .attr('d', 'M0,-5L10,0L0,5'); - /* Start arrow head (for bidirectional edges) */ - svg.append('defs').append('marker') - .attr('id', 'arrow-start') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 25) - .attr('refY', 0) - .attr('markerWidth', 4) - .attr('markerHeight', 4) - /* auto-start-reverse: point towards src. node */ - .attr('orient', 'auto-start-reverse') - .append('svg:path') - .attr('d', 'M0,-5L10,0L0,5'); - /* Link style */ const link = svg.selectAll('line') .data(links) From 16483b36aec1709a7fb2c7d661d8ae98834e6d63 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Tue, 22 Oct 2024 16:48:15 -0700 Subject: [PATCH 13/34] both graph's nodes are colored based on what unit type they are. --- .../CreateCikVisualModalMapComponent.tsx | 16 +++++++++++----- .../CreateVisualUnitModalMapComponent.tsx | 8 ++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index 79752aced..a0638f415 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -29,7 +29,8 @@ export default function CreateCikVisualMapComponent() { units.forEach(function (value) { const unit = unitDataById[value]; data.nodes.push({'name': unit.name, - 'id': unit.id + 'id': unit.id, + 'typeOfUnit': unit.typeOfUnit }); }); cikData.map(function (value) { @@ -50,18 +51,25 @@ export default function CreateCikVisualMapComponent() { const nodes = data.nodes.map(d => ({...d})); const links = data.links.map(d => ({...d})); + /* color for nodes (Up to 10 different colors) */ + const color = d3.scaleOrdinal(d3.schemeCategory10); + const simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links) /* Set all link ids (from data.links) */ .id((d: any) => d.id) /* This controls how long each link is */ .distance(120) + /* This controls the link strength between nodes */ + // .strength(0.2) ) /* Create new many-body force */ .force('charge', d3.forceManyBody() /* This controls the 'repelling' force on each node */ .strength(-800) ) + /* Create colliding force for nodes */ + // .force('collide', d3.forceCollide().radius(70)) .force('x', d3.forceX()) .force('y', d3.forceY()); @@ -92,9 +100,7 @@ export default function CreateCikVisualMapComponent() { .enter().append('line') .style('stroke', '#aaa') .attr('stroke-width', 3) - .attr('marker-end', 'url(#arrow-end)') - /* Only draw start arrow head if bidirectional */ - .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); + .attr('marker-end', 'url(#arrow-end)'); /* Node style */ const node = svg.selectAll('.node') @@ -102,7 +108,7 @@ export default function CreateCikVisualMapComponent() { .enter().append('circle') /* Node radius */ .attr('r', 20) - .style('fill', '#69b3a2'); + .style('fill', d => color(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index c4c7c2419..05b1fdb28 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -19,6 +19,9 @@ export default function CreateVisualUnitMapModalComponent() { const unitData = useAppSelector(selectAllUnits); const conversionData = useAppSelector(selectConversionsDetails); + /* color for nodes (Up to 10 different colors) */ + const color = d3.scaleOrdinal(d3.schemeCategory10); + /* Create data container to pass to D3 force graph */ const data: { nodes: any[], links: any[] } = { nodes: [], @@ -26,7 +29,8 @@ export default function CreateVisualUnitMapModalComponent() { }; unitData.map(function (value) { data.nodes.push({'name': value.name, - 'id': value.id + 'id': value.id, + 'typeOfUnit': value.typeOfUnit }); }); conversionData.map(function (value) { @@ -104,7 +108,7 @@ export default function CreateVisualUnitMapModalComponent() { .data(nodes) .enter().append('circle') .attr('r', 20) /* Node radius */ - .style('fill', '#69b3a2'); + .style('fill', d => color(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() From 98a4232052c74783496950266b125f1fccfd5300 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Tue, 22 Oct 2024 17:07:27 -0700 Subject: [PATCH 14/34] color shema for nodes match for both graphs. --- .../visual-unit/CreateCikVisualModalMapComponent.tsx | 9 ++++++--- .../visual-unit/CreateVisualUnitModalMapComponent.tsx | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index a0638f415..bb496f373 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -51,8 +51,11 @@ export default function CreateCikVisualMapComponent() { const nodes = data.nodes.map(d => ({...d})); const links = data.links.map(d => ({...d})); - /* color for nodes (Up to 10 different colors) */ - const color = d3.scaleOrdinal(d3.schemeCategory10); + /* creating color schema for nodes based on their unit type */ + const colors = ['#1F77B4', '#2CA02C', '#fd7e14']; + const colorSchema = d3.scaleOrdinal() + .domain(['meter', 'unit', 'suffix']) + .range(colors); const simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links) @@ -108,7 +111,7 @@ export default function CreateCikVisualMapComponent() { .enter().append('circle') /* Node radius */ .attr('r', 20) - .style('fill', d => color(d.typeOfUnit)); + .attr('fill', d => colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 05b1fdb28..03de3642a 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -19,8 +19,11 @@ export default function CreateVisualUnitMapModalComponent() { const unitData = useAppSelector(selectAllUnits); const conversionData = useAppSelector(selectConversionsDetails); - /* color for nodes (Up to 10 different colors) */ - const color = d3.scaleOrdinal(d3.schemeCategory10); + /* creating color schema for nodes based on their unit type */ + const colors = ['#1F77B4', '#2CA02C', '#fd7e14']; + const colorSchema = d3.scaleOrdinal() + .domain(['meter', 'unit', 'suffix']) + .range(colors); /* Create data container to pass to D3 force graph */ const data: { nodes: any[], links: any[] } = { @@ -108,7 +111,7 @@ export default function CreateVisualUnitMapModalComponent() { .data(nodes) .enter().append('circle') .attr('r', 20) /* Node radius */ - .style('fill', d => color(d.typeOfUnit)); + .attr('fill', d => colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() From b78fb05149258b4e546e1a7cc8646eb30ed637b6 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 14:44:43 -0700 Subject: [PATCH 15/34] added color legend to graphs and changed translated titles. --- .../CreateCikVisualModalMapComponent.tsx | 24 +++++++++++++++++++ .../CreateVisualUnitModalMapComponent.tsx | 24 +++++++++++++++++++ src/client/app/translations/data.ts | 4 ++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index bb496f373..d4c91fc1d 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -167,6 +167,30 @@ export default function CreateCikVisualMapComponent() { event.subject.fy = null; } + /* Color Legend */ + const legend = svg.append('g') + .attr('transform', `translate(${-width / 2 + 20}, ${-height / 2 + 20})`); + + colorSchema.domain().forEach((item, i) => { + const legendEntry = legend.append('g') + .attr('transform', `translate(0, ${i * 25})`); + + // Rectangle color box + legendEntry.append('rect') + .attr('width', 20) + .attr('height', 20) + .attr('fill', colorSchema(item)); + + // Text label + legendEntry.append('text') + .attr('x', 30) + .attr('y', 15) + .style('fill', '#000') + .style('font-size', '14px') + .style('alignment-middle', 'middle') + .text(item); + }); + }, []); // Empty dependency array to run the effect only once return ( diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 03de3642a..163be0090 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -167,6 +167,30 @@ export default function CreateVisualUnitMapModalComponent() { event.subject.fy = null; } + /* Color Legend */ + const legend = svg.append('g') + .attr('transform', `translate(${-width / 2 + 20}, ${-height / 2 + 20})`); + + colorSchema.domain().forEach((item, i) => { + const legendEntry = legend.append('g') + .attr('transform', `translate(0, ${i * 25})`); + + // Rectangle color box + legendEntry.append('rect') + .attr('width', 20) + .attr('height', 20) + .attr('fill', colorSchema(item)); + + // Text label + legendEntry.append('text') + .attr('x', 30) + .attr('y', 15) + .style('fill', '#000') + .style('font-size', '14px') + .style('alignment-middle', 'middle') + .text(item); + }); + }, []); // Empty dependency array to run the effect only once return ( diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index cf2a64552..c1d0171ab 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -1018,7 +1018,7 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Visionner les groupes", "visit": " ou visitez notre ", - "visual-unit": "Visual Units Graph.\u{26A1}", + "visual-unit": "Visual Units Graph\u{26A1}", "website": "site web", "week": "Semaine", "yes": " yes\u{26A1}", @@ -1528,7 +1528,7 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Ver grupos", "visit": " o visite nuestro ", - "visual-unit": "Visual Units Graph.\u{26A1}", + "visual-unit": "Visual Units Graph\u{26A1}", "website": "sitio web", "week": "semana", "yes": "sí", From 83057894b268b33ee1abbcd69d324d6595b6fa1e Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 15:33:33 -0700 Subject: [PATCH 16/34] internationalized both graph titles. --- .../visual-unit/VisualUnitDetailComponent.tsx | 2 +- src/client/app/translations/data.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index e81b275b3..9888658ba 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -44,7 +44,7 @@ export default function VisualUnitDetailComponent() {

- + {/*
*/} diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index c1d0171ab..6ff7ddd18 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -508,8 +508,8 @@ const LocaleTranslationData = { "uses": "uses", "view.groups": "View Groups", "visit": " or visit our ", - "visual-unit": "Visual Units Graph", - "cik-visual-unit-temp": "Cik Visual Units Graph(TEMP)", + "visual-unit": "Visual Input Units Graphic", + "visual-unit-cik": "Visual Analyzed Units Graphic", "website": "website", "week": "Week", "yes": "yes", @@ -1018,7 +1018,8 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Visionner les groupes", "visit": " ou visitez notre ", - "visual-unit": "Visual Units Graph\u{26A1}", + "visual-unit": "Visual Input Units Graphic\u{26A1}", + "visual-unit-cik": "Visual Analyzed Units Graphic\u{26A1}", "website": "site web", "week": "Semaine", "yes": " yes\u{26A1}", @@ -1528,7 +1529,8 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Ver grupos", "visit": " o visite nuestro ", - "visual-unit": "Visual Units Graph\u{26A1}", + "visual-unit": "Visual Input Units Graphic\u{26A1}", + "visual-unit-cik": "Visual Analyzed Units Graphic\u{26A1}", "website": "sitio web", "week": "semana", "yes": "sí", From 25fbff2867edb9c4ebafe81efc7de9ff10317a8e Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 15:47:11 -0700 Subject: [PATCH 17/34] shows all units in cik graphic. --- .../CreateCikVisualModalMapComponent.tsx | 39 ++++++++++++------- .../CreateVisualUnitModalMapComponent.tsx | 15 +++---- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index d4c91fc1d..f50c0b202 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -3,7 +3,7 @@ import * as d3 from 'd3'; import { useEffect } from 'react'; import { selectCik } from '../../redux/api/conversionsApi'; import { useAppSelector } from '../../redux/reduxHooks'; -import { selectUnitDataById } from '../../redux/api/unitsApi'; +import { selectAllUnits } from '../../redux/api/unitsApi'; /** * Visual cik graph component @@ -11,28 +11,39 @@ import { selectUnitDataById } from '../../redux/api/unitsApi'; */ export default function CreateCikVisualMapComponent() { /* Get unit and Cik data from redux */ + const unitData = useAppSelector(selectAllUnits); const cikData = useAppSelector(selectCik); - const units = new Set(); - const unitDataById = useAppSelector(selectUnitDataById); + // const units = new Set(); + // const unitDataById = useAppSelector(selectUnitDataById); /* add all units being used in cik */ - cikData.forEach(unit => ( - units.add(unit.meterUnitId), - units.add(unit.nonMeterUnitId) - )); + // cikData.forEach(unit => ( + // units.add(unit.meterUnitId), + // units.add(unit.nonMeterUnitId) + // )); /* Create data container to pass to D3 force graph */ const data: { nodes: any[], links: any[] } = { nodes: [], links: [] }; - units.forEach(function (value) { - const unit = unitDataById[value]; - data.nodes.push({'name': unit.name, - 'id': unit.id, - 'typeOfUnit': unit.typeOfUnit - }); - }); + + // deletes nodes with no connections + // units.forEach(value => { + // const unit = unitDataById[value]; + // data.nodes.push({'name': unit.name, + // 'id': unit.id, + // 'typeOfUnit': unit.typeOfUnit + // }); + // }); + + unitData.map(value => + data.nodes.push({'name': value.name, + 'id': value.id, + 'typeOfUnit': value.typeOfUnit + }) + ); + cikData.map(function (value) { data.links.push({ 'source': value.meterUnitId, diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 163be0090..2a0c9e172 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -30,19 +30,20 @@ export default function CreateVisualUnitMapModalComponent() { nodes: [], links: [] }; - unitData.map(function (value) { + unitData.map(value => data.nodes.push({'name': value.name, 'id': value.id, 'typeOfUnit': value.typeOfUnit - }); - }); - conversionData.map(function (value) { + }) + ); + conversionData.map(value => data.links.push({ 'source': value.sourceId, 'target': value.destinationId, - 'bidirectional': value.bidirectional /* boolean value */ - }); - }); + /* boolean value */ + 'bidirectional': value.bidirectional + }) + ); /* Visuals start here */ useEffect(() => { From 91918634362267fb77cb5c0d010357e62cba9b6c Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 15:52:24 -0700 Subject: [PATCH 18/34] updated comment position. --- .../CreateCikVisualModalMapComponent.tsx | 9 ++++-- .../CreateVisualUnitModalMapComponent.tsx | 30 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index f50c0b202..96c2bf6f0 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -31,14 +31,16 @@ export default function CreateCikVisualMapComponent() { // deletes nodes with no connections // units.forEach(value => { // const unit = unitDataById[value]; - // data.nodes.push({'name': unit.name, + // data.nodes.push({ + // 'name': unit.name, // 'id': unit.id, // 'typeOfUnit': unit.typeOfUnit // }); // }); unitData.map(value => - data.nodes.push({'name': value.name, + data.nodes.push({ + 'name': value.name, 'id': value.id, 'typeOfUnit': value.typeOfUnit }) @@ -202,7 +204,8 @@ export default function CreateCikVisualMapComponent() { .text(item); }); - }, []); // Empty dependency array to run the effect only once + // Empty dependency array to run the effect only once + }, []); return (
diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 2a0c9e172..0c92568c7 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -31,7 +31,8 @@ export default function CreateVisualUnitMapModalComponent() { links: [] }; unitData.map(value => - data.nodes.push({'name': value.name, + data.nodes.push({ + 'name': value.name, 'id': value.id, 'typeOfUnit': value.typeOfUnit }) @@ -57,11 +58,15 @@ export default function CreateVisualUnitMapModalComponent() { const simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links) - .id((d: any) => d.id) /* Set all link ids (from data.links) */ - .distance(90) /* This controls how long each link is */ + /* Set all link ids (from data.links) */ + .id((d: any) => d.id) + /* This controls how long each link is */ + .distance(90) ) - .force('charge', d3.forceManyBody() /* Create new many-body force */ - .strength(-500) /* This controls the 'repelling' force on each node */ + /* Create new many-body force */ + .force('charge', d3.forceManyBody() + /* This controls the 'repelling' force on each node */ + .strength(-500) ) .force('x', d3.forceX()) .force('y', d3.forceY()); @@ -82,7 +87,8 @@ export default function CreateVisualUnitMapModalComponent() { .attr('refY', 0) .attr('markerWidth', 4) .attr('markerHeight', 4) - .attr('orient', 'auto') /* auto: point towards dest. node */ + /* auto: point towards dest. node */ + .attr('orient', 'auto') .append('svg:path') .attr('d', 'M0,-5L10,0L0,5'); @@ -94,7 +100,8 @@ export default function CreateVisualUnitMapModalComponent() { .attr('refY', 0) .attr('markerWidth', 4) .attr('markerHeight', 4) - .attr('orient', 'auto-start-reverse') /* auto-start-reverse: point towards src. node */ + /* auto-start-reverse: point towards src. node */ + .attr('orient', 'auto-start-reverse') .append('svg:path') .attr('d', 'M0,-5L10,0L0,5'); @@ -105,13 +112,15 @@ export default function CreateVisualUnitMapModalComponent() { .style('stroke', '#aaa') .attr('stroke-width', 3) .attr('marker-end', 'url(#arrow-end)') - .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); /* Only draw start arrow head if bidirectional */ + /* Only draw start arrow head if bidirectional */ + .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); /* Node style */ const node = svg.selectAll('.node') .data(nodes) .enter().append('circle') - .attr('r', 20) /* Node radius */ + /* Node radius */ + .attr('r', 20) .attr('fill', d => colorSchema(d.typeOfUnit)); /* Drag behavior */ @@ -192,7 +201,8 @@ export default function CreateVisualUnitMapModalComponent() { .text(item); }); - }, []); // Empty dependency array to run the effect only once + // Empty dependency array to run the effect only once + }, []); return (
From 9a53aa06c172305bc8af20af8d1dda17e66ab605 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 16:43:19 -0700 Subject: [PATCH 19/34] added new color for units with an inpu suffix. --- .../CreateCikVisualModalMapComponent.tsx | 21 +++++++++++-------- .../CreateVisualUnitModalMapComponent.tsx | 13 +++++++----- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index 96c2bf6f0..b11e25935 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -16,6 +16,12 @@ export default function CreateCikVisualMapComponent() { // const units = new Set(); // const unitDataById = useAppSelector(selectUnitDataById); + /* creating color schema for nodes based on their unit type */ + const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; + const colorSchema = d3.scaleOrdinal() + .domain(['meter', 'unit', 'suffix', 'suffix input']) + .range(colors); + /* add all units being used in cik */ // cikData.forEach(unit => ( // units.add(unit.meterUnitId), @@ -42,7 +48,8 @@ export default function CreateCikVisualMapComponent() { data.nodes.push({ 'name': value.name, 'id': value.id, - 'typeOfUnit': value.typeOfUnit + 'typeOfUnit': value.typeOfUnit, + 'suffix' : value.suffix }) ); @@ -64,12 +71,6 @@ export default function CreateCikVisualMapComponent() { const nodes = data.nodes.map(d => ({...d})); const links = data.links.map(d => ({...d})); - /* creating color schema for nodes based on their unit type */ - const colors = ['#1F77B4', '#2CA02C', '#fd7e14']; - const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix']) - .range(colors); - const simulation = d3.forceSimulation(nodes) .force('link', d3.forceLink(links) /* Set all link ids (from data.links) */ @@ -124,7 +125,8 @@ export default function CreateCikVisualMapComponent() { .enter().append('circle') /* Node radius */ .attr('r', 20) - .attr('fill', d => colorSchema(d.typeOfUnit)); + /* checks if unit has a non empty suffix to color differently */ + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix input') : colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() @@ -201,7 +203,8 @@ export default function CreateCikVisualMapComponent() { .style('fill', '#000') .style('font-size', '14px') .style('alignment-middle', 'middle') - .text(item); + /* change suffix to suffix analyzed in the legend */ + .text(item === 'suffix' ? 'suffix analyzed' : item); }); // Empty dependency array to run the effect only once diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 0c92568c7..a1c548777 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -20,9 +20,9 @@ export default function CreateVisualUnitMapModalComponent() { const conversionData = useAppSelector(selectConversionsDetails); /* creating color schema for nodes based on their unit type */ - const colors = ['#1F77B4', '#2CA02C', '#fd7e14']; + const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix']) + .domain(['meter', 'unit', 'suffix', 'suffix input']) .range(colors); /* Create data container to pass to D3 force graph */ @@ -34,7 +34,8 @@ export default function CreateVisualUnitMapModalComponent() { data.nodes.push({ 'name': value.name, 'id': value.id, - 'typeOfUnit': value.typeOfUnit + 'typeOfUnit': value.typeOfUnit, + 'suffix': value.suffix }) ); conversionData.map(value => @@ -121,7 +122,8 @@ export default function CreateVisualUnitMapModalComponent() { .enter().append('circle') /* Node radius */ .attr('r', 20) - .attr('fill', d => colorSchema(d.typeOfUnit)); + /* checks if unit has a non empty suffix to color differently */ + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix input') : colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() @@ -198,7 +200,8 @@ export default function CreateVisualUnitMapModalComponent() { .style('fill', '#000') .style('font-size', '14px') .style('alignment-middle', 'middle') - .text(item); + /* change suffix to suffix analyzed in the legend */ + .text(item === 'suffix' ? 'suffix analyzed' : item); }); // Empty dependency array to run the effect only once From baa8f40fe820af451efe40dcc83b23177becca03 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 17:57:46 -0700 Subject: [PATCH 20/34] internationalized color legend text of graphs. --- .../CreateCikVisualModalMapComponent.tsx | 11 +++++--- .../CreateVisualUnitModalMapComponent.tsx | 10 ++++--- .../visual-unit/VisualUnitDetailComponent.tsx | 4 +-- src/client/app/translations/data.ts | 27 ++++++++++++++----- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index b11e25935..b4fc103fb 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; +import { useIntl } from 'react-intl'; import { selectCik } from '../../redux/api/conversionsApi'; import { useAppSelector } from '../../redux/reduxHooks'; import { selectAllUnits } from '../../redux/api/unitsApi'; @@ -10,6 +11,8 @@ import { selectAllUnits } from '../../redux/api/unitsApi'; * @returns D3 force graph visual */ export default function CreateCikVisualMapComponent() { + const intl = useIntl(); + /* Get unit and Cik data from redux */ const unitData = useAppSelector(selectAllUnits); const cikData = useAppSelector(selectCik); @@ -19,7 +22,7 @@ export default function CreateCikVisualMapComponent() { /* creating color schema for nodes based on their unit type */ const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix', 'suffix input']) + .domain(['meter', 'unit', 'suffix', 'suffix-input']) .range(colors); /* add all units being used in cik */ @@ -126,7 +129,7 @@ export default function CreateCikVisualMapComponent() { /* Node radius */ .attr('r', 20) /* checks if unit has a non empty suffix to color differently */ - .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix input') : colorSchema(d.typeOfUnit)); + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix-input') : colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() @@ -203,8 +206,8 @@ export default function CreateCikVisualMapComponent() { .style('fill', '#000') .style('font-size', '14px') .style('alignment-middle', 'middle') - /* change suffix to suffix analyzed in the legend */ - .text(item === 'suffix' ? 'suffix analyzed' : item); + /* internationalizing color legend text */ + .text(intl.formatMessage({id : `legend-graph-text-${item}`})); }); // Empty dependency array to run the effect only once diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index a1c548777..6abaf4a56 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; +import { useIntl } from 'react-intl'; import { useAppSelector } from '../../redux/reduxHooks'; import { selectAllUnits } from '../../redux/api/unitsApi'; import { selectConversionsDetails } from '../../redux/api/conversionsApi'; @@ -14,6 +15,7 @@ import { selectConversionsDetails } from '../../redux/api/conversionsApi'; * @returns D3 force graph visual */ export default function CreateVisualUnitMapModalComponent() { + const intl = useIntl(); /* Get unit and conversion data from redux */ const unitData = useAppSelector(selectAllUnits); @@ -22,7 +24,7 @@ export default function CreateVisualUnitMapModalComponent() { /* creating color schema for nodes based on their unit type */ const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix', 'suffix input']) + .domain(['meter', 'unit', 'suffix', 'suffix-input']) .range(colors); /* Create data container to pass to D3 force graph */ @@ -123,7 +125,7 @@ export default function CreateVisualUnitMapModalComponent() { /* Node radius */ .attr('r', 20) /* checks if unit has a non empty suffix to color differently */ - .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix input') : colorSchema(d.typeOfUnit)); + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix-input') : colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() @@ -200,8 +202,8 @@ export default function CreateVisualUnitMapModalComponent() { .style('fill', '#000') .style('font-size', '14px') .style('alignment-middle', 'middle') - /* change suffix to suffix analyzed in the legend */ - .text(item === 'suffix' ? 'suffix analyzed' : item); + /* internationalizing color legend text */ + .text(intl.formatMessage({id : `legend-graph-text-${item}`})); }); // Empty dependency array to run the effect only once diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 9888658ba..d16059c46 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -31,7 +31,7 @@ export default function VisualUnitDetailComponent() {

- + {/*
*/} @@ -44,7 +44,7 @@ export default function VisualUnitDetailComponent() {

- + {/*
*/} diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 6ff7ddd18..1ca994676 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -285,6 +285,10 @@ const LocaleTranslationData = { "last.four.weeks": "Last four weeks", "last.week": "Last week", "leave": "Leave", + "legend-graph-text-meter": "Meter", + "legend-graph-text-suffix": "Suffix Analyzed", + "legend-graph-text-suffix-input": "Suffix Input", + "legend-graph-text-unit": "Unit", "less.energy": "less energy", "line": "Line", "log.in": "Log in", @@ -508,8 +512,9 @@ const LocaleTranslationData = { "uses": "uses", "view.groups": "View Groups", "visit": " or visit our ", - "visual-unit": "Visual Input Units Graphic", - "visual-unit-cik": "Visual Analyzed Units Graphic", + "visual-unit": "Units Visual Graphics", + "visual-input-units-graphic": "Input Units Visual Graphic", + "visual-analyzed-units-graphic": "Analyzed Units Visual Graphic", "website": "website", "week": "Week", "yes": "yes", @@ -795,6 +800,10 @@ const LocaleTranslationData = { "last.four.weeks": "Quatre dernières semaines", "last.week": "La semaine dernière", "leave": "Leave\u{26A1}", + "legend-graph-text-meter": "Meter\u{26A1}", + "legend-graph-text-suffix": "Suffix Analyzed\u{26A1}", + "legend-graph-text-suffix-input": "Suffix Input\u{26A1}", + "legend-graph-text-unit": "Unit\u{26A1}", "less.energy": "moins d'énergie", "line": "Ligne", "log.in": "Se Connecter", @@ -1018,8 +1027,9 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Visionner les groupes", "visit": " ou visitez notre ", - "visual-unit": "Visual Input Units Graphic\u{26A1}", - "visual-unit-cik": "Visual Analyzed Units Graphic\u{26A1}", + "visual-unit": "Units Visual Graphics\u{26A1}", + "visual-input-units-graphic": "Graphique Visuel des Unités D'Entrée", + "visual-analyzed-units-graphic": "Graphique Visuel des Unités Analysées", "website": "site web", "week": "Semaine", "yes": " yes\u{26A1}", @@ -1306,6 +1316,10 @@ const LocaleTranslationData = { "last.four.weeks": "Últimas cuatro semanas", "last.week": "La semana pasada", "leave": "Salir", + "legend-graph-text-meter": "Medidor", + "legend-graph-text-suffix": "Sufijo Analizado", + "legend-graph-text-suffix-input": "Suffix Entrado", + "legend-graph-text-unit": "Unidad", "less.energy": "menos energía", "line": "Línea", "log.in": "Iniciar sesión", @@ -1529,8 +1543,9 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Ver grupos", "visit": " o visite nuestro ", - "visual-unit": "Visual Input Units Graphic\u{26A1}", - "visual-unit-cik": "Visual Analyzed Units Graphic\u{26A1}", + "visual-unit": "Units Visual Graphics\u{26A1}", + "visual-input-units-graphic": "Gráfico Visual de Unidades de Entrada", + "visual-analyzed-units-graphic": "Gráfico Visual de Unidades Analizadas", "website": "sitio web", "week": "semana", "yes": "sí", From e7e4d71bd7f8b893c97e7da4e8150e066ca3a18a Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 30 Oct 2024 18:59:05 -0700 Subject: [PATCH 21/34] removed unnecessary code and fixed spanish translation for color legend. --- .../CreateCikVisualModalMapComponent.tsx | 22 ------------------- src/client/app/translations/data.ts | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index b4fc103fb..c70eba43f 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -16,8 +16,6 @@ export default function CreateCikVisualMapComponent() { /* Get unit and Cik data from redux */ const unitData = useAppSelector(selectAllUnits); const cikData = useAppSelector(selectCik); - // const units = new Set(); - // const unitDataById = useAppSelector(selectUnitDataById); /* creating color schema for nodes based on their unit type */ const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; @@ -25,28 +23,12 @@ export default function CreateCikVisualMapComponent() { .domain(['meter', 'unit', 'suffix', 'suffix-input']) .range(colors); - /* add all units being used in cik */ - // cikData.forEach(unit => ( - // units.add(unit.meterUnitId), - // units.add(unit.nonMeterUnitId) - // )); - /* Create data container to pass to D3 force graph */ const data: { nodes: any[], links: any[] } = { nodes: [], links: [] }; - // deletes nodes with no connections - // units.forEach(value => { - // const unit = unitDataById[value]; - // data.nodes.push({ - // 'name': unit.name, - // 'id': unit.id, - // 'typeOfUnit': unit.typeOfUnit - // }); - // }); - unitData.map(value => data.nodes.push({ 'name': value.name, @@ -80,16 +62,12 @@ export default function CreateCikVisualMapComponent() { .id((d: any) => d.id) /* This controls how long each link is */ .distance(120) - /* This controls the link strength between nodes */ - // .strength(0.2) ) /* Create new many-body force */ .force('charge', d3.forceManyBody() /* This controls the 'repelling' force on each node */ .strength(-800) ) - /* Create colliding force for nodes */ - // .force('collide', d3.forceCollide().radius(70)) .force('x', d3.forceX()) .force('y', d3.forceY()); diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 1ca994676..71672c8f0 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -1318,7 +1318,7 @@ const LocaleTranslationData = { "leave": "Salir", "legend-graph-text-meter": "Medidor", "legend-graph-text-suffix": "Sufijo Analizado", - "legend-graph-text-suffix-input": "Suffix Entrado", + "legend-graph-text-suffix-input": "Sufijo Entrado", "legend-graph-text-unit": "Unidad", "less.energy": "menos energía", "line": "Línea", From 18f37c0ec192c04af390e0b82df5757fd3bbbe59 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Mon, 4 Nov 2024 14:20:11 -0800 Subject: [PATCH 22/34] added title to page. --- .../visual-unit/VisualUnitDetailComponent.tsx | 32 ++++++++++++------- src/client/app/translations/data.ts | 1 + 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index d16059c46..ce07d2409 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -7,6 +7,7 @@ import TooltipHelpComponent from '../TooltipHelpComponent'; import * as React from 'react'; import CreateVisualUnitMapModalComponent from './CreateVisualUnitModalMapComponent'; import CreateCikVisualMapComponent from './CreateCikVisualModalMapComponent'; +import TooltipMarkerComponent from '../TooltipMarkerComponent'; /** * Defines the units page card view @@ -18,23 +19,32 @@ export default function VisualUnitDetailComponent() { textAlign: 'center' }; - // const tooltipStyle = { - // display: 'inline-block', - // fontSize: '50%', - // // For now, only an admin can see the unit page. - // tooltipVisualUnitView: 'help.admin.unitview' - // }; + const tooltipStyle = { + display: 'inline-block', + fontSize: '60%', + padding: '0.3rem', + // For now, only an admin can see the unit page. + tooltipVisualUnitView: 'help.admin.unitview' + }; return (
+

+ +
+ +
+

+
+

- {/*
- -
*/} +
+ +

@@ -46,8 +56,8 @@ export default function VisualUnitDetailComponent() {

{/*
- -
*/} + +

*/}

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 71672c8f0..14ac9cebf 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -481,6 +481,7 @@ const LocaleTranslationData = { "unit.type.of.unit": "Type of Unit:", "unit.type.of.unit.suffix": "Added suffix will set type of unit to suffix", "units": "Units", + "units-conversion-page-title": "Units and Conversions Visual Graphics", "unsaved.failure": "Changes failed to save", "unsaved.success": "Changes saved", "unsaved.warning": "You have unsaved change(s). Are you sure you want to leave?", From 5c767b2ef185e5114af0468b16a20ed2b691ed28 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Mon, 4 Nov 2024 14:59:41 -0800 Subject: [PATCH 23/34] created tooltip for page and internationlized title and tooltip. --- src/client/app/components/TooltipHelpComponent.tsx | 3 ++- .../visual-unit/VisualUnitDetailComponent.tsx | 13 +++---------- src/client/app/translations/data.ts | 7 ++++++- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index 6f446a65e..97a0b59e0 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -70,7 +70,8 @@ export default function TooltipHelpComponent(props: TooltipHelpProps) { 'help.home.toggle.chart.link': { link: `${helpUrl}/chartLink/` }, 'help.groups.groupdetails': { link: `${helpUrl}/groupViewing/#groupDetails` }, 'help.groups.groupview': { link: `${helpUrl}/groupViewing/` }, - 'help.meters.meterview': { link: `${helpUrl}/meterViewing/` } + 'help.meters.meterview': { link: `${helpUrl}/meterViewing/` }, + 'help.admin.unitconversionvisuals': { link: ''} }; return ( diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index ce07d2409..877c2600f 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -21,10 +21,9 @@ export default function VisualUnitDetailComponent() { const tooltipStyle = { display: 'inline-block', - fontSize: '60%', - padding: '0.3rem', + fontSize: '50%', // For now, only an admin can see the unit page. - tooltipVisualUnitView: 'help.admin.unitview' + tooltipVisualUnitView: 'help.admin.unitconversionvisuals' }; return ( @@ -33,7 +32,7 @@ export default function VisualUnitDetailComponent() {

- +
@@ -42,9 +41,6 @@ export default function VisualUnitDetailComponent() {

-
- -

@@ -55,9 +51,6 @@ export default function VisualUnitDetailComponent() {

- {/*
- -
*/}

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 14ac9cebf..76afa28e1 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -229,6 +229,7 @@ const LocaleTranslationData = { "help.admin.metercreate": "This page allows admins to create meters. Please visit {link} for further details and information.", "help.admin.meteredit": "This page allows admins to edit meters. Please visit {link} for further details and information.", "help.admin.meterview": "This page allows admins to view and edit meters. Please visit {link} for further details and information.", + "help.admin.unitconversionvisuals": "The \"Input Units Visual Graphic\" visually shows the units and conversions made by admins. The \"Analyzed Units Visual Graphic\" visually shows the analysis OED does on the units and conversions entered by admins.", "help.admin.unitcreate": "This page allows admins to create units. Please visit {link} for further details and information.", "help.admin.unitedit": "This page allows admins to edit units. Please visit {link} for further details and information.", "help.admin.unitview": "This page shows information on units. Please visit {link} for further details and information.", @@ -481,7 +482,7 @@ const LocaleTranslationData = { "unit.type.of.unit": "Type of Unit:", "unit.type.of.unit.suffix": "Added suffix will set type of unit to suffix", "units": "Units", - "units-conversion-page-title": "Units and Conversions Visual Graphics", + "units.conversion.page.title": "Units and Conversions Visual Graphics", "unsaved.failure": "Changes failed to save", "unsaved.success": "Changes saved", "unsaved.warning": "You have unsaved change(s). Are you sure you want to leave?", @@ -745,6 +746,7 @@ const LocaleTranslationData = { "help.admin.metercreate": "This page allows admins to create meters. Please visit {link} for further details and information.\u{26A1}", "help.admin.meteredit": "This page allows admins to edit meters. Please visit {link} for further details and information.\u{26A1}", "help.admin.meterview": "This page allows admins to view and edit meters. Please visit {link} for further details and information.\u{26A1}", + "help.admin.unitconversionvisuals": "The \"Input Units Visual Graphic\" visually shows the units and conversions made by admins. The \"Analyzed Units Visual Graphic\" visually shows the analysis OED does on the units and conversions entered by admins.\u{26A1}", "help.admin.unitcreate": "This page allows admins to create units. Please visit {link} for further details and information.\u{26A1}", "help.admin.unitedit": "This page allows admins to edit units. Please visit {link} for further details and information.", "help.admin.unitview": "This page shows information on units. Please visit {link} for further details and information.\u{26A1}", @@ -997,6 +999,7 @@ const LocaleTranslationData = { "unit.type.of.unit": "Type of Unit:\u{26A1}", "unit.type.of.unit.suffix": "Added suffix will set type of unit to suffix\u{26A1}", "units": "Units\u{26A1}", + "units.conversion.page.title": "Units and Conversions Visual Graphics\u{26A1}", "unsaved.failure": "Changes failed to save\u{26A1}", "unsaved.success": "Changes saved\u{26A1}", "unsaved.warning": "You have unsaved change(s). Are you sure you want to leave?\u{26A1}", @@ -1261,6 +1264,7 @@ const LocaleTranslationData = { "help.admin.metercreate": "Esta página permite a los administradores crear medidores. Por favor, visite {link} para más detalles e información.", "help.admin.meteredit": "Esta página permite a los administradores editar medidores. Por favor, visite {link} para más detalles e información.", "help.admin.meterview": "Esta página permite a los administradores ver y editar mediores. Por favor, visite {link} para más detalles e información.", + "help.admin.unitconversionvisuals": "El \"Gráfico Visual de Unidades de Entrada\" muestra visualmente las unidades y las conversiones ingresadas por los administradores. El \"Gráfico Visual de Unidades Analizadas\" muestra visualmente el análisis que realiza el OED en las unidades y las conversiones ingresadas por los administradores.", "help.admin.unitcreate": "Esta página permite a los administradores crear unidades. Por favor, visite {link} para más detalles e información.", "help.admin.unitedit": "Esta página permite a los administradores editar unidades Por favor, visite {link} para más detalles e información.", "help.admin.unitview": "Esta página muestra información sobre unidades. Por favor, visite {link} para más detalles e información.", @@ -1513,6 +1517,7 @@ const LocaleTranslationData = { "unit.type.of.unit": "Tipo de unidad:", "unit.type.of.unit.suffix": "El sufijo agregado determina que el tipo de la unidad es sufijo", "units": "Unidades", + "units.conversion.page.title": "Gráficos Visuales de Unidades y Conversiones", "unsaved.failure": "No se pudieron guardar los cambios", "unsaved.success": "Se guardaron los cambios", "unsaved.warning": "Tienes cambios sin guardar. ¿Estás seguro que quieres salir?", From c62a0bbe6769f79f403c5d0f3e40c86898b46257 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Thu, 7 Nov 2024 17:40:20 -0800 Subject: [PATCH 24/34] renamed data names. --- .../app/components/HeaderButtonsComponent.tsx | 2 +- .../CreateCikVisualModalMapComponent.tsx | 6 +-- .../CreateVisualUnitModalMapComponent.tsx | 6 +-- .../visual-unit/VisualUnitDetailComponent.tsx | 4 +- src/client/app/translations/data.ts | 42 +++++++++---------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index 5318fb16b..3950409b4 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -212,7 +212,7 @@ export default function HeaderButtonsComponent() { disabled={state.shouldVisualUnitMapButtonDisabled} tag={Link} to="/visual-unit"> - + () - .domain(['meter', 'unit', 'suffix', 'suffix-input']) + .domain(['meter', 'unit', 'suffix', 'suffix.input']) .range(colors); /* Create data container to pass to D3 force graph */ @@ -107,7 +107,7 @@ export default function CreateCikVisualMapComponent() { /* Node radius */ .attr('r', 20) /* checks if unit has a non empty suffix to color differently */ - .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix-input') : colorSchema(d.typeOfUnit)); + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix.input') : colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() @@ -185,7 +185,7 @@ export default function CreateCikVisualMapComponent() { .style('font-size', '14px') .style('alignment-middle', 'middle') /* internationalizing color legend text */ - .text(intl.formatMessage({id : `legend-graph-text-${item}`})); + .text(intl.formatMessage({id : `legend.graph.text.${item}`})); }); // Empty dependency array to run the effect only once diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index 6abaf4a56..cc33a7f2f 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -24,7 +24,7 @@ export default function CreateVisualUnitMapModalComponent() { /* creating color schema for nodes based on their unit type */ const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix', 'suffix-input']) + .domain(['meter', 'unit', 'suffix', 'suffix.input']) .range(colors); /* Create data container to pass to D3 force graph */ @@ -125,7 +125,7 @@ export default function CreateVisualUnitMapModalComponent() { /* Node radius */ .attr('r', 20) /* checks if unit has a non empty suffix to color differently */ - .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix-input') : colorSchema(d.typeOfUnit)); + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix.input') : colorSchema(d.typeOfUnit)); /* Drag behavior */ node.call(d3.drag() @@ -203,7 +203,7 @@ export default function CreateVisualUnitMapModalComponent() { .style('font-size', '14px') .style('alignment-middle', 'middle') /* internationalizing color legend text */ - .text(intl.formatMessage({id : `legend-graph-text-${item}`})); + .text(intl.formatMessage({id : `legend.graph.text.${item}`})); }); // Empty dependency array to run the effect only once diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 877c2600f..16439ac93 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -40,7 +40,7 @@ export default function VisualUnitDetailComponent() {

- +

@@ -50,7 +50,7 @@ export default function VisualUnitDetailComponent() {

- +

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 76afa28e1..a6441a5d4 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -286,10 +286,10 @@ const LocaleTranslationData = { "last.four.weeks": "Last four weeks", "last.week": "Last week", "leave": "Leave", - "legend-graph-text-meter": "Meter", - "legend-graph-text-suffix": "Suffix Analyzed", - "legend-graph-text-suffix-input": "Suffix Input", - "legend-graph-text-unit": "Unit", + "legend.graph.text.meter": "Meter", + "legend.graph.text.suffix": "Suffix Analyzed", + "legend.graph.text.suffix.input": "Suffix Input", + "legend.graph.text.unit": "Unit", "less.energy": "less energy", "line": "Line", "log.in": "Log in", @@ -514,9 +514,9 @@ const LocaleTranslationData = { "uses": "uses", "view.groups": "View Groups", "visit": " or visit our ", - "visual-unit": "Units Visual Graphics", - "visual-input-units-graphic": "Input Units Visual Graphic", - "visual-analyzed-units-graphic": "Analyzed Units Visual Graphic", + "visual.unit": "Units Visual Graphics", + "visual.input.units.graphic": "Input Units Visual Graphic", + "visual.analyzed.units.graphic": "Analyzed Units Visual Graphic", "website": "website", "week": "Week", "yes": "yes", @@ -803,10 +803,10 @@ const LocaleTranslationData = { "last.four.weeks": "Quatre dernières semaines", "last.week": "La semaine dernière", "leave": "Leave\u{26A1}", - "legend-graph-text-meter": "Meter\u{26A1}", - "legend-graph-text-suffix": "Suffix Analyzed\u{26A1}", - "legend-graph-text-suffix-input": "Suffix Input\u{26A1}", - "legend-graph-text-unit": "Unit\u{26A1}", + "legend.graph.text.meter": "Meter\u{26A1}", + "legend.graph.text.suffix": "Suffix Analyzed\u{26A1}", + "legend.graph.text.suffix.input": "Suffix Input\u{26A1}", + "legend.graph.text.unit": "Unit\u{26A1}", "less.energy": "moins d'énergie", "line": "Ligne", "log.in": "Se Connecter", @@ -1031,9 +1031,9 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Visionner les groupes", "visit": " ou visitez notre ", - "visual-unit": "Units Visual Graphics\u{26A1}", - "visual-input-units-graphic": "Graphique Visuel des Unités D'Entrée", - "visual-analyzed-units-graphic": "Graphique Visuel des Unités Analysées", + "visual.unit": "Units Visual Graphics\u{26A1}", + "visual.input.units.graphic": "Graphique Visuel des Unités D'Entrée", + "visual.analyzed.units.graphic": "Graphique Visuel des Unités Analysées", "website": "site web", "week": "Semaine", "yes": " yes\u{26A1}", @@ -1321,10 +1321,10 @@ const LocaleTranslationData = { "last.four.weeks": "Últimas cuatro semanas", "last.week": "La semana pasada", "leave": "Salir", - "legend-graph-text-meter": "Medidor", - "legend-graph-text-suffix": "Sufijo Analizado", - "legend-graph-text-suffix-input": "Sufijo Entrado", - "legend-graph-text-unit": "Unidad", + "legend.graph.text.meter": "Medidor", + "legend.graph.text.suffix": "Sufijo Analizado", + "legend.graph.text.suffix.input": "Sufijo Entrado", + "legend.graph.text.unit": "Unidad", "less.energy": "menos energía", "line": "Línea", "log.in": "Iniciar sesión", @@ -1549,9 +1549,9 @@ const LocaleTranslationData = { "uses": "uses\u{26A1}", "view.groups": "Ver grupos", "visit": " o visite nuestro ", - "visual-unit": "Units Visual Graphics\u{26A1}", - "visual-input-units-graphic": "Gráfico Visual de Unidades de Entrada", - "visual-analyzed-units-graphic": "Gráfico Visual de Unidades Analizadas", + "visual.unit": "Units Visual Graphics\u{26A1}", + "visual.input.units.graphic": "Gráfico Visual de Unidades de Entrada", + "visual.analyzed.units.graphic": "Gráfico Visual de Unidades Analizadas", "website": "sitio web", "week": "semana", "yes": "sí", From a89bca319a63bd6e292eb7fd65c2128dac1bf464 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Thu, 7 Nov 2024 17:47:02 -0800 Subject: [PATCH 25/34] added comments to the components. --- .../visual-unit/CreateCikVisualModalMapComponent.tsx | 4 +++- .../visual-unit/CreateVisualUnitModalMapComponent.tsx | 3 ++- .../app/components/visual-unit/VisualUnitDetailComponent.tsx | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index b3a945ddd..424294b4e 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -7,7 +7,9 @@ import { useAppSelector } from '../../redux/reduxHooks'; import { selectAllUnits } from '../../redux/api/unitsApi'; /** - * Visual cik graph component + * Visual graph component that shows the result of OED's analysis on the + * relationship between units and conversions entered by an admin. (Uses + * Cik data for the conversion links). * @returns D3 force graph visual */ export default function CreateCikVisualMapComponent() { diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index cc33a7f2f..cf3a2227f 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -11,7 +11,8 @@ import { selectAllUnits } from '../../redux/api/unitsApi'; import { selectConversionsDetails } from '../../redux/api/conversionsApi'; /** - * Visual unit-conversion graph component + * Visual graph component that shows the relationship between units and conversions + * entered by an admin. (Uses conversion data for conversion links). * @returns D3 force graph visual */ export default function CreateVisualUnitMapModalComponent() { diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 16439ac93..00ac5e218 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -10,8 +10,8 @@ import CreateCikVisualMapComponent from './CreateCikVisualModalMapComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; /** - * Defines the units page card view - * @returns Units page element + * Defines the units and conversion graphics view. + * @returns Units visual graphics page element */ export default function VisualUnitDetailComponent() { From 844e28892dabf1faeb88dc12f216dfaa29f38edb Mon Sep 17 00:00:00 2001 From: rchagolla Date: Fri, 8 Nov 2024 12:30:02 -0800 Subject: [PATCH 26/34] added copyright header. --- .../visual-unit/CreateCikVisualModalMapComponent.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index 424294b4e..edeb698d1 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -1,3 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; From 2bf262f710b428bcf417a0795713e020e015903b Mon Sep 17 00:00:00 2001 From: rchagolla Date: Tue, 19 Nov 2024 14:52:31 -0800 Subject: [PATCH 27/34] added link to tooltip and changed color legend colors to circles. --- src/client/app/components/TooltipHelpComponent.tsx | 2 +- .../CreateCikVisualModalMapComponent.tsx | 13 +++++++------ .../CreateVisualUnitModalMapComponent.tsx | 13 +++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index b15aac4b7..f48de775e 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -71,7 +71,7 @@ export default function TooltipHelpComponent(props: TooltipHelpProps) { 'help.groups.groupdetails': { link: `${helpUrl}/groupViewing/#groupDetails` }, 'help.groups.groupview': { link: `${helpUrl}/groupViewing/` }, 'help.meters.meterview': { link: `${helpUrl}/meterViewing/` }, - 'help.admin.unitconversionvisuals': { link: ''} + 'help.admin.unitconversionvisuals': { link: `${helpUrl}/adminUnitVisual/` } }; return ( diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx index edeb698d1..84c570901 100644 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx @@ -175,18 +175,19 @@ export default function CreateCikVisualMapComponent() { colorSchema.domain().forEach((item, i) => { const legendEntry = legend.append('g') - .attr('transform', `translate(0, ${i * 25})`); + .attr('transform', `translate(0, ${i * 30})`); // Rectangle color box - legendEntry.append('rect') - .attr('width', 20) - .attr('height', 20) + legendEntry.append('circle') + .attr('r', 15) + .attr('cx', 15) // Center the circle horizontally + .attr('cy', 15) // Center the circle vertically .attr('fill', colorSchema(item)); // Text label legendEntry.append('text') - .attr('x', 30) - .attr('y', 15) + .attr('x', 40) // Position the text to the right of the circle + .attr('y', 20) // Align the text vertically with the circle .style('fill', '#000') .style('font-size', '14px') .style('alignment-middle', 'middle') diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx index cf3a2227f..09f41fd8a 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx @@ -188,18 +188,19 @@ export default function CreateVisualUnitMapModalComponent() { colorSchema.domain().forEach((item, i) => { const legendEntry = legend.append('g') - .attr('transform', `translate(0, ${i * 25})`); + .attr('transform', `translate(0, ${i * 30})`); // Rectangle color box - legendEntry.append('rect') - .attr('width', 20) - .attr('height', 20) + legendEntry.append('circle') + .attr('r', 15) + .attr('cx', 15) // Center the circle horizontally + .attr('cy', 15) // Center the circle vertically .attr('fill', colorSchema(item)); // Text label legendEntry.append('text') - .attr('x', 30) - .attr('y', 15) + .attr('x', 40) // Position the text to the right of the circle + .attr('y', 20) // Align the text vertically with the circle .style('fill', '#000') .style('font-size', '14px') .style('alignment-middle', 'middle') From c35a34a6c2f05c7852e16fea02b6dc6f844885e7 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Tue, 19 Nov 2024 16:45:27 -0800 Subject: [PATCH 28/34] moved the visual components into one. --- .../visual-unit/CreateVisualUnitComponent.tsx | 241 ++++++++++++++++++ .../visual-unit/VisualUnitDetailComponent.tsx | 17 +- 2 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx diff --git a/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx new file mode 100644 index 000000000..6c7a4184a --- /dev/null +++ b/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react'; +import * as d3 from 'd3'; +import { useEffect } from 'react'; +import { useIntl } from 'react-intl'; +// import { useAppSelector } from '../../redux/reduxHooks'; +import { UnitData } from 'types/redux/units'; +import { ConversionData } from 'types/redux/conversions'; +import { CikData } from 'types/redux/ciks'; +// import { selectAllUnits } from '../../redux/api/unitsApi'; + +/** + * Visual graph component that shows the relationship between units and conversions + * entered by an admin. + * @returns D3 force graph visual + */ +interface CreateVisualUnitProps { + units: UnitData[]; + conversions: ConversionData[] | CikData[]; + isCik?: boolean; +} + +export const CreateVisualUnitComponent: React.FC = ({ + units, + conversions, + isCik = false +}) => { + const intl = useIntl(); + + /* creating color schema for nodes based on their unit type */ + const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; + const colorSchema = d3.scaleOrdinal() + .domain(['meter', 'unit', 'suffix', 'suffix.input']) + .range(colors); + + /* Create data container to pass to D3 force graph */ + const data: { nodes: any[], links: any[] } = { + nodes: [], + links: [] + }; + + units.map(value => + data.nodes.push({ + 'name': value.name, + 'id': value.id, + 'typeOfUnit': value.typeOfUnit, + 'suffix': value.suffix + }) + ); + + conversions.map(value => { + if (isCik) { + const cikValue = value as CikData; + data.links.push({ + 'source': cikValue.meterUnitId, + 'target': cikValue.nonMeterUnitId, + 'bidirectional': false + }); + } else { + const conversionValue = value as ConversionData; + data.links.push({ + 'source': conversionValue.sourceId, + 'target': conversionValue.destinationId, + /* boolean value */ + 'bidirectional': conversionValue.bidirectional + }); + } + }); + + /* Visuals start here */ + useEffect(() => { + /* View-box dimensions */ + const width = window.innerWidth; + const height = isCik ? 1000 : 750; + + /* Grab data */ + const nodes = data.nodes.map(d => ({...d})); + const links = data.links.map(d => ({...d})); + + const simulation = d3.forceSimulation(nodes) + .force('link', d3.forceLink(links) + /* Set all link ids (from data.links) */ + .id((d: any) => d.id) + /* This controls how long each link is */ + .distance(isCik ? 120 : 90) + ) + /* Create new many-body force */ + .force('charge', d3.forceManyBody() + /* This controls the 'repelling' force on each node */ + .strength(isCik ? -800 : -500) + ) + .force('x', d3.forceX()) + .force('y', d3.forceY()); + + const svg = d3.select(isCik ? '#sample-cik' : '#sample') + .append('svg') + .attr('width', width) + .attr('height', height) + .attr('viewBox', [-width / 2, -height / 2, width, height]) + .attr('style', 'max-width: 100%; height: auto;') + .append('g'); + + /* End arrow head */ + svg.append('defs').append('marker') + .attr('id', 'arrow-end') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 25) + .attr('refY', 0) + .attr('markerWidth', 4) + .attr('markerHeight', 4) + /* auto: point towards dest. node */ + .attr('orient', 'auto') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); + + if (!isCik) { + /* Start arrow head (for bidirectional edges) */ + svg.append('defs').append('marker') + .attr('id', 'arrow-start') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 25) + .attr('refY', 0) + .attr('markerWidth', 4) + .attr('markerHeight', 4) + /* auto-start-reverse: point towards src. node */ + .attr('orient', 'auto-start-reverse') + .append('svg:path') + .attr('d', 'M0,-5L10,0L0,5'); + } + + /* Link style */ + const link = svg.selectAll('line') + .data(links) + .enter().append('line') + .style('stroke', '#aaa') + .attr('stroke-width', 3) + .attr('marker-end', 'url(#arrow-end)') + /* Only draw start arrow head if bidirectional */ + .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); + + /* Node style */ + const node = svg.selectAll('.node') + .data(nodes) + .enter().append('circle') + /* Node radius */ + .attr('r', 20) + /* checks if unit has a non empty suffix to color differently */ + .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix.input') : colorSchema(d.typeOfUnit)); + + /* Drag behavior */ + node.call(d3.drag() + .on('start', dragstart) + .on('drag', dragged) + .on('end', dragend)); + + /* Node label style */ + const label = svg.selectAll('.label') + .data(nodes) + .enter() + .append('text') + .text(function (d) { return d.name; }) + .style('text-anchor', 'middle') + .style('fill', '#000') + .style('font-family', 'Arial') + .style('font-size', 14); + + /* Update element positions when moved */ + simulation.on('tick', () => { + link + .attr('x1', d => d.source.x) + .attr('y1', d => d.source.y) + .attr('x2', d => d.target.x) + .attr('y2', d => d.target.y); + + node + .attr('cx', d => d.x) + .attr('cy', d => d.y); + + label + .attr('x', function(d){ return d.x; }) + .attr('y', function (d) {return d.y - 25; }); + }); + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragstart(event: any) { + if (!event.active) simulation.alphaTarget(0.3).restart(); + event.subject.fx = event.subject.x; + event.subject.fy = event.subject.y; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragged(event: any) { + event.subject.fx = event.x; + event.subject.fy = event.y; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + function dragend(event: any) { + if (!event.active) simulation.alphaTarget(0); + event.subject.fx = null; + event.subject.fy = null; + } + + /* Color Legend */ + const legend = svg.append('g') + .attr('transform', `translate(${-width / 2 + 20}, ${-height / 2 + 20})`); + + colorSchema.domain().forEach((item, i) => { + const legendEntry = legend.append('g') + .attr('transform', `translate(0, ${i * 30})`); + + // Rectangle color box + legendEntry.append('circle') + .attr('r', 15) + .attr('cx', 15) // Center the circle horizontally + .attr('cy', 15) // Center the circle vertically + .attr('fill', colorSchema(item)); + + // Text label + legendEntry.append('text') + .attr('x', 40) // Position the text to the right of the circle + .attr('y', 20) // Align the text vertically with the circle + .style('fill', '#000') + .style('font-size', '14px') + .style('alignment-middle', 'middle') + /* internationalizing color legend text */ + .text(intl.formatMessage({id : `legend.graph.text.${item}`})); + }); + + // Empty dependency array to run the effect only once + }, []); + + return( +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 00ac5e218..1cd69ce71 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -5,15 +5,24 @@ import { FormattedMessage } from 'react-intl'; import TooltipHelpComponent from '../TooltipHelpComponent'; import * as React from 'react'; -import CreateVisualUnitMapModalComponent from './CreateVisualUnitModalMapComponent'; -import CreateCikVisualMapComponent from './CreateCikVisualModalMapComponent'; +import { CreateVisualUnitComponent } from './CreateVisualUnitComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; +import { selectCik } from '../../redux/api/conversionsApi'; +import { selectConversionsDetails } from '../../redux/api/conversionsApi'; +import { useAppSelector } from '../../redux/reduxHooks'; +import { selectAllUnits } from '../../redux/api/unitsApi'; /** * Defines the units and conversion graphics view. * @returns Units visual graphics page element */ export default function VisualUnitDetailComponent() { + /* Get unit and conversion data from redux */ + const unitData = useAppSelector(selectAllUnits); + const conversionData = useAppSelector(selectConversionsDetails); + const cikData = useAppSelector(selectCik); + + const titleStyle: React.CSSProperties = { textAlign: 'center' @@ -45,7 +54,7 @@ export default function VisualUnitDetailComponent() {
- +
@@ -55,7 +64,7 @@ export default function VisualUnitDetailComponent() {
- +
); From 7b151a1966fe58013404d465be990bcf98486911 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Tue, 19 Nov 2024 16:50:06 -0800 Subject: [PATCH 29/34] added link to tooltip. --- src/client/app/translations/data.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 8cc689083..8a5815cb4 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -225,7 +225,7 @@ const LocaleTranslationData = { "help.admin.metercreate": "This page allows admins to create meters. Please visit {link} for further details and information.", "help.admin.meteredit": "This page allows admins to edit meters. Please visit {link} for further details and information.", "help.admin.meterview": "This page allows admins to view and edit meters. Please visit {link} for further details and information.", - "help.admin.unitconversionvisuals": "The \"Input Units Visual Graphic\" visually shows the units and conversions made by admins. The \"Analyzed Units Visual Graphic\" visually shows the analysis OED does on the units and conversions entered by admins.", + "help.admin.unitconversionvisuals": "The \"Input Units Visual Graphic\" visually shows the units and conversions made by admins. The \"Analyzed Units Visual Graphic\" visually shows the analysis OED does on the units and conversions entered by admins. Please visit {link} for further details and information.", "help.admin.unitcreate": "This page allows admins to create units. Please visit {link} for further details and information.", "help.admin.unitedit": "This page allows admins to edit units. Please visit {link} for further details and information.", "help.admin.unitview": "This page shows information on units. Please visit {link} for further details and information.", @@ -740,7 +740,7 @@ const LocaleTranslationData = { "help.admin.metercreate": "This page allows admins to create meters. Please visit {link} for further details and information.\u{26A1}", "help.admin.meteredit": "This page allows admins to edit meters. Please visit {link} for further details and information.\u{26A1}", "help.admin.meterview": "This page allows admins to view and edit meters. Please visit {link} for further details and information.\u{26A1}", - "help.admin.unitconversionvisuals": "The \"Input Units Visual Graphic\" visually shows the units and conversions made by admins. The \"Analyzed Units Visual Graphic\" visually shows the analysis OED does on the units and conversions entered by admins.\u{26A1}", + "help.admin.unitconversionvisuals": "The \"Input Units Visual Graphic\" visually shows the units and conversions made by admins. The \"Analyzed Units Visual Graphic\" visually shows the analysis OED does on the units and conversions entered by admins. Please visit {link} for further details and information.\u{26A1}", "help.admin.unitcreate": "This page allows admins to create units. Please visit {link} for further details and information.\u{26A1}", "help.admin.unitedit": "This page allows admins to edit units. Please visit {link} for further details and information.", "help.admin.unitview": "This page shows information on units. Please visit {link} for further details and information.\u{26A1}", @@ -1256,7 +1256,7 @@ const LocaleTranslationData = { "help.admin.metercreate": "Esta página permite a los administradores crear medidores. Por favor, visite {link} para más detalles e información.", "help.admin.meteredit": "Esta página permite a los administradores editar medidores. Por favor, visite {link} para más detalles e información.", "help.admin.meterview": "Esta página permite a los administradores ver y editar mediores. Por favor, visite {link} para más detalles e información.", - "help.admin.unitconversionvisuals": "El \"Gráfico Visual de Unidades de Entrada\" muestra visualmente las unidades y las conversiones ingresadas por los administradores. El \"Gráfico Visual de Unidades Analizadas\" muestra visualmente el análisis que realiza el OED en las unidades y las conversiones ingresadas por los administradores.", + "help.admin.unitconversionvisuals": "El \"Gráfico Visual de Unidades de Entrada\" muestra visualmente las unidades y las conversiones ingresadas por los administradores. El \"Gráfico Visual de Unidades Analizadas\" muestra visualmente el análisis que realiza el OED en las unidades y las conversiones ingresadas por los administradores. Por favor, visite {link} para más detalles e información.", "help.admin.unitcreate": "Esta página permite a los administradores crear unidades. Por favor, visite {link} para más detalles e información.", "help.admin.unitedit": "Esta página permite a los administradores editar unidades Por favor, visite {link} para más detalles e información.", "help.admin.unitview": "Esta página muestra información sobre unidades. Por favor, visite {link} para más detalles e información.", From f37d3804c4e36076d4c3589c9ef686dc4c3a5468 Mon Sep 17 00:00:00 2001 From: rchagolla Date: Wed, 20 Nov 2024 19:45:27 -0800 Subject: [PATCH 30/34] removed old components, moved all graphics and title into a single div, and changed tooltip style name. --- .../CreateCikVisualModalMapComponent.tsx | 206 ---------------- .../CreateVisualUnitModalMapComponent.tsx | 220 ------------------ .../visual-unit/VisualUnitDetailComponent.tsx | 21 +- 3 files changed, 9 insertions(+), 438 deletions(-) delete mode 100644 src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx delete mode 100644 src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx diff --git a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx deleted file mode 100644 index 84c570901..000000000 --- a/src/client/app/components/visual-unit/CreateCikVisualModalMapComponent.tsx +++ /dev/null @@ -1,206 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import * as React from 'react'; -import * as d3 from 'd3'; -import { useEffect } from 'react'; -import { useIntl } from 'react-intl'; -import { selectCik } from '../../redux/api/conversionsApi'; -import { useAppSelector } from '../../redux/reduxHooks'; -import { selectAllUnits } from '../../redux/api/unitsApi'; - -/** - * Visual graph component that shows the result of OED's analysis on the - * relationship between units and conversions entered by an admin. (Uses - * Cik data for the conversion links). - * @returns D3 force graph visual - */ -export default function CreateCikVisualMapComponent() { - const intl = useIntl(); - - /* Get unit and Cik data from redux */ - const unitData = useAppSelector(selectAllUnits); - const cikData = useAppSelector(selectCik); - - /* creating color schema for nodes based on their unit type */ - const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; - const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix', 'suffix.input']) - .range(colors); - - /* Create data container to pass to D3 force graph */ - const data: { nodes: any[], links: any[] } = { - nodes: [], - links: [] - }; - - unitData.map(value => - data.nodes.push({ - 'name': value.name, - 'id': value.id, - 'typeOfUnit': value.typeOfUnit, - 'suffix' : value.suffix - }) - ); - - cikData.map(function (value) { - data.links.push({ - 'source': value.meterUnitId, - 'target': value.nonMeterUnitId, - 'bidirectional': false - }); - }); - - /* Visuals start here */ - useEffect(() => { - /* View-box dimensions */ - const width = window.innerWidth; - const height = 1000; - - /* Grab data */ - const nodes = data.nodes.map(d => ({...d})); - const links = data.links.map(d => ({...d})); - - const simulation = d3.forceSimulation(nodes) - .force('link', d3.forceLink(links) - /* Set all link ids (from data.links) */ - .id((d: any) => d.id) - /* This controls how long each link is */ - .distance(120) - ) - /* Create new many-body force */ - .force('charge', d3.forceManyBody() - /* This controls the 'repelling' force on each node */ - .strength(-800) - ) - .force('x', d3.forceX()) - .force('y', d3.forceY()); - - const svg = d3.select('#sample-cik') - .append('svg') - .attr('width', width) - .attr('height', height) - .attr('viewBox', [-width / 2, -height / 2, width, height]) - .attr('style', 'max-width: 100%; height: auto;') - .append('g'); - - /* End arrow head */ - svg.append('defs').append('marker') - .attr('id', 'arrow-end') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 25) - .attr('refY', 0) - .attr('markerWidth', 4) - .attr('markerHeight', 4) - /* auto: point towards dest. node */ - .attr('orient', 'auto') - .append('svg:path') - .attr('d', 'M0,-5L10,0L0,5'); - - /* Link style */ - const link = svg.selectAll('line') - .data(links) - .enter().append('line') - .style('stroke', '#aaa') - .attr('stroke-width', 3) - .attr('marker-end', 'url(#arrow-end)'); - - /* Node style */ - const node = svg.selectAll('.node') - .data(nodes) - .enter().append('circle') - /* Node radius */ - .attr('r', 20) - /* checks if unit has a non empty suffix to color differently */ - .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix.input') : colorSchema(d.typeOfUnit)); - - /* Drag behavior */ - node.call(d3.drag() - .on('start', dragstart) - .on('drag', dragged) - .on('end', dragend)); - - /* Node label style */ - const label = svg.selectAll('.label') - .data(nodes) - .enter() - .append('text') - .text(function (d) { return d.name; }) - .style('text-anchor', 'middle') - .style('fill', '#000') - .style('font-family', 'Arial') - .style('font-size', 14); - - /* Update element positions when moved */ - simulation.on('tick', () => { - link - .attr('x1', d => d.source.x) - .attr('y1', d => d.source.y) - .attr('x2', d => d.target.x) - .attr('y2', d => d.target.y); - - node - .attr('cx', d => d.x) - .attr('cy', d => d.y); - - label - .attr('x', function(d){ return d.x; }) - .attr('y', function (d) {return d.y - 25; }); - }); - - // eslint-disable-next-line jsdoc/require-jsdoc - function dragstart(event: any) { - if (!event.active) simulation.alphaTarget(0.3).restart(); - event.subject.fx = event.subject.x; - event.subject.fy = event.subject.y; - } - - // eslint-disable-next-line jsdoc/require-jsdoc - function dragged(event: any) { - event.subject.fx = event.x; - event.subject.fy = event.y; - } - - // eslint-disable-next-line jsdoc/require-jsdoc - function dragend(event: any) { - if (!event.active) simulation.alphaTarget(0); - event.subject.fx = null; - event.subject.fy = null; - } - - /* Color Legend */ - const legend = svg.append('g') - .attr('transform', `translate(${-width / 2 + 20}, ${-height / 2 + 20})`); - - colorSchema.domain().forEach((item, i) => { - const legendEntry = legend.append('g') - .attr('transform', `translate(0, ${i * 30})`); - - // Rectangle color box - legendEntry.append('circle') - .attr('r', 15) - .attr('cx', 15) // Center the circle horizontally - .attr('cy', 15) // Center the circle vertically - .attr('fill', colorSchema(item)); - - // Text label - legendEntry.append('text') - .attr('x', 40) // Position the text to the right of the circle - .attr('y', 20) // Align the text vertically with the circle - .style('fill', '#000') - .style('font-size', '14px') - .style('alignment-middle', 'middle') - /* internationalizing color legend text */ - .text(intl.formatMessage({id : `legend.graph.text.${item}`})); - }); - - // Empty dependency array to run the effect only once - }, []); - - return ( -
-
-
- ); -} \ No newline at end of file diff --git a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx deleted file mode 100644 index 09f41fd8a..000000000 --- a/src/client/app/components/visual-unit/CreateVisualUnitModalMapComponent.tsx +++ /dev/null @@ -1,220 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import * as React from 'react'; -import * as d3 from 'd3'; -import { useEffect } from 'react'; -import { useIntl } from 'react-intl'; -import { useAppSelector } from '../../redux/reduxHooks'; -import { selectAllUnits } from '../../redux/api/unitsApi'; -import { selectConversionsDetails } from '../../redux/api/conversionsApi'; - -/** - * Visual graph component that shows the relationship between units and conversions - * entered by an admin. (Uses conversion data for conversion links). - * @returns D3 force graph visual - */ -export default function CreateVisualUnitMapModalComponent() { - const intl = useIntl(); - - /* Get unit and conversion data from redux */ - const unitData = useAppSelector(selectAllUnits); - const conversionData = useAppSelector(selectConversionsDetails); - - /* creating color schema for nodes based on their unit type */ - const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; - const colorSchema = d3.scaleOrdinal() - .domain(['meter', 'unit', 'suffix', 'suffix.input']) - .range(colors); - - /* Create data container to pass to D3 force graph */ - const data: { nodes: any[], links: any[] } = { - nodes: [], - links: [] - }; - unitData.map(value => - data.nodes.push({ - 'name': value.name, - 'id': value.id, - 'typeOfUnit': value.typeOfUnit, - 'suffix': value.suffix - }) - ); - conversionData.map(value => - data.links.push({ - 'source': value.sourceId, - 'target': value.destinationId, - /* boolean value */ - 'bidirectional': value.bidirectional - }) - ); - - /* Visuals start here */ - useEffect(() => { - /* View-box dimensions */ - const width = window.innerWidth; - const height = 750; - - /* Grab data */ - const nodes = data.nodes.map(d => ({...d})); - const links = data.links.map(d => ({...d})); - - const simulation = d3.forceSimulation(nodes) - .force('link', d3.forceLink(links) - /* Set all link ids (from data.links) */ - .id((d: any) => d.id) - /* This controls how long each link is */ - .distance(90) - ) - /* Create new many-body force */ - .force('charge', d3.forceManyBody() - /* This controls the 'repelling' force on each node */ - .strength(-500) - ) - .force('x', d3.forceX()) - .force('y', d3.forceY()); - - const svg = d3.select('#sample') - .append('svg') - .attr('width', width) - .attr('height', height) - .attr('viewBox', [-width / 2, -height / 2, width, height]) - .attr('style', 'max-width: 100%; height: auto;') - .append('g'); - - /* End arrow head */ - svg.append('defs').append('marker') - .attr('id', 'arrow-end') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 25) - .attr('refY', 0) - .attr('markerWidth', 4) - .attr('markerHeight', 4) - /* auto: point towards dest. node */ - .attr('orient', 'auto') - .append('svg:path') - .attr('d', 'M0,-5L10,0L0,5'); - - /* Start arrow head (for bidirectional edges) */ - svg.append('defs').append('marker') - .attr('id', 'arrow-start') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 25) - .attr('refY', 0) - .attr('markerWidth', 4) - .attr('markerHeight', 4) - /* auto-start-reverse: point towards src. node */ - .attr('orient', 'auto-start-reverse') - .append('svg:path') - .attr('d', 'M0,-5L10,0L0,5'); - - /* Link style */ - const link = svg.selectAll('line') - .data(links) - .enter().append('line') - .style('stroke', '#aaa') - .attr('stroke-width', 3) - .attr('marker-end', 'url(#arrow-end)') - /* Only draw start arrow head if bidirectional */ - .attr('marker-start', d => d.bidirectional === true ? 'url(#arrow-start)' : ''); - - /* Node style */ - const node = svg.selectAll('.node') - .data(nodes) - .enter().append('circle') - /* Node radius */ - .attr('r', 20) - /* checks if unit has a non empty suffix to color differently */ - .attr('fill', d => d.suffix && d.typeOfUnit === 'unit' ? colorSchema('suffix.input') : colorSchema(d.typeOfUnit)); - - /* Drag behavior */ - node.call(d3.drag() - .on('start', dragstart) - .on('drag', dragged) - .on('end', dragend)); - - /* Node label style */ - const label = svg.selectAll('.label') - .data(nodes) - .enter() - .append('text') - .text(function (d) { return d.name; }) - .style('text-anchor', 'middle') - .style('fill', '#000') - .style('font-family', 'Arial') - .style('font-size', 14); - - /* Update element positions when moved */ - simulation.on('tick', () => { - link - .attr('x1', d => d.source.x) - .attr('y1', d => d.source.y) - .attr('x2', d => d.target.x) - .attr('y2', d => d.target.y); - - node - .attr('cx', d => d.x) - .attr('cy', d => d.y); - - label - .attr('x', function(d){ return d.x; }) - .attr('y', function (d) {return d.y - 25; }); - }); - - // eslint-disable-next-line jsdoc/require-jsdoc - function dragstart(event: any) { - if (!event.active) simulation.alphaTarget(0.3).restart(); - event.subject.fx = event.subject.x; - event.subject.fy = event.subject.y; - } - - // eslint-disable-next-line jsdoc/require-jsdoc - function dragged(event: any) { - event.subject.fx = event.x; - event.subject.fy = event.y; - } - - // eslint-disable-next-line jsdoc/require-jsdoc - function dragend(event: any) { - if (!event.active) simulation.alphaTarget(0); - event.subject.fx = null; - event.subject.fy = null; - } - - /* Color Legend */ - const legend = svg.append('g') - .attr('transform', `translate(${-width / 2 + 20}, ${-height / 2 + 20})`); - - colorSchema.domain().forEach((item, i) => { - const legendEntry = legend.append('g') - .attr('transform', `translate(0, ${i * 30})`); - - // Rectangle color box - legendEntry.append('circle') - .attr('r', 15) - .attr('cx', 15) // Center the circle horizontally - .attr('cy', 15) // Center the circle vertically - .attr('fill', colorSchema(item)); - - // Text label - legendEntry.append('text') - .attr('x', 40) // Position the text to the right of the circle - .attr('y', 20) // Align the text vertically with the circle - .style('fill', '#000') - .style('font-size', '14px') - .style('alignment-middle', 'middle') - /* internationalizing color legend text */ - .text(intl.formatMessage({id : `legend.graph.text.${item}`})); - }); - - // Empty dependency array to run the effect only once - }, []); - - return ( -
-
-
- ); - -} \ No newline at end of file diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 1cd69ce71..28f86eeb3 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -28,7 +28,7 @@ export default function VisualUnitDetailComponent() { textAlign: 'center' }; - const tooltipStyle = { + const seeUnitVisualizationPageStyle = { display: 'inline-block', fontSize: '50%', // For now, only an admin can see the unit page. @@ -42,29 +42,26 @@ export default function VisualUnitDetailComponent() {

-
- +
+

-

-
-
- -
+
+ +
-

-
-
- +
+ +

); From dfa4a90698fe9b8f78931bb023cc95057da6c4d4 Mon Sep 17 00:00:00 2001 From: Rolando Chagolla <123438125+rchagolla@users.noreply.github.com> Date: Thu, 21 Nov 2024 03:46:27 -0500 Subject: [PATCH 31/34] Removed unused imports in CreateVisualUnitComponent --- .../visual-unit/CreateVisualUnitComponent.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx index 6c7a4184a..e22b22ba4 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx @@ -6,23 +6,21 @@ import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; import { useIntl } from 'react-intl'; -// import { useAppSelector } from '../../redux/reduxHooks'; import { UnitData } from 'types/redux/units'; import { ConversionData } from 'types/redux/conversions'; import { CikData } from 'types/redux/ciks'; -// import { selectAllUnits } from '../../redux/api/unitsApi'; -/** - * Visual graph component that shows the relationship between units and conversions - * entered by an admin. - * @returns D3 force graph visual - */ interface CreateVisualUnitProps { units: UnitData[]; conversions: ConversionData[] | CikData[]; isCik?: boolean; } +/** + * Visual graph component that shows the relationship between units and conversions + * entered by an admin. + * @returns D3 force graph visual + */ export const CreateVisualUnitComponent: React.FC = ({ units, conversions, From 070f979d9f204c011e212a99f75ef218099ea8bd Mon Sep 17 00:00:00 2001 From: rchagolla Date: Thu, 21 Nov 2024 16:20:06 -0800 Subject: [PATCH 32/34] changed page name and moved unit data from redux to the visual component. --- src/client/app/components/RouteComponent.tsx | 4 ++-- .../visual-unit/CreateVisualUnitComponent.tsx | 17 +++++++++-------- ...mponent.tsx => SeeUnitVisualizationPage.tsx} | 16 +++++++--------- 3 files changed, 18 insertions(+), 19 deletions(-) rename src/client/app/components/visual-unit/{VisualUnitDetailComponent.tsx => SeeUnitVisualizationPage.tsx} (73%) diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index cdf4389a3..cbb569e27 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -25,7 +25,7 @@ import RoleOutlet from './router/RoleOutlet'; import UnitsDetailComponent from './unit/UnitsDetailComponent'; import ErrorComponent from './router/ErrorComponent'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; -import VisualUnitDetailComponent from './visual-unit/VisualUnitDetailComponent'; +import SeeUnitVisualizationPage from './visual-unit/SeeUnitVisualizationPage'; /** * @returns the router component Responsible for client side routing. @@ -60,7 +60,7 @@ const router = createBrowserRouter([ { path: 'maps', element: }, { path: 'units', element: }, { path: 'users', element: }, - { path: 'visual-unit', element: } + { path: 'visual-unit', element: } ] }, { diff --git a/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx b/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx index e22b22ba4..e9e70757a 100644 --- a/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx +++ b/src/client/app/components/visual-unit/CreateVisualUnitComponent.tsx @@ -6,27 +6,28 @@ import * as React from 'react'; import * as d3 from 'd3'; import { useEffect } from 'react'; import { useIntl } from 'react-intl'; -import { UnitData } from 'types/redux/units'; +import { useAppSelector } from '../../redux/reduxHooks'; import { ConversionData } from 'types/redux/conversions'; import { CikData } from 'types/redux/ciks'; - -interface CreateVisualUnitProps { - units: UnitData[]; - conversions: ConversionData[] | CikData[]; - isCik?: boolean; -} +import { selectAllUnits } from '../../redux/api/unitsApi'; /** * Visual graph component that shows the relationship between units and conversions * entered by an admin. * @returns D3 force graph visual */ +interface CreateVisualUnitProps { + conversions: ConversionData[] | CikData[]; + isCik?: boolean; +} + export const CreateVisualUnitComponent: React.FC = ({ - units, conversions, isCik = false }) => { const intl = useIntl(); + /* Get unit data from redux */ + const units = useAppSelector(selectAllUnits); /* creating color schema for nodes based on their unit type */ const colors = ['#1F77B4', '#2CA02C', '#fd7e14', '#e377c2']; diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/SeeUnitVisualizationPage.tsx similarity index 73% rename from src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx rename to src/client/app/components/visual-unit/SeeUnitVisualizationPage.tsx index 28f86eeb3..b0bbb47e0 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/SeeUnitVisualizationPage.tsx @@ -10,15 +10,13 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; import { selectCik } from '../../redux/api/conversionsApi'; import { selectConversionsDetails } from '../../redux/api/conversionsApi'; import { useAppSelector } from '../../redux/reduxHooks'; -import { selectAllUnits } from '../../redux/api/unitsApi'; /** * Defines the units and conversion graphics view. * @returns Units visual graphics page element */ -export default function VisualUnitDetailComponent() { - /* Get unit and conversion data from redux */ - const unitData = useAppSelector(selectAllUnits); +export default function SeeUnitVisualizationPage() { + /* Get conversion data from redux */ const conversionData = useAppSelector(selectConversionsDetails); const cikData = useAppSelector(selectCik); @@ -28,7 +26,7 @@ export default function VisualUnitDetailComponent() { textAlign: 'center' }; - const seeUnitVisualizationPageStyle = { + const tooltipStyle = { display: 'inline-block', fontSize: '50%', // For now, only an admin can see the unit page. @@ -42,8 +40,8 @@ export default function VisualUnitDetailComponent() {

-
- +
+

@@ -52,7 +50,7 @@ export default function VisualUnitDetailComponent() {
- +

@@ -60,7 +58,7 @@ export default function VisualUnitDetailComponent() {

- +
From 12cc6b4dc391a46a812e80ccacab6e3624daca62 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Thu, 21 Nov 2024 19:21:49 -0600 Subject: [PATCH 33/34] put back component name --- src/client/app/components/RouteComponent.tsx | 4 ++-- ...nitVisualizationPage.tsx => VisualUnitDetailComponent.tsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/client/app/components/visual-unit/{SeeUnitVisualizationPage.tsx => VisualUnitDetailComponent.tsx} (97%) diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index cbb569e27..cdf4389a3 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -25,7 +25,7 @@ import RoleOutlet from './router/RoleOutlet'; import UnitsDetailComponent from './unit/UnitsDetailComponent'; import ErrorComponent from './router/ErrorComponent'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; -import SeeUnitVisualizationPage from './visual-unit/SeeUnitVisualizationPage'; +import VisualUnitDetailComponent from './visual-unit/VisualUnitDetailComponent'; /** * @returns the router component Responsible for client side routing. @@ -60,7 +60,7 @@ const router = createBrowserRouter([ { path: 'maps', element: }, { path: 'units', element: }, { path: 'users', element: }, - { path: 'visual-unit', element: } + { path: 'visual-unit', element: } ] }, { diff --git a/src/client/app/components/visual-unit/SeeUnitVisualizationPage.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx similarity index 97% rename from src/client/app/components/visual-unit/SeeUnitVisualizationPage.tsx rename to src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index b0bbb47e0..7044e86fd 100644 --- a/src/client/app/components/visual-unit/SeeUnitVisualizationPage.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -15,7 +15,7 @@ import { useAppSelector } from '../../redux/reduxHooks'; * Defines the units and conversion graphics view. * @returns Units visual graphics page element */ -export default function SeeUnitVisualizationPage() { +export default function VisualUnitDetailComponent() { /* Get conversion data from redux */ const conversionData = useAppSelector(selectConversionsDetails); const cikData = useAppSelector(selectCik); @@ -63,4 +63,4 @@ export default function SeeUnitVisualizationPage() { ); -} \ No newline at end of file +} From ba2b0d721f06ee4778f474769a7ee6b25f0d7e6c Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Thu, 21 Nov 2024 19:23:44 -0600 Subject: [PATCH 34/34] fix comment per PR comment --- .../app/components/visual-unit/VisualUnitDetailComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx index 7044e86fd..bc4e8dc28 100644 --- a/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx +++ b/src/client/app/components/visual-unit/VisualUnitDetailComponent.tsx @@ -29,7 +29,7 @@ export default function VisualUnitDetailComponent() { const tooltipStyle = { display: 'inline-block', fontSize: '50%', - // For now, only an admin can see the unit page. + // For now, only an admin can see the unit visualization page. tooltipVisualUnitView: 'help.admin.unitconversionvisuals' };