Merge pull request #1601 from mempool/nymkappa/feature/chart-download

Add graph download feature
This commit is contained in:
wiz 2022-05-10 22:45:14 +09:00 committed by GitHub
commit 11cdbb3118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 293 additions and 55 deletions

View File

@ -48,7 +48,8 @@ import { DashboardComponent } from './dashboard/dashboard.component';
import { DifficultyComponent } from './components/difficulty/difficulty.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload } from '@fortawesome/free-solid-svg-icons';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
@ -195,5 +196,6 @@ export class AppModule {
library.addIcons(faAngleLeft);
library.addIcons(faBook);
library.addIcons(faListUl);
library.addIcons(faDownload);
}
}

View File

@ -1,6 +1,10 @@
<div class="full-container">
<div class="card-header mb-0 mb-md-4">
<span i18n="mining.block-fee-rates">Block fee rates</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.availableTimespanDay >= 1">

View File

@ -59,11 +59,11 @@
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -6,7 +6,7 @@ import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms';
import { formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
@ -297,4 +297,22 @@ export class BlockFeeRatesGraphComponent implements OnInit {
isMobile() {
return (window.innerWidth <= 767.98);
}
onSaveChart() {
// @ts-ignore
const prevBottom = this.chartOptions.grid.bottom;
const now = new Date();
// @ts-ignore
this.chartOptions.grid.bottom = 40;
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `block-fee-rates-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.chartOptions.grid.bottom = prevBottom;
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -1,6 +1,10 @@
<div class="full-container">
<div class="card-header mb-0 mb-md-4">
<span i18n="mining.block-fees">Block fees</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 144">
@ -37,7 +41,8 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>

View File

@ -59,11 +59,11 @@
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -6,7 +6,7 @@ import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms';
import { formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { download, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
@ -40,6 +40,7 @@ export class BlockFeesGraphComponent implements OnInit {
isLoading = true;
formatNumber = formatNumber;
timespan = '';
chartInstance: any = undefined;
constructor(
@Inject(LOCALE_ID) public locale: string,
@ -194,7 +195,29 @@ export class BlockFeesGraphComponent implements OnInit {
};
}
onChartInit(ec) {
this.chartInstance = ec;
}
isMobile() {
return (window.innerWidth <= 767.98);
}
onSaveChart() {
// @ts-ignore
const prevBottom = this.chartOptions.grid.bottom;
const now = new Date();
// @ts-ignore
this.chartOptions.grid.bottom = 40;
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `block-fees-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.chartOptions.grid.bottom = prevBottom;
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -2,6 +2,10 @@
<div class="card-header mb-0 mb-md-4">
<span i18n="mining.block-rewards">Block rewards</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 144">
@ -38,7 +42,8 @@
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>

View File

@ -59,11 +59,11 @@
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -6,7 +6,7 @@ import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms';
import { formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { download, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { MiningService } from 'src/app/services/mining.service';
import { StorageService } from 'src/app/services/storage.service';
@ -40,6 +40,7 @@ export class BlockRewardsGraphComponent implements OnInit {
isLoading = true;
formatNumber = formatNumber;
timespan = '';
chartInstance: any = undefined;
constructor(
@Inject(LOCALE_ID) public locale: string,
@ -194,7 +195,29 @@ export class BlockRewardsGraphComponent implements OnInit {
};
}
onChartInit(ec) {
this.chartInstance = ec;
}
isMobile() {
return (window.innerWidth <= 767.98);
}
onSaveChart() {
// @ts-ignore
const prevBottom = this.chartOptions.grid.bottom;
const now = new Date();
// @ts-ignore
this.chartOptions.grid.bottom = 40;
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `block-rewards-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.chartOptions.grid.bottom = prevBottom;
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -19,6 +19,10 @@
<div class="card-header mb-0 mb-md-4" [style]="widget ? 'display:none' : ''">
<span i18n="mining.hashrate-difficulty">Hashrate & Difficulty</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 4320">
@ -46,7 +50,8 @@
</form>
</div>
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions">
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>

View File

@ -59,11 +59,11 @@
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -9,6 +9,7 @@ import { FormBuilder, FormGroup } from '@angular/forms';
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
import { download } from 'src/app/shared/graphs.utils';
@Component({
selector: 'app-hashrate-chart',
@ -43,6 +44,8 @@ export class HashrateChartComponent implements OnInit {
hashrateObservable$: Observable<any>;
isLoading = true;
formatNumber = formatNumber;
timespan = '';
chartInstance: any = undefined;
constructor(
@Inject(LOCALE_ID) public locale: string,
@ -74,6 +77,7 @@ export class HashrateChartComponent implements OnInit {
if (!this.widget && !firstRun) {
this.storageService.setValue('miningWindowPreference', timespan);
}
this.timespan = timespan;
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
@ -340,7 +344,29 @@ export class HashrateChartComponent implements OnInit {
};
}
onChartInit(ec) {
this.chartInstance = ec;
}
isMobile() {
return (window.innerWidth <= 767.98);
}
onSaveChart() {
// @ts-ignore
const prevBottom = this.chartOptions.grid.bottom;
const now = new Date();
// @ts-ignore
this.chartOptions.grid.bottom = 30;
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `hashrate-difficulty-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.chartOptions.grid.bottom = prevBottom;
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -2,6 +2,10 @@
<div class="card-header mb-0 mb-md-4">
<span i18n="mining.pools-dominance">Mining pools dominance</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 4320">
@ -29,8 +33,9 @@
</form>
</div>
<div class="chart"
echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@ -53,11 +53,11 @@
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -8,6 +8,8 @@ import { FormBuilder, FormGroup } from '@angular/forms';
import { poolsColor } from 'src/app/app.constants';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
import { download } from 'src/app/shared/graphs.utils';
import { time } from 'console';
@Component({
selector: 'app-hashrate-chart-pools',
@ -39,6 +41,8 @@ export class HashrateChartPoolsComponent implements OnInit {
hashrateObservable$: Observable<any>;
isLoading = true;
timespan = '';
chartInstance: any = undefined;
constructor(
@Inject(LOCALE_ID) public locale: string,
@ -68,6 +72,7 @@ export class HashrateChartPoolsComponent implements OnInit {
if (!firstRun) {
this.storageService.setValue('miningWindowPreference', timespan);
}
this.timespan = timespan;
firstRun = false;
this.isLoading = true;
return this.apiService.getHistoricalPoolsHashrate$(timespan)
@ -247,7 +252,29 @@ export class HashrateChartPoolsComponent implements OnInit {
};
}
onChartInit(ec) {
this.chartInstance = ec;
}
isMobile() {
return (window.innerWidth <= 767.98);
}
onSaveChart() {
// @ts-ignore
const prevBottom = this.chartOptions.grid.bottom;
const now = new Date();
// @ts-ignore
this.chartOptions.grid.bottom = 30;
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `pools-dominance-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.chartOptions.grid.bottom = prevBottom;
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -1,4 +1,6 @@
<div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"></div>
<div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@ -2,7 +2,7 @@ import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit }
import { EChartsOption } from 'echarts';
import { OnChanges } from '@angular/core';
import { StorageService } from 'src/app/services/storage.service';
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { formatNumber } from '@angular/common';
@Component({
@ -34,6 +34,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
renderer: 'svg'
};
windowPreference: string;
chartInstance: any = undefined;
constructor(
@Inject(LOCALE_ID) private locale: string,
@ -224,7 +225,29 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
};
}
onChartInit(ec) {
this.chartInstance = ec;
}
isMobile() {
return window.innerWidth <= 767.98;
}
onSaveChart(timespan) {
// @ts-ignore
const prevHeight = this.mempoolStatsChartOption.grid.height;
const now = new Date();
// @ts-ignore
this.mempoolStatsChartOption.grid.height = prevHeight + 20;
this.mempoolStatsChartOption.backgroundColor = '#11131f';
this.chartInstance.setOption(this.mempoolStatsChartOption);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `incoming-vbytes-${timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.mempoolStatsChartOption.grid.height = prevHeight;
this.mempoolStatsChartOption.backgroundColor = 'none';
this.chartInstance.setOption(this.mempoolStatsChartOption);
}
}

View File

@ -1,12 +1,12 @@
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
import { formatNumber } from "@angular/common";
import { formatNumber } from '@angular/common';
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
import { StateService } from 'src/app/services/state.service';
import { StorageService } from 'src/app/services/storage.service';
import { EChartsOption } from 'echarts';
import { feeLevels, chartColors } from 'src/app/app.constants';
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
@Component({
selector: 'app-mempool-graph',
@ -45,6 +45,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
feeLevelsOrdered = [];
chartColorsOrdered = chartColors;
inverted: boolean;
chartInstance: any = undefined;
constructor(
private vbytesPipe: VbytesPipe,
@ -83,6 +84,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
this.hoverIndexSerie = e.target.parent.parent.__ecComponentInfo.index;
}
});
this.chartInstance = myChart;
}
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
@ -99,7 +101,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
generateArray(mempoolStats: OptimizedMempoolStats[]) {
const finalArray: number[][][] = [];
let feesArray: number[][] = [];
let limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
const limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
for (let index = limitFeesTemplate; index > -1; index--) {
feesArray = [];
mempoolStats.forEach((stats) => {
@ -387,5 +389,23 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
isMobile() {
return window.innerWidth <= 767.98;
}
onSaveChart(timespan) {
// @ts-ignore
const prevHeight = this.mempoolVsizeFeesOptions.grid.height;
const now = new Date();
// @ts-ignore
this.mempoolVsizeFeesOptions.grid.height = prevHeight + 20;
this.mempoolVsizeFeesOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.mempoolVsizeFeesOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `mempool-graph-${timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.mempoolVsizeFeesOptions.grid.height = prevHeight;
this.mempoolVsizeFeesOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.mempoolVsizeFeesOptions);
}
}

View File

@ -25,6 +25,10 @@
<div class="card-header" *ngIf="!widget">
<span i18n="mining.mining-pool-share">Mining pools share</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup"
*ngIf="!widget && (miningStatsObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">

View File

@ -37,11 +37,11 @@
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -11,6 +11,7 @@ import { MiningService, MiningStats } from '../../services/mining.service';
import { StateService } from '../../services/state.service';
import { chartColors, poolsColor } from 'src/app/app.constants';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
import { download } from 'src/app/shared/graphs.utils';
@Component({
selector: 'app-pool-ranking',
@ -29,6 +30,7 @@ export class PoolRankingComponent implements OnInit {
chartInitOptions = {
renderer: 'svg',
};
timespan = '';
chartInstance: any = undefined;
@HostBinding('attr.dir') dir = 'ltr';
@ -69,6 +71,7 @@ export class PoolRankingComponent implements OnInit {
.pipe(
startWith(this.miningWindowPreference), // (trigger when the page loads)
tap((value) => {
this.timespan = value;
if (!this.widget) {
this.storageService.setValue('miningWindowPreference', value);
}
@ -283,5 +286,17 @@ export class PoolRankingComponent implements OnInit {
},
};
}
onSaveChart() {
const now = new Date();
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `pools-ranking-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -3,14 +3,22 @@
<div>
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-area-chart"></i> <span i18n="statistics.memory-by-vBytes">Mempool by vBytes (sat/vByte)</span>
<form [formGroup]="radioGroupForm" class="formRadioGroup" [class]="stateService.env.MINING_DASHBOARD ? 'mining' : ''" (click)="saveGraphPreference()">
<i class="fa fa-area-chart"></i>
<span i18n="statistics.memory-by-vBytes">Mempool by vBytes (sat/vByte)</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart('mempool')">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup"
[class]="stateService.env.MINING_DASHBOARD ? 'mining' : ''" (click)="saveGraphPreference()">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h"> 2H (LIVE)
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h"> 2H
(LIVE)
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/graphs' | relativeUrl]" fragment="24h"> 24H
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/graphs' | relativeUrl]" fragment="24h">
24H
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/graphs' | relativeUrl]" fragment="1w"> 1W
@ -37,39 +45,37 @@
<div class="small-buttons">
<div ngbDropdown #myDrop="ngbDropdown">
<button class="btn btn-primary btn-sm" id="dropdownFees" ngbDropdownAnchor (click)="myDrop.toggle()">
<fa-icon [icon]="['fas', 'filter']" [fixedWidth]="true" i18n-title="statistics.component-filter.title" title="Filter"></fa-icon>
<fa-icon [icon]="['fas', 'filter']" [fixedWidth]="true" i18n-title="statistics.component-filter.title"
title="Filter"></fa-icon>
</button>
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
<ul>
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
<ng-template [ngIf]="feeData.fee <= 400">
<li (click)="filterFeeIndex = feeData.fee" [class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
<li (click)="filterFeeIndex = feeData.fee"
[class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
<span class="fee-text">{{ feeData.range }}</span>
</li>
</li>
</ng-template>
</ng-template>
</ul>
</div>
</div>
<button (click)="invertGraph()" class="btn btn-primary btn-sm"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button>
<button (click)="invertGraph()" class="btn btn-primary btn-sm">
<fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true"
i18n-title="statistics.component-invert.title" title="Invert"></fa-icon>
</button>
</div>
</form>
<div class="spinner-border text-light bootstrap-spinner" *ngIf="spinnerLoading && mempoolStats.length"></div>
</div>
<div class="card-body">
<div class="incoming-transactions-graph">
<app-mempool-graph
dir="ltr"
[template]="'advanced'"
[limitFee]="500"
[limitFilterFee]="filterFeeIndex"
[height]="500"
[left]="65"
[right]="10"
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"
></app-mempool-graph>
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [limitFee]="500"
[limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="10"
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
</div>
</div>
</div>
@ -78,19 +84,20 @@
<div>
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
<i class="fa fa-area-chart"></i>
<span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart('incoming')">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
</div>
<div class="card-body">
<div class="incoming-transactions-graph">
<app-incoming-transactions-graph
[height]="500"
[left]="65"
[template]="'advanced'"
[data]="mempoolTransactionsWeightPerSecondData"
></app-incoming-transactions-graph>
<app-incoming-transactions-graph #incominggraph [height]="500" [left]="65" [template]="'advanced'"
[data]="mempoolTransactionsWeightPerSecondData"></app-incoming-transactions-graph>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -53,11 +53,11 @@
}
}
.formRadioGroup.mining {
@media (min-width: 1130px) {
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 1130px) {
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}

View File

@ -1,4 +1,4 @@
import { Component, OnInit, LOCALE_ID, Inject } from '@angular/core';
import { Component, OnInit, LOCALE_ID, Inject, ViewChild, ElementRef } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { FormGroup, FormBuilder } from '@angular/forms';
import { of, merge} from 'rxjs';
@ -12,6 +12,8 @@ import { StateService } from 'src/app/services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { StorageService } from 'src/app/services/storage.service';
import { feeLevels, chartColors } from 'src/app/app.constants';
import { MempoolGraphComponent } from '../mempool-graph/mempool-graph.component';
import { IncomingTransactionsGraphComponent } from '../incoming-transactions-graph/incoming-transactions-graph.component';
@Component({
selector: 'app-statistics',
@ -19,6 +21,9 @@ import { feeLevels, chartColors } from 'src/app/app.constants';
styleUrls: ['./statistics.component.scss']
})
export class StatisticsComponent implements OnInit {
@ViewChild('mempoolgraph') mempoolGraph: MempoolGraphComponent;
@ViewChild('incominggraph') incomingGraph: IncomingTransactionsGraphComponent;
network = '';
loading = true;
@ -38,6 +43,7 @@ export class StatisticsComponent implements OnInit {
graphWindowPreference: string;
inverted: boolean;
feeLevelDropdownData = [];
timespan = '';
constructor(
@Inject(LOCALE_ID) private locale: string,
@ -75,6 +81,7 @@ export class StatisticsComponent implements OnInit {
)
.pipe(
switchMap(() => {
this.timespan = this.radioGroupForm.controls.dateSpan.value;
this.spinnerLoading = true;
if (this.radioGroupForm.controls.dateSpan.value === '2h') {
this.websocketService.want(['blocks', 'live-2h-chart']);
@ -195,4 +202,12 @@ export class StatisticsComponent implements OnInit {
stat.vbytes_per_second = Math.min(median * capRatio, stat.vbytes_per_second);
}
}
onSaveChart(name) {
if (name === 'mempool') {
this.mempoolGraph.onSaveChart(this.timespan);
} else if (name === 'incoming') {
this.incomingGraph.onSaveChart(this.timespan);
}
}
}

View File

@ -77,3 +77,12 @@ export const formatterXAxisTimeCategory = (
return date.toLocaleDateString(locale, { year: 'numeric', month: 'long' });
}
};
export const download = (href, name) => {
const a = document.createElement('a');
a.download = name;
a.href = href;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};