Add difficulty chart timespan selection
This commit is contained in:
parent
9fa7e58d82
commit
f45103e7e3
@ -69,6 +69,19 @@ class Mining {
|
|||||||
emptyBlocks: emptyBlocks,
|
emptyBlocks: emptyBlocks,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the historical difficulty adjustments and oldest indexed block timestamp
|
||||||
|
*/
|
||||||
|
public async $getHistoricalDifficulty(interval: string | null): Promise<object> {
|
||||||
|
const difficultyAdjustments = await BlocksRepository.$getBlocksDifficulty(interval);
|
||||||
|
const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
|
||||||
|
|
||||||
|
return {
|
||||||
|
adjustments: difficultyAdjustments,
|
||||||
|
oldestIndexedBlockTimestamp: oldestBlock.getTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Mining();
|
export default new Mining();
|
||||||
|
@ -232,7 +232,7 @@ class BlocksRepository {
|
|||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.pool.getConnection();
|
||||||
|
|
||||||
let query = `SELECT MIN(blockTimestamp) as timestamp, difficulty, height
|
let query = `SELECT MIN(UNIX_TIMESTAMP(blockTimestamp)) as timestamp, difficulty, height
|
||||||
FROM blocks`;
|
FROM blocks`;
|
||||||
|
|
||||||
if (interval) {
|
if (interval) {
|
||||||
|
@ -577,7 +577,7 @@ class Routes {
|
|||||||
|
|
||||||
public async $getHistoricalDifficulty(req: Request, res: Response) {
|
public async $getHistoricalDifficulty(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const stats = await BlocksRepository.$getBlocksDifficulty(req.params.interval ?? null);
|
const stats = await mining.$getHistoricalDifficulty(req.params.interval ?? null);
|
||||||
res.header('Pragma', 'public');
|
res.header('Pragma', 'public');
|
||||||
res.header('Cache-control', 'public');
|
res.header('Cache-control', 'public');
|
||||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||||
|
@ -5,6 +5,31 @@
|
|||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card-header mb-0 mb-lg-4">
|
||||||
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(difficultyObservable$ | async) as diffChanges">
|
||||||
|
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 90">
|
||||||
|
<input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 180">
|
||||||
|
<input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 365">
|
||||||
|
<input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 730">
|
||||||
|
<input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 1095">
|
||||||
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/mining/difficulty' | relativeUrl]" fragment="all"> ALL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table table-borderless table-sm text-center">
|
<table class="table table-borderless table-sm text-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -14,12 +39,12 @@
|
|||||||
<th i18n="mining.change">Change</th>
|
<th i18n="mining.change">Change</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="(difficultyObservable$ | async) as diffChange">
|
<tbody *ngIf="(difficultyObservable$ | async) as diffChanges">
|
||||||
<tr *ngFor="let change of diffChange">
|
<tr *ngFor="let diffChange of diffChanges.data">
|
||||||
<td><a [routerLink]="['/block' | relativeUrl, change[2]]">{{ change[2] }}</a></td>
|
<td><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height }}</a></td>
|
||||||
<td>‎{{ change[0] | date:'yyyy-MM-dd HH:mm' }}</td>
|
<td>‎{{ diffChange.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||||
<td>{{ formatNumber(change[1], locale, '1.2-2') }}</td>
|
<td>{{ formatNumber(diffChange.difficulty, locale, '1.2-2') }}</td>
|
||||||
<td [style]="change[3] >= 0 ? 'color: #42B747' : 'color: #B74242'">{{ formatNumber(change[3], locale, '1.2-2') }}%</td>
|
<td [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">{{ formatNumber(diffChange.change, locale, '1.2-2') }}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
import { Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
import { EChartsOption } from 'echarts';
|
import { EChartsOption } from 'echarts';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map, share, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ApiService } from 'src/app/services/api.service';
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { formatNumber } from "@angular/common";
|
import { formatNumber } from '@angular/common';
|
||||||
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-difficulty-chart',
|
selector: 'app-difficulty-chart',
|
||||||
@ -20,6 +21,8 @@ import { formatNumber } from "@angular/common";
|
|||||||
`],
|
`],
|
||||||
})
|
})
|
||||||
export class DifficultyChartComponent implements OnInit {
|
export class DifficultyChartComponent implements OnInit {
|
||||||
|
radioGroupForm: FormGroup;
|
||||||
|
|
||||||
chartOptions: EChartsOption = {};
|
chartOptions: EChartsOption = {};
|
||||||
chartInitOptions = {
|
chartInitOptions = {
|
||||||
renderer: 'svg'
|
renderer: 'svg'
|
||||||
@ -33,34 +36,45 @@ export class DifficultyChartComponent implements OnInit {
|
|||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
) {
|
) {
|
||||||
this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`);
|
this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`);
|
||||||
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
||||||
|
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.difficultyObservable$ = this.apiService.getHistoricalDifficulty$(undefined)
|
this.difficultyObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
map(data => {
|
startWith('1y'),
|
||||||
let formatted = [];
|
switchMap((timespan) => {
|
||||||
for (let i = 0; i < data.length - 1; ++i) {
|
return this.apiService.getHistoricalDifficulty$(timespan)
|
||||||
const change = (data[i].difficulty / data[i + 1].difficulty - 1) * 100;
|
.pipe(
|
||||||
formatted.push([
|
tap(data => {
|
||||||
data[i].timestamp,
|
this.prepareChartOptions(data.adjustments.map(val => [val.timestamp * 1000, val.difficulty]));
|
||||||
data[i].difficulty,
|
this.isLoading = false;
|
||||||
data[i].height,
|
}),
|
||||||
formatNumber(change, this.locale, '1.2-2'),
|
map(data => {
|
||||||
change,
|
const availableTimespanDay = (
|
||||||
formatNumber(data[i].difficulty, this.locale, '1.2-2'),
|
(new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp / 1000)
|
||||||
]);
|
) / 3600 / 24;
|
||||||
}
|
|
||||||
return formatted;
|
const tableData = [];
|
||||||
}),
|
for (let i = 0; i < data.adjustments.length - 1; ++i) {
|
||||||
tap(data => {
|
const change = (data.adjustments[i].difficulty / data.adjustments[i + 1].difficulty - 1) * 100;
|
||||||
this.prepareChartOptions(data);
|
tableData.push(Object.assign(data.adjustments[i], {
|
||||||
this.isLoading = false;
|
change: change
|
||||||
}),
|
}));
|
||||||
share()
|
}
|
||||||
)
|
return {
|
||||||
|
availableTimespanDay: availableTimespanDay,
|
||||||
|
data: tableData
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
share()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(data) {
|
prepareChartOptions(data) {
|
||||||
@ -88,7 +102,7 @@ export class DifficultyChartComponent implements OnInit {
|
|||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
formatter: function(val) {
|
formatter: (val) => {
|
||||||
const diff = val / Math.pow(10, 12); // terra
|
const diff = val / Math.pow(10, 12); // terra
|
||||||
return diff.toString() + 'T';
|
return diff.toString() + 'T';
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
if (parseFloat(pool.share) < poolShareThreshold) {
|
if (parseFloat(pool.share) < poolShareThreshold) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data.push(<PieSeriesOption>{
|
data.push({
|
||||||
value: pool.share,
|
value: pool.share,
|
||||||
name: pool.name + (this.isMobile() ? `` : ` (${pool.share}%)`),
|
name: pool.name + (this.isMobile() ? `` : ` (${pool.share}%)`),
|
||||||
label: {
|
label: {
|
||||||
@ -115,9 +115,9 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
overflow: 'break',
|
overflow: 'break',
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
backgroundColor: "#282d47",
|
backgroundColor: '#282d47',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#FFFFFF",
|
color: '#FFFFFF',
|
||||||
},
|
},
|
||||||
formatter: () => {
|
formatter: () => {
|
||||||
if (this.poolsWindowPreference === '24h') {
|
if (this.poolsWindowPreference === '24h') {
|
||||||
@ -131,7 +131,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: pool.poolId,
|
data: pool.poolId,
|
||||||
});
|
} as PieSeriesOption);
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -205,10 +205,10 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
|
|
||||||
this.chartInstance = ec;
|
this.chartInstance = ec;
|
||||||
this.chartInstance.on('click', (e) => {
|
this.chartInstance.on('click', (e) => {
|
||||||
this.router.navigate(['/mining/pool/', e.data.data]);
|
this.router.navigate(['/mining/pool/', e.data.data]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default mining stats if something goes wrong
|
* Default mining stats if something goes wrong
|
||||||
*/
|
*/
|
||||||
|
@ -150,7 +150,7 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistoricalDifficulty$(interval: string | undefined): Observable<any[]> {
|
getHistoricalDifficulty$(interval: string | undefined): Observable<any> {
|
||||||
return this.httpClient.get<any[]>(
|
return this.httpClient.get<any[]>(
|
||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
||||||
(interval !== undefined ? `/${interval}` : '')
|
(interval !== undefined ? `/${interval}` : '')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user