Skip to content

Commit

Permalink
feat: add chart showing relative supply distribution by balance ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
mistakia committed Apr 29, 2024
1 parent d13cccb commit df0428b
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 13 deletions.
19 changes: 19 additions & 0 deletions src/views/components/ledger-chart-distribution/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { connect } from 'react-redux'
import { createSelector } from 'reselect'

import { nanodb_actions } from '@core/nanodb/actions'

import LedgerChartDistribution from './ledger-chart-distribution'

const map_state_to_props = createSelector(
(state) => state.getIn(['nanodb', 'price_history']),
(price_history) => ({
price_history
})
)

const map_dispatch_to_props = {
get_price_history: nanodb_actions.get_price_history
}

export default connect(map_state_to_props, map_dispatch_to_props)(LedgerChartDistribution)
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import React, { useEffect, useMemo } from 'react'
import PropTypes from 'prop-types'
import Button from '@mui/material/Button'
import ReactEChartsCore from 'echarts-for-react/lib/core'
import * as echarts from 'echarts/core'
import { LineChart } from 'echarts/charts'
import { TooltipComponent, GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'

import { download_csv, download_json } from '@core/utils'

echarts.use([LineChart, TooltipComponent, GridComponent, CanvasRenderer])

const color_map = {
// high
_1000000_relative_supply: '#ffd700', // Gold
_100000_relative_supply: '#f9c74f', // Maize Crayola
_10000_relative_supply: '#f4a261', // Sandy Brown
_1000_relative_supply: '#e63946', // Red

// medium
_100_relative_supply: '#0077b6', // Star Command Blue
_10_relative_supply: '#023e8a', // Royal Blue Dark
_1_relative_supply: '#0096c7', // Pacific Blue

// low
_01_relative_supply: '#6DB65B', // Apple Green
_001_relative_supply: '#57A55A', // Medium Sea Green
_0001_relative_supply: '#3D8B57', // Jungle Green
_00001_relative_supply: '#2C6E49', // Bottle Green

// micro
_000001_relative_supply: '#911eb4', // Purple
_000001_below_relative_supply: '#dcbeff' // Lavender Blush
}

export default function LedgerChartDistribution({ data, isLoading, price_history, get_price_history }) {
useEffect(() => {
if (price_history.length === 0) {
get_price_history()
}
}, [])

const series_data_usd_price = useMemo(() => {
const data = []
price_history.forEach((item) => {
data.push([item.timestamp_utc, item.price])
})
return data
}, [price_history])

const ranges = {
_1000000_relative_supply: '>1M',
_100000_relative_supply: '100K to 1M',
_10000_relative_supply: '10K to 100K',
_1000_relative_supply: '1K to 10K',
_100_relative_supply: '100 to 1K',
_10_relative_supply: '10 to 100',
_1_relative_supply: '1 to 10',
_01_relative_supply: '0.1 to 1',
_001_relative_supply: '0.01 to 0.1',
_0001_relative_supply: '0.001 to 0.01',
_00001_relative_supply: '0.0001 to 0.001',
_000001_relative_supply: '0.00001 to 0.0001',
_000001_below_relative_supply: '<0.00001'
}

const series_data = Object.entries(ranges).map(([key, value], index) => {
const color = color_map[key]

return {
name: value,
type: 'line',
stack: 'total',
color,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
color,
opacity: 0.6
},
emphasis: {
focus: 'series'
},
data: data[key].map((item) => [item[0], item[1]])
}
})

const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
bottom: 0
},
xAxis: [
{
type: 'time',
boundaryGap: false
}
],
yAxis: [
{
type: 'value',
min: 0,
max: 100,
axisLabel: {
formatter: '{value} %'
}
},
{
type: 'log',
position: 'right',
axisLabel: {
formatter: '{value} USD'
}
}
],
series: [
...series_data,
{
name: 'USD Price',
type: 'line',
yAxisIndex: 1,
data: series_data_usd_price,
showSymbol: false,
lineStyle: {
width: 2,
color: '#333333'
}
}
]
}

const handle_download_csv = () => {
const download_data = []
Object.entries(ranges).forEach(([key, value]) => {
data[key].forEach((item) => {
download_data.push({
range: value,
date: item[0],
relative_supply: item[1]
})
})
})

download_csv({
headers: ['Range', 'Date', 'Relative Supply'],
data: download_data,
file_name: 'nano-community-ledger-daily-relative-balance-distribution'
})
}

const handle_download_json = () => {
const download_data = {
title: 'Nano Community Ledger Daily Relative Balance Distribution',
data: {}
}

Object.entries(ranges).forEach(([key, value]) => {
download_data.data[value] = data[key].map((item) => ({
date: item[0],
relative_supply: item[1]
}))
})
download_json({
data: download_data,
file_name: 'nano-community-ledger-daily-relative-balance-distribution'
})
}

return (
<>
<ReactEChartsCore
echarts={echarts}
option={option}
showLoading={isLoading}
loadingOption={{
maskColor: 'rgba(255, 255, 255, 0)',
text: '',
spinnerRadius: 24,
lineWidth: 2
}}
style={{ width: '100%', height: '600px' }}
/>
<div className='ledger__chart-sections'>
<div className='ledger__chart-section'>
<div className='section__heading'>
<span>Description</span>
</div>
<div className='ledger__chart-section-body description'>
The relative distribution of the supply held by addresses within specific balance ranges.
</div>
{!isLoading && (
<div className='ledger__chart-section-body download'>
<Button size='small' onClick={handle_download_csv}>
Download CSV
</Button>
<Button size='small' onClick={handle_download_json}>
Download JSON
</Button>
</div>
)}
</div>
</div>
</>
)
}

LedgerChartDistribution.propTypes = {
data: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
price_history: PropTypes.array.isRequired,
get_price_history: PropTypes.func.isRequired
}
49 changes: 36 additions & 13 deletions src/views/pages/ledger/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { connect } from 'react-redux'
import { createSelector } from 'reselect'
import BigNumber from 'bignumber.js'

import { getLedger, ledgerActions } from '@core/ledger'

Expand All @@ -10,6 +11,22 @@ const mapStateToProps = createSelector(getLedger, (ledger) => {
const data = {
reused_addresses: []
}
const base_ranges = [
'_1000000',
'_100000',
'_10000',
'_1000',
'_100',
'_10',
'_1',
'_01',
'_001',
'_0001',
'_00001',
'_000001',
'_000001_below'
]

const metrics = [
'active_addresses',

Expand All @@ -27,26 +44,18 @@ const mapStateToProps = createSelector(getLedger, (ledger) => {
// TODO open_volume
// TODO receive_volume

'_1000000_count',
'_100000_count',
'_10000_count',
'_1000_count',
'_100_count',
'_10_count',
'_1_count',
'_01_count',
'_001_count',
'_0001_count',
'_00001_count',
'_000001_count',
'_000001_below_count'
...base_ranges.map((r) => `${r}_count`)
]

for (let i = 0; i < metrics.length; i++) {
const metric = metrics[i]
data[metric] = []
}

base_ranges.forEach((r) => {
data[`${r}_relative_supply`] = []
})

daily.forEach((d) => {
const timestamp = parseInt(d.timestamp, 10) * 1000
for (let i = 0; i < metrics.length; i++) {
Expand All @@ -55,6 +64,20 @@ const mapStateToProps = createSelector(getLedger, (ledger) => {
}

data.reused_addresses.push([timestamp, d.active_addresses - d.open_count])

// calculate relative supply distribution
const total_supply = base_ranges.reduce((acc, r) => {
return acc + BigInt(d[`${r}_total_balance`])
}, BigInt(0))

base_ranges.forEach((r) => {
const total_balance = BigInt(d[`${r}_total_balance`])
const relative_supply = BigNumber(total_balance)
.div(total_supply)
.times(100)
.toFixed(5)
data[`${r}_relative_supply`].push([timestamp, relative_supply])
})
})

return { data, isLoading: ledger.get('isLoading') }
Expand Down
5 changes: 5 additions & 0 deletions src/views/pages/ledger/ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LedgerChartAddresses from '@components/ledger-chart-addresses'
import LedgerChartVolume from '@components/ledger-chart-volume'
import LedgerChartAmounts from '@components/ledger-chart-amounts'
import LedgerChartUSDTransferred from '@components/ledger-chart-usd-transferred'
import LedgerChartDistribution from '@components/ledger-chart-distribution'
import Seo from '@components/seo'
import Menu from '@components/menu'

Expand Down Expand Up @@ -73,6 +74,7 @@ export default function LedgerPage({ load, data, isLoading }) {
<Tab label='Volume' />
<Tab label='Value Transferred' />
<Tab label='Amounts' />
<Tab label='Distribution' />
</Tabs>
<TabPanel value={value} index={0}>
<LedgerChartAddresses data={data} isLoading={isLoading} />
Expand All @@ -89,6 +91,9 @@ export default function LedgerPage({ load, data, isLoading }) {
<TabPanel value={value} index={4}>
<LedgerChartAmounts data={data} isLoading={isLoading} />
</TabPanel>
<TabPanel value={value} index={5}>
<LedgerChartDistribution data={data} isLoading={isLoading} />
</TabPanel>
</div>
<div className='ledger__footer'>
<Menu />
Expand Down

0 comments on commit df0428b

Please sign in to comment.