Merge pull request #847 from MiguelMedeiros/add-filtering-mempool-charts
Add mempool chart filtering.
This commit is contained in:
		
						commit
						bf563cc195
					
				| @ -47,7 +47,7 @@ import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FeesBoxComponent } from './components/fees-box/fees-box.component'; | ||||
| import { DashboardComponent } from './dashboard/dashboard.component'; | ||||
| import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; | ||||
| import { faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle, | ||||
| import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle, | ||||
|   faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { ApiDocsComponent } from './components/api-docs/api-docs.component'; | ||||
| import { CodeTemplateComponent } from './components/api-docs/code-template.component'; | ||||
| @ -140,6 +140,7 @@ export class AppModule { | ||||
|     library.addIcons(faLink); | ||||
|     library.addIcons(faBolt); | ||||
|     library.addIcons(faTint); | ||||
|     library.addIcons(faFilter); | ||||
|     library.addIcons(faAngleDown); | ||||
|     library.addIcons(faAngleUp); | ||||
|     library.addIcons(faExchangeAlt); | ||||
|  | ||||
| @ -17,6 +17,7 @@ import { feeLevels, chartColors } from 'src/app/app.constants'; | ||||
| export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
|   @Input() data: any[]; | ||||
|   @Input() limitFee = 350; | ||||
|   @Input() limitFilterFee = 1; | ||||
|   @Input() height: number | string = 200; | ||||
|   @Input() top: number | string = 20; | ||||
|   @Input() right: number | string = 10; | ||||
| @ -45,19 +46,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|     this.inverted = this.storageService.getValue('inverted-graph') === 'true'; | ||||
|     for (let i = 0; i < feeLevels.length; i++) { | ||||
|       if (feeLevels[i] === this.limitFee) { | ||||
|         this.feeLimitIndex = i; | ||||
|       } | ||||
|       if (feeLevels[i] <= this.limitFee) { | ||||
|         if (i === 0) { | ||||
|           this.feeLevelsOrdered.push('0 - 1'); | ||||
|         } else { | ||||
|           this.feeLevelsOrdered.push(`${feeLevels[i - 1]} - ${feeLevels[i]}`); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     this.chartColorsOrdered =  chartColors.slice(0, this.feeLevelsOrdered.length); | ||||
|     this.mountFeeChart(); | ||||
|   } | ||||
| 
 | ||||
| @ -68,9 +56,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
|   } | ||||
| 
 | ||||
|   onChartReady(myChart: any) { | ||||
|     myChart.getZr().on('mousemove', e => { | ||||
|       if (e.target !== undefined) { | ||||
|         this.hoverIndexSerie = e.target.parent.parent.__ecComponentInfo.index; | ||||
|     myChart.getZr().on('mousemove', (e: any) => { | ||||
|       if (e.target !== undefined && | ||||
|         e.target.parent !== undefined && | ||||
|         e.target.parent.parent !== null && | ||||
|         e.target.parent.parent.__ecComponentInfo !== undefined) { | ||||
|           this.hoverIndexSerie = e.target.parent.parent.__ecComponentInfo.index; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| @ -106,10 +97,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
|   } | ||||
| 
 | ||||
|   mountFeeChart() { | ||||
|     this.orderLevels(); | ||||
|     const { labels, series } = this.mempoolVsizeFeesData; | ||||
| 
 | ||||
|     const seriesGraph = series.map((value: Array<number>, index: number) => { | ||||
|       if (index <= this.feeLimitIndex){ | ||||
|       if (index >= this.feeLimitIndex){ | ||||
|         return { | ||||
|           name: this.feeLevelsOrdered[index], | ||||
|           type: 'line', | ||||
| @ -175,85 +167,81 @@ export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
|           type: 'line', | ||||
|         }, | ||||
|         formatter: (params: any) => { | ||||
|           const colorSpan = (index: any) => `<span class="indicator" style="background-color: ${this.chartColorsOrdered[index]}"></span>
 | ||||
|             <span> | ||||
|               ${this.feeLevelsOrdered[index]} | ||||
|             </span>`;
 | ||||
|           const totals = (values: any) => { | ||||
|             let totalValueTemp = 0; | ||||
|             const totalValueArrayTemp = []; | ||||
|             const valuesInverted = this.inverted ? values : [...values].reverse(); | ||||
|             for (const item of valuesInverted) { | ||||
|               totalValueTemp += item.value; | ||||
|               totalValueArrayTemp.push(totalValueTemp); | ||||
|             } | ||||
|             return { | ||||
|               totalValue: totalValueTemp, | ||||
|               totalValueArray: totalValueArrayTemp.reverse(), | ||||
|               valuesOrdered: this.inverted ? [...values].reverse() : values, | ||||
|             }; | ||||
|           }; | ||||
|           const { totalValue, totalValueArray, valuesOrdered } = totals(params); | ||||
|           const title = `<div class="title">
 | ||||
|             ${params[0].axisValue} | ||||
|             <span class="total-value"> | ||||
|               ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)} | ||||
|             </span> | ||||
|           </div>`;
 | ||||
|           const { totalValue, totalValueArray } = this.getTotalValues(params); | ||||
|           const itemFormatted = []; | ||||
|           let totalParcial = 0; | ||||
|           let progressPercentageText = ''; | ||||
|           params.map((item: any, index: number) => { | ||||
|           const items = this.inverted ? [...params].reverse() : params; | ||||
|           items.map((item: any, index: number) => { | ||||
|             totalParcial += item.value; | ||||
|             let progressPercentage = 0; | ||||
|             let progressPercentageSum = 0; | ||||
|             if (index <= this.feeLimitIndex) { | ||||
|               progressPercentage = (item.value / totalValue) * 100; | ||||
|               progressPercentageSum = (totalValueArray[index] / totalValue) * 100; | ||||
|               let activeItemClass = ''; | ||||
|               const hoverActive = (this.inverted) ? Math.abs(item.seriesIndex - params.length + 1) : item.seriesIndex; | ||||
|               if (this.hoverIndexSerie === hoverActive) { | ||||
|                 progressPercentageText = `<div class="total-parcial-active">
 | ||||
|                   <span class="progress-percentage"> | ||||
|                     ${formatNumber(progressPercentage, this.locale, '1.2-2')} | ||||
|                     <span class="symbol">%</span> | ||||
|             progressPercentage = (item.value / totalValue) * 100; | ||||
|             progressPercentageSum = (totalValueArray[index] / totalValue) * 100; | ||||
|             let activeItemClass = ''; | ||||
|             let hoverActive: number; | ||||
|             if (this.inverted) { | ||||
|               hoverActive = Math.abs(this.feeLevelsOrdered.length - item.seriesIndex - this.feeLevelsOrdered.length); | ||||
|             } else { | ||||
|               hoverActive = item.seriesIndex; | ||||
|             } | ||||
|             if (this.hoverIndexSerie === hoverActive) { | ||||
|               progressPercentageText = `<div class="total-parcial-active">
 | ||||
|                 <span class="progress-percentage"> | ||||
|                   ${formatNumber(progressPercentage, this.locale, '1.2-2')} | ||||
|                   <span class="symbol">%</span> | ||||
|                 </span> | ||||
|                 <span class="total-parcial-vbytes"> | ||||
|                   ${this.vbytesPipe.transform(totalParcial, 2, 'vB', 'MvB', false)} | ||||
|                 </span> | ||||
|                 <div class="total-percentage-bar"> | ||||
|                   <span class="total-percentage-bar-background"> | ||||
|                     <span style=" | ||||
|                       width: ${progressPercentage}%; | ||||
|                       background: ${this.inverted ? this.chartColorsOrdered[index] : item.color} | ||||
|                     "></span> | ||||
|                   </span> | ||||
|                   <span class="total-parcial-vbytes"> | ||||
|                     ${this.vbytesPipe.transform(totalParcial, 2, 'vB', 'MvB', false)} | ||||
|                   </span> | ||||
|                   <div class="total-percentage-bar"> | ||||
|                     <span class="total-percentage-bar-background"> | ||||
|                       <span style="width: ${progressPercentage}%; background: ${this.chartColorsOrdered[index]}"></span> | ||||
|                     </span> | ||||
|                   </div> | ||||
|                 </div>`;
 | ||||
|                 activeItemClass = 'active'; | ||||
|               } | ||||
|               itemFormatted.push(`<tr class="item ${activeItemClass}">
 | ||||
|                 </div> | ||||
|               </div>`;
 | ||||
|               activeItemClass = 'active'; | ||||
|             } | ||||
|             itemFormatted.push(`<tr class="item ${activeItemClass}">
 | ||||
|               <td class="indicator-container"> | ||||
|                 ${colorSpan(item.seriesIndex)} | ||||
|               </td> | ||||
|               <td class="total-progress-sum"> | ||||
|                 <span class="indicator" style=" | ||||
|                   background-color: ${this.inverted ? this.chartColorsOrdered[index] : item.color} | ||||
|                 "></span> | ||||
|                 <span> | ||||
|                   ${this.vbytesPipe.transform(valuesOrdered[item.seriesIndex].value, 2, 'vB', 'MvB', false)} | ||||
|                   ${this.inverted ? this.feeLevelsOrdered[index] : item.seriesName} | ||||
|                 </span> | ||||
|               </td> | ||||
|               <td class="total-progress-sum"> | ||||
|                 <span> | ||||
|                   ${this.vbytesPipe.transform(totalValueArray[item.seriesIndex], 2, 'vB', 'MvB', false)} | ||||
|                   ${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)} | ||||
|                 </span> | ||||
|               </td> | ||||
|               <td class="total-progress-sum"> | ||||
|                 <span> | ||||
|                   ${this.vbytesPipe.transform(totalValueArray[index], 2, 'vB', 'MvB', false)} | ||||
|                 </span> | ||||
|               </td> | ||||
|               <td class="total-progress-sum-bar"> | ||||
|                 <span class="total-percentage-bar-background"> | ||||
|                   <span style="width: ${progressPercentageSum.toFixed(2)}%; background-color: ${this.chartColorsOrdered[3]}"></span> | ||||
|                   <span style=" | ||||
|                     width: ${progressPercentageSum.toFixed(2)}%; | ||||
|                     background-color: ${this.chartColorsOrdered[3]} | ||||
|                   "></span> | ||||
|                 </span> | ||||
|               </td> | ||||
|             </tr>`);
 | ||||
|             } | ||||
|           }); | ||||
|           const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : ''; | ||||
|           return `<div class="fees-wrapper-tooltip-chart ${classActive}">
 | ||||
|             ${title} | ||||
|             <div class="title"> | ||||
|               ${params[0].axisValue} | ||||
|               <span class="total-value"> | ||||
|                 ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)} | ||||
|               </span> | ||||
|             </div> | ||||
|             <table> | ||||
|               <thead> | ||||
|                 <tr> | ||||
| @ -332,5 +320,35 @@ export class MempoolGraphComponent implements OnInit, OnChanges { | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   getTotalValues = (values: any) => { | ||||
|     let totalValueTemp = 0; | ||||
|     const totalValueArray = []; | ||||
|     const valuesInverted = this.inverted ? values : [...values].reverse(); | ||||
|     for (const item of valuesInverted) { | ||||
|       totalValueTemp += item.value; | ||||
|       totalValueArray.push(totalValueTemp); | ||||
|     } | ||||
|     return { | ||||
|       totalValue: totalValueTemp, | ||||
|       totalValueArray: totalValueArray.reverse(), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   orderLevels() { | ||||
|     for (let i = 0; i < feeLevels.length; i++) { | ||||
|       if (feeLevels[i] === this.limitFilterFee) { | ||||
|         this.feeLimitIndex = i; | ||||
|       } | ||||
|       if (feeLevels[i] <= this.limitFee) { | ||||
|         if (i === 0) { | ||||
|           this.feeLevelsOrdered.push('0 - 1'); | ||||
|         } else { | ||||
|           this.feeLevelsOrdered.push(`${feeLevels[i - 1]} - ${feeLevels[i]}`); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     this.chartColorsOrdered =  chartColors.slice(0, this.feeLevelsOrdered.length); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -36,6 +36,42 @@ | ||||
|                 <input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y | ||||
|               </label> | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="d-inline-block" ngbDropdown #myDrop="ngbDropdown"> | ||||
|               <button class="btn btn-primary btn-sm ml-2" id="dropdownFees" ngbDropdownAnchor (click)="myDrop.toggle()"> | ||||
|                 <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-fee let-i="index" [ngForOf]="feeLevels"> | ||||
|                     <ng-template [ngIf]="fee === 1"> | ||||
|                       <li (click)="filterFees(fee)" [class]="filterFeeIndex > fee ? 'inactive' : ''"> | ||||
|                         <ng-template [ngIf]="inverted"> | ||||
|                           <span class="square" [ngStyle]="{'backgroundColor': chartColors[i]}"></span> | ||||
|                         </ng-template> | ||||
|                         <ng-template [ngIf]="!inverted"> | ||||
|                           <span class="square" [ngStyle]="{'backgroundColor': chartColors[i - 1]}"></span> | ||||
|                         </ng-template> | ||||
|                         <span class="fee-text" >0 - {{ fee }}</span> | ||||
|                       </li> | ||||
|                     </ng-template> | ||||
|                     <ng-template [ngIf]="fee <= 500 && fee !== 1"> | ||||
|                       <li (click)="filterFees(fee)" [class]="filterFeeIndex > fee ? 'inactive' : ''"> | ||||
|                       <ng-template [ngIf]="inverted"> | ||||
|                         <span class="square" [ngStyle]="{'backgroundColor': chartColors[i]}"></span> | ||||
|                         <span class="fee-text" >{{feeLevels[i - 1]}} - {{ fee }}</span> | ||||
|                       </ng-template> | ||||
|                       <ng-template [ngIf]="!inverted"> | ||||
|                           <span class="square" [ngStyle]="{'backgroundColor': chartColors[i - 1]}"></span> | ||||
|                           <span class="fee-text" >{{feeLevels[i + 1]}} - {{ fee }}</span> | ||||
|                       </ng-template> | ||||
|                     </li> | ||||
|                     </ng-template> | ||||
|                   </ng-template> | ||||
|                 </ul> | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button> | ||||
|           </form> | ||||
|         </div> | ||||
| @ -45,8 +81,10 @@ | ||||
|               dir="ltr" | ||||
|               [template]="'advanced'" | ||||
|               [limitFee]="500" | ||||
|               [limitFilterFee]="filterFeeIndex" | ||||
|               [height]="500" | ||||
|               [left]="65" | ||||
|               [right]="10" | ||||
|               [data]="mempoolStats" | ||||
|             ></app-mempool-graph> | ||||
|           </div> | ||||
|  | ||||
| @ -61,3 +61,48 @@ | ||||
| .incoming-transactions-graph { | ||||
|   height: 600px; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .dropdown-fees { | ||||
|   padding: 10px 0px; | ||||
|   min-width: 130px; | ||||
|   padding: 2px 20px 0px; | ||||
|   left: -38px !important; | ||||
|   position: absolute !important; | ||||
| 
 | ||||
|   ul { | ||||
|     list-style: none; | ||||
|     padding: 0px; | ||||
|     margin-bottom: 0px; | ||||
|   } | ||||
|   li { | ||||
|     width: 100%; | ||||
|     font-size: 14px; | ||||
|     padding: 0px 0px; | ||||
|     padding-left: 20px; | ||||
|     transition: 200ms all ease-in-out; | ||||
|     &:hover { | ||||
|       background-color: #10121e; | ||||
|       cursor: pointer; | ||||
|     } | ||||
|   } | ||||
|   .square { | ||||
|     transition: 200ms all ease-in-out; | ||||
|     height: 12px; | ||||
|     width: 12px; | ||||
|     margin-right: 10px; | ||||
|     border-radius: 1px; | ||||
|     display: inline-block; | ||||
|     position: relative; | ||||
|     top: 1px; | ||||
|   } | ||||
|   .inactive { | ||||
|     .square { | ||||
|       background-color: #ffffff66 !important; | ||||
|     } | ||||
|     .fee-text { | ||||
|       text-decoration: line-through; | ||||
|       color: #777; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -11,6 +11,7 @@ import { ApiService } from '../../services/api.service'; | ||||
| 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'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-statistics', | ||||
| @ -22,6 +23,10 @@ export class StatisticsComponent implements OnInit { | ||||
| 
 | ||||
|   loading = true; | ||||
|   spinnerLoading = false; | ||||
|   feeLevels = feeLevels; | ||||
|   chartColors = chartColors; | ||||
|   filterFeeIndex = 1; | ||||
|   dropDownOpen = false; | ||||
| 
 | ||||
|   mempoolStats: OptimizedMempoolStats[] = []; | ||||
| 
 | ||||
| @ -30,7 +35,7 @@ export class StatisticsComponent implements OnInit { | ||||
|   mempoolTransactionsWeightPerSecondData: any; | ||||
| 
 | ||||
|   radioGroupForm: FormGroup; | ||||
|   graphWindowPreference: String; | ||||
|   graphWindowPreference: string; | ||||
|   inverted: boolean; | ||||
| 
 | ||||
|   constructor( | ||||
| @ -46,6 +51,10 @@ export class StatisticsComponent implements OnInit { | ||||
| 
 | ||||
|   ngOnInit() { | ||||
|     this.inverted = this.storageService.getValue('inverted-graph') === 'true'; | ||||
|     if (!this.inverted) { | ||||
|       this.feeLevels = [...feeLevels].reverse(); | ||||
|       this.chartColors = [...chartColors].reverse(); | ||||
|     } | ||||
|     this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`); | ||||
|     this.stateService.networkChanged$.subscribe((network) => this.network = network); | ||||
|     this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h'; | ||||
| @ -131,4 +140,12 @@ export class StatisticsComponent implements OnInit { | ||||
|     this.storageService.setValue('inverted-graph', !this.inverted); | ||||
|     document.location.reload(); | ||||
|   } | ||||
| 
 | ||||
|   filterFees(index: number) { | ||||
|     this.filterFeeIndex = index; | ||||
|   } | ||||
| 
 | ||||
|   filterClick() { | ||||
|     this.dropDownOpen = !this.dropDownOpen; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
|         [limitFee]="500" | ||||
|         [height]="600" | ||||
|         [left]="60" | ||||
|         [right]="10" | ||||
|         [data]="mempoolStats" | ||||
|         [showZoom]="false" | ||||
|       ></app-mempool-graph> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user