Staker breakdown graph (#1882)

This commit is contained in:
Jaco Greeff 2019-11-14 14:35:49 +01:00 committed by GitHub
parent 7a10113b64
commit 39dcdca190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 39 deletions

View File

@ -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>

View File

@ -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>(

View File

@ -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]);