Blocks per session graph (hidden) (#1871)
This commit is contained in:
parent
e15b69e62c
commit
2b9dd5baa9
79
packages/app-staking/src/Query/Validator.tsx
Normal file
79
packages/app-staking/src/Query/Validator.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { SessionIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Chart } from '@polkadot/react-components';
|
||||
import { withCalls } from '@polkadot/react-api';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
blockCounts?: BN[];
|
||||
className?: string;
|
||||
currentIndex: SessionIndex;
|
||||
validatorId: string;
|
||||
}
|
||||
|
||||
interface Value {
|
||||
label: string;
|
||||
value: BN;
|
||||
}
|
||||
|
||||
function getIndexRange (currentIndex: SessionIndex): BN[] {
|
||||
const range: BN[] = [];
|
||||
let thisIndex: BN = currentIndex;
|
||||
|
||||
while (thisIndex.gtn(0) && range.length < 50) {
|
||||
range.push(thisIndex);
|
||||
|
||||
thisIndex = thisIndex.subn(1);
|
||||
}
|
||||
|
||||
return range.reverse();
|
||||
}
|
||||
|
||||
function Validator ({ blockCounts, className, currentIndex, t }: Props): React.ReactElement<Props> {
|
||||
const [labels, setLabels] = useState<string[]>([]);
|
||||
const [values, setValues] = useState<Value[]>([]);
|
||||
|
||||
useEffect((): void => {
|
||||
setLabels(
|
||||
getIndexRange(currentIndex).map((index): string => formatNumber(index))
|
||||
);
|
||||
}, [currentIndex]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (blockCounts) {
|
||||
setValues(
|
||||
blockCounts.map((value, index): Value => ({
|
||||
label: labels[index],
|
||||
value
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [blockCounts, labels]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<h1>{t('blocks per session')}</h1>
|
||||
<Chart.Line values={values} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(
|
||||
withCalls<Props>(
|
||||
['query.imOnline.authoredBlocks', {
|
||||
isMulti: true,
|
||||
propName: 'blockCounts',
|
||||
paramPick: ({ currentIndex, validatorId }: Props): [BN, string][] =>
|
||||
getIndexRange(currentIndex).map((index): [BN, string] => [index, validatorId])
|
||||
}]
|
||||
)(Validator)
|
||||
);
|
||||
37
packages/app-staking/src/Query/index.tsx
Normal file
37
packages/app-staking/src/Query/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/react-components/types';
|
||||
import { ComponentProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import Validator from './Validator';
|
||||
|
||||
interface Props extends BareProps, ComponentProps, RouteComponentProps<{}> {
|
||||
match: {
|
||||
isExact: boolean;
|
||||
params: {
|
||||
value: string;
|
||||
};
|
||||
path: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
|
||||
function Query ({ stakingOverview, match: { params: { value } } }: Props): React.ReactElement<Props> {
|
||||
if (!stakingOverview) {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Validator
|
||||
currentIndex={stakingOverview.currentIndex}
|
||||
validatorId={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(Query);
|
||||
@ -21,6 +21,7 @@ import accountObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
import Accounts from './Actions/Accounts';
|
||||
import basicMd from './md/basic.md';
|
||||
import Overview from './Overview';
|
||||
import Query from './Query';
|
||||
import translate from './translate';
|
||||
|
||||
interface Props extends AppProps, ApiProps, I18nProps {
|
||||
@ -80,6 +81,7 @@ function App ({ allAccounts, allStashesAndControllers: [allStashes, allControlle
|
||||
</header>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/actions`} render={_renderComponent(Accounts)} />
|
||||
<Route path={`${basePath}/query/:value`} render={_renderComponent(Query)} />
|
||||
<Route render={_renderComponent(Overview)} />
|
||||
</Switch>
|
||||
</main>
|
||||
|
||||
@ -4,17 +4,9 @@
|
||||
|
||||
import { DerivedFees, DerivedBalances, DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { EraPoints } from '@polkadot/types/interfaces';
|
||||
import { u32 } from '@polkadot/types';
|
||||
|
||||
export type Nominators = Record<string, string[]>;
|
||||
|
||||
export interface DerivedStakingOverviewExt {
|
||||
currentElected: string[];
|
||||
eraPointsEarned: EraPoints;
|
||||
validatorCount: u32;
|
||||
}
|
||||
|
||||
export interface ComponentProps {
|
||||
allAccounts?: SubjectInfo;
|
||||
allControllers: string[];
|
||||
|
||||
109
packages/react-components/src/Chart/Line.tsx
Normal file
109
packages/react-components/src/Chart/Line.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2017-2019 @polkadot/react-components authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '../types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ChartJs from 'chart.js';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { bnToBn } from '@polkadot/util';
|
||||
|
||||
interface Value {
|
||||
label: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
interface Props extends BareProps {
|
||||
aspectRatio?: number;
|
||||
values: Value[];
|
||||
}
|
||||
|
||||
interface State {
|
||||
chartData?: ChartJs.ChartData;
|
||||
chartOptions?: ChartJs.ChartOptions;
|
||||
jsonValues?: string;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
labels: string[];
|
||||
datasets: {
|
||||
data: number[];
|
||||
fill: boolean;
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
hoverBackgroundColor: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const alphaColor = (hexColor: string): string =>
|
||||
ChartJs.helpers.color(hexColor).alpha(0.65).rgbString();
|
||||
|
||||
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string): State {
|
||||
const chartData = values.reduce((data, { label, value }): Config => {
|
||||
const dataset = data.datasets[0];
|
||||
|
||||
dataset.data.push(bnToBn(value).toNumber());
|
||||
data.labels.push(label);
|
||||
|
||||
return data;
|
||||
}, {
|
||||
labels: [] as string[],
|
||||
datasets: [{
|
||||
data: [] as number[],
|
||||
fill: false,
|
||||
backgroundColor: alphaColor('#ff8c00'),
|
||||
borderColor: alphaColor('#ff8c00'),
|
||||
hoverBackgroundColor: alphaColor('#ff8c00')
|
||||
}]
|
||||
});
|
||||
|
||||
return {
|
||||
chartData,
|
||||
chartOptions: {
|
||||
// width/height by default this is "1", i.e. a square box
|
||||
aspectRatio,
|
||||
// no need for the legend, expect the labels contain everything
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
jsonValues
|
||||
};
|
||||
}
|
||||
|
||||
export default function LineChart ({ aspectRatio = 4, className, 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));
|
||||
}
|
||||
}, [values]);
|
||||
|
||||
if (!chartData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<Line
|
||||
data={chartData}
|
||||
options={chartOptions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -4,8 +4,10 @@
|
||||
|
||||
import Doughnut from './Doughnut';
|
||||
import HorizBar from './HorizBar';
|
||||
import Line from './Line';
|
||||
|
||||
export default {
|
||||
Doughnut,
|
||||
HorizBar
|
||||
HorizBar,
|
||||
Line
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user