class Panel { canvas: HTMLCanvasElement; context: CanvasRenderingContext2D | null; name: string; fg: string; bg: string; gradient: CanvasGradient | null; id: number = 0; PR: number; WIDTH: number; HEIGHT: number; TEXT_X: number; TEXT_Y: number; GRAPH_X: number; GRAPH_Y: number; GRAPH_WIDTH: number; GRAPH_HEIGHT: number; constructor(name: string, fg: string, bg: string) { this.name = name; this.fg = fg; this.bg = bg; this.gradient = null; this.PR = Math.round(typeof window !== 'undefined' ? (window.devicePixelRatio || 1) : 1); this.WIDTH = 90 * this.PR; this.HEIGHT = 48 * this.PR; this.TEXT_X = 3 * this.PR; this.TEXT_Y = 2 * this.PR; this.GRAPH_X = 3 * this.PR; this.GRAPH_Y = 15 * this.PR; this.GRAPH_WIDTH = 84 * this.PR; this.GRAPH_HEIGHT = 30 * this.PR; this.canvas = typeof document !== 'undefined' ? document.createElement('canvas') : new OffscreenCanvas(this.WIDTH, this.HEIGHT) as unknown as HTMLCanvasElement; this.canvas.width = this.WIDTH; this.canvas.height = this.HEIGHT; this.canvas.style.width = '90px'; this.canvas.style.height = '48px'; this.canvas.style.position = 'absolute'; this.canvas.style.cssText = 'width:90px;height:48px;background-color: transparent !important;'; this.context = this.canvas.getContext('2d'); this.initializeCanvas(); } private createGradient(): CanvasGradient { if (!this.context) throw new Error('No context'); const gradient = this.context.createLinearGradient( 0, this.GRAPH_Y, 0, this.GRAPH_Y + this.GRAPH_HEIGHT ); let startColor: string; const endColor: string = this.fg; switch (this.fg.toLowerCase()) { case '#0ff': startColor = '#006666'; break; case '#0f0': startColor = '#006600'; break; case '#ff0': startColor = '#666600'; break; case '#e1e1e1': startColor = '#666666'; break; default: startColor = this.bg; break; } gradient.addColorStop(0, startColor); gradient.addColorStop(1, endColor); return gradient; } public initializeCanvas() { if (!this.context) return; this.context.imageSmoothingEnabled = false; this.context.font = 'bold ' + (9 * this.PR) + 'px Helvetica,Arial,sans-serif'; this.context.textBaseline = 'top'; this.gradient = this.createGradient(); this.context.fillStyle = this.bg; this.context.fillRect(0, 0, this.WIDTH, this.HEIGHT); this.context.fillStyle = this.fg; this.context.fillText(this.name, this.TEXT_X, this.TEXT_Y); this.context.fillStyle = this.bg; this.context.globalAlpha = 0.9; this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT); } // Update only text portion public update(value: number, maxValue: number, decimals: number = 0, suffix: string = '') { if (!this.context || !this.gradient) return; const min = Math.min(Infinity, value); const max = Math.max(maxValue, value); // Clear only the text area (from top to GRAPH_Y) this.context.globalAlpha = 1; this.context.fillStyle = this.bg; this.context.fillRect(0, 0, this.WIDTH, this.GRAPH_Y); // Draw value and name const valueAndName = `${value.toFixed(decimals)} ${this.name}`; this.context.fillStyle = this.fg; this.context.fillText(valueAndName, this.TEXT_X, this.TEXT_Y); let textX = this.TEXT_X + this.context.measureText(valueAndName).width; // Draw suffix in orange if present if (suffix) { this.context.fillStyle = '#f90'; this.context.fillText(suffix, textX, this.TEXT_Y); textX += this.context.measureText(suffix).width; } // Draw range this.context.fillStyle = this.fg; this.context.fillText( ` (${min.toFixed(decimals)}-${parseFloat(max.toFixed(decimals))})`, textX, this.TEXT_Y ); } // Update only graph portion public updateGraph(valueGraph: number, maxGraph: number) { if (!this.context || !this.gradient) return; // Handle zero values appropriately if (valueGraph === 0 && maxGraph === 0) { maxGraph = 1; // Prevent division by zero } // Ensure maxGraph is valid and values are positive maxGraph = Math.max(maxGraph, valueGraph, 0.1); valueGraph = Math.max(valueGraph, 0); // Ensure all coordinates are rounded to avoid sub-pixel rendering const graphX = Math.round(this.GRAPH_X); const graphY = Math.round(this.GRAPH_Y); const graphWidth = Math.round(this.GRAPH_WIDTH); const graphHeight = Math.round(this.GRAPH_HEIGHT); const pr = Math.round(this.PR); // Shift the graph left this.context.drawImage( this.canvas, graphX + pr, graphY, graphWidth - pr, graphHeight, graphX, graphY, graphWidth - pr, graphHeight ); // Clear only the new column area this.context.fillStyle = this.bg; this.context.fillRect( graphX + graphWidth - pr, graphY, pr, graphHeight ); // Calculate column height const columnHeight = Math.min( graphHeight, Math.round(valueGraph / maxGraph * graphHeight) ); // Draw the gradient column if (columnHeight > 0) { this.context.globalAlpha = 0.9; this.context.fillStyle = this.gradient; this.context.fillRect( graphX + graphWidth - pr, graphY + (graphHeight - columnHeight), pr, columnHeight ); } this.context.globalAlpha = 1; } } export { Panel };