Skip to content

Commit

Permalink
feat: show Nodes distribution by Country charts (#279)
Browse files Browse the repository at this point in the history
Co-authored-by: Chen Yu <keithwhisper@gmail.com>
  • Loading branch information
zmcNotafraid and Keith-CY authored Apr 3, 2024
1 parent 9d0e75b commit 623fd45
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default {
BASE_URL: process.env.REACT_APP_BASE_URL || 'explorer.nervos.org/',
BACKUP_NODES: process.env.REACT_APP_BACKUP_NODES?.split(',') || [],
BITCOIN_EXPLORER: process.env.REACT_APP_BITCOIN_EXPLORER || 'https://mempool.space',
PROB_NODE: process.env.REACT_APP_PROB_NODE || 'http://localhost:1800',
}
1 change: 1 addition & 0 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,6 @@ export const TESTNET_URL = `https://${ChainName.Testnet}.${config.BASE_URL}`

export const TYPE_ID_CODE_HASH = '0x00000000000000000000000000000000000000000000000000545950455f4944'

export const NETWORK = config.CHAIN_TYPE === 'testnet' ? 'pudge' : 'minara'
export const NERVOS_DAO_RFC_URL =
'https://www.github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md'
4 changes: 3 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,9 @@
"node_count_in_total": "in total",
"ckb_amount": "CKB Amount",
"contract_resource_distributed": "Contract Resource Distribution",
"contract_resource_distributed_description": "The x axis represents contract's unique address count, the y axis represents the contract's CKB amount, the symbol size represents the contract's transaction count."
"contract_resource_distributed_description": "The x axis represents contract's unique address count, the y axis represents the contract's CKB amount, the symbol size represents the contract's transaction count.",
"country": "Country/Region",
"node_country_distribution": "Nodes distribution by Country/Region"
},
"home": {
"height": "Height",
Expand Down
4 changes: 3 additions & 1 deletion src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,9 @@
"node_count_in_total": "总共",
"ckb_amount": "CKB 数量",
"contract_resource_distributed": "合约资源分布图",
"contract_resource_distributed_description": "横轴表示合约的唯一地址数量, 纵轴是合约的 CKB 数量, 图标大小代表合约的交易数量"
"contract_resource_distributed_description": "横轴表示合约的唯一地址数量, 纵轴是合约的 CKB 数量, 图标大小代表合约的交易数量",
"country": "国家(地区)",
"node_country_distribution": "节点国家(地区)分布图"
},
"home": {
"height": "高度",
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 @@ -28,6 +28,7 @@ import { InflationRateChart } from './monetary/InflationRate'
import { LiquidityChart } from './monetary/Liquidity'
import { MinerAddressDistributionChart } from './mining/MinerAddressDistribution'
import { MinerVersionDistributionChart } from './mining/MinerVersionDistribution'
import { NodeCountryDistributionChart } from './mining/NodeCountryDistribution'
import { useIsMobile } from '../../hooks'
import { HelpTip } from '../../components/HelpTip'
import { Link } from '../../components/Link'
Expand Down Expand Up @@ -132,6 +133,11 @@ const useChartsData = () => {
chart: <MinerVersionDistributionChart isThumbnail />,
path: '/charts/miner-version-distribution',
},
{
title: `${t('statistic.node_country_distribution')}`,
chart: <NodeCountryDistributionChart isThumbnail />,
path: '/charts/node-country-distribution',
},
],
},
{
Expand Down
132 changes: 132 additions & 0 deletions src/pages/StatisticsChart/mining/NodeCountryDistribution.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useTranslation } from 'react-i18next'
import { EChartOption } from 'echarts'
import { tooltipColor, tooltipWidth, SmartChartPage } from '../common'
import { getPeers, RawPeer } from '../../../services/NodeProbService'
import { useCurrentLanguage } from '../../../utils/i18n'
import { ChartColorConfig } from '../../../constants/common'

const Colors = [
'#069ECD',
'#69C7D4',
'#AACFE9',
'#29B97A',
'#66CC99',
'#228159',
'#525860',
'#74808E',
'#9DA6B0',
'#FBB04C',
]

interface CountryRecord {
country: string
percent: number
}

const useOption = (
list: CountryRecord[],
chartColor: ChartColorConfig,
isMobile: boolean,
isThumbnail = false,
): echarts.EChartOption => {
const { t } = useTranslation()
const currentLanguage = useCurrentLanguage()

const gridThumbnail = {
left: '4%',
right: '10%',
top: '8%',
bottom: '6%',
containLabel: true,
}
const grid = {
left: '3%',
right: '3%',
top: '5%',
bottom: '5%',
containLabel: true,
}

const tooltip: EChartOption.Tooltip | undefined = !isThumbnail
? {
formatter: data => {
const item = Array.isArray(data) ? data[0] : data
const widthSpan = (value: string) => tooltipWidth(value, currentLanguage === 'en' ? 100 : 120)
let result = `<div>${tooltipColor('#333333')}${widthSpan(t('statistic.country'))} ${item.data.title}</div>`
result += `<div>${tooltipColor(chartColor.colors[0])}${widthSpan(t('statistic.percent'))} ${
item.data.value
}%</div>`
return result
},
}
: {
show: false,
}

return {
color: [chartColor.colors[0], ...Colors],
tooltip,
grid: isThumbnail ? gridThumbnail : grid,
legend: {
show: !isThumbnail,
right: 40,
bottom: 40,
orient: 'vertical',
icon: 'circle',
},
series: [
{
name: t('statistic.node_country_distribution'),
type: 'pie',
radius: isMobile || isThumbnail ? '50%' : '75%',
center: ['50%', '50%'],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
data: list.map(data => {
const country = data.country === 'others' ? t(`statistic.others`) : data.country
return {
name: `${country} (${data.percent}%)`,
title: country,
value: data.percent,
}
}),
},
],
}
}

const fetchData = async (): Promise<CountryRecord[]> => {
const list: RawPeer[] = await getPeers()
const result: { key: string; value: number } = list.reduce((acc: any, cur: any) => {
acc[cur.country] = (acc[cur.country] || 0) + 1
return acc
}, {})
return Object.entries(result).map(v => ({
country: v[0],
percent: +(((v[1] as number) * 100) / list.length).toFixed(2),
}))
}

const toCSV = (countryList: CountryRecord[]) => countryList?.map(r => [r.country, `${r.percent}%`]) ?? []

export const NodeCountryDistributionChart = ({ isThumbnail = false }: { isThumbnail?: boolean }) => {
const [t] = useTranslation()

return (
<SmartChartPage
title={t('statistic.node_country_distribution')}
isThumbnail={isThumbnail}
fetchData={fetchData}
getEChartOption={useOption}
toCSV={toCSV}
queryKey="fetchStatisticNodeCountryDistribution"
/>
)
}

export default NodeCountryDistributionChart
5 changes: 5 additions & 0 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const HashRateChart = lazy(() => import('../pages/StatisticsChart/mining/HashRat
const UncleRateChart = lazy(() => import('../pages/StatisticsChart/mining/UncleRate'))
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 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 @@ -184,6 +185,10 @@ const routes: RouteProps[] = [
path: '/charts/miner-version-distribution',
component: MinerVersionDistributionChart,
},
{
path: '/charts/node-country-distribution',
component: NodeCountryDistributionChart,
},
{
path: '/charts/transaction-count',
component: TransactionCountChart,
Expand Down
38 changes: 38 additions & 0 deletions src/services/NodeProbService/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import axios from 'axios'
import config from '../../config'
import { NETWORK } from '../../constants/common'

const { PROB_NODE: node } = config

if (!node) {
throw new Error('NodeProbService not implemented')
}

export const getPeers = (): Promise<RawPeer[]> => {
return axios
.get(`${node}/peer`, {
params: {
network: NETWORK,
offline_timeout: 10080,
unknown_offline_timeout: 10080,
},
})
.then(res => res.data)
}

interface LastSeen {
secs_since_epoch: number
nanos_since_epoch: number
}

export interface RawPeer {
id: number
version: string
version_short: string
last_seen: LastSeen[]
country: string
city: string
latitude: number
longitude: number
node_type: number
}

0 comments on commit 623fd45

Please sign in to comment.