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/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 new file mode 100644 index 0000000000..3198315e39 --- /dev/null +++ b/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx @@ -0,0 +1,190 @@ +import { Bar, BarChart, XAxis, YAxis, Tooltip, Rectangle } from 'recharts'; +import { useTheme } from 'styled-components'; +import { useEffect, useState } from 'react'; +import { useHistoryAlert } from './HistoryProvider'; +import { getDataListOptions, getRadius, getTickFormatter } from './utils'; +import { HistoryAlertSlider } from './HistorySlider'; +import { CustomTooltip } from './CustomTooltip'; + +export type GlobalHealthProps = { + id: string; + alerts: { + description: string; + startsAt: string; + endsAt: string; + severity: string; + }[]; + start: string; + end: string; +}; +const barWidth = 600; // width of the bar chart + +export function GlobalHealthBar({ id, alerts, start, end }: GlobalHealthProps) { + const history = useHistoryAlert(); + const [tooltipData, setTooltipData] = useState(null); + const theme = useTheme(); + + useEffect(() => { + if (history.selectedDate === 0) { + history.setSelectedDate(endDate); + } + }, []); + + const startDate = new Date(start).getTime(); + const endDate = new Date(end).getTime(); + + const data = [ + { + start: startDate, + end: endDate, + range: [startDate, endDate], + ...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'), + ); + + 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 ( +
+ + + + { + const { x, y, payload } = props; + return ( + + + {getTickFormatter( + startDate, + endDate, + 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) => rectangleRenderer(props, key)} + > + ))} + + {criticalKeys.map((key) => ( + { + setTooltipData(e.tooltipPayload); + }} + onPointerLeave={() => setTooltipData(null)} + shape={(props) => rectangleRenderer(props, key)} + /> + ))} + +
+ ); +} diff --git a/src/lib/components/globalhealthbar/HistoryProvider.tsx b/src/lib/components/globalhealthbar/HistoryProvider.tsx new file mode 100644 index 0000000000..820cb2ae96 --- /dev/null +++ b/src/lib/components/globalhealthbar/HistoryProvider.tsx @@ -0,0 +1,26 @@ +import { createContext, useContext, useState } from 'react'; + +type HistoryAlertContextType = { + selectedDate: number; + setSelectedDate: (date: number) => void; +}; +export const HistoryAlertContext = createContext< + HistoryAlertContextType | undefined +>(undefined); + +export const HistoryAlertProvider = ({ children }) => { + const [selectedDate, setSelectedDate] = useState(Date.now()); + return ( + + {children} + + ); +}; + +export const useHistoryAlert = () => { + const context = useContext(HistoryAlertContext); + if (!context) { + return { selectedDate: null }; + } + return context; +}; diff --git a/src/lib/components/globalhealthbar/HistorySlider.tsx b/src/lib/components/globalhealthbar/HistorySlider.tsx new file mode 100644 index 0000000000..1890d075ca --- /dev/null +++ b/src/lib/components/globalhealthbar/HistorySlider.tsx @@ -0,0 +1,145 @@ +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'; +import { useEffect } from 'react'; + +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; + + /*==============================*/ + /* 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; + } + :focus-visible::-webkit-slider-thumb { + ${FocusVisibleStyle} + } + :focus-visible::-moz-range-thumb { + ${FocusVisibleStyle} + } +`; + +const HistoryContainer = styled.div` + width: 100%; + position: relative; +`; + +const HistoryTooltipContainer = styled.div<{ inset }>` + position: absolute; + display: ${(props) => (props.inset ? 'flex' : 'none')}; + 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; + width: 116px; + color: ${theme.textSecondary}; + `; + }} +`; +type HistorySliderProps = { + start: string; + end: string; + startDate: number; + endDate: number; +}; + +export const HistoryAlertSlider = ({ + start, + end, + startDate, + endDate, +}: 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; + } + + return ( + + + + + + + + + { + 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..7c1a7ea424 --- /dev/null +++ b/src/lib/components/globalhealthbar/utils.tsx @@ -0,0 +1,97 @@ +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, + selectedDate: number | undefined, +): string | null => { + if (selectedDate) { + const width = ((selectedDate - startDate) / (endDate - startDate)) * 600; + const leftPosition = width - 132 / 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); +}; diff --git a/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx new file mode 100644 index 0000000000..518e7141b7 --- /dev/null +++ b/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx @@ -0,0 +1,169 @@ +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_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-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 = [ + { + id: '1', + severity: 'warning', + startsAt: '2021-02-01T07: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-02T22: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', + }, + { + id: '6', + severity: 'warning', + startsAt: '2021-01-30T22:30:00Z', + endsAt: '2021-01-31T23:59:00Z', + description: 'Global health warning', + }, +]; + +export const Default: Story = { + args: { + start, + end, + alerts, + }, +}; + +const InputDate = ({ start, end }) => { + const history = useHistoryAlert(); + if (history.selectedDate !== null) { + const handleChange = (e) => { + history.setSelectedDate(new Date(e.target.value).valueOf()); + }; + + return ( + + ); + } + + return <>; +}; + +export const WithSelectedDate24h: Story = { + render: () => { + return ( + <> + + + + + + + ); + }, +}; +export const WithSelectedDateWeek: Story = { + render: () => { + return ( + <> + + + + + + + ); + }, +}; + +export const WithSelectedDateHour: 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 d611e94ce3..d5124637e2 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', @@ -34,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-06T22:30:00Z', + description: 'Global health warning', + }, ]; const alertsLast24h = [ { @@ -105,13 +114,18 @@ const endNotFirstDay = '2021-03-01T23:00:00Z'; export default { title: 'Components/Data Display/Charts/GlobalHealthBar', - component: GlobalHealthBar, + component: (props) => ( + + + + + ), decorators: [ (story) => ( -
+
{story()}