Public
Edited
Aug 7, 2022
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
multiaddrsIpsLatestBucketUrl
Insert cell
Insert cell
Insert cell
Insert cell
latestIpsBaiduReport = (await fetch(`${geoIpLookupsBucketUrl}/ips-baidu-latest.json`)).json()
Insert cell
chinaProvinces = FileAttachment("china-provinces.json").json()
Insert cell
chinaProvincesByCnName = d3.index(chinaProvinces, d => d.cn)
Insert cell
chinaProvincesByCode = d3.index(chinaProvinces, d => d.code)
Insert cell
Insert cell
latestPowerReport = (await fetch(`${minerPowerDailyAverageLatestBucketUrl}/miner-power-latest.json`)).json()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
regionHierarchy = ({
name: 'root',
children: [
{
name: 'Asia',
code: 'AS',
children: [
{
name: 'Mainland China',
code: 'CN',
note: 'Does not include Hong Kong, Taiwan',
children: [
{
name: 'Northeast',
code: 'NORTHEAST',
members: ['LN', 'JL', 'HL'],
children: [
{
name: chinaProvincesByCode.get('LN').en,
code: 'LN'
},
{
name: chinaProvincesByCode.get('JL').en,
code: 'JL'
},
{
name: chinaProvincesByCode.get('HL').en,
code: 'HL'
}
]
},
{
name: 'North',
code: 'NORTH',
members: ['SD', 'SX', 'NM', 'HA', 'HE', 'BJ', 'TJ'],
children: [
{
name: chinaProvincesByCode.get('BJ').en,
code: 'BJ'
},
{
name: chinaProvincesByCode.get('SD').en,
code: 'SD'
},
{
name: chinaProvincesByCode.get('NM').en,
code: 'NM'
},
{
name: chinaProvincesByCode.get('HA').en,
code: 'HA'
},
{
name: chinaProvincesByCode.get('TJ').en,
code: 'TJ'
},
{
name: chinaProvincesByCode.get('HE').en,
code: 'HE'
},
{
name: chinaProvincesByCode.get('SX').en,
code: 'SX'
}
]
},
{
name: 'Northwest',
code: 'NORTHWEST',
members: ['SN', 'GS', 'NX', 'QH', 'XJ'],
children: [
{
name: chinaProvincesByCode.get('SN').en,
code: 'SN'
},
{
name: chinaProvincesByCode.get('GS').en,
code: 'GS'
},
{
name: chinaProvincesByCode.get('NX').en,
code: 'NX'
},
{
name: chinaProvincesByCode.get('QH').en,
code: 'QH'
},
{
name: chinaProvincesByCode.get('XJ').en,
code: 'XJ'
}
]
},
{
name: 'Southwest',
code: 'SOUTHWEST',
members: ['XZ', 'SC', 'CQ', 'YN', 'GZ'],
children: [
{
name: chinaProvincesByCode.get('SC').en,
code: 'SC'
},
{
name: chinaProvincesByCode.get('CQ').en,
code: 'CQ'
},
{
name: chinaProvincesByCode.get('GZ').en,
code: 'GZ'
},
{
name: chinaProvincesByCode.get('YN').en,
code: 'YN'
},
{
name: chinaProvincesByCode.get('XZ').en,
code: 'XZ'
},
]
},
{
name: 'South-Central',
code: 'SOUTHCENTRAL',
members: ['AH', 'HB', 'HN', 'JX'],
children: [
{
name: chinaProvincesByCode.get('HN').en,
code: 'HN'
},
{
name: chinaProvincesByCode.get('JX').en,
code: 'JX'
},
{
name: chinaProvincesByCode.get('AH').en,
code: 'AH'
},
{
name: chinaProvincesByCode.get('HB').en,
code: 'HB'
}
]
},
{
name: 'South',
code: 'SOUTH',
members: ['GD', 'GX', 'HI'],
children: [
{
name: chinaProvincesByCode.get('GD').en,
code: 'GD'
},
{
name: chinaProvincesByCode.get('GX').en,
code: 'GX'
},
{
name: chinaProvincesByCode.get('HI').en,
code: 'HI'
}
]
},
{
name: 'East',
code: 'EAST',
members: ['JS', 'SH', 'ZJ', 'FJ'],
children: [
{
name: chinaProvincesByCode.get('FJ').en,
code: 'FJ'
},
{
name: chinaProvincesByCode.get('SH').en,
code: 'SH'
},
{
name: chinaProvincesByCode.get('ZJ').en,
code: 'ZJ'
},
{
name: chinaProvincesByCode.get('JS').en,
code: 'JS'
}
]
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Singapore',
code: 'SG'
},
{
name: 'Korea',
code: 'KR',
note: 'Currently only South Korea'
},
{
name: 'Hong Kong',
code: 'HK'
},
{
name: 'Japan',
code: 'JP'
},
{
name: 'Taiwan',
code: 'TW'
},
{
name: 'Vietnam',
code: 'VN'
},
{
name: 'Malaysia',
code: 'MY'
},
{
name: 'Indonesia',
code: 'ID'
},
{
name: 'Iran',
code: 'IR'
},
{
name: 'Thailand',
code: 'TH'
},
{
name: 'United Arab Emirates',
code: 'AE'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'North America',
code: 'NA',
children: [
{
name: 'USA',
code: 'US',
children: [
{
name: 'West',
code: 'WEST',
members: ['AK', 'HI', 'WA', 'OR', 'CA', 'MT', 'ID', 'WY', 'NV', 'UT', 'CO', 'AZ', 'NM'],
children: [
{
name: 'California',
code: 'CA'
},
{
name: 'Washington',
code: 'WA'
},
{
name: 'Oregon',
code: 'OR'
},
{
name: 'Colorado',
code: 'CO'
},
{
name: 'Utah',
code: 'UT'
},
{
name: 'Arizona',
code: 'AZ'
},
{
name: 'Wyoming',
code: 'WY'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Midwest',
code: 'MIDWEST',
members: ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO', 'WI', 'MI', 'IL', 'IN', 'OH'],
children: [
{
name: 'Michigan',
code: 'MI'
},
{
name: 'Nebraska',
code: 'NE'
},
{
name: 'Iowa',
code: 'IA'
},
{
name: 'Illinois',
code: 'IL'
},
{
name: 'Wisconsin',
code: 'WI'
},
{
name: 'Ohio',
code: 'OH'
},
{
name: 'Missouri',
code: 'MO'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Northeast',
code: 'NORTHEAST',
members: ['NY', 'PA', 'NJ', 'RI', 'ME', 'VT', 'NH', 'MA', 'CT'],
children: [
{
name: 'New York',
code: 'NY'
},
{
name: 'Massachusetts',
code: 'MA'
},
{
name: 'Pennsylvania',
code: 'PA'
},
{
name: 'New Jersey',
code: 'NJ'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'South',
code: 'SOUTH',
members: ['OK', 'AR', 'TX', 'LA', 'KY', 'TN', 'MS', 'AL', 'WV', 'MD', 'DE', 'DC', 'VA', 'NC', 'SC', 'GA', 'FL'],
children: [
{
name: 'Virginia',
code: 'VA'
},
{
name: 'North Carolina',
code: 'NC'
},
{
name: 'Florida',
code: 'FL'
},
{
name: 'Texas',
code: 'TX'
},
{
name: 'Georgia',
code: 'GA'
},
{
name: 'Kentucky',
code: 'KY'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Others Combined',
code: 'XX'
}
],
},
{
name: 'Canada',
code: 'CA',
children: [
{
name: 'Quebec',
code: 'QC'
},
{
name: 'Ontario',
code: 'ON'
},
{
name: 'Alberta',
code: 'AB'
},
{
name: 'British Columbia',
code: 'BC'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Mexico + Others Combined',
code: 'XX'
}
]
},
{
name: 'Europe',
code: 'EU',
children: [
{
name: 'Western Europe',
code: 'WEST',
members: ['DE', 'NL', 'BE', 'LU', 'AT', 'CH', 'FR', 'MC', 'LI'],
children: [
{
name: 'Germany',
code: 'DE'
},
{
name: 'Netherlands',
code: 'NL'
},
{
name: 'France',
code: 'FR'
},
{
name: 'Belgium',
code: 'BE'
},
{
name: 'Switzerland',
code: 'CH'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Eastern Europe',
code: 'EAST',
members: ['UA', 'RU', 'BY', 'PL', 'CZ', 'SK', 'HU', 'RO', 'MD', 'BG'],
children: [
{
name: 'Ukraine',
code: 'UA'
},
{
name: 'Russia',
code: 'RU'
},
{
name: 'Bulgaria',
code: 'BG'
},
{
name: 'Poland',
code: 'PL'
},
{
name: 'Romania',
code: 'RO'
},
{
name: 'Czechia',
code: 'CZ'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Northern Europe',
code: 'NORTH',
members: ['GB', 'IS', 'NO', 'SE', 'FI', 'DK', 'IE', 'EE', 'LV', 'LT'],
children: [
{
name: 'United Kingdom',
code: 'GB'
},
{
name: 'Norway',
code: 'NO'
},
{
name: 'Denmark',
code: 'DK'
},
{
name: 'Latvia',
code: 'LV'
},
{
name: 'Sweden',
code: 'SE'
},
{
name: 'Finland',
code: 'FI'
},
{
name: 'Iceland',
code: 'IS'
},
{
name: 'Ireland',
code: 'IE'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Southern Europe',
code: 'SOUTH',
members: ['SI', 'HR', 'PT', 'ES', 'AD', 'SM', 'VA', 'IT', 'MT', 'ME', 'BA', 'RS', 'MK', 'AL', 'GR'],
children: [
{
name: 'Spain',
code: 'ES'
},
{
name: 'Portugal',
code: 'PT'
},
{
name: 'Serbia',
code: 'RS'
},
{
name: 'Slovenia',
code: 'SI'
},
{
name: 'Italy',
code: 'IT'
},
{
name: 'North Macedonia',
code: 'MK'
},
{
name: 'Greece',
code: 'GR'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Unexpected',
code: 'XX'
}
],
},
{
name: 'Oceania',
code: 'OC',
children: [
{
name: 'Australia',
code: 'AU'
},
{
name: 'New Zealand',
code: 'NZ'
},
{
name: 'Others Combined',
code: 'XX'
}
]
},
{
name: 'Africa',
code: 'AF',
children: [
{
name: 'Combined',
code: 'XX'
}
]
},
{
name: 'South America',
code: 'SA',
children: [
{
name: 'Combined',
code: 'XX'
}
]
},
{
name: 'No Region',
code: 'none',
note: "Miners without IP addresses that can't be geo-located."
},
]
})
Insert cell
root = d3.hierarchy(_.cloneDeep(regionHierarchy))
Insert cell
root.children[6].data
Insert cell
graph(root, {label: d => `${d.data.code ? d.data.code + ': ' : ''}${d.data.name}`})
Insert cell
numberOfLeaves = root.copy().count().value
Insert cell
numberOfNodes = root.copy().sum(d => 1).value
Insert cell
indexedRoot = {
function childrenIndex(d) {
return d3.index(d.children.map(d => {
const value = d.data
if (value && value.children) {
value.children = childrenIndex(d)
}
return value
}), d => d.code)
}
return childrenIndex(root)
}
Insert cell
function regionMapper (geolite2, baidu) {
if (!geolite2) return null
// Try to break into country-based subcategories if >10 miners
const continent = indexedRoot.get(geolite2.continent)
if (continent) {
if (!continent.children) return continent.code
const country = continent.children.get(geolite2.country)
if (country) {
if (country.code === 'CN') {
for (const [ , region ] of country.children) {
// Try Baidu first
if (baidu && baidu.province) {
if (region.members &&
region.members.includes(baidu.province.code)) {
if (region.children.get(baidu.province.code)) {
return `${continent.code}-${country.code}-${region.code}-${baidu.province.code}`
} else {
return `${continent.code}-${country.code}-${region.code}-XX`
}
}
}
// Fallback to GeoLite2
if (geolite2.subdiv1) {
if (region.members &&
region.members.includes(geolite2.subdiv1)) {
if (region.children.get(geolite2.subdiv1)) {
return `${continent.code}-${country.code}-${region.code}-${geolite2.subdiv1}`
} else {
return `${continent.code}-${country.code}-${region.code}-XX`
}
}
}
}
return `${continent.code}-${country.code}-XX`
}
if (country.children) {
// Regions/States/Provinces
for (const [ , region ] of country.children) {
if (geolite2.subdiv1) {
if (region.members &&
region.members.includes(geolite2.subdiv1)) {
if (region.children.get(geolite2.subdiv1)) {
return `${continent.code}-${country.code}-${region.code}-${geolite2.subdiv1}`
} else {
return `${continent.code}-${country.code}-${region.code}-XX`
}
}
}
}
// States/Provinces
if (geolite2.subdiv1) {
if (country.children.get(geolite2.subdiv1)) {
return `${continent.code}-${country.code}-${geolite2.subdiv1}`
} else {
return `${continent.code}-${country.code}-XX`
}
}
return `${continent.code}-${country.code}-XX`
}
return `${continent.code}-${country.code}`
} else {
for (const [ , region ] of continent.children) {
if (region.members && region.members.includes(geolite2.country)) {
const regionCountry = region.children.get(geolite2.country)
if (regionCountry) {
return `${continent.code}-${region.code}-${regionCountry.code}`
}
return `${continent.code}-${region.code}-XX`
}
}
return `${continent.code}-XX`
}
} else {
return null
}
}
Insert cell
minerRegions = {
const minerRegions = []
for (const miner of [...minerIps.keys()]) {
const regions = minerIps.get(miner).reduce(
(regionSet, { region }) => region ? regionSet.add(region) : regionSet,
new Set()
)
minerRegions.push({
miner,
regions: [...regions].sort()
})
}
return minerRegions
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more