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==