From 999899c0df8b7dc681bcf6ad73e87c72c4fa955a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Thu, 14 Mar 2024 00:35:51 +0100 Subject: [PATCH 1/9] GlobalHealthMonitoring: POC new implementation using Recharts --- package-lock.json | 311 +++++++++++++++++- package.json | 3 +- .../GlobalHealthBarRecharts.component.tsx | 115 +++++++ stories/globalhealthbar.stories.tsx | 13 +- 4 files changed, 432 insertions(+), 10 deletions(-) create mode 100644 src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx diff --git a/package-lock.json b/package-lock.json index 67a90970f5..c35d3d0123 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "react-virtualized": "9.22.3", "react-virtualized-auto-sizer": "^1.0.5", "react-window": "^1.8.6", + "recharts": "^2.12.2", "styled-components": "^5.2.1", "styled-system": "^5.1.5", "vega": "^5.17.3", @@ -7701,6 +7702,60 @@ "@types/node": "*" } }, + "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==" + }, + "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==" + }, + "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==" + }, + "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==", + "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==" + }, + "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==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "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==", + "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==" + }, + "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==" + }, "node_modules/@types/debug": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", @@ -11711,6 +11766,14 @@ "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-force": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", @@ -11913,6 +11976,11 @@ "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", "dev": true }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -13878,6 +13946,11 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -14462,6 +14535,14 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -23535,6 +23616,20 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-smooth": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.0.tgz", + "integrity": "sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -23586,9 +23681,9 @@ } }, "node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -23793,6 +23888,49 @@ "node": ">= 4" } }, + "node_modules/recharts": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.2.tgz", + "integrity": "sha512-9bpxjXSF5g81YsKkTSlaX7mM4b6oYI1mIYck6YkUcWuL3tomADccI51/6thY4LmvhYuRTwpfrOvE80Zc3oBRfQ==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -27694,6 +27832,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/victory-vendor": { + "version": "36.9.1", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.1.tgz", + "integrity": "sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -33532,6 +33691,60 @@ "@types/node": "*" } }, + "@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==" + }, + "@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==" + }, + "@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==" + }, + "@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==", + "requires": { + "@types/d3-color": "*" + } + }, + "@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==" + }, + "@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==", + "requires": { + "@types/d3-time": "*" + } + }, + "@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==", + "requires": { + "@types/d3-path": "*" + } + }, + "@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==" + }, + "@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==" + }, "@types/debug": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", @@ -36620,6 +36833,11 @@ } } }, + "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==" + }, "d3-force": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", @@ -36761,6 +36979,11 @@ "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", "dev": true }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -38188,6 +38411,11 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -38654,6 +38882,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==" + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -45329,6 +45562,16 @@ "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" } }, + "react-smooth": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.0.tgz", + "integrity": "sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==", + "requires": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + } + }, "react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -45358,9 +45601,9 @@ } }, "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "requires": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -45513,6 +45756,41 @@ "tslib": "^2.0.1" } }, + "recharts": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.2.tgz", + "integrity": "sha512-9bpxjXSF5g81YsKkTSlaX7mM4b6oYI1mIYck6YkUcWuL3tomADccI51/6thY4LmvhYuRTwpfrOvE80Zc3oBRfQ==", + "requires": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "dependencies": { + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -48662,6 +48940,27 @@ "unist-util-stringify-position": "^3.0.0" } }, + "victory-vendor": { + "version": "36.9.1", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.1.tgz", + "integrity": "sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==", + "requires": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index 6a3d046870..766fe2a037 100644 --- a/package.json +++ b/package.json @@ -101,10 +101,10 @@ "@fortawesome/react-fontawesome": "^0.1.14", "@js-temporal/polyfill": "^0.4.4", "@storybook/preview-api": "^7.6.17", + "downshift": "^7.0.5", "framer-motion": "^4.1.17", "polished": "3.4.1", "pretty-bytes": "^5.6.0", - "downshift": "^7.0.5", "react": "^17.0.2", "react-debounce-input": "3.2.2", "react-dom": "^17.0.2", @@ -118,6 +118,7 @@ "react-virtualized": "9.22.3", "react-virtualized-auto-sizer": "^1.0.5", "react-window": "^1.8.6", + "recharts": "^2.12.2", "styled-components": "^5.2.1", "styled-system": "^5.1.5", "vega": "^5.17.3", diff --git a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx new file mode 100644 index 0000000000..253ba2625e --- /dev/null +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -0,0 +1,115 @@ +import { Bar, BarChart, Tooltip, XAxis, YAxis } from 'recharts'; +import { useTheme } from 'styled-components'; + +export const TOP = 'top'; +export const BOTTOM = 'bottom'; +type Position = typeof TOP | typeof BOTTOM; +export type GlobalHealthProps = { + id: string; + alerts: { + description: string; + startsAt: string; + endsAt: string; + severity: string; + }[]; + start: string; + end: string; + height?: number; + tooltipPosition?: Position; +}; + +export function GlobalHealthBar({ + id, + alerts, + start, + end, + height = 8, + tooltipPosition = TOP, +}: GlobalHealthProps) { + const theme = useTheme(); + const data = [ + { + start: new Date(start).getTime(), + end: new Date(end).getTime(), + range: [new Date(start).getTime(), new Date(end).getTime()], + ...alerts.reduce((acc, alert, index) => { + const key = `${alert.severity}${index}`; + acc['range' + key] = [ + new Date(alert.startsAt).getTime(), + new Date(alert.endsAt).getTime(), + ]; + acc[`${key}Severity`] = alert.severity; + acc[`${key}Description`] = alert.description; + return acc; + }, {}), + + id, + }, + ]; + const warningKeys = Object.keys(data[0]).filter((key) => + key.startsWith('rangewarning'), + ); + const criticalKeys = Object.keys(data[0]).filter((key) => + key.startsWith('rangecritical'), + ); + return ( + + new Date(unixTime).toLocaleDateString()} + tickCount={5} + interval="preserveStartEnd" + tick={{ stroke: theme.textSecondary }} + tickLine={true} + axisLine={false} + tickSize={15} + /> + + {[...criticalKeys, ...warningKeys].map((key) => ( + + ))} + + + + { + const { x, y, width, height } = props; + return ( + + ); + }} + /> + {warningKeys.map((key) => ( + + ))} + {criticalKeys.map((key) => ( + + ))} + + ); +} diff --git a/stories/globalhealthbar.stories.tsx b/stories/globalhealthbar.stories.tsx index d611e94ce3..c978ee9eb3 100644 --- a/stories/globalhealthbar.stories.tsx +++ b/stories/globalhealthbar.stories.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { GlobalHealthBar } from '../src/lib/components/globalhealthbar/GlobalHealthBar.component'; +import { GlobalHealthBar } from '../src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component'; +import { GlobalHealthBar as VegaGlobalHealthBar } from '../src/lib/components/globalhealthbar/GlobalHealthBar.component'; import { SyncedCursorCharts } from '../src/lib/components/vegachartv2/SyncedCursorCharts'; import { Wrapper } from './common'; +import { Stack } from '../src/lib/spacing'; const alerts = [ { id: '1', @@ -105,13 +107,18 @@ const endNotFirstDay = '2021-03-01T23:00:00Z'; export default { title: 'Components/Data Display/Charts/GlobalHealthBar', - component: GlobalHealthBar, + component: (props) => ( + + + + + ), decorators: [ (story) => ( -
+
{story()}
From d6e23a16e4117acdec8802fd6e3e681d0c75ab90 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Fri, 3 May 2024 12:00:13 +0200 Subject: [PATCH 2/9] first version functional component, standard and history feature, with tooltip --- .../GlobalHealthBarRecharts.component.tsx | 383 ++++++++++++++---- .../globalhealthbar/HistoryProvider.tsx | 28 ++ 2 files changed, 343 insertions(+), 68 deletions(-) create mode 100644 src/lib/components/globalhealthbar/HistoryProvider.tsx diff --git a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx index 253ba2625e..b3410c1b8e 100644 --- a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -1,9 +1,21 @@ -import { Bar, BarChart, Tooltip, XAxis, YAxis } from 'recharts'; -import { useTheme } from 'styled-components'; +import { Bar, BarChart, XAxis, YAxis, Tooltip } from 'recharts'; +import styled, { useTheme } from 'styled-components'; +import { useEffect, useRef, useState } from 'react'; +import './ghbstyle.css'; +import { Stack, Wrap, spacing } from '../../spacing'; + +import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component'; +import { + DATE_FORMATER, + FormattedDateTime, + TIME_FORMATER, +} from '../date/FormattedDateTime'; +import { useHistoryAlert } from './HistoryProvider'; + +import { Text } from '../text/Text.component'; export const TOP = 'top'; export const BOTTOM = 'bottom'; -type Position = typeof TOP | typeof BOTTOM; export type GlobalHealthProps = { id: string; alerts: { @@ -14,19 +26,81 @@ export type GlobalHealthProps = { }[]; start: string; end: string; - height?: number; - tooltipPosition?: Position; }; -export function GlobalHealthBar({ - id, - alerts, - start, - end, - height = 8, - tooltipPosition = TOP, -}: GlobalHealthProps) { +export const StyledRange = styled.input` + width: 600px; + padding: 0; /* nécessaire pour IE */ + margin: 0; + margin-top: 2px; + appearance: none; /* nécessaire pour IE */ + -moz-appearance: none; /* nécessaire pour Firefox */ + -webkit-appearance: none; /* nécessaire pour Chrome */ + font: inherit; /* même rendu suivant font document */ + outline: none; + opacity: 1; + background: transparent; /* sert pour couleur de fond de la zone de déplacement */ + box-sizing: content-box; /* même modèle de boîte pour tous */ + transition: opacity 0.2s; + cursor: pointer; + position: absolute; + z-index: 10; + height: 16px; + :focus-visible::-webkit-slider-thumb { + ${FocusVisibleStyle} + } + /*==============================*/ + /* cursor */ + /*==============================*/ + &::-webkit-slider-thumb { + -webkit-appearance: none; + padding: 0; + appearance: none; + margin: 0; + margin-top: -7px; + width: 2px; + height: 16px; + background-color: green; + } + &::-moz-range-thumb { + margin: 0; + width: 2px; + height: 16px; + background-color: green; + border: none; + } +`; + +export const StyledDataList = styled.datalist` + display: none; +`; + +export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { + const history = useHistoryAlert(); + const [widthValue, setWidthValue] = useState(0); + const [popoverWidth, setPopoverWidth] = useState(0); + const [isVisible, setIsVisible] = useState(false); + const [isFocused, setIsFocused] = useState(false); + const [tooltip, setTooltip] = useState(null); + const popoverRef = useRef(null); const theme = useTheme(); + + //initialize selectedDate + useEffect(() => { + if (history.selectedDate !== null) history.setSelectedDate(0); + }, []); + useEffect(() => { + if (popoverRef.current) setPopoverWidth(popoverRef.current.offsetWidth); + if (history.selectedDate) + setWidthValue( + ((history.selectedDate - new Date(start).getTime()) * 100) / + (new Date(end).getTime() - new Date(start).getTime()), + ); + }, [history.selectedDate, popoverRef.current]); + + useEffect(() => { + setIsVisible(true); + }, [history.selectedDate]); const data = [ { start: new Date(start).getTime(), @@ -52,64 +126,237 @@ export function GlobalHealthBar({ const criticalKeys = Object.keys(data[0]).filter((key) => key.startsWith('rangecritical'), ); + + const toggleVisibility = (e) => { + console.log('e', e); + if (!isFocused) { + if (e.type === 'mouseleave') setIsVisible(false); + } + if (e.type === 'mouseenter') setIsVisible(true); + if (e.type === 'blur') { + setIsVisible(false); + setIsFocused(false); + } + if (e.type === 'focus') { + setIsVisible(true); + setIsFocused(true); + } + }; + const CustomTooltip = (props) => { + const { coordinate } = props; + if (tooltip) { + const { tooltipPayload } = tooltip; + // @ts-expect-error + const name = tooltipPayload[0].name.replace('range', ''); + return ( +
+ + View details on Alert Page + + + + Severity: + + {/* @ts-expect-error */} + {tooltip.tooltipPayload[0].payload[`${name}Severity`]} + + + + Start: + + + + End: + + + + Description: + + {/* @ts-expect-error */} + {tooltip.tooltipPayload[0].payload[`${name}Description`]} + + + +
+ ); + } + + return null; + }; + return ( - - new Date(unixTime).toLocaleDateString()} - tickCount={5} - interval="preserveStartEnd" - tick={{ stroke: theme.textSecondary }} - tickLine={true} - axisLine={false} - tickSize={15} - /> - - {[...criticalKeys, ...warningKeys].map((key) => ( - - ))} +
+ {history.selectedDate !== null && ( +
+
+ +
+ { + history.setSelectedDate(+e.target.value); + }} + /> + + + + + +
+ )} +
+ + { + const { x, y, payload, index } = props; + return ( + + + {DATE_FORMATER.format(new Date(payload.value)) + + ' ' + + TIME_FORMATER.format(new Date(payload.value))} + + + ); + }} + interval={'preserveStartEnd'} + tickLine={{ stroke: theme.textSecondary }} + axisLine={false} + tickSize={8} + /> + {!history.selectedDate && ( + } + /> + )} - + + {[...criticalKeys, ...warningKeys].map((key) => ( + + ))} + { + const { x, y, width, height } = props; + return ( + + ); + }} + /> - { - const { x, y, width, height } = props; - return ( - ( + setTooltip(e)} + onMouseLeave={() => setTooltip(null)} + fill={theme.statusWarning} + > + ))} + {criticalKeys.map((key) => ( + setTooltip(e)} + onMouseLeave={() => setTooltip(null)} /> - ); - }} - /> - {warningKeys.map((key) => ( - - ))} - {criticalKeys.map((key) => ( - - ))} - + ))} + +
+
); } diff --git a/src/lib/components/globalhealthbar/HistoryProvider.tsx b/src/lib/components/globalhealthbar/HistoryProvider.tsx new file mode 100644 index 0000000000..c8e8cbf5b9 --- /dev/null +++ b/src/lib/components/globalhealthbar/HistoryProvider.tsx @@ -0,0 +1,28 @@ +import { createContext, useContext, useState } from 'react'; + +type HistoryAlertContextType = { + selectedDate: number | undefined; + setSelectedDate: (date: number) => void; +}; +export const HistoryAlertContext = createContext< + HistoryAlertContextType | undefined +>(undefined); + +export const HistoryAlertProvider = ({ children }) => { + const [selectedDate, setSelectedDate] = useState( + undefined, + ); + return ( + + {children} + + ); +}; + +export const useHistoryAlert = () => { + const context = useContext(HistoryAlertContext); + if (!context) { + return { selectedDate: null }; + } + return context; +}; From 160f810a3037c39b4ad48d5155e71cee880fb396 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Fri, 3 May 2024 12:02:53 +0200 Subject: [PATCH 3/9] init stories and guideline for new globalhealthbar --- .../GlobalHealthBarRecharts.component.tsx | 5 +- .../globalhealthbarRecharts.stories.tsx | 109 ++++++++++++++++++ .../globalheathbarrecharts.guideline.mdx | 5 + stories/globalhealthbar.stories.tsx | 9 +- 4 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx create mode 100644 stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx diff --git a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx index b3410c1b8e..b9c4fb3e90 100644 --- a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -1,8 +1,7 @@ import { Bar, BarChart, XAxis, YAxis, Tooltip } from 'recharts'; import styled, { useTheme } from 'styled-components'; import { useEffect, useRef, useState } from 'react'; -import './ghbstyle.css'; -import { Stack, Wrap, spacing } from '../../spacing'; +import { Wrap, spacing } from '../../spacing'; import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component'; import { @@ -12,8 +11,6 @@ import { } from '../date/FormattedDateTime'; import { useHistoryAlert } from './HistoryProvider'; -import { Text } from '../text/Text.component'; - export const TOP = 'top'; export const BOTTOM = 'bottom'; export type GlobalHealthProps = { diff --git a/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx new file mode 100644 index 0000000000..bc02a0fff7 --- /dev/null +++ b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import { + GlobalHealthBar, + GlobalHealthProps, +} from '../../src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component'; +import { + HistoryAlertProvider, + useHistoryAlert, +} from '../../src/lib/components/globalhealthbar/HistoryProvider'; +import { + DATE_FORMATER, + TIME_FORMATER, + TIME_SECOND_FORMATER, +} from '../../src/lib/components/date/FormattedDateTime'; + +type Story = StoryObj; + +const meta: Meta = { + title: 'Components/GlobalHealthBarRecharts', + component: GlobalHealthBar, +}; +export default meta; + +const start = '2021-01-31T23:00:00Z'; // UTC time +const start2 = '2021-01-31T23:00:00'; +const end2 = '2021-02-06T23:00:00'; +const end = '2021-02-06T23:00:00Z'; +const alerts = [ + { + id: '1', + severity: 'warning', + startsAt: '2021-02-01T07:00:00Z', + endsAt: '2021-02-01T21:00:00Z', + description: 'Global health warning', + }, + { + id: '2', + severity: 'warning', + startsAt: '2021-02-01T23:00:00Z', + endsAt: '2021-02-02T23:00:00Z', + description: 'Global health warning', + }, + { + id: '3', + severity: 'critical', + startsAt: '2021-02-03T00:00:00Z', + endsAt: '2021-02-04T00:00:00Z', + description: 'Global health critical', + }, + { + id: '4', + severity: 'warning', + startsAt: '2021-02-04T10:00:00Z', + endsAt: '2021-02-06T00:00:00Z', + description: 'Global health warning', + }, + { + id: '5', + severity: 'warning', + startsAt: '2021-02-06T12:00:00Z', + endsAt: '2021-02-07T00:00:00Z', + description: 'Global health warning', + }, +]; + +export const Default: Story = { + args: { + start, + end, + alerts, + }, +}; + +const InputDate = () => { + const history = useHistoryAlert(); + if (history.selectedDate !== null) { + const handleChange = (e) => { + history.setSelectedDate(new Date(e.target.value).valueOf()); + }; + + return ( + + ); + } + + return <>; +}; + +export const WithSelectedDate: Story = { + render: () => { + return ( + + + + + ); + }, +}; diff --git a/stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx b/stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx new file mode 100644 index 0000000000..ebddbc5620 --- /dev/null +++ b/stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx @@ -0,0 +1,5 @@ +import { Meta } from '@storybook/blocks'; +import { GlobalHealthBar } from '../../src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component'; +import * as GlobalHealthBarStories from './globalhealthbarRecharts.stories'; + + diff --git a/stories/globalhealthbar.stories.tsx b/stories/globalhealthbar.stories.tsx index c978ee9eb3..588902bbc0 100644 --- a/stories/globalhealthbar.stories.tsx +++ b/stories/globalhealthbar.stories.tsx @@ -36,10 +36,17 @@ const alerts = [ { id: '5', severity: 'warning', - startsAt: '2021-02-06T12:00:00Z', + startsAt: '2021-02-06T10:00:00Z', endsAt: '2021-02-06T20:00:00Z', description: 'Global health warning', }, + { + id: '6', + severity: 'warning', + startsAt: '2021-02-06T12:00:00Z', + endsAt: '2021-02-06T23:00:00Z', + description: 'Global health warning', + }, ]; const alertsLast24h = [ { From a468573c6d80cefe85846beeedc71eeeece0a245 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Wed, 22 May 2024 15:55:39 +0200 Subject: [PATCH 4/9] add history bar to stories --- .../globalhealthbarRecharts.stories.tsx | 84 ++++++++++++++++--- stories/globalhealthbar.stories.tsx | 2 +- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx index bc02a0fff7..ef3222eb46 100644 --- a/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx +++ b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx @@ -10,7 +10,6 @@ import { } from '../../src/lib/components/globalhealthbar/HistoryProvider'; import { DATE_FORMATER, - TIME_FORMATER, TIME_SECOND_FORMATER, } from '../../src/lib/components/date/FormattedDateTime'; @@ -23,7 +22,11 @@ const meta: Meta = { export default meta; const start = '2021-01-31T23:00:00Z'; // UTC time -const start2 = '2021-01-31T23:00:00'; +const start2 = '2021-01-30T23:00:00'; +const startLast24h = '2021-02-01T00:00:00'; +const endLast24h = '2021-02-02T00:00:00'; +const startLastHour = '2021-02-01T00:00:00'; +const endLastHour = '2021-02-01T01:00:00'; const end2 = '2021-02-06T23:00:00'; const end = '2021-02-06T23:00:00Z'; const alerts = [ @@ -31,14 +34,14 @@ const alerts = [ id: '1', severity: 'warning', startsAt: '2021-02-01T07:00:00Z', - endsAt: '2021-02-01T21:00:00Z', + endsAt: '2021-02-02T01:00:00Z', description: 'Global health warning', }, { id: '2', severity: 'warning', startsAt: '2021-02-01T23:00:00Z', - endsAt: '2021-02-02T23:00:00Z', + endsAt: '2021-02-02T22:00:00Z', description: 'Global health warning', }, { @@ -62,6 +65,13 @@ const alerts = [ endsAt: '2021-02-07T00:00:00Z', description: 'Global health warning', }, + { + id: '6', + severity: 'warning', + startsAt: '2021-01-30T23:40:00Z', + endsAt: '2021-01-31T23:59:00Z', + description: 'Global health warning', + }, ]; export const Default: Story = { @@ -72,7 +82,7 @@ export const Default: Story = { }, }; -const InputDate = () => { +const InputDate = ({ start, end }) => { const history = useHistoryAlert(); if (history.selectedDate !== null) { const handleChange = (e) => { @@ -81,8 +91,8 @@ const InputDate = () => { return ( { return <>; }; -export const WithSelectedDate: Story = { +export const WithSelectedDate24h: Story = { + render: () => { + return ( + <> + + + + + + + ); + }, +}; +export const WithSelectedDateWeek: Story = { + render: () => { + return ( + <> + + + + + + + ); + }, +}; + +export const WithSelectedDateHour: Story = { render: () => { return ( - - - - + <> + + + + + + ); }, }; diff --git a/stories/globalhealthbar.stories.tsx b/stories/globalhealthbar.stories.tsx index 588902bbc0..d5124637e2 100644 --- a/stories/globalhealthbar.stories.tsx +++ b/stories/globalhealthbar.stories.tsx @@ -44,7 +44,7 @@ const alerts = [ id: '6', severity: 'warning', startsAt: '2021-02-06T12:00:00Z', - endsAt: '2021-02-06T23:00:00Z', + endsAt: '2021-02-06T22:30:00Z', description: 'Global health warning', }, ]; From cc84bd91b2522be6d61da530beafbfdf2566d843 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Wed, 22 May 2024 16:01:02 +0200 Subject: [PATCH 5/9] add functions to render ticks, bars with round corners, tooltip --- .../GlobalHealthBarRecharts.component.tsx | 445 +++++++++++------- .../globalhealthbar/HistoryProvider.tsx | 6 +- 2 files changed, 288 insertions(+), 163 deletions(-) diff --git a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx index b9c4fb3e90..2a2fbf017a 100644 --- a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -1,4 +1,13 @@ -import { Bar, BarChart, XAxis, YAxis, Tooltip } from 'recharts'; +import { + Bar, + BarChart, + XAxis, + YAxis, + Tooltip, + Rectangle, + Customized, + CartesianGrid, +} from 'recharts'; import styled, { useTheme } from 'styled-components'; import { useEffect, useRef, useState } from 'react'; import { Wrap, spacing } from '../../spacing'; @@ -8,8 +17,14 @@ import { DATE_FORMATER, FormattedDateTime, TIME_FORMATER, + TIME_SECOND_FORMATER, } from '../date/FormattedDateTime'; import { useHistoryAlert } from './HistoryProvider'; +import { Box } from '../box/Box'; +import { Text } from '../text/Text.component'; +import { Icon } from '../icon/Icon.component'; +import { get } from 'styled-system'; +import { RectRadius } from 'recharts/types/shape/Rectangle'; export const TOP = 'top'; export const BOTTOM = 'bottom'; @@ -29,7 +44,7 @@ export const StyledRange = styled.input` width: 600px; padding: 0; /* nécessaire pour IE */ margin: 0; - margin-top: 2px; + margin-top: -1px; appearance: none; /* nécessaire pour IE */ -moz-appearance: none; /* nécessaire pour Firefox */ -webkit-appearance: none; /* nécessaire pour Chrome */ @@ -54,16 +69,15 @@ export const StyledRange = styled.input` padding: 0; appearance: none; margin: 0; - margin-top: -7px; - width: 2px; + width: 3px; height: 16px; - background-color: green; + background-color: ${(props) => props.theme.selectedActive}; } &::-moz-range-thumb { margin: 0; width: 2px; height: 16px; - background-color: green; + background-color: ${(props) => props.theme.selectedActive}; border: none; } `; @@ -74,35 +88,86 @@ export const StyledDataList = styled.datalist` export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { const history = useHistoryAlert(); - const [widthValue, setWidthValue] = useState(0); - const [popoverWidth, setPopoverWidth] = useState(0); const [isVisible, setIsVisible] = useState(false); const [isFocused, setIsFocused] = useState(false); - const [tooltip, setTooltip] = useState(null); + const [tooltipData, setTooltipData] = useState(null); const popoverRef = useRef(null); const theme = useTheme(); - //initialize selectedDate - useEffect(() => { - if (history.selectedDate !== null) history.setSelectedDate(0); - }, []); - useEffect(() => { - if (popoverRef.current) setPopoverWidth(popoverRef.current.offsetWidth); - if (history.selectedDate) - setWidthValue( - ((history.selectedDate - new Date(start).getTime()) * 100) / - (new Date(end).getTime() - new Date(start).getTime()), - ); - }, [history.selectedDate, popoverRef.current]); - useEffect(() => { setIsVisible(true); }, [history.selectedDate]); + + // TODO : Border radius on alerts close to the start and end + // TODO : + + // TODO change tooltip position if it's out of the screen + const startDate = new Date(start).getTime(); + const endDate = new Date(end).getTime(); + + const oneHour = 60 * 60 * 1000; + const oneDay = 24 * oneHour; + const oneWeek = 7 * oneDay; + const span = endDate - startDate; + + // get 20%, 40%, 60%, 80% of span interval for ticks + // value = (percentage * (max - min) / 100) + min + const getDataListOptions = (startDate, endDate) => { + const oneDay = 24 * 60 * 60 * 1000; + const span = endDate - startDate; + if (span === 7 * oneDay) { + return Array.from({ length: 6 }, (_, i) => endDate - (i + 1) * oneDay); + } + return Array.from({ length: 4 }, (_, i) => endDate - ((i + 1) / 5) * span); + }; + const getStep = (startDate, endDate) => { + const oneHour = 60 * 60 * 1000; + const oneDay = 24 * oneHour; + const span = endDate - startDate; + if (span === 7 * oneDay) { + return oneHour; + } else if (span === oneDay) { + return oneHour / 4; + } else if (span === oneHour) { + return 60 * 1000; + } + }; + const getRadius = (start, end, startDate, endDate): RectRadius => { + const marge = span >= oneDay ? 0.011 * span : 0; + // TODO need to correct the conditions (don't take into || only &&) + if (start === startDate && end === endDate) { + return [15, 15, 15, 15]; + } else if (start <= startDate + marge && end >= endDate - marge) { + return [6, 6, 6, 6]; + } else if (start === startDate) { + return [15, 0, 0, 15]; + } else if (end === endDate) { + return [0, 15, 15, 0]; + } else if (start <= startDate + marge) { + return [6, 0, 0, 6]; + } else if (end >= endDate - marge) { + return [0, 6, 6, 0]; + } else { + return [0, 0, 0, 0]; + } + }; + + const setHistoryTooltipPosition = (): string => { + const history = useHistoryAlert(); + if (history.selectedDate && popoverRef.current) { + const width = + ((history.selectedDate - startDate) / (endDate - startDate)) * 600; + const leftPosition = width - popoverRef.current.offsetWidth / 2; + return `auto auto -4px ${leftPosition}px`; + } + return 'auto'; + }; + const data = [ { - start: new Date(start).getTime(), - end: new Date(end).getTime(), - range: [new Date(start).getTime(), new Date(end).getTime()], + start: startDate, + end: endDate, + range: [startDate, endDate], ...alerts.reduce((acc, alert, index) => { const key = `${alert.severity}${index}`; acc['range' + key] = [ @@ -113,7 +178,6 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { acc[`${key}Description`] = alert.description; return acc; }, {}), - id, }, ]; @@ -125,7 +189,6 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { ); const toggleVisibility = (e) => { - console.log('e', e); if (!isFocused) { if (e.type === 'mouseleave') setIsVisible(false); } @@ -140,61 +203,65 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { } }; const CustomTooltip = (props) => { - const { coordinate } = props; - if (tooltip) { - const { tooltipPayload } = tooltip; - // @ts-expect-error - const name = tooltipPayload[0].name.replace('range', ''); + const { tooltipData, coordinate } = props; + const tooltipRef = useRef(null); + const [tooltipInset, setTooltipInset] = useState({ top: 0, left: 0 }); + + useEffect(() => { + if (tooltipRef.current) { + // console.log('tooltip', tooltipRef.current); + // console.log('tooltipCoord', tooltipRef.current.getBoundingClientRect()); + // left and top < 0 = tooltip is out of the screen + // right or bottom > window.innerWidth or window.innerheight = tooltip is out of the screen + + setTooltipInset({ + left: coordinate.x - tooltipRef.current.offsetWidth / 2, + top: coordinate.y + 20, + }); + } + }, [tooltipRef.current, coordinate]); + if (tooltipData) { + const { payload, name } = tooltipData[0]; + const tooltipName = name.replace('range', ''); return (
- - View details on Alert Page - + + View details on Alert Page + Severity: - - {/* @ts-expect-error */} - {tooltip.tooltipPayload[0].payload[`${name}Severity`]} - + {payload[`${tooltipName}Severity`]} Start: End: Description: - - {/* @ts-expect-error */} - {tooltip.tooltipPayload[0].payload[`${name}Description`]} - + {payload[`${tooltipName}Description`]}
@@ -205,35 +272,45 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { }; return ( -
+
{history.selectedDate !== null && (
- +
+ +
+
+ { + if (e.target.valueAsNumber > endDate) + history.setSelectedDate(endDate); + if (e.target.valueAsNumber < startDate) + history.setSelectedDate(startDate); history.setSelectedDate(+e.target.value); }} /> - - - - - + + {getDataListOptions(startDate, endDate).map((date) => ( + + ))} +
)} -
- - { - const { x, y, payload, index } = props; + + + { + const { x, y, payload } = props; + return ( + + + {span === oneWeek ? ( + <> + + {DATE_FORMATER.format(new Date(payload.value))} + + + + {TIME_FORMATER.format(new Date(payload.value))} + + + ) : span === oneDay ? ( + DATE_FORMATER.format(new Date(payload.value)) + + ' ' + + TIME_FORMATER.format(new Date(payload.value)) + ) : ( + TIME_SECOND_FORMATER.format(new Date(payload.value)) + )} + + + ); + }} + tickLine={{ stroke: theme.textSecondary }} + axisLine={false} + /> + {!history.selectedDate && ( + } + /> + )} + + + + {[...criticalKeys, ...warningKeys].map((key) => ( + + ))} + + + + {warningKeys.map((key) => ( + { + setTooltipData(e.tooltipPayload); + }} + onPointerLeave={() => setTooltipData(null)} + fill={theme.statusWarning} + shape={(props) => { + const { x, y, height, fill } = props; + + const start = + props[key][0] < startDate ? startDate : props[key][0]; + const end = props[key][1] > endDate ? endDate : props[key][1]; + const test = (end - start) / (endDate - startDate); return ( - - - {DATE_FORMATER.format(new Date(payload.value)) + - ' ' + - TIME_FORMATER.format(new Date(payload.value))} - - + ); }} - interval={'preserveStartEnd'} - tickLine={{ stroke: theme.textSecondary }} - axisLine={false} - tickSize={8} - /> - {!history.selectedDate && ( - } - /> - )} + > + ))} - - {[...criticalKeys, ...warningKeys].map((key) => ( - - ))} + {criticalKeys.map((key) => ( { + setTooltipData(e.tooltipPayload); + }} + onPointerLeave={() => setTooltipData(null)} shape={(props) => { - const { x, y, width, height } = props; + const { x, y, height, fill } = props; + + const start = + props[key][0] < startDate ? startDate : props[key][0]; + const end = props[key][1] > endDate ? endDate : props[key][1]; + const test = (end - start) / (endDate - startDate); return ( - + fill={fill} + radius={getRadius(start, end, startDate, endDate)} + // radius={[15, 0, 0, 15]} + > ); }} /> - - {warningKeys.map((key) => ( - setTooltip(e)} - onMouseLeave={() => setTooltip(null)} - fill={theme.statusWarning} - > - ))} - {criticalKeys.map((key) => ( - setTooltip(e)} - onMouseLeave={() => setTooltip(null)} - /> - ))} - -
+ ))} +
); } diff --git a/src/lib/components/globalhealthbar/HistoryProvider.tsx b/src/lib/components/globalhealthbar/HistoryProvider.tsx index c8e8cbf5b9..fb0b4c202b 100644 --- a/src/lib/components/globalhealthbar/HistoryProvider.tsx +++ b/src/lib/components/globalhealthbar/HistoryProvider.tsx @@ -1,7 +1,7 @@ import { createContext, useContext, useState } from 'react'; type HistoryAlertContextType = { - selectedDate: number | undefined; + selectedDate: number; setSelectedDate: (date: number) => void; }; export const HistoryAlertContext = createContext< @@ -9,9 +9,7 @@ export const HistoryAlertContext = createContext< >(undefined); export const HistoryAlertProvider = ({ children }) => { - const [selectedDate, setSelectedDate] = useState( - undefined, - ); + const [selectedDate, setSelectedDate] = useState(0); return ( {children} From 38c55c091736ce5735b0ed40418c7086bfea82ce Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Thu, 23 May 2024 14:43:14 +0200 Subject: [PATCH 6/9] split GlobalHealthBarRecharts code in multiple files : tooltip, history slider, and utils with calc functions --- .../globalhealthbar/CustomTooltip.tsx | 88 ++++ .../GlobalHealthBarRecharts.component.tsx | 394 +++--------------- .../globalhealthbar/HistorySlider.tsx | 131 ++++++ src/lib/components/globalhealthbar/utils.tsx | 98 +++++ 4 files changed, 366 insertions(+), 345 deletions(-) create mode 100644 src/lib/components/globalhealthbar/CustomTooltip.tsx create mode 100644 src/lib/components/globalhealthbar/HistorySlider.tsx create mode 100644 src/lib/components/globalhealthbar/utils.tsx diff --git a/src/lib/components/globalhealthbar/CustomTooltip.tsx b/src/lib/components/globalhealthbar/CustomTooltip.tsx new file mode 100644 index 0000000000..836ea6b220 --- /dev/null +++ b/src/lib/components/globalhealthbar/CustomTooltip.tsx @@ -0,0 +1,88 @@ +import { useEffect, useRef, useState } from 'react'; +import styled, { css, useTheme } from 'styled-components'; +import { Box } from '../../next'; +import { Text, FormattedDateTime, Wrap, spacing } from '../../index'; + +const TootlipContainer = styled.div<{ tooltipInset }>` + ${(props) => { + const theme = useTheme(); + return css` + border: 1px solid ${theme.border}; + width: 24rem; + color: ${theme.textSecondary}; + background-color: ${theme.backgroundLevel1}; + border-radius: 4px; + position: absolute; + inset: ${props.tooltipInset.top}px auto auto ${props.tooltipInset.left}px; + padding: ${spacing.r8}; + font-size: 1rem; + `; + }} +`; + +export const CustomTooltip = (props) => { + const { tooltipData, coordinate } = props; + const tooltipRef = useRef(null); + const [tooltipInset, setTooltipInset] = useState({ top: 0, left: 0 }); + + useEffect(() => { + if (tooltipRef.current) { + // console.log('tooltip', tooltipRef.current); + // console.log('tooltipCoord', tooltipRef.current.getBoundingClientRect()); + // left and top < 0 = tooltip is out of the screen + // right or bottom > window.innerWidth or window.innerheight = tooltip is out of the screen + + setTooltipInset({ + left: coordinate.x - tooltipRef.current.offsetWidth / 2, + top: coordinate.y + 20, + }); + } + }, [tooltipRef.current, coordinate]); + if (tooltipData) { + const { payload, name } = tooltipData[0]; + const tooltipName = name.replace('range', ''); + return ( + + + View details on Alert Page + + + + Severity: + {payload[`${tooltipName}Severity`]} + + + Start: + + + + + + End: + + + + + + Description: + {payload[`${tooltipName}Description`]} + + + + ); + } + + return null; +}; diff --git a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx index 2a2fbf017a..ea5fd4b6e8 100644 --- a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -1,33 +1,11 @@ -import { - Bar, - BarChart, - XAxis, - YAxis, - Tooltip, - Rectangle, - Customized, - CartesianGrid, -} from 'recharts'; -import styled, { useTheme } from 'styled-components'; -import { useEffect, useRef, useState } from 'react'; -import { Wrap, spacing } from '../../spacing'; - -import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component'; -import { - DATE_FORMATER, - FormattedDateTime, - TIME_FORMATER, - TIME_SECOND_FORMATER, -} from '../date/FormattedDateTime'; +import { Bar, BarChart, XAxis, YAxis, Tooltip, Rectangle } from 'recharts'; +import { useTheme } from 'styled-components'; +import { useEffect, useState } from 'react'; import { useHistoryAlert } from './HistoryProvider'; -import { Box } from '../box/Box'; -import { Text } from '../text/Text.component'; -import { Icon } from '../icon/Icon.component'; -import { get } from 'styled-system'; -import { RectRadius } from 'recharts/types/shape/Rectangle'; +import { getDataListOptions, getRadius, getTickFormatter } from './utils'; +import { HistoryAlertSlider } from './HistorySlider'; +import { CustomTooltip } from './CustomTooltip'; -export const TOP = 'top'; -export const BOTTOM = 'bottom'; export type GlobalHealthProps = { id: string; alerts: { @@ -39,130 +17,22 @@ export type GlobalHealthProps = { start: string; end: string; }; - -export const StyledRange = styled.input` - width: 600px; - padding: 0; /* nécessaire pour IE */ - margin: 0; - margin-top: -1px; - appearance: none; /* nécessaire pour IE */ - -moz-appearance: none; /* nécessaire pour Firefox */ - -webkit-appearance: none; /* nécessaire pour Chrome */ - font: inherit; /* même rendu suivant font document */ - outline: none; - opacity: 1; - background: transparent; /* sert pour couleur de fond de la zone de déplacement */ - box-sizing: content-box; /* même modèle de boîte pour tous */ - transition: opacity 0.2s; - cursor: pointer; - position: absolute; - z-index: 10; - height: 16px; - :focus-visible::-webkit-slider-thumb { - ${FocusVisibleStyle} - } - /*==============================*/ - /* cursor */ - /*==============================*/ - &::-webkit-slider-thumb { - -webkit-appearance: none; - padding: 0; - appearance: none; - margin: 0; - width: 3px; - height: 16px; - background-color: ${(props) => props.theme.selectedActive}; - } - &::-moz-range-thumb { - margin: 0; - width: 2px; - height: 16px; - background-color: ${(props) => props.theme.selectedActive}; - border: none; - } -`; - -export const StyledDataList = styled.datalist` - display: none; -`; +const barWidth = 600; // width of the bar chart export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { const history = useHistoryAlert(); - const [isVisible, setIsVisible] = useState(false); - const [isFocused, setIsFocused] = useState(false); const [tooltipData, setTooltipData] = useState(null); - const popoverRef = useRef(null); const theme = useTheme(); useEffect(() => { - setIsVisible(true); - }, [history.selectedDate]); - - // TODO : Border radius on alerts close to the start and end - // TODO : + if (history.selectedDate === 0) { + history.setSelectedDate(endDate); + } + }, []); - // TODO change tooltip position if it's out of the screen const startDate = new Date(start).getTime(); const endDate = new Date(end).getTime(); - const oneHour = 60 * 60 * 1000; - const oneDay = 24 * oneHour; - const oneWeek = 7 * oneDay; - const span = endDate - startDate; - - // get 20%, 40%, 60%, 80% of span interval for ticks - // value = (percentage * (max - min) / 100) + min - const getDataListOptions = (startDate, endDate) => { - const oneDay = 24 * 60 * 60 * 1000; - const span = endDate - startDate; - if (span === 7 * oneDay) { - return Array.from({ length: 6 }, (_, i) => endDate - (i + 1) * oneDay); - } - return Array.from({ length: 4 }, (_, i) => endDate - ((i + 1) / 5) * span); - }; - const getStep = (startDate, endDate) => { - const oneHour = 60 * 60 * 1000; - const oneDay = 24 * oneHour; - const span = endDate - startDate; - if (span === 7 * oneDay) { - return oneHour; - } else if (span === oneDay) { - return oneHour / 4; - } else if (span === oneHour) { - return 60 * 1000; - } - }; - const getRadius = (start, end, startDate, endDate): RectRadius => { - const marge = span >= oneDay ? 0.011 * span : 0; - // TODO need to correct the conditions (don't take into || only &&) - if (start === startDate && end === endDate) { - return [15, 15, 15, 15]; - } else if (start <= startDate + marge && end >= endDate - marge) { - return [6, 6, 6, 6]; - } else if (start === startDate) { - return [15, 0, 0, 15]; - } else if (end === endDate) { - return [0, 15, 15, 0]; - } else if (start <= startDate + marge) { - return [6, 0, 0, 6]; - } else if (end >= endDate - marge) { - return [0, 6, 6, 0]; - } else { - return [0, 0, 0, 0]; - } - }; - - const setHistoryTooltipPosition = (): string => { - const history = useHistoryAlert(); - if (history.selectedDate && popoverRef.current) { - const width = - ((history.selectedDate - startDate) / (endDate - startDate)) * 600; - const leftPosition = width - popoverRef.current.offsetWidth / 2; - return `auto auto -4px ${leftPosition}px`; - } - return 'auto'; - }; - const data = [ { start: startDate, @@ -188,165 +58,46 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { key.startsWith('rangecritical'), ); - const toggleVisibility = (e) => { - if (!isFocused) { - if (e.type === 'mouseleave') setIsVisible(false); - } - if (e.type === 'mouseenter') setIsVisible(true); - if (e.type === 'blur') { - setIsVisible(false); - setIsFocused(false); - } - if (e.type === 'focus') { - setIsVisible(true); - setIsFocused(true); - } - }; - const CustomTooltip = (props) => { - const { tooltipData, coordinate } = props; - const tooltipRef = useRef(null); - const [tooltipInset, setTooltipInset] = useState({ top: 0, left: 0 }); - - useEffect(() => { - if (tooltipRef.current) { - // console.log('tooltip', tooltipRef.current); - // console.log('tooltipCoord', tooltipRef.current.getBoundingClientRect()); - // left and top < 0 = tooltip is out of the screen - // right or bottom > window.innerWidth or window.innerheight = tooltip is out of the screen - - setTooltipInset({ - left: coordinate.x - tooltipRef.current.offsetWidth / 2, - top: coordinate.y + 20, - }); - } - }, [tooltipRef.current, coordinate]); - if (tooltipData) { - const { payload, name } = tooltipData[0]; - const tooltipName = name.replace('range', ''); - return ( -
- - View details on Alert Page - - - - Severity: - {payload[`${tooltipName}Severity`]} - - - Start: - - - - End: - - - - Description: - {payload[`${tooltipName}Description`]} - - -
- ); - } - - return null; + const rectangleRenderer = (props, key) => { + const { x, y, height, fill } = props; + + const start = props[key][0] < startDate ? startDate : props[key][0]; + const end = props[key][1] > endDate ? endDate : props[key][1]; + const relativeSize = (end - start) / (endDate - startDate); + return ( + + ); }; return ( -
- {history.selectedDate !== null && ( -
-
-
- -
- -
- - { - if (e.target.valueAsNumber > endDate) - history.setSelectedDate(endDate); - if (e.target.valueAsNumber < startDate) - history.setSelectedDate(startDate); - history.setSelectedDate(+e.target.value); - }} - /> - - {getDataListOptions(startDate, endDate).map((date) => ( - - ))} - -
- )} +
+ - {span === oneWeek ? ( - <> - - {DATE_FORMATER.format(new Date(payload.value))} - - - - {TIME_FORMATER.format(new Date(payload.value))} - - - ) : span === oneDay ? ( - DATE_FORMATER.format(new Date(payload.value)) + - ' ' + - TIME_FORMATER.format(new Date(payload.value)) - ) : ( - TIME_SECOND_FORMATER.format(new Date(payload.value)) + {getTickFormatter( + startDate, + endDate, + new Date(payload.value), )} @@ -414,6 +153,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { fill={theme.statusHealthy} radius={15} yAxisId="background" + isAnimationActive={false} /> {warningKeys.map((key) => ( @@ -426,25 +166,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { }} onPointerLeave={() => setTooltipData(null)} fill={theme.statusWarning} - shape={(props) => { - const { x, y, height, fill } = props; - - const start = - props[key][0] < startDate ? startDate : props[key][0]; - const end = props[key][1] > endDate ? endDate : props[key][1]; - const test = (end - start) / (endDate - startDate); - return ( - - ); - }} + shape={(props) => rectangleRenderer(props, key)} > ))} @@ -459,25 +181,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { setTooltipData(e.tooltipPayload); }} onPointerLeave={() => setTooltipData(null)} - shape={(props) => { - const { x, y, height, fill } = props; - - const start = - props[key][0] < startDate ? startDate : props[key][0]; - const end = props[key][1] > endDate ? endDate : props[key][1]; - const test = (end - start) / (endDate - startDate); - return ( - - ); - }} + shape={(props) => rectangleRenderer(props, key)} /> ))} diff --git a/src/lib/components/globalhealthbar/HistorySlider.tsx b/src/lib/components/globalhealthbar/HistorySlider.tsx new file mode 100644 index 0000000000..9a2755d327 --- /dev/null +++ b/src/lib/components/globalhealthbar/HistorySlider.tsx @@ -0,0 +1,131 @@ +import { useEffect, useRef, useState } from 'react'; +import styled, { css, useTheme } from 'styled-components'; +import { FormattedDateTime, Icon, spacing } from '../../index'; +import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component'; +import { useHistoryAlert } from './HistoryProvider'; +import { getStep, setHistoryTooltipPosition } from './utils'; + +const StyledRange = styled.input` + width: 600px; + padding: 0; /* nécessaire pour IE */ + margin: 0; + margin-top: 2px; + appearance: none; /* nécessaire pour IE */ + -moz-appearance: none; /* nécessaire pour Firefox */ + -webkit-appearance: none; /* nécessaire pour Chrome */ + font: inherit; /* même rendu suivant font document */ + outline: none; + opacity: 1; + background: transparent; /* sert pour couleur de fond de la zone de déplacement */ + box-sizing: content-box; /* même modèle de boîte pour tous */ + transition: opacity 0.2s; + cursor: pointer; + position: absolute; + z-index: 10; + height: 16px; + :focus-visible::-webkit-slider-thumb { + ${FocusVisibleStyle} + } + /*==============================*/ + /* cursor */ + /*==============================*/ + &::-webkit-slider-thumb { + -webkit-appearance: none; + padding: 0; + appearance: none; + margin: 0; + width: 2px; + height: 16px; + background-color: ${(props) => props.theme.selectedActive}; + } + &::-moz-range-thumb { + margin: 0; + width: 2px; + height: 16px; + background-color: ${(props) => props.theme.selectedActive}; + border: none; + } +`; + +const HistoryContainer = styled.div` + width: 100%; + position: relative; +`; + +const HistoryTooltipContainer = styled.div<{ inset: string }>` + position: absolute; + display: flex; + inset: ${(props) => props.inset}; + align-items: center; + flex-direction: column; + gap: ${spacing.r2}; +`; + +const HistoryTooltip = styled.div` + ${(props) => { + const theme = useTheme(); + return css` + padding: ${spacing.r4} ${spacing.r8}; + white-space: 'nowrap'; + border: 1px solid ${theme.border}; + border-radius: '4px'; + color: ${theme.textSecondary}; + `; + }} +`; + +export const HistoryAlertSlider = ({ start, end, startDate, endDate }) => { + const history = useHistoryAlert(); + const popoverRef = useRef(null); + const [tooltipPosition, setTooltipPosition] = useState('auto'); + useEffect(() => { + if (popoverRef.current) { + setTooltipPosition( + setHistoryTooltipPosition( + startDate, + endDate, + popoverRef.current, + history.selectedDate, + ), + ); + } + }, [history.selectedDate, startDate, endDate, popoverRef.current]); + + if (!history.selectedDate) { + return null; + } + return ( + + + + + + + + + { + if (e.target.valueAsNumber > endDate) + history.setSelectedDate(endDate); + if (e.target.valueAsNumber < startDate) + history.setSelectedDate(startDate); + history.setSelectedDate(+e.target.value); + }} + /> + + ); +}; diff --git a/src/lib/components/globalhealthbar/utils.tsx b/src/lib/components/globalhealthbar/utils.tsx new file mode 100644 index 0000000000..39f8c0e460 --- /dev/null +++ b/src/lib/components/globalhealthbar/utils.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { RectRadius } from 'recharts/types/shape/Rectangle'; +import { + DATE_FORMATER, + TIME_FORMATER, + TIME_SECOND_FORMATER, +} from '../date/FormattedDateTime'; + +const oneHour = 60 * 60 * 1000; +const oneDay = 24 * oneHour; + +export const setHistoryTooltipPosition = ( + startDate: number, + endDate: number, + popover: HTMLDivElement, + selectedDate: number | null, +): string => { + if (selectedDate && popover) { + const width = ((selectedDate - startDate) / (endDate - startDate)) * 600; + const leftPosition = width - popover.offsetWidth / 2; + return `auto auto -4px ${leftPosition}px`; + } + return 'auto'; +}; + +export const getRadius = ( + start: number, + end: number, + startDate: number, + endDate: number, +): RectRadius => { + const span = endDate - startDate; + const marge = span >= oneDay ? 0.011 * span : 0; + let radius = [0, 0]; + let rightRadius = [0, 0]; + + if (start === startDate) { + radius = [15, 15]; + } else if (start <= startDate + marge) { + radius = [6, 6]; + } + if (end === endDate) { + rightRadius = [15, 15]; + } else if (end >= endDate - marge) { + rightRadius = [6, 6]; + } + radius.splice(1, 0, ...rightRadius); + + return radius as RectRadius; +}; + +export const getStep = (startDate: number, endDate: number): number => { + const span = endDate - startDate; + if (span === 7 * oneDay) { + return oneHour; + } else if (span === oneDay) { + return oneHour / 4; + } else return 60 * 1000; +}; + +export const getDataListOptions = ( + startDate: number, + endDate: number, +): number[] => { + const span = endDate - startDate; + if (span === 7 * oneDay) { + return Array.from({ length: 6 }, (_, i) => endDate - (i + 1) * oneDay); + } + return Array.from({ length: 4 }, (_, i) => endDate - ((i + 1) / 5) * span); +}; + +export const getTickFormatter = ( + startDate: number, + endDate: number, + payloadValue: Date, +): React.ReactNode => { + const span = endDate - startDate; + if (span === 7 * oneDay) { + return ( + <> + + {DATE_FORMATER.format(payloadValue)} + + + + {TIME_FORMATER.format(payloadValue)} + + + ); + } + if (span === oneDay) { + return ( + DATE_FORMATER.format(payloadValue) + + ' ' + + TIME_FORMATER.format(payloadValue) + ); + } else return TIME_SECOND_FORMATER.format(payloadValue); +}; From 6c6dfad5a003ce8a0b1596e9f3b4bfcb3bcb6615 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Thu, 23 May 2024 18:26:23 +0200 Subject: [PATCH 7/9] remove ref for tooltip of history and add fixed value for width --- .../GlobalHealthBarRecharts.component.tsx | 2 +- .../globalhealthbar/HistoryProvider.tsx | 2 +- .../globalhealthbar/HistorySlider.tsx | 66 +++++++++++-------- src/lib/components/globalhealthbar/utils.tsx | 9 ++- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx index ea5fd4b6e8..3198315e39 100644 --- a/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -81,7 +81,7 @@ export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { style={{ padding: 60, position: 'relative', - width: barWidth, + width: '100%', }} > (undefined); export const HistoryAlertProvider = ({ children }) => { - const [selectedDate, setSelectedDate] = useState(0); + const [selectedDate, setSelectedDate] = useState(Date.now()); return ( {children} diff --git a/src/lib/components/globalhealthbar/HistorySlider.tsx b/src/lib/components/globalhealthbar/HistorySlider.tsx index 9a2755d327..61546a5a2f 100644 --- a/src/lib/components/globalhealthbar/HistorySlider.tsx +++ b/src/lib/components/globalhealthbar/HistorySlider.tsx @@ -1,4 +1,3 @@ -import { useEffect, useRef, useState } from 'react'; import styled, { css, useTheme } from 'styled-components'; import { FormattedDateTime, Icon, spacing } from '../../index'; import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component'; @@ -23,9 +22,7 @@ const StyledRange = styled.input` position: absolute; z-index: 10; height: 16px; - :focus-visible::-webkit-slider-thumb { - ${FocusVisibleStyle} - } + /*==============================*/ /* cursor */ /*==============================*/ @@ -45,6 +42,12 @@ const StyledRange = styled.input` background-color: ${(props) => props.theme.selectedActive}; border: none; } + :focus-visible::-webkit-slider-thumb { + ${FocusVisibleStyle} + } + :focus-visible::-moz-range-thumb { + ${FocusVisibleStyle} + } `; const HistoryContainer = styled.div` @@ -52,9 +55,9 @@ const HistoryContainer = styled.div` position: relative; `; -const HistoryTooltipContainer = styled.div<{ inset: string }>` +const HistoryTooltipContainer = styled.div<{ inset }>` position: absolute; - display: flex; + display: ${(props) => (props.inset ? 'flex' : 'none')}; inset: ${(props) => props.inset}; align-items: center; flex-direction: column; @@ -68,38 +71,47 @@ const HistoryTooltip = styled.div` padding: ${spacing.r4} ${spacing.r8}; white-space: 'nowrap'; border: 1px solid ${theme.border}; - border-radius: '4px'; + border-radius: 4px; + width: 116px; color: ${theme.textSecondary}; `; }} `; +type HistorySliderProps = { + start: string; + end: string; + startDate: number; + endDate: number; +}; -export const HistoryAlertSlider = ({ start, end, startDate, endDate }) => { +export const HistoryAlertSlider = ({ + start, + end, + startDate, + endDate, +}: HistorySliderProps) => { const history = useHistoryAlert(); - const popoverRef = useRef(null); - const [tooltipPosition, setTooltipPosition] = useState('auto'); - useEffect(() => { - if (popoverRef.current) { - setTooltipPosition( - setHistoryTooltipPosition( - startDate, - endDate, - popoverRef.current, - history.selectedDate, - ), - ); - } - }, [history.selectedDate, startDate, endDate, popoverRef.current]); - if (!history.selectedDate) { + if (history.selectedDate === null) { return null; } + // check in 1hour range : bug with input date going from 1:00 to 0:00 + if (history.selectedDate > endDate) { + history.setSelectedDate(endDate); + } + if (history.selectedDate < startDate) { + history.setSelectedDate(startDate); + } + return ( { step={getStep(startDate, endDate)} value={history.selectedDate} onChange={(e) => { - if (e.target.valueAsNumber > endDate) - history.setSelectedDate(endDate); - if (e.target.valueAsNumber < startDate) - history.setSelectedDate(startDate); history.setSelectedDate(+e.target.value); }} /> diff --git a/src/lib/components/globalhealthbar/utils.tsx b/src/lib/components/globalhealthbar/utils.tsx index 39f8c0e460..7c1a7ea424 100644 --- a/src/lib/components/globalhealthbar/utils.tsx +++ b/src/lib/components/globalhealthbar/utils.tsx @@ -12,12 +12,11 @@ const oneDay = 24 * oneHour; export const setHistoryTooltipPosition = ( startDate: number, endDate: number, - popover: HTMLDivElement, - selectedDate: number | null, -): string => { - if (selectedDate && popover) { + selectedDate: number | undefined, +): string | null => { + if (selectedDate) { const width = ((selectedDate - startDate) / (endDate - startDate)) * 600; - const leftPosition = width - popover.offsetWidth / 2; + const leftPosition = width - 132 / 2; return `auto auto -4px ${leftPosition}px`; } return 'auto'; From b6849388887aee1079dec52a578400e738de2780 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Thu, 23 May 2024 18:27:18 +0200 Subject: [PATCH 8/9] change value in storie to test next to start --- stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx index ef3222eb46..518e7141b7 100644 --- a/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx +++ b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx @@ -68,7 +68,7 @@ const alerts = [ { id: '6', severity: 'warning', - startsAt: '2021-01-30T23:40:00Z', + startsAt: '2021-01-30T22:30:00Z', endsAt: '2021-01-31T23:59:00Z', description: 'Global health warning', }, From 70018a390b5634aad5b03e0cafd75cbed176a8d0 Mon Sep 17 00:00:00 2001 From: Jean-Marc Millet Date: Thu, 23 May 2024 18:39:44 +0200 Subject: [PATCH 9/9] put if statements in useEffect --- .../globalhealthbar/HistorySlider.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lib/components/globalhealthbar/HistorySlider.tsx b/src/lib/components/globalhealthbar/HistorySlider.tsx index 61546a5a2f..1890d075ca 100644 --- a/src/lib/components/globalhealthbar/HistorySlider.tsx +++ b/src/lib/components/globalhealthbar/HistorySlider.tsx @@ -3,6 +3,7 @@ import { FormattedDateTime, Icon, spacing } from '../../index'; import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component'; import { useHistoryAlert } from './HistoryProvider'; import { getStep, setHistoryTooltipPosition } from './utils'; +import { useEffect } from 'react'; const StyledRange = styled.input` width: 600px; @@ -92,16 +93,21 @@ export const HistoryAlertSlider = ({ }: HistorySliderProps) => { const history = useHistoryAlert(); + // check in 1hour range : bug with input date going from 1:00 to 0:00 + useEffect(() => { + if (history.selectedDate != null) { + if (history.selectedDate > endDate) { + history.setSelectedDate(endDate); + } + if (history.selectedDate < startDate) { + history.setSelectedDate(startDate); + } + } + }, [history.selectedDate, startDate, endDate]); + if (history.selectedDate === null) { return null; } - // check in 1hour range : bug with input date going from 1:00 to 0:00 - if (history.selectedDate > endDate) { - history.setSelectedDate(endDate); - } - if (history.selectedDate < startDate) { - history.setSelectedDate(startDate); - } return (