From a6d9dc383431a3721f1a34e11d2f1e9648a99fcd Mon Sep 17 00:00:00 2001 From: tplocic20 Date: Mon, 26 Aug 2024 12:22:16 +0200 Subject: [PATCH] #7-tree-structure-view --- src/components/ClientAllocationPage/index.js | 2 - .../DashboardPageV2/dataCapFlowGraph.js | 1 - .../DashboardPageV2/dataCapFlowTree.js | 269 ++++++++++++++---- src/components/DashboardPageV2/index.js | 24 +- src/components/DashboardPageV2/s.module.css | 40 +++ src/hooks/useScrollToHash.js | 29 ++ src/index.css | 7 + 7 files changed, 308 insertions(+), 64 deletions(-) create mode 100644 src/hooks/useScrollToHash.js diff --git a/src/components/ClientAllocationPage/index.js b/src/components/ClientAllocationPage/index.js index 14244e9..ddfc13b 100644 --- a/src/components/ClientAllocationPage/index.js +++ b/src/components/ClientAllocationPage/index.js @@ -1,7 +1,5 @@ import { useParams } from 'react-router-dom'; - import { useFetch } from 'hooks/fetch'; - import { PageHeading } from 'components/PageHeading'; import { TableHeading } from 'components/TableHeading'; import { Table } from 'components/Table'; diff --git a/src/components/DashboardPageV2/dataCapFlowGraph.js b/src/components/DashboardPageV2/dataCapFlowGraph.js index 18a2667..92d0b12 100644 --- a/src/components/DashboardPageV2/dataCapFlowGraph.js +++ b/src/components/DashboardPageV2/dataCapFlowGraph.js @@ -112,7 +112,6 @@ export const DataCapFlowGraph = () => { }); skData = {...skData, ...sankeyLevel2(allocators, dc)}; }) - return skData; } diff --git a/src/components/DashboardPageV2/dataCapFlowTree.js b/src/components/DashboardPageV2/dataCapFlowTree.js index 1de9b12..39b4fbd 100644 --- a/src/components/DashboardPageV2/dataCapFlowTree.js +++ b/src/components/DashboardPageV2/dataCapFlowTree.js @@ -1,78 +1,243 @@ import { useFetch } from '../../hooks/fetch'; import { convertBytesToIEC } from '../../utils/bytes'; import { useEffect, useMemo, useState } from 'react'; -import { ResponsiveContainer, Sankey, Tooltip } from 'recharts'; -import filesize from 'filesize'; +import Tree from 'react-d3-tree'; import s from './s.module.css'; -import { xFormatter } from '../../utils/chart'; -import { ArrowLeft, ArrowRight } from 'lucide-react'; -import { Link } from 'react-router-dom'; import cn from 'classnames'; -import Tree from 'react-d3-tree'; const PB_10 = 10 * 1024 * 1024 * 1024 * 1024 * 1024; const PB_15 = 15 * 1024 * 1024 * 1024 * 1024 * 1024; export const DataCapFlowTree = () => { - const fetchUrl = '/get-dc-flow-graph'; - + const fetchUrl = '/get-dc-flow-graph-grouped-by-audit-status'; const [data, { loading, loaded }] = useFetch(fetchUrl); + const [dimensions, setDimensions] = useState({ + width: 0, height: 0 + }); + const [translation, setTranslation] = useState({ + x: 0, y: 0 + }); + const [treeChartContainerRef, setTreeChartContainerRef] = useState(undefined); - const groupChildren = (children) => { + const render = (props) => ( + + ); - } + const getName = (key) => { + switch (key) { + case 'rkh': + return 'Root Key Holder'; + case 'inactiveAllocators': + return 'Inactive Allocators'; + case 'activeAllocators': + return 'Active Allocators'; + case 'passedAudit': + return 'Passed Audit'; + case 'passedAuditConditionally': + return 'Passed Audit Conditionally'; + case 'failedAudit': + return 'Failed Audit'; + case 'notAudited': + return 'Not Audited'; + default: + return key; + } + }; - const treeData = useMemo(() => { - if (!data?.rkh) { - return null; + const countChildAllocators = (data) => { + if (data?.datacap) { + return undefined; } - let skData = { - name: 'Root Key Holder', - children: [ - { - name: '<10 PiB', - children: Object.entries(data.rkh).filter(([key, value]) => { - return value.datacap < PB_10 - }).map(([key, value]) => ({ - name: key, - children: [] - })) - }, - { - name: '>10 PiB & <15 PiB', - children: Object.entries(data.rkh).filter(([key, value]) => { - return value.datacap >= PB_10 && value.datacap < PB_15 - }).map(([key, value]) => ({ - name: key, - children: [] - })) - }, { - name: '>15 PiB', - children: Object.entries(data.rkh).filter(([key, value]) => { - return value.datacap >= PB_15 - }).map(([key, value]) => ({ - name: key, - children: [] - })) - } - ] - }; + if (data?.allocators?.length) { + return data.allocators.length; + } else { + return Object.entries(data).reduce((acc, [key, data]) => { + return acc + countChildAllocators(data); + }, 0); + } + }; - console.log(data); + const groupAllocators = (allocators, skipUnique) => { - console.log('skData', skData) + const uniqueAllocationValues = [...new Set(allocators.map(a => a.datacap))]; - return skData; + if (uniqueAllocationValues.length > 3 && !skipUnique) { + const datacapAllocatorsGrouped = Object.groupBy(Object.values(allocators), item => { + if (item.datacap < PB_10) { + return '<10 PiB'; + } else if (item.datacap < PB_15) { + return '>10 PiB & <15 PiB'; + } else { + return '>15 PiB'; + } + }); + return Object.entries(datacapAllocatorsGrouped).map(([key, data]) => { + return { + name: key, + attributes: { + datacap: convertBytesToIEC(data.reduce((acc, curr) => acc + +curr.datacap, 0)), + allocators: data.length + }, + children: groupAllocators(data, true) + }; + }); + } else { + const datacapAllocatorsGrouped = Object.groupBy(Object.values(allocators), item => convertBytesToIEC(+item.datacap)); + if (Object.keys(datacapAllocatorsGrouped).length === 1) { + return Object.values(datacapAllocatorsGrouped)[0].map((data) => { + return { + name: data.name ?? data.addressId, + attributes: { + datacap: convertBytesToIEC(+data.datacap), + id: data.addressId + }, + children: undefined + }; + }); + } + return Object.entries(datacapAllocatorsGrouped).map(([key, data]) => { + if (data.length === 1) { + return { + name: data[0].name ?? data[0].addressId, + attributes: { + datacap: convertBytesToIEC(+data[0].datacap), + id: data[0].addressId + }, + children: undefined + }; + } + return { + name: key, + attributes: { + datacap: convertBytesToIEC(data.reduce((acc, curr) => acc + +curr.datacap, 0)), + allocators: data.length + }, + children: data.map((data) => { + return { + name: data.name ?? data.addressId, + attributes: { + datacap: convertBytesToIEC(+data.datacap), + id: data.addressId + }, + children: undefined + }; + }) + }; + }); + } + }; + + const parseChildren = (data) => { + if (data?.allocators?.length) { + if (data?.allocators?.length > 10) { + return groupAllocators(data.allocators); + } + return data.allocators.map((data) => { + return { + name: data.name ?? data.addressId, + attributes: { + datacap: convertBytesToIEC(+data.datacap), + id: data.addressId + }, + children: undefined + }; + }); + } else { + return Object.entries(data).map(([key, data]) => { + return { + name: getName(key), + attributes: { + datacap: convertBytesToIEC(data.totalDc ? +data.totalDc : Object.values(data).reduce((acc, val) => acc + +val.totalDc, 0)), + allocators: countChildAllocators(data) + }, + children: parseChildren(data) + }; + }); + } + }; + + const treeData = useMemo(() => { + return Object.entries(data).map(([key, data]) => { + return { + name: getName(key), + children: parseChildren(data) + }; + }); }, [data]); - return <> - {treeData &&
-
- + useEffect(() => { + if (treeChartContainerRef?.getBoundingClientRect) { + const dimensions = treeChartContainerRef.getBoundingClientRect(); + setDimensions(dimensions); + setTranslation({ + x: dimensions.width / 5, + y: dimensions.height / 2 + }); + } + }, [treeChartContainerRef]); + + return
+ {!!treeData?.length &&
+
setTreeChartContainerRef(ref)} id="treeWrapper" style={{ width: '100%', height: '1000px' }}> +
} - ; +
; +}; + + +const TreeNode = ({ nodeDatum, toggleNode, onNodeClick }) => { + return ( + <> + + + + {nodeDatum.name} + + + {nodeDatum.attributes && + Object.entries(nodeDatum.attributes).map(([labelKey, labelValue], i) => ( + + {labelKey}: {labelValue} + + ))} + + {!!nodeDatum?.attributes?.id && { + window.open(`notaries\\${nodeDatum?.attributes?.id}`, '_blank'); + }} + > + See details + } + + + ); }; diff --git a/src/components/DashboardPageV2/index.js b/src/components/DashboardPageV2/index.js index e64debc..37708a9 100644 --- a/src/components/DashboardPageV2/index.js +++ b/src/components/DashboardPageV2/index.js @@ -2,7 +2,7 @@ import cn from 'classnames'; import { useFetch } from 'hooks/fetch'; import { convertBytesToIEC } from 'utils/bytes'; import s from './s.module.css'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { Link } from 'react-router-dom'; import { ArrowRight } from 'lucide-react'; import { DataCapAllocVsAvailable } from './dataCapAllocVsAvailable'; @@ -12,10 +12,12 @@ import { DataCapFlowGraph } from './dataCapFlowGraph'; import { DataCapFlowTree } from './dataCapFlowTree'; import { LoadingValue } from '../LoadingValue'; import { ContentTabs } from '../ContentTabs'; +import { useScrollToHash } from '../../hooks/useScrollToHash'; export default function DashboardPage() { const fetchUrl = '/getFilPlusStats'; const [toggle, setToggle] = useState(false); + const scrollToHash = useScrollToHash(); const [data, { loading, loaded }] = useFetch(fetchUrl); @@ -24,9 +26,16 @@ export default function DashboardPage() { numberOfActiveNotariesV2, numberOfAllocators, totalDcGivenToAllocators, - totalDcUsedByAllocators, + totalDcUsedByAllocators } = data; + const toggleChartsWrapper = useCallback(() => { + if (!toggle) { + scrollToHash('chartsWrapper'); + } + setToggle(!toggle); + }, [toggle, scrollToHash]); + return (

State of Fil+

@@ -98,10 +107,10 @@ export default function DashboardPage() {
-
+
System Structure -
- + + -
- Coming soon... -
- {/**/}
diff --git a/src/components/DashboardPageV2/s.module.css b/src/components/DashboardPageV2/s.module.css index 317d495..0cf1ec2 100644 --- a/src/components/DashboardPageV2/s.module.css +++ b/src/components/DashboardPageV2/s.module.css @@ -339,3 +339,43 @@ .statusCardOutOfSync { background-color: red; } + +.treeLabelNode { + fill: var(--color-medium-turquoise); + stroke: var(--color-dodger-blue); + stroke-width: 1; +} + +.treeLabelNodeLeaf { + fill: var(--color-white); + stroke: var(--color-dodger-blue); + stroke-width: 1; +} + +.treeLabelTitle { + font-size: 16px; + line-height: 18px; + font-weight: 300; + fill: var(--color-black); + margin: 0; + stroke-width: 0.5; +} + +.treeLabelTitleHoverable { + cursor: pointer; + stroke-width: 0.4; + fill: var(--color-mountain-meadow); +} + +.treeLabelTitleHoverable:hover { + text-decoration: underline; +} + +.treeLabelInfo { + font-size: 14px; + line-height: 16px; + font-weight: 300; + fill: var(--color-dodger-blue); + margin: 0; + stroke-width: 0.3; +} diff --git a/src/hooks/useScrollToHash.js b/src/hooks/useScrollToHash.js new file mode 100644 index 0000000..57bae71 --- /dev/null +++ b/src/hooks/useScrollToHash.js @@ -0,0 +1,29 @@ +import { useCallback } from 'react'; + +const useScrollToHash = () => { + return useCallback((elementId, offset = 50) => { + + const scroll = () => { + const element = document.getElementById(elementId); + if (!element) { + return; + } + + const bodyRect = document.body.getBoundingClientRect().top; + const elementRect = element.getBoundingClientRect().top; + const elementPosition = elementRect - bodyRect; + const offsetPosition = elementPosition - offset; + + window.scrollTo({ + top: offsetPosition, + behavior: 'smooth', + }); + }; + + requestAnimationFrame(() => { + requestAnimationFrame(scroll); + }); + }, []); +}; + +export { useScrollToHash }; diff --git a/src/index.css b/src/index.css index 3d8e271..d396414 100644 --- a/src/index.css +++ b/src/index.css @@ -244,3 +244,10 @@ main { padding: 0 80px; } } + +.rd3t-leaf-node { + cursor: pointer; + fill: var(--color-dodger-blue) !important; + stroke: #000; + stroke-width: 1; +}