Merge pull request #3621 from mempool/mononaut/sharper-blocks
Pixel-aligned grids for sharper block visualizations
This commit is contained in:
		
						commit
						0ec98d03e5
					
				| @ -1,10 +1,11 @@ | |||||||
| <div class="block-overview-graph"> | 
 | ||||||
|  | <div class="grid-align" [style.gridTemplateColumns]="'repeat(auto-fit, ' + resolution + 'px)'"> | ||||||
|  |   <div class="block-overview-graph"> | ||||||
|     <canvas class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas> |     <canvas class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas> | ||||||
|     <div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable"> |     <div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable"> | ||||||
|       <div *ngIf="isLoading" class="spinner-border ml-3 loading" role="status"></div> |       <div *ngIf="isLoading" class="spinner-border ml-3 loading" role="status"></div> | ||||||
|       <div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div> |       <div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div> | ||||||
|     </div> |     </div> | ||||||
| 
 |  | ||||||
|     <app-block-overview-tooltip |     <app-block-overview-tooltip | ||||||
|       [tx]="selectedTx || hoverTx" |       [tx]="selectedTx || hoverTx" | ||||||
|       [cursorPosition]="tooltipPosition" |       [cursorPosition]="tooltipPosition" | ||||||
| @ -12,4 +13,5 @@ | |||||||
|       [auditEnabled]="auditHighlighting" |       [auditEnabled]="auditHighlighting" | ||||||
|       [blockConversion]="blockConversion" |       [blockConversion]="blockConversion" | ||||||
|     ></app-block-overview-tooltip> |     ></app-block-overview-tooltip> | ||||||
|  |   </div> | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -6,8 +6,16 @@ | |||||||
|   display: flex; |   display: flex; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|  |   grid-column: 1/-1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .grid-align { | ||||||
|  |   position: relative; | ||||||
|  |   width: 100%; | ||||||
|  |   display: grid; | ||||||
|  |   grid-template-columns: repeat(auto-fit, 75px); | ||||||
|  |   justify-content: center; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| .block-overview-canvas { | .block-overview-canvas { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|  | |||||||
| @ -25,7 +25,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|   @Input() unavailable: boolean = false; |   @Input() unavailable: boolean = false; | ||||||
|   @Input() auditHighlighting: boolean = false; |   @Input() auditHighlighting: boolean = false; | ||||||
|   @Input() blockConversion: Price; |   @Input() blockConversion: Price; | ||||||
|   @Input() pixelAlign: boolean = false; |  | ||||||
|   @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>(); |   @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>(); | ||||||
|   @Output() txHoverEvent = new EventEmitter<string>(); |   @Output() txHoverEvent = new EventEmitter<string>(); | ||||||
|   @Output() readyEvent = new EventEmitter(); |   @Output() readyEvent = new EventEmitter(); | ||||||
| @ -219,7 +218,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | |||||||
|     } else { |     } else { | ||||||
|       this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution, |       this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution, | ||||||
|         blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, |         blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, | ||||||
|         highlighting: this.auditHighlighting, pixelAlign: this.pixelAlign }); |         highlighting: this.auditHighlighting }); | ||||||
|       this.start(); |       this.start(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ export default class BlockScene { | |||||||
|   gridWidth: number; |   gridWidth: number; | ||||||
|   gridHeight: number; |   gridHeight: number; | ||||||
|   gridSize: number; |   gridSize: number; | ||||||
|   pixelAlign: boolean; |  | ||||||
|   vbytesPerUnit: number; |   vbytesPerUnit: number; | ||||||
|   unitPadding: number; |   unitPadding: number; | ||||||
|   unitWidth: number; |   unitWidth: number; | ||||||
| @ -24,24 +23,19 @@ export default class BlockScene { | |||||||
|   animateUntil = 0; |   animateUntil = 0; | ||||||
|   dirty: boolean; |   dirty: boolean; | ||||||
| 
 | 
 | ||||||
|   constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting, pixelAlign }: |   constructor({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting }: | ||||||
|       { width: number, height: number, resolution: number, blockLimit: number, |       { width: number, height: number, resolution: number, blockLimit: number, | ||||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, pixelAlign: boolean } |         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean } | ||||||
|   ) { |   ) { | ||||||
|     this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting, pixelAlign }); |     this.init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void { |   resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void { | ||||||
|     this.width = width; |     this.width = width; | ||||||
|     this.height = height; |     this.height = height; | ||||||
|     this.gridSize = this.width / this.gridWidth; |     this.gridSize = this.width / this.gridWidth; | ||||||
|     if (this.pixelAlign) { |     this.unitPadding =  Math.max(1, Math.floor(this.gridSize / 5)); | ||||||
|       this.unitPadding =  Math.max(1, Math.floor(this.gridSize / 2.5)); |  | ||||||
|       this.unitWidth = this.gridSize - (this.unitPadding); |  | ||||||
|     } else { |  | ||||||
|       this.unitPadding =  width / 500; |  | ||||||
|     this.unitWidth = this.gridSize - (this.unitPadding * 2); |     this.unitWidth = this.gridSize - (this.unitPadding * 2); | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     this.dirty = true; |     this.dirty = true; | ||||||
|     if (this.initialised && this.scene) { |     if (this.initialised && this.scene) { | ||||||
| @ -219,15 +213,14 @@ export default class BlockScene { | |||||||
|     this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value)); |     this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting, pixelAlign }: |   private init({ width, height, resolution, blockLimit, orientation, flip, vertexArray, highlighting }: | ||||||
|       { width: number, height: number, resolution: number, blockLimit: number, |       { width: number, height: number, resolution: number, blockLimit: number, | ||||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, pixelAlign: boolean } |         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean } | ||||||
|   ): void { |   ): void { | ||||||
|     this.orientation = orientation; |     this.orientation = orientation; | ||||||
|     this.flip = flip; |     this.flip = flip; | ||||||
|     this.vertexArray = vertexArray; |     this.vertexArray = vertexArray; | ||||||
|     this.highlightingEnabled = highlighting; |     this.highlightingEnabled = highlighting; | ||||||
|     this.pixelAlign = pixelAlign; |  | ||||||
| 
 | 
 | ||||||
|     this.scene = { |     this.scene = { | ||||||
|       count: 0, |       count: 0, | ||||||
| @ -353,12 +346,7 @@ export default class BlockScene { | |||||||
|   private gridToScreen(position: Square | void): Square { |   private gridToScreen(position: Square | void): Square { | ||||||
|     if (position) { |     if (position) { | ||||||
|       const slotSize = (position.s * this.gridSize); |       const slotSize = (position.s * this.gridSize); | ||||||
|       let squareSize; |       const squareSize = slotSize - (this.unitPadding * 2); | ||||||
|       if (this.pixelAlign) { |  | ||||||
|         squareSize = slotSize - (this.unitPadding); |  | ||||||
|       } else { |  | ||||||
|         squareSize = slotSize - (this.unitPadding * 2); |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       // The grid is laid out notionally left-to-right, bottom-to-top,
 |       // The grid is laid out notionally left-to-right, bottom-to-top,
 | ||||||
|       // so we rotate and/or flip the y axis to match the target configuration.
 |       // so we rotate and/or flip the y axis to match the target configuration.
 | ||||||
| @ -434,7 +422,7 @@ export default class BlockScene { | |||||||
| 
 | 
 | ||||||
|   // calculates and returns the size of the tx in multiples of the grid size
 |   // calculates and returns the size of the tx in multiples of the grid size
 | ||||||
|   private txSize(tx: TxView): number { |   private txSize(tx: TxView): number { | ||||||
|     const scale = Math.max(1, Math.round(Math.sqrt(tx.vsize / this.vbytesPerUnit))); |     const scale = Math.max(1, Math.round(Math.sqrt(1.1 * tx.vsize / this.vbytesPerUnit))); | ||||||
|     return Math.min(this.gridWidth, Math.max(1, scale)); // bound between 1 and the max displayable size (just in case!)
 |     return Math.min(this.gridWidth, Math.max(1, scale)); // bound between 1 and the max displayable size (just in case!)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -71,7 +71,7 @@ | |||||||
|       <app-block-overview-graph |       <app-block-overview-graph | ||||||
|         #blockGraph |         #blockGraph | ||||||
|         [isLoading]="false" |         [isLoading]="false" | ||||||
|         [resolution]="75" |         [resolution]="80" | ||||||
|         [blockLimit]="stateService.blockVSize" |         [blockLimit]="stateService.blockVSize" | ||||||
|         [orientation]="'top'" |         [orientation]="'top'" | ||||||
|         [flip]="false" |         [flip]="false" | ||||||
|  | |||||||
| @ -52,8 +52,8 @@ | |||||||
| .chart-container { | .chart-container { | ||||||
|   flex-grow: 0; |   flex-grow: 0; | ||||||
|   flex-shrink: 0; |   flex-shrink: 0; | ||||||
|   width: 470px; |   width: 480px; | ||||||
|   min-width: 470px; |   min-width: 480px; | ||||||
|   padding: 0; |   padding: 0; | ||||||
|   margin-right: 15px; |   margin-right: 15px; | ||||||
| } | } | ||||||
|  | |||||||
| @ -100,7 +100,7 @@ | |||||||
|           </tbody> |           </tbody> | ||||||
|         </table> |         </table> | ||||||
|       </div> |       </div> | ||||||
|       <div class="col-sm"> |       <div class="col-sm" [class.graph-col]="webGlEnabled && !showAudit"> | ||||||
|         <table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && !showAudit)"> |         <table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && !showAudit)"> | ||||||
|           <tbody> |           <tbody> | ||||||
|             <ng-container *ngTemplateOutlet="restOfTable"></ng-container> |             <ng-container *ngTemplateOutlet="restOfTable"></ng-container> | ||||||
| @ -110,7 +110,7 @@ | |||||||
|           <app-block-overview-graph |           <app-block-overview-graph | ||||||
|             #blockGraphActual |             #blockGraphActual | ||||||
|             [isLoading]="isLoadingOverview" |             [isLoading]="isLoadingOverview" | ||||||
|             [resolution]="75" |             [resolution]="86" | ||||||
|             [blockLimit]="stateService.blockVSize" |             [blockLimit]="stateService.blockVSize" | ||||||
|             [orientation]="'top'" |             [orientation]="'top'" | ||||||
|             [flip]="false" |             [flip]="false" | ||||||
| @ -227,7 +227,7 @@ | |||||||
|       <div class="col-sm"> |       <div class="col-sm"> | ||||||
|         <h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3> |         <h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3> | ||||||
|         <div class="block-graph-wrapper"> |         <div class="block-graph-wrapper"> | ||||||
|           <app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75" |           <app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="86" | ||||||
|             [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit" |             [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit" | ||||||
|             (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"></app-block-overview-graph> |             (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"></app-block-overview-graph> | ||||||
|           <ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container> |           <ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container> | ||||||
| @ -239,7 +239,7 @@ | |||||||
|       <div class="col-sm" *ngIf="!isMobile"> |       <div class="col-sm" *ngIf="!isMobile"> | ||||||
|         <h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3> |         <h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3> | ||||||
|         <div class="block-graph-wrapper"> |         <div class="block-graph-wrapper"> | ||||||
|           <app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75" |           <app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="86" | ||||||
|             [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined"  [auditHighlighting]="showAudit" |             [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined"  [auditHighlighting]="showAudit" | ||||||
|             (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"></app-block-overview-graph> |             (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"></app-block-overview-graph> | ||||||
|           <ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container> |           <ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container> | ||||||
|  | |||||||
| @ -239,6 +239,7 @@ h1 { | |||||||
| .nav-tabs { | .nav-tabs { | ||||||
|   border-color: white; |   border-color: white; | ||||||
|   border-width: 1px; |   border-width: 1px; | ||||||
|  |   margin-bottom: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .nav-tabs .nav-link { | .nav-tabs .nav-link { | ||||||
| @ -293,3 +294,7 @@ h1 { | |||||||
|     margin-top: 0.75rem; |     margin-top: 0.75rem; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .graph-col { | ||||||
|  |   flex-grow: 1.11; | ||||||
|  | } | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ | |||||||
|           </ng-container> |           </ng-container> | ||||||
|           <ng-template #mempoolMode> |           <ng-template #mempoolMode> | ||||||
|             <div class="block-sizer" [style]="blockSizerStyle"> |             <div class="block-sizer" [style]="blockSizerStyle"> | ||||||
|               <app-mempool-block-overview [index]="blockIndex" [pixelAlign]="true"></app-mempool-block-overview> |               <app-mempool-block-overview [index]="blockIndex"></app-mempool-block-overview> | ||||||
|             </div> |             </div> | ||||||
|           </ng-template> |           </ng-template> | ||||||
|           <div class="fader"></div> |           <div class="fader"></div> | ||||||
|  | |||||||
| @ -1,10 +1,9 @@ | |||||||
| <app-block-overview-graph | <app-block-overview-graph | ||||||
|   #blockGraph |   #blockGraph | ||||||
|   [isLoading]="isLoading$ | async" |   [isLoading]="isLoading$ | async" | ||||||
|   [resolution]="75" |   [resolution]="86" | ||||||
|   [blockLimit]="stateService.blockVSize" |   [blockLimit]="stateService.blockVSize" | ||||||
|   [orientation]="timeLtr ? 'right' : 'left'" |   [orientation]="timeLtr ? 'right' : 'left'" | ||||||
|   [flip]="true" |   [flip]="true" | ||||||
|   [pixelAlign]="pixelAlign" |  | ||||||
|   (txClickEvent)="onTxClick($event)" |   (txClickEvent)="onTxClick($event)" | ||||||
| ></app-block-overview-graph> | ></app-block-overview-graph> | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ import { Router } from '@angular/router'; | |||||||
| }) | }) | ||||||
| export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { | export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { | ||||||
|   @Input() index: number; |   @Input() index: number; | ||||||
|   @Input() pixelAlign: boolean = false; |  | ||||||
|   @Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>(); |   @Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>(); | ||||||
| 
 | 
 | ||||||
|   @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent; |   @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user