Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add node geo distribution chart #283

Merged
merged 5 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@ckb-lumos/base": "0.21.0-next.1",
"@microlink/react-json-view": "1.23.0",
"@nervosnetwork/ckb-sdk-utils": "0.109.1",
"@rgbpp-sdk/ckb": "0.0.0-snap-20240408100333",
"@sentry/react": "7.94.1",
"@sentry/tracing": "7.94.1",
"@spore-sdk/core": "0.2.0-beta.3",
Expand All @@ -22,6 +23,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",
Expand Down
Binary file added public/images/chart/dark.webp
Binary file not shown.
Binary file added public/images/chart/earth.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/chart/geo_cover_mainnet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/chart/geo_cover_testnet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
"city": "城市",
"node_count": "节点数量",
"node_distribution": "节点分布",
"node_geo_distribution": "节点地理分布",
"total_supply": "总供给量",
"circulating_supply": "总流通量",
"burnt": "销毁量",
Expand Down
6 changes: 6 additions & 0 deletions src/pages/StatisticsChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -138,6 +139,11 @@ const useChartsData = () => {
chart: <NodeCountryDistributionChart isThumbnail />,
path: '/charts/node-country-distribution',
},
{
title: `${t('statistic.node_geo_distribution')}`,
chart: <NodeGeoDistributionChart isThumbnail />,
path: '/charts/node-geo-distribution',
},
],
},
{
Expand Down
172 changes: 172 additions & 0 deletions src/pages/StatisticsChart/mining/NodeGeoDistribution.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useEffect, useRef } from 'react'
import { useQuery } from '@tanstack/react-query'
import 'echarts/lib/component/tooltip'
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'

type Point = [long: number, lat: number, city: string]
type Line = [Point, Point]

const fetchData = async (): Promise<{ lines: Line[]; points: Point[] }> => {
const list: RawPeer[] = await getPeers()
const points: Point[] = list.map(peer => [peer.longitude, peer.latitude, peer.city])
const lines: Line[] = []
for (let i = 0; i < points.length - 1; i++) {
for (let j = i + 1; j < points.length; j++) {
const p1: Point = points[i]
const p2: Point = points[j]
lines.push([p1, p2])
}
}

return { lines, points }
}

const option = {
backgroundColor: '#000',
tooltip: {
show: true,
formatter: (params: { data: Point }) => {
return params.data[2]
},
},
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a constant value named the comment could be better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated by 3889408

},
},
viewControl: {
autoRotate: true,
autoRotateSpeed: 1,
distance: 800,
},
silent: true,
},
}

const color = getPrimaryColor()

export const NodeGeoDistribution = ({ isThumbnail = false }: { isThumbnail?: boolean }) => {
const containerRef = useRef<HTMLDivElement | null>(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: data.lines,
},
{
type: 'scatter3D',
coordinateSystem: 'globe',
blendMode: 'lighter',
symbolSize: 10,
itemStyle: {
color,
opacity: 0.2,
},
label: {
show: true,
formatter: '{b}',
},
data: data.points,
},
]

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 (
<div
style={{
width: '280px',
height: 200,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use both number or both string maybe better here I think.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated by 3889408

background: `center / cover url(/images/chart/geo_cover_${IS_MAINNET ? 'mainnet' : 'testnet'}.png)`,
}}
/>
)
}

if (isLoading) {
return <Loading />
}

if (!data) {
return <div>Fail to load data</div>
}

return <div style={{ width: '100vw', height: '100vh' }} ref={containerRef} />
}

export default NodeGeoDistribution
5 changes: 5 additions & 0 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 5 additions & 15 deletions src/utils/rgbpp.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import { toBigEndian } from '@nervosnetwork/ckb-sdk-utils'
import { blockchain } from '@ckb-lumos/base'
import { BTCTimeLock } from '@rgbpp-sdk/ckb'

export const parseBtcTimeLockArgs = (args: string) => {
// btc time lock: https://github.com/ckb-cell/RGBPlusPlus-design/blob/main/docs/locscript-design-prd-cn.md#btc_time_lock
const OFFSET_END = 8 * 4
const LOCK_SCRIPT_LEN = 178
const AFTER_LEN = 8
const TXID_LEN = 64
const LOCK_SCRIPT_END = 2 + LOCK_SCRIPT_LEN
const AFTER_END = LOCK_SCRIPT_END + AFTER_LEN
const TXID_END = AFTER_END + TXID_LEN

const script = args.slice(2 + OFFSET_END, LOCK_SCRIPT_END)
const after = args.slice(LOCK_SCRIPT_END, AFTER_END)
const btcTx = args.slice(AFTER_END, TXID_END)
const { lockScript, after, btcTxid } = BTCTimeLock.unpack(args)

const res = {
script: blockchain.Script.unpack(`0x${script}`),
after: Number(toBigEndian(`0x${after}`)),
txid: toBigEndian(`0x${btcTx}`).slice(2),
script: lockScript,
after,
txid: btcTxid.slice(2).match(/\w{2}/g)?.reverse().join(''),
}

return res
Expand Down
Loading