diff --git a/src/core/constants.js b/src/core/constants.js
index c8b1dfb2..f02400d2 100644
--- a/src/core/constants.js
+++ b/src/core/constants.js
@@ -12,3 +12,35 @@ export const WS_URL = IS_DEV ? 'ws://localhost:8080' : 'wss://nano.community'
// 3 Million Nano (3e36)
export const REP_MAX_WEIGHT = BigNumber(3).shiftedBy(36)
+
+export const base_ranges = [
+ '_1000000',
+ '_100000',
+ '_10000',
+ '_1000',
+ '_100',
+ '_10',
+ '_1',
+ '_01',
+ '_001',
+ '_0001',
+ '_00001',
+ '_000001',
+ '_000001_below'
+]
+
+export const base_range_labels = {
+ _1000000: '>1M',
+ _100000: '100k to 1M',
+ _10000: '10k to 100k',
+ _1000: '1k to 10k',
+ _100: '100 to 1k',
+ _10: '10 to 100',
+ _1: '1 to 10',
+ _01: '0.1 to 1',
+ _001: '0.01 to 0.1',
+ _0001: '0.001 to 0.01',
+ _00001: '0.0001 to 0.001',
+ _000001: '0.00001 to 0.0001',
+ _000001_below: '<0.00001'
+}
diff --git a/src/views/components/ledger-chart-addresses-with-balance/index.js b/src/views/components/ledger-chart-addresses-with-balance/index.js
new file mode 100644
index 00000000..a4d47e62
--- /dev/null
+++ b/src/views/components/ledger-chart-addresses-with-balance/index.js
@@ -0,0 +1,22 @@
+import { connect } from 'react-redux'
+import { createSelector } from 'reselect'
+
+import { nanodb_actions } from '@core/nanodb/actions'
+
+import LedgerChartAddressesWithBalance from './ledger-chart-addresses-with-balance'
+
+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
+)(LedgerChartAddressesWithBalance)
diff --git a/src/views/components/ledger-chart-addresses-with-balance/ledger-chart-addresses-with-balance.js b/src/views/components/ledger-chart-addresses-with-balance/ledger-chart-addresses-with-balance.js
new file mode 100644
index 00000000..60c55040
--- /dev/null
+++ b/src/views/components/ledger-chart-addresses-with-balance/ledger-chart-addresses-with-balance.js
@@ -0,0 +1,174 @@
+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'
+import { base_range_labels } from '@core/constants'
+
+echarts.use([LineChart, TooltipComponent, GridComponent, CanvasRenderer])
+
+export default function LedgerChartAddressesWithBalance({
+ data,
+ isLoading,
+ price_history,
+ get_price_history,
+ range
+}) {
+ useEffect(() => {
+ if (price_history.length === 0) {
+ get_price_history()
+ }
+ }, [])
+
+ const handle_download_csv = () => {
+ const download_data = []
+ data[`${range}_account_count`].forEach((item) => {
+ download_data.push({
+ range: base_range_labels[range],
+ date: item[0],
+ account_count: item[1]
+ })
+ })
+ download_csv({
+ headers: ['Range', 'Date', 'Account Count'],
+ data: download_data,
+ file_name: `nano-community-ledger-daily-addresses-with-balance-${base_range_labels[
+ range
+ ].toLowerCase()}`
+ })
+ }
+
+ const handle_download_json = () => {
+ const download_data = {
+ title: `Nano Community Ledger Daily Addresses With Balance - ${base_range_labels[range]}`,
+ data: data[`${range}_account_count`].map((item) => ({
+ date: item[0],
+ account_count: item[1]
+ }))
+ }
+ download_json({
+ data: download_data,
+ file_name: `nano-community-ledger-daily-addresses-with-balance-${base_range_labels[
+ range
+ ].toLowerCase()}`
+ })
+ }
+
+ const series_data_usd_price = useMemo(() => {
+ const data = []
+ price_history.forEach((item) => {
+ data.push([item.timestamp_utc, item.price])
+ })
+ return data
+ }, [price_history])
+
+ const series_data = useMemo(() => {
+ return {
+ name: base_range_labels[range],
+ type: 'line',
+ lineStyle: {
+ width: 2
+ },
+ showSymbol: false,
+ data: data[`${range}_account_count`].map((item) => [item[0], item[1]])
+ }
+ }, [data, range])
+
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross',
+ label: {
+ backgroundColor: '#6a7985'
+ }
+ }
+ },
+ legend: {
+ bottom: 0
+ },
+ xAxis: [
+ {
+ type: 'time',
+ boundaryGap: false
+ }
+ ],
+ yAxis: [
+ {
+ type: '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'
+ }
+ }
+ ]
+ }
+
+ return (
+ <>
+
+
+
+ Description
+
+
+
+ The number of unique addresses holding {base_range_labels[range]}{' '}
+ Nano.
+
+
+ {!isLoading && (
+
+
+
+
+ )}
+
+ >
+ )
+}
+
+LedgerChartAddressesWithBalance.propTypes = {
+ data: PropTypes.object.isRequired,
+ isLoading: PropTypes.bool.isRequired,
+ price_history: PropTypes.array.isRequired,
+ get_price_history: PropTypes.func.isRequired,
+ range: PropTypes.string
+}
diff --git a/src/views/components/ledger-chart-amounts/ledger-chart-amounts.js b/src/views/components/ledger-chart-amounts/ledger-chart-amounts.js
index b0aa3e06..ddd99076 100644
--- a/src/views/components/ledger-chart-amounts/ledger-chart-amounts.js
+++ b/src/views/components/ledger-chart-amounts/ledger-chart-amounts.js
@@ -13,6 +13,7 @@ import { CanvasRenderer } from 'echarts/renderers'
import Button from '@mui/material/Button'
import { download_csv, download_json } from '@core/utils'
+import { base_ranges, base_range_labels } from '@core/constants'
echarts.use([
TooltipComponent,
@@ -26,49 +27,33 @@ echarts.use([
const color_map = {
// high
- _1000000_count: '#ffd700', // Gold
- _100000_count: '#f9c74f', // Maize Crayola
- _10000_count: '#f4a261', // Sandy Brown
- _1000_count: '#e63946', // Red
+ _1000000: '#ffd700', // Gold
+ _100000: '#f9c74f', // Maize Crayola
+ _10000: '#f4a261', // Sandy Brown
+ _1000: '#e63946', // Red
// medium
- _100_count: '#0077b6', // Star Command Blue
- _10_count: '#023e8a', // Royal Blue Dark
- _1_count: '#0096c7', // Pacific Blue
+ _100: '#0077b6', // Star Command Blue
+ _10: '#023e8a', // Royal Blue Dark
+ _1: '#0096c7', // Pacific Blue
// low
- _01_count: '#6DB65B', // Apple Green
- _001_count: '#57A55A', // Medium Sea Green
- _0001_count: '#3D8B57', // Jungle Green
- _00001_count: '#2C6E49', // Bottle Green
+ _01: '#6DB65B', // Apple Green
+ _001: '#57A55A', // Medium Sea Green
+ _0001: '#3D8B57', // Jungle Green
+ _00001: '#2C6E49', // Bottle Green
// micro
- _000001_count: '#911eb4', // Purple
- _000001_below_count: '#dcbeff' // Lavender Blush
+ _000001: '#911eb4', // Purple
+ _000001_below: '#dcbeff' // Lavender Blush
}
export default function LedgerChartAmounts({ data, isLoading }) {
- const ranges = {
- _1000000_count: '>1M',
- _100000_count: '100k to 1M',
- _10000_count: '10k to 100k',
- _1000_count: '1k to 10k',
- _100_count: '100 to 1k',
- _10_count: '10 to 100',
- _1_count: '1 to 10',
- _01_count: '0.1 to 1',
- _001_count: '0.01 to 0.1',
- _0001_count: '0.001 to 0.01',
- _00001_count: '0.0001 to 0.001',
- _000001_count: '0.00001 to 0.0001',
- _000001_below_count: '<0.00001'
- }
-
- const series_data = Object.entries(ranges).map(([key, value], index) => {
+ const series_data = base_ranges.map((key) => {
const color = color_map[key]
return {
- name: value,
+ name: base_range_labels[key],
type: 'line',
stack: 'total',
color,
@@ -82,7 +67,7 @@ export default function LedgerChartAmounts({ data, isLoading }) {
emphasis: {
focus: 'series'
},
- data: data[key].map((item) => [item[0], item[1]])
+ data: data[`${key}_count`].map((item) => [item[0], item[1]])
}
})
@@ -122,10 +107,10 @@ export default function LedgerChartAmounts({ data, isLoading }) {
const handle_download_csv = () => {
const download_data = []
- Object.entries(ranges).forEach(([key, value]) => {
- data[key].forEach((item) => {
+ base_ranges.forEach((key) => {
+ data[`${key}_count`].forEach((item) => {
download_data.push({
- range: value,
+ range: base_range_labels[key],
date: item[0],
count: item[1]
})
@@ -145,11 +130,13 @@ export default function LedgerChartAmounts({ data, isLoading }) {
data: {}
}
- Object.entries(ranges).forEach(([key, value]) => {
- download_data.data[value] = data[key].map((item) => ({
- date: item[0],
- count: item[1]
- }))
+ base_ranges.forEach((key) => {
+ download_data.data[base_range_labels[key]] = data[`${key}_count`].map(
+ (item) => ({
+ date: item[0],
+ count: item[1]
+ })
+ )
})
download_json({
diff --git a/src/views/components/ledger-chart-distribution/ledger-chart-distribution.js b/src/views/components/ledger-chart-distribution/ledger-chart-distribution.js
index 38999b87..fe123a16 100644
--- a/src/views/components/ledger-chart-distribution/ledger-chart-distribution.js
+++ b/src/views/components/ledger-chart-distribution/ledger-chart-distribution.js
@@ -7,31 +7,32 @@ import { LineChart } from 'echarts/charts'
import { TooltipComponent, GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
+import { base_ranges, base_range_labels } from '@core/constants'
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
+ _1000000: '#ffd700', // Gold
+ _100000: '#f9c74f', // Maize Crayola
+ _10000: '#f4a261', // Sandy Brown
+ _1000: '#e63946', // Red
// medium
- _100_relative_supply: '#0077b6', // Star Command Blue
- _10_relative_supply: '#023e8a', // Royal Blue Dark
- _1_relative_supply: '#0096c7', // Pacific Blue
+ _100: '#0077b6', // Star Command Blue
+ _10: '#023e8a', // Royal Blue Dark
+ _1: '#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
+ _01: '#6DB65B', // Apple Green
+ _001: '#57A55A', // Medium Sea Green
+ _0001: '#3D8B57', // Jungle Green
+ _00001: '#2C6E49', // Bottle Green
// micro
- _000001_relative_supply: '#911eb4', // Purple
- _000001_below_relative_supply: '#dcbeff' // Lavender Blush
+ _000001: '#911eb4', // Purple
+ _000001_below: '#dcbeff' // Lavender Blush
}
export default function LedgerChartDistribution({
@@ -54,27 +55,11 @@ export default function LedgerChartDistribution({
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 series_data = base_ranges.map((key) => {
const color = color_map[key]
return {
- name: value,
+ name: base_range_labels[key],
type: 'line',
stack: 'total',
color,
@@ -89,7 +74,7 @@ export default function LedgerChartDistribution({
emphasis: {
focus: 'series'
},
- data: data[key].map((item) => [item[0], item[1]])
+ data: data[`${key}_relative_supply`].map((item) => [item[0], item[1]])
}
})
@@ -147,10 +132,10 @@ export default function LedgerChartDistribution({
const handle_download_csv = () => {
const download_data = []
- Object.entries(ranges).forEach(([key, value]) => {
- data[key].forEach((item) => {
+ base_ranges.forEach((key) => {
+ data[`${key}_relative_supply`].forEach((item) => {
download_data.push({
- range: value,
+ range: base_range_labels[key],
date: item[0],
relative_supply: item[1]
})
@@ -170,8 +155,10 @@ export default function LedgerChartDistribution({
data: {}
}
- Object.entries(ranges).forEach(([key, value]) => {
- download_data.data[value] = data[key].map((item) => ({
+ base_ranges.forEach((key) => {
+ download_data.data[base_range_labels[key]] = data[
+ `${key}_relative_supply`
+ ].map((item) => ({
date: item[0],
relative_supply: item[1]
}))
diff --git a/src/views/pages/ledger/index.js b/src/views/pages/ledger/index.js
index 56c9c7f8..ed448a6c 100644
--- a/src/views/pages/ledger/index.js
+++ b/src/views/pages/ledger/index.js
@@ -3,6 +3,7 @@ import { createSelector } from 'reselect'
import BigNumber from 'bignumber.js'
import { getLedger, ledgerActions } from '@core/ledger'
+import { base_ranges } from '@core/constants'
import LedgerPage from './ledger'
@@ -11,21 +12,6 @@ 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',
@@ -44,7 +30,8 @@ const mapStateToProps = createSelector(getLedger, (ledger) => {
// TODO open_volume
// TODO receive_volume
- ...base_ranges.map((r) => `${r}_count`)
+ ...base_ranges.map((r) => `${r}_count`),
+ ...base_ranges.map((r) => `${r}_account_count`)
]
for (let i = 0; i < metrics.length; i++) {
diff --git a/src/views/pages/ledger/ledger.js b/src/views/pages/ledger/ledger.js
index bca4cd43..c677e278 100644
--- a/src/views/pages/ledger/ledger.js
+++ b/src/views/pages/ledger/ledger.js
@@ -16,8 +16,10 @@ 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 LedgerChartAddressesWithBalance from '@components/ledger-chart-addresses-with-balance'
import Seo from '@components/seo'
import Menu from '@components/menu'
+import { base_ranges, base_range_labels } from '@core/constants'
import './ledger.styl'
@@ -36,6 +38,48 @@ export default function LedgerPage({ load, data, isLoading }) {
const [menu_open, set_menu_open] = useState(false)
const filter_input_ref = useRef(null)
+ const categories = {
+ Addresses: {
+ 'Address Counts': (
+
+ )
+ },
+ Blocks: {
+ 'Block Counts':
+ },
+ Transactions: {
+ 'Value Transferred': (
+
+ ),
+ 'Transfer Volume': (
+
+ ),
+ 'Transfer Amounts': (
+
+ )
+ },
+ Distribution: {
+ 'Address Supply Distribution': (
+
+ ),
+ 'Address Balances (Nano)': {
+ ...base_ranges.reduce(
+ (acc, range) => ({
+ ...acc,
+ [`Addresses with Balance ${base_range_labels[range]}`]: (
+
+ )
+ }),
+ {}
+ )
+ }
+ }
+ }
+
useEffect(() => {
load()
}, [])
@@ -78,15 +122,21 @@ export default function LedgerPage({ load, data, isLoading }) {
useEffect(() => {
if (filter_text.length > 0) {
- // Uncollapse all categories when filtering
- set_open_categories(
- Object.keys(filtered_categories).reduce(
- (acc, key) => ({ ...acc, [key]: true }),
- {}
- )
- )
+ // Uncollapse all categories and their children when filtering
+ const all_open_categories = Object.keys(categories).reduce((acc, key) => {
+ const category = categories[key]
+ if (typeof category === 'object') {
+ const sub_keys = Object.keys(category)
+ sub_keys.forEach((sub_key) => {
+ acc[sub_key] = true
+ })
+ }
+ acc[key] = true
+ return acc
+ }, {})
+ set_open_categories(all_open_categories)
}
- }, [filtered_categories])
+ }, [filtered_categories, categories, filter_text])
useEffect(() => {
if (menu_open) {
@@ -135,57 +185,48 @@ export default function LedgerPage({ load, data, isLoading }) {
set_open_categories((prev) => ({ ...prev, [category]: !prev[category] }))
}
- const categories = {
- Addresses: {
- 'Address Counts': (
-
- )
- },
- Blocks: {
- 'Block Counts':
- },
- Transactions: {
- 'Value Transferred': (
-
- ),
- 'Transfer Volume': (
-
- ),
- 'Transfer Amounts': (
-
- )
- },
- Distribution: {
- 'Address Supply Distribution': (
-
- )
- }
+ const count_children = (children) => {
+ return Object.values(children).reduce((acc, child) => {
+ if (React.isValidElement(child)) {
+ return acc + 1
+ } else {
+ return acc + count_children(child)
+ }
+ }, 0)
}
- const render_category = (category, charts, depth) => {
- const is_open = open_categories[category] || false
+ const render_category = (parent_category, children, depth, path = []) => {
+ const is_open = open_categories[parent_category] || false
+ const total_children_count = count_children(children)
return (
<>
toggle_category(category)}
+ onClick={() => toggle_category(parent_category)}
className={`ledger__category ledger__category-depth-${depth}`}>
-
-
+
+
- {Object.entries(charts).map(([key, value]) => {
+ {Object.entries(children).map(([key, value]) => {
+ console.log({
+ path,
+ parent_category,
+ key
+ })
+ const current_path = [...path, parent_category]
const is_selected =
- selected_chart[0] === category && selected_chart[1] === key
+ selected_chart.join(' > ') ===
+ [...current_path, key].join(' > ')
const item_class = is_selected ? 'ledger__chart--selected' : ''
if (React.isValidElement(value)) {
return (
set_selected_chart([category, key])}
+ onClick={() => set_selected_chart([...current_path, key])}
className={`ledger__chart ledger__category-depth-${
depth + 1
} ${item_class}`}>
@@ -193,7 +234,7 @@ export default function LedgerPage({ load, data, isLoading }) {
)
} else {
- return render_category(key, value, depth + 1)
+ return render_category(key, value, depth + 1, current_path)
}
})}
@@ -255,8 +296,8 @@ export default function LedgerPage({ load, data, isLoading }) {
{Object.entries(
filter_text.length > 0 ? filtered_categories : categories
- ).map(([category, charts]) =>
- render_category(category, charts, 0)
+ ).map(([parent_category, children]) =>
+ render_category(parent_category, children, 0)
)}
diff --git a/src/views/pages/ledger/ledger.styl b/src/views/pages/ledger/ledger.styl
index d87a296c..46527cd2 100644
--- a/src/views/pages/ledger/ledger.styl
+++ b/src/views/pages/ledger/ledger.styl
@@ -53,10 +53,20 @@
background hsl(210, 100%, 60%)
.ledger__category-depth-2
- padding-left 49px
+ padding-left 72px
+ &:before
+ content: ""
+ display block
+ position: absolute
+ z-index 1
+ left 44px
+ height 100%
+ width 1px
+ opacity 1
+ background #E5EAF2
.ledger__category-depth-3
- padding-left 62px
+ padding-left 90px
.ledger__footer
margin 32px auto