Add block viz filter UI

This commit is contained in:
Mononaut
2023-12-13 10:59:28 +00:00
parent e12f43e741
commit 24dbe5d4ee
15 changed files with 408 additions and 114 deletions

View File

@@ -13,5 +13,6 @@
[auditEnabled]="auditHighlighting"
[blockConversion]="blockConversion"
></app-block-overview-tooltip>
<app-block-filters (onFilterChanged)="setFilterFlags($event)"></app-block-filters>
</div>
</div>

View File

@@ -8,6 +8,19 @@ import { Color, Position } from './sprite-types';
import { Price } from '../../services/price.service';
import { StateService } from '../../services/state.service';
import { Subscription } from 'rxjs';
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
const unmatchedOpacity = 0.2;
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity));
const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity));
const unmatchedAuditColors = {
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
added: setOpacity(defaultAuditColors.added, unmatchedOpacity),
selected: setOpacity(defaultAuditColors.selected, unmatchedOpacity),
accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity),
};
@Component({
selector: 'app-block-overview-graph',
@@ -26,7 +39,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@Input() mirrorTxid: string | void;
@Input() unavailable: boolean = false;
@Input() auditHighlighting: boolean = false;
@Input() filterFlags: bigint | null = 0b00000100_00000000_00000000_00000000n;
@Input() showFilters: boolean = false;
@Input() filterFlags: bigint | null = null;
@Input() blockConversion: Price;
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
@@ -93,7 +107,18 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
if (changes.auditHighlighting) {
this.setHighlightingEnabled(this.auditHighlighting);
}
if (changes.overrideColor) {
if (changes.overrideColor && this.scene) {
this.scene.setColorFunction(this.overrideColors);
}
if ((changes.filterFlags || changes.showFilters) && this.scene) {
this.setFilterFlags(this.filterFlags);
}
}
setFilterFlags(flags: bigint | null): void {
if (flags != null) {
this.scene.setColorFunction(this.getFilterColorFunction(flags));
} else {
this.scene.setColorFunction(this.overrideColors);
}
}
@@ -375,6 +400,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
onPointerMove(event) {
if (event.target === this.canvas.nativeElement) {
this.setPreviewTx(event.offsetX, event.offsetY, false);
} else {
this.onPointerLeave(event);
}
}
@@ -463,14 +490,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
}
}
setFilterFlags(flags: bigint | null): void {
if (this.scene) {
console.log('setting filter flags to ', this.filterFlags.toString(2));
this.scene.setFilterFlags(flags);
this.start();
}
}
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
const x = cssX * window.devicePixelRatio;
const y = cssY * window.devicePixelRatio;
@@ -483,6 +502,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
onTxHover(hoverId: string) {
this.txHoverEvent.emit(hoverId);
}
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
return (tx: TxView) => {
if ((tx.bigintFlags & flags) === flags) {
return defaultColorFunction(tx);
} else {
return defaultColorFunction(
tx,
unmatchedFeeColors,
unmatchedAuditFeeColors,
unmatchedMarginalFeeColors,
unmatchedAuditColors
);
}
};
}
}
// WebGL shader attributes

View File

@@ -2,19 +2,7 @@ import { FastVertexArray } from './fast-vertex-array';
import TxView from './tx-view';
import { TransactionStripped } from '../../interfaces/websocket.interface';
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
import { feeLevels, mempoolFeeColors } from '../../app.constants';
import { darken, desaturate, hexToColor } from './utils';
const feeColors = mempoolFeeColors.map(hexToColor);
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
const marginalFeeColors = feeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
const auditColors = {
censored: hexToColor('f344df'),
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
added: hexToColor('0099ff'),
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
accelerated: hexToColor('8F5FF6'),
};
import { defaultColorFunction } from './utils';
export default class BlockScene {
scene: { count: number, offset: { x: number, y: number}};
@@ -79,7 +67,7 @@ export default class BlockScene {
}
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
this.getColor = colorFunction;
this.getColor = colorFunction || defaultColorFunction;
this.dirty = true;
if (this.initialised && this.scene) {
this.updateColors(performance.now(), 50);
@@ -280,7 +268,7 @@ export default class BlockScene {
private updateTxColor(tx: TxView, startTime: number, delay: number, animate: boolean = true, duration?: number): void {
if (tx.dirty || this.dirty) {
const txColor = tx.getColor();
const txColor = this.getColor(tx);
this.applyTxUpdate(tx, {
display: {
color: txColor
@@ -918,49 +906,4 @@ class BlockLayout {
function feeRateDescending(a: TxView, b: TxView) {
return b.feerate - a.feerate;
}
function defaultColorFunction(tx: TxView): Color {
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
// Normal mode
if (!tx.scene?.highlightingEnabled) {
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
return feeLevelColor;
}
// Block audit
switch(tx.status) {
case 'censored':
return auditColors.censored;
case 'missing':
case 'sigop':
case 'rbf':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'fresh':
case 'freshcpfp':
return auditColors.missing;
case 'added':
return auditColors.added;
case 'selected':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'accelerated':
return auditColors.accelerated;
case 'found':
if (tx.context === 'projected') {
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
} else {
return feeLevelColor;
}
default:
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
}
}

View File

@@ -1,4 +1,6 @@
import { feeLevels, mempoolFeeColors } from '../../app.constants';
import { Color } from './sprite-types';
import TxView from './tx-view';
export function hexToColor(hex: string): Color {
return {
@@ -25,5 +27,75 @@ export function darken(color: Color, amount: number): Color {
g: color.g * amount,
b: color.b * amount,
a: color.a,
};
}
export function setOpacity(color: Color, opacity: number): Color {
return {
...color,
a: opacity
};
}
// precomputed colors
export const defaultFeeColors = mempoolFeeColors.map(hexToColor);
export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
export const defaultAuditColors = {
censored: hexToColor('f344df'),
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
added: hexToColor('0099ff'),
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
accelerated: hexToColor('8F5FF6'),
};
export function defaultColorFunction(
tx: TxView,
feeColors: Color[] = defaultFeeColors,
auditFeeColors: Color[] = defaultAuditFeeColors,
marginalFeeColors: Color[] = defaultMarginalFeeColors,
auditColors: { [status: string]: Color } = defaultAuditColors
): Color {
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
// Normal mode
if (!tx.scene?.highlightingEnabled) {
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
return feeLevelColor;
}
// Block audit
switch(tx.status) {
case 'censored':
return auditColors.censored;
case 'missing':
case 'sigop':
case 'rbf':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'fresh':
case 'freshcpfp':
return auditColors.missing;
case 'added':
return auditColors.added;
case 'selected':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'accelerated':
return auditColors.accelerated;
case 'found':
if (tx.context === 'projected') {
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
} else {
return feeLevelColor;
}
default:
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
}
}