Staker breakdown graph (#1882)
This commit is contained in:
parent
7a10113b64
commit
39dcdca190
@ -3,11 +3,12 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { BlockNumber, Exposure, SessionIndex } from '@polkadot/types/interfaces';
|
||||
import { Balance, BlockNumber, Hash, Exposure, SessionIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Chart, Columar, Column } from '@polkadot/react-components';
|
||||
import { toShortAddress } from '@polkadot/react-components/util';
|
||||
import { ApiContext, withCalls } from '@polkadot/react-api';
|
||||
import { getHistoric } from '@polkadot/react-api/util';
|
||||
import { formatBalance, formatNumber } from '@polkadot/util';
|
||||
@ -24,9 +25,21 @@ interface Props extends I18nProps {
|
||||
|
||||
type LineData = (BN | number)[][];
|
||||
|
||||
interface SplitEntry {
|
||||
colors: string[];
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
type SplitData = SplitEntry[];
|
||||
|
||||
// assuming 4 hrs sessions, we grab results for 10 days
|
||||
const SESSIONS = 10 * (24 / 4);
|
||||
|
||||
const COLORS_MINE = ['#ff8c00'];
|
||||
const COLORS_OTHER = ['#acacac'];
|
||||
const COLORS_BLOCKS = [undefined, '#acacac'];
|
||||
|
||||
function getIndexRange (currentIndex: SessionIndex): BN[] {
|
||||
const range: BN[] = [];
|
||||
let thisIndex: BN = currentIndex;
|
||||
@ -40,12 +53,46 @@ function getIndexRange (currentIndex: SessionIndex): BN[] {
|
||||
return range.reverse();
|
||||
}
|
||||
|
||||
function extractStake (values: [BN, Hash, Exposure][], divisor: BN): [string[], LineData] {
|
||||
return [
|
||||
values.map(([bn]): string => formatNumber(bn)),
|
||||
[
|
||||
values.map(([,, { total }]): BN =>
|
||||
total.unwrap().div(divisor))
|
||||
// exposures.map(({ own }): BN =>
|
||||
// own.unwrap().div(divisor)),
|
||||
// exposures.map(({ others }): BN =>
|
||||
// others.reduce((total, { value }): BN => total.add(value.unwrap()), new BN(0)).div(divisor))
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
function extractSplit (values: [BN, Hash, Exposure][], validatorId: string): SplitData | null {
|
||||
const last = values[values.length - 1][2];
|
||||
const total = last.total.unwrap();
|
||||
|
||||
if (total.eqn(0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [{ accountId: validatorId, isOwn: true, value: last.own.unwrap() }]
|
||||
.concat(last.others.map(({ who, value }): { accountId: string; isOwn: boolean; value: Balance } => ({
|
||||
accountId: who.toString(), isOwn: false, value: value.unwrap()
|
||||
})))
|
||||
.sort((a, b): number => b.value.cmp(a.value))
|
||||
.map(({ accountId, isOwn, value }): SplitEntry => ({
|
||||
colors: isOwn ? COLORS_MINE : COLORS_OTHER,
|
||||
label: toShortAddress(accountId),
|
||||
value: value.muln(10000).div(total).toNumber() / 100
|
||||
}));
|
||||
}
|
||||
|
||||
function Validator ({ blockCounts, className, currentIndex, startNumber, t, validatorId }: Props): React.ReactElement<Props> {
|
||||
const { api } = useContext(ApiContext);
|
||||
const [blocksLabels, setBlocksLabels] = useState<string[]>([]);
|
||||
const [blocksChart, setBlocksChart] = useState<LineData | undefined>();
|
||||
const [stakeLabels, setStakeLabels] = useState<string[]>([]);
|
||||
const [stakeChart, setStateChart] = useState<LineData | undefined>();
|
||||
const [blocksChart, setBlocksChart] = useState<LineData | null>(null);
|
||||
const [splitChart, setSplitInfo] = useState<SplitData | null>(null);
|
||||
const [{ stakeChart, stakeLabels }, setStakeInfo] = useState<{ stakeChart: LineData | null; stakeLabels: string[]}>({ stakeChart: null, stakeLabels: [] });
|
||||
|
||||
useEffect((): void => {
|
||||
api.isReady.then(async (): Promise<void> => {
|
||||
@ -55,16 +102,11 @@ function Validator ({ blockCounts, className, currentIndex, startNumber, t, vali
|
||||
max: SESSIONS,
|
||||
startNumber
|
||||
});
|
||||
const [stakeLabels, stakeChart] = extractStake(values, divisor);
|
||||
const splitChart = extractSplit(values, validatorId);
|
||||
|
||||
setStakeLabels(values.map(([bn]): string => formatNumber(bn)));
|
||||
setStateChart([
|
||||
values.map(([,, { total }]): BN =>
|
||||
total.unwrap().div(divisor))
|
||||
// exposures.map(({ own }): BN =>
|
||||
// own.unwrap().div(divisor)),
|
||||
// exposures.map(({ others }): BN =>
|
||||
// others.reduce((total, { value }): BN => total.add(value.unwrap()), new BN(0)).div(divisor))
|
||||
]);
|
||||
setStakeInfo({ stakeChart, stakeLabels });
|
||||
setSplitInfo(splitChart);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@ -94,29 +136,43 @@ function Validator ({ blockCounts, className, currentIndex, startNumber, t, vali
|
||||
|
||||
return (
|
||||
<Columar className={className}>
|
||||
<Column
|
||||
emptyText={t('Loading data')}
|
||||
headerText={t('blocks per session')}
|
||||
>
|
||||
<Column emptyText={t('Loading block data')}>
|
||||
{blocksChart && (
|
||||
<Chart.Line
|
||||
colors={[undefined, '#acacac']}
|
||||
labels={blocksLabels}
|
||||
legends={[t('blocks'), t('average')]}
|
||||
values={blocksChart}
|
||||
/>
|
||||
<div className='staking--Chart'>
|
||||
<h1>{t('blocks per session')}</h1>
|
||||
<Chart.Line
|
||||
colors={COLORS_BLOCKS}
|
||||
labels={blocksLabels}
|
||||
legends={[t('blocks'), t('average')]}
|
||||
values={blocksChart}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Column>
|
||||
<Column
|
||||
emptyText={t('Loading data')}
|
||||
headerText={t('elected stake')}
|
||||
>
|
||||
{stakeChart && (
|
||||
<Chart.Line
|
||||
labels={stakeLabels}
|
||||
legends={[t('total'), t('own'), t('other')]}
|
||||
values={stakeChart}
|
||||
/>
|
||||
<Column emptyText={t('Loading staker data')}>
|
||||
{(stakeChart || splitChart) && (
|
||||
<>
|
||||
{stakeChart && (
|
||||
<div className='staking--Chart'>
|
||||
<h1>{t('elected stake')}</h1>
|
||||
<Chart.Line
|
||||
labels={stakeLabels}
|
||||
legends={[t('total'), t('own'), t('other')]}
|
||||
values={stakeChart}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{splitChart && (
|
||||
<div className='staking--Chart'>
|
||||
<h1>{t('staker percentages')}</h1>
|
||||
<Chart.HorizBar
|
||||
aspectRatio={2}
|
||||
max={Math.min(Math.ceil(splitChart[0].value), 100)}
|
||||
values={splitChart}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
</Columar>
|
||||
|
||||
@ -110,6 +110,14 @@ export default withMulti(
|
||||
.staking--queryInput {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.staking--Chart h1 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.staking--Chart+.staking--Chart {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
`,
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
|
||||
@ -8,7 +8,7 @@ import BN from 'bn.js';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ChartJs from 'chart.js';
|
||||
import { HorizontalBar } from 'react-chartjs-2';
|
||||
import { bnToBn } from '@polkadot/util';
|
||||
import { bnToBn, isNumber } from '@polkadot/util';
|
||||
|
||||
interface Value {
|
||||
colors: string[];
|
||||
@ -18,7 +18,9 @@ interface Value {
|
||||
|
||||
interface Props extends BareProps {
|
||||
aspectRatio?: number;
|
||||
max?: number;
|
||||
values: Value[];
|
||||
withColors?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -39,13 +41,13 @@ interface Config {
|
||||
const alphaColor = (hexColor: string): string =>
|
||||
ChartJs.helpers.color(hexColor).alpha(0.65).rgbString();
|
||||
|
||||
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string): State {
|
||||
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string, max: number): State {
|
||||
const chartData = values.reduce((data, { colors: [normalColor = '#00f', hoverColor], label, value }): Config => {
|
||||
const dataset = data.datasets[0];
|
||||
|
||||
dataset.backgroundColor.push(alphaColor(normalColor));
|
||||
dataset.hoverBackgroundColor.push(alphaColor(hoverColor || normalColor));
|
||||
dataset.data.push(bnToBn(value).toNumber());
|
||||
dataset.data.push(isNumber(value) ? value : bnToBn(value).toNumber());
|
||||
data.labels.push(label);
|
||||
|
||||
return data;
|
||||
@ -71,7 +73,7 @@ function calculateOptions (aspectRatio: number, values: Value[], jsonValues: str
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
max: 100
|
||||
max
|
||||
}
|
||||
}]
|
||||
}
|
||||
@ -80,14 +82,14 @@ function calculateOptions (aspectRatio: number, values: Value[], jsonValues: str
|
||||
};
|
||||
}
|
||||
|
||||
export default function ChartHorizBar ({ aspectRatio = 4, className, style, values }: Props): React.ReactElement<Props> | null {
|
||||
export default function ChartHorizBar ({ aspectRatio = 4, className, max = 100, style, values }: Props): React.ReactElement<Props> | null {
|
||||
const [{ chartData, chartOptions, jsonValues }, setState] = useState<State>({});
|
||||
|
||||
useEffect((): void => {
|
||||
const newJsonValues = JSON.stringify(values);
|
||||
|
||||
if (newJsonValues !== jsonValues) {
|
||||
setState(calculateOptions(aspectRatio, values, newJsonValues));
|
||||
setState(calculateOptions(aspectRatio, values, newJsonValues, max));
|
||||
}
|
||||
}, [values]);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user