diff --git a/package.json b/package.json index c6cc9f2ddf..db84d14a7b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dayjs": "1.11.10", "default-passive-events": "2.0.0", "echarts": "4.9.0", + "echarts-gl": "1.1.2", "history": "5.3.0", "i18next": "20.6.1", "jsbi": "3.2.5", diff --git a/public/images/chart/dark.webp b/public/images/chart/dark.webp new file mode 100644 index 0000000000..e42499caf7 Binary files /dev/null and b/public/images/chart/dark.webp differ diff --git a/public/images/chart/earth.jpg b/public/images/chart/earth.jpg new file mode 100644 index 0000000000..c4a5d335cf Binary files /dev/null and b/public/images/chart/earth.jpg differ diff --git a/public/images/chart/geo_cover_mainnet.png b/public/images/chart/geo_cover_mainnet.png new file mode 100644 index 0000000000..fece75ca69 Binary files /dev/null and b/public/images/chart/geo_cover_mainnet.png differ diff --git a/public/images/chart/geo_cover_testnet.png b/public/images/chart/geo_cover_testnet.png new file mode 100644 index 0000000000..5ec2d6a9b7 Binary files /dev/null and b/public/images/chart/geo_cover_testnet.png differ diff --git a/src/locales/en.json b/src/locales/en.json index fa274e5444..965ddc3f75 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -265,6 +265,7 @@ "city": "City", "node_count": "Node Count", "node_distribution": "Node Distribution", + "node_geo_distribution": "Node Geo Distribution", "total_supply": "Total Supply", "circulating_supply": "Circulating Supply", "burnt": "Burnt", diff --git a/src/locales/zh.json b/src/locales/zh.json index 0bd0af9ff5..baf08a4434 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -264,6 +264,7 @@ "city": "城市", "node_count": "节点数量", "node_distribution": "节点分布", + "node_geo_distribution": "节点地理分布", "total_supply": "总供给量", "circulating_supply": "总流通量", "burnt": "销毁量", diff --git a/src/pages/StatisticsChart/index.tsx b/src/pages/StatisticsChart/index.tsx index ae3c1a63f9..4effa262c0 100644 --- a/src/pages/StatisticsChart/index.tsx +++ b/src/pages/StatisticsChart/index.tsx @@ -29,6 +29,7 @@ import { LiquidityChart } from './monetary/Liquidity' import { MinerAddressDistributionChart } from './mining/MinerAddressDistribution' import { MinerVersionDistributionChart } from './mining/MinerVersionDistribution' import { NodeCountryDistributionChart } from './mining/NodeCountryDistribution' +import NodeGeoDistributionChart from './mining/NodeGeoDistribution' import { useIsMobile } from '../../hooks' import { HelpTip } from '../../components/HelpTip' import { Link } from '../../components/Link' @@ -138,6 +139,11 @@ const useChartsData = () => { chart: , path: '/charts/node-country-distribution', }, + { + title: `${t('statistic.node_geo_distribution')}`, + chart: , + path: '/charts/node-geo-distribution', + }, ], }, { diff --git a/src/pages/StatisticsChart/mining/NodeGeoDistribution.tsx b/src/pages/StatisticsChart/mining/NodeGeoDistribution.tsx new file mode 100644 index 0000000000..11d526547b --- /dev/null +++ b/src/pages/StatisticsChart/mining/NodeGeoDistribution.tsx @@ -0,0 +1,159 @@ +import { useEffect, useRef } from 'react' +import { useQuery } from '@tanstack/react-query' +import 'echarts-gl' +import echarts from 'echarts/lib/echarts' +import Loading from '../../../components/Loading/SmallLoading' +import { getPeers, RawPeer } from '../../../services/NodeProbService' +import { getPrimaryColor, IS_MAINNET } from '../../../constants/common' + +const fetchData = async (): Promise<[[number, number], [number, number]][]> => { + const list: RawPeer[] = await getPeers() + const points: [number, number][] = list.map(peer => [peer.longitude, peer.latitude]) + const lines: [[number, number], [number, number]][] = [] + for (let i = 0; i < points.length - 1; i++) { + for (let j = i + 1; j < points.length; j++) { + const p1: [number, number] = points[i] + const p2: [number, number] = points[j] + lines.push([p1, p2]) + } + } + + return lines +} + +const option = { + backgroundColor: '#000', + globe: { + environment: '/images/chart/dark.webp', + baseTexture: '/images/chart/earth.jpg', + heightTexture: '/images/chart/earth.jpg', + displacementScale: 0.04, + displacementQuality: 'high', + shading: 'realistic', + realisticMaterial: { + roughness: 0.9, + metalness: 0, + }, + temporalSuperSampling: { + enable: true, + }, + postEffect: { + enable: true, + depthOfField: { + enable: false, + focalDistance: 150, + }, + }, + light: { + main: { + intensity: 10, + shadow: true, + time: new Date(0x16e70e6985c), // launch time of CKB mainnet + }, + }, + viewControl: { + autoRotate: true, + autoRotateSpeed: 1, + distance: 800, + }, + silent: true, + }, +} + +const color = getPrimaryColor() + +export const NodeGeoDistribution = ({ isThumbnail = false }: { isThumbnail?: boolean }) => { + const containerRef = useRef(null) + const { data, isLoading } = useQuery(['node distribution'], fetchData, { enabled: !isThumbnail }) + + useEffect(() => { + if (!containerRef.current) return + if (!data) return + let ins = echarts.getInstanceByDom(containerRef.current) + if (!ins) { + ins = echarts.init(containerRef.current) + } + + const series = [ + { + type: 'lines3D', + name: 'blocks', + coordinateSystem: 'globe', + blendMode: 'lighter', + symbolSize: 2, + itemStyle: { + color, + opacity: 0.1, + }, + effect: { + show: true, + trailWidth: 1, + trailLength: 0.15, + trailOpacity: 0.1, + constantSpeed: 10, + }, + lineStyle: { + width: 1, + color, + opacity: 0.02, + }, + data, + }, + { + type: 'scatter3D', + coordinateSystem: 'globe', + blendMode: 'lighter', + symbolSize: 30, + itemStyle: { + color, + opacity: 0.2, + }, + silent: true, + data: data.flat(), + }, + ] + + ins.setOption({ + ...option, + series, + } as any) + }, [data]) + + useEffect(() => { + if (!containerRef.current) return + const ins = echarts.getInstanceByDom(containerRef.current) + const handleResize = () => { + if (ins) { + ins.resize() + } + } + window.addEventListener('resize', handleResize) + return () => { + window.removeEventListener('resize', handleResize) + } + }) + + if (isThumbnail) { + return ( +
+ ) + } + + if (isLoading) { + return + } + + if (!data) { + return
Fail to load data
+ } + + return
+} + +export default NodeGeoDistribution diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 7884438767..87f693cbbb 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -44,6 +44,7 @@ const UncleRateChart = lazy(() => import('../pages/StatisticsChart/mining/UncleR const MinerAddressDistributionChart = lazy(() => import('../pages/StatisticsChart/mining/MinerAddressDistribution')) const MinerVersionDistributionChart = lazy(() => import('../pages/StatisticsChart/mining/MinerVersionDistribution')) const NodeCountryDistributionChart = lazy(() => import('../pages/StatisticsChart/mining/NodeCountryDistribution')) +const NodeGeoDistributionChart = lazy(() => import('../pages/StatisticsChart/mining/NodeGeoDistribution')) const TransactionCountChart = lazy(() => import('../pages/StatisticsChart/activities/TransactionCount')) const AddressCountChart = lazy(() => import('../pages/StatisticsChart/activities/AddressCount')) const CellCountChart = lazy(() => import('../pages/StatisticsChart/activities/CellCount')) @@ -189,6 +190,10 @@ const routes: RouteProps[] = [ path: '/charts/node-country-distribution', component: NodeCountryDistributionChart, }, + { + path: '/charts/node-geo-distribution', + component: NodeGeoDistributionChart, + }, { path: '/charts/transaction-count', component: TransactionCountChart, diff --git a/yarn.lock b/yarn.lock index ae52c49f88..d8973536ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7808,6 +7808,11 @@ classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classna resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== +claygl@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/claygl/-/claygl-1.3.0.tgz#7a6e2903210519ac358848f5d78070ed211685f3" + integrity sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ== + clean-css@^5.2.2: version "5.3.1" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.1.tgz#d0610b0b90d125196a2894d35366f734e5d7aa32" @@ -8975,6 +8980,14 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +echarts-gl@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/echarts-gl/-/echarts-gl-1.1.2.tgz#fb38454031bb64c91afb84c57b1a99c370e4571e" + integrity sha512-EVGx9RS2eMzaCgAMJSDCeLId4g8oFCFn78Fdh+0xIXASiZw/gPnJqr1vQgnQhmXhiUKixkIhIzfdc//qrct/Hg== + dependencies: + claygl "^1.2.1" + zrender "^4.0.4" + echarts@4.9.0: version "4.9.0" resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.9.0.tgz#a9b9baa03f03a2a731e6340c55befb57a9e1347d" @@ -16485,16 +16498,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16649,14 +16653,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18386,7 +18383,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18404,15 +18401,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -18608,7 +18596,7 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== -zrender@4.3.2: +zrender@4.3.2, zrender@^4.0.4: version "4.3.2" resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.3.2.tgz#ec7432f9415c82c73584b6b7b8c47e1b016209c6" integrity sha512-bIusJLS8c4DkIcdiK+s13HiQ/zjQQVgpNohtd8d94Y2DnJqgM1yjh/jpDb8DoL6hd7r8Awagw8e3qK/oLaWr3g==