Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 89 additions & 33 deletions packages/app-staking/src/Query/Validator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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> => {
Expand All @@ -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);
});
}, []);

Expand Down Expand Up @@ -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>
Expand Down
8 changes: 8 additions & 0 deletions packages/app-staking/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand Down
14 changes: 8 additions & 6 deletions packages/react-components/src/Chart/HorizBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -18,7 +18,9 @@ interface Value {

interface Props extends BareProps {
aspectRatio?: number;
max?: number;
values: Value[];
withColors?: boolean;
}

interface State {
Expand All @@ -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;
Expand All @@ -71,7 +73,7 @@ function calculateOptions (aspectRatio: number, values: Value[], jsonValues: str
xAxes: [{
ticks: {
beginAtZero: true,
max: 100
max
}
}]
}
Expand All @@ -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]);

Expand Down