Refactor GUI rendering and align RTA axis

This commit is contained in:
Mikei386
2026-07-22 11:35:51 +02:00
parent 2e868bfa3a
commit 890a47a272
16 changed files with 1199 additions and 1415 deletions
+57 -31
View File
@@ -1,5 +1,6 @@
import { getRtwCenters, resolveRtwBpoValue } from '../core/rtw_centers.js';
import { DEFAULT_TOP_INSET, FRAME_COLOR, MID_COLOR, PANEL_BG, WARN_COLOR } from '../core/theme.js';
import { drawCachedStaticLayer } from './static_layer.js';
// views/realtime.js — IEC-konformer Real-Time Analyzer
@@ -115,6 +116,23 @@ export async function render(env, state) {
const gutterL = Number.isFinite(CONFIG?.AXIS_GUTTER_LEFT)
? Math.max(8, Number(CONFIG.AXIS_GUTTER_LEFT))
: 14;
const rtwBarGrid = CONFIG.RTA_BAR_LAYOUT === 'rtw'
&& (CONFIG.REALTIME_RENDER_STYLE || 'bars') === 'bars';
const rtwGridCenters = rtwBarGrid
? (isVectorLike(displayRtaPacket?.centers)
? Array.from(displayRtaPacket.centers)
: getRtwCenters(CONFIG.RTA_BPO_MODE || '1_3'))
.filter((center) => Number.isFinite(center)
&& center >= freqBounds.min
&& center <= freqBounds.max)
: null;
const freqTickPositions = rtwGridCenters
? buildRtwTickPositions(rtwGridCenters, freqTicks)
: null;
const gridLeftExtension = rtwBarGrid ? gutterL : 0;
const tickPositionKey = freqTickPositions
? freqTicks.map((f) => `${f}:${freqTickPositions[String(f)] ?? 'log'}`).join(',')
: 'log';
const plotPadLeft = gutterL + STATIC_LABEL_PAD_LEFT;
const plotPadBottom = STATIC_LABEL_PAD_BOTTOM;
@@ -133,6 +151,8 @@ export async function render(env, state) {
refDb ?? 'none',
hide20HzTick ? 1 : 0,
plotPadLeft,
gridLeftExtension,
tickPositionKey,
plotPadBottom,
].join('|'),
{ x: plotX - plotPadLeft, y: plotY, w: plotW + plotPadLeft, h: plotH + plotPadBottom },
@@ -147,7 +167,9 @@ export async function render(env, state) {
freqBounds.max,
{
freqTicks,
freqTickPositions,
freqMin: freqBounds.min,
xLeftExtension: gridLeftExtension,
refDb,
refStyle: 'rgba(0,231,255,0.35)',
refDash: [3, 4],
@@ -197,7 +219,8 @@ export async function render(env, state) {
range,
freqBounds,
ballisticsData?.overlay || null,
ballisticsData?.mode || CONFIG.RTA_BALLISTICS_MODE || 'average'
ballisticsData?.mode || CONFIG.RTA_BALLISTICS_MODE || 'average',
gridLeftExtension,
);
}
} else if (useNativeFftEngine) {
@@ -230,7 +253,8 @@ export async function render(env, state) {
range,
freqBounds,
ballisticsData?.overlay || null,
ballisticsData?.mode || CONFIG.RTA_BALLISTICS_MODE || 'average'
ballisticsData?.mode || CONFIG.RTA_BALLISTICS_MODE || 'average',
gridLeftExtension,
);
}
} else if (!analyser || !buf) {
@@ -259,7 +283,8 @@ export async function render(env, state) {
range,
freqBounds,
null,
CONFIG.RTA_BALLISTICS_MODE || 'average'
CONFIG.RTA_BALLISTICS_MODE || 'average',
gridLeftExtension,
);
}
}
@@ -552,7 +577,33 @@ function applyRealtimeBarBallistics(state, levels, CONFIG, range) {
return buffer;
}
function renderSpectrum(g, state, levels, plotX, plotY, plotW, plotH, CONFIG, range, freqBounds, overlayPeaks, ballisticsMode) {
export function buildRtwTickPositions(centers, ticks = RTA_X_TICKS) {
if (!isVectorLike(centers) || !centers.length) return {};
const normalizedCenters = Array.from(centers, Number)
.filter((value) => Number.isFinite(value) && value > 0);
if (!normalizedCenters.length) return {};
const positions = {};
const count = normalizedCenters.length;
for (const tick of ticks || []) {
const frequency = Number(tick);
if (!Number.isFinite(frequency) || frequency <= 0) continue;
let bestIndex = -1;
let bestRelativeError = Infinity;
for (let index = 0; index < count; index++) {
const relativeError = Math.abs(normalizedCenters[index] - frequency) / frequency;
if (relativeError < bestRelativeError) {
bestRelativeError = relativeError;
bestIndex = index;
}
}
if (bestIndex >= 0 && bestRelativeError <= 0.02) {
positions[String(tick)] = (bestIndex + 0.5) / count;
}
}
return positions;
}
function renderSpectrum(g, state, levels, plotX, plotY, plotW, plotH, CONFIG, range, freqBounds, overlayPeaks, ballisticsMode, rtwLeftExtension = 0) {
const layout = CONFIG.RTA_BAR_LAYOUT === 'rtw' ? 'rtw' : 'iec';
const overlay = (ballisticsMode === 'both' && isVectorLike(overlayPeaks) && overlayPeaks.length === state.mapping.length)
? overlayPeaks
@@ -560,7 +611,8 @@ function renderSpectrum(g, state, levels, plotX, plotY, plotW, plotH, CONFIG, ra
if ((CONFIG.REALTIME_RENDER_STYLE || 'bars') === 'line') {
renderLine(g, state, levels, plotX, plotY, plotW, plotH, CONFIG, range, freqBounds, overlay);
} else if (layout === 'rtw') {
renderRtwBars(g, state, levels, plotX, plotY, plotW, plotH, CONFIG, range, overlay);
const extension = Math.max(0, Number(rtwLeftExtension) || 0);
renderRtwBars(g, state, levels, plotX - extension, plotY, plotW + extension, plotH, CONFIG, range, overlay);
} else {
renderIecBars(g, state, levels, plotX, plotY, plotW, plotH, CONFIG, range, freqBounds, overlay);
}
@@ -742,32 +794,6 @@ async function drawMeter(env, state, plotX, plotY, plotW, plotH) {
g.restore();
}
function createStaticLayerCanvas(width, height) {
const w = Math.max(1, width | 0);
const h = Math.max(1, height | 0);
if (typeof OffscreenCanvas !== 'undefined') return new OffscreenCanvas(w, h);
const c = document.createElement('canvas');
c.width = w;
c.height = h;
return c;
}
function drawCachedStaticLayer(state, g, layerId, key, rect, build) {
if (!state || !rect || rect.w <= 0 || rect.h <= 0) return;
if (!state.staticLayers) state.staticLayers = new Map();
const fullKey = `${layerId}:${key}:${Math.max(0, rect.w | 0)}x${Math.max(0, rect.h | 0)}`;
let layer = state.staticLayers.get(fullKey);
if (!layer) {
const canvas = createStaticLayerCanvas(rect.w, rect.h);
const ctx = canvas.getContext('2d');
build(ctx);
layer = { canvas };
state.staticLayers.set(fullKey, layer);
}
g.drawImage(layer.canvas, rect.x, rect.y);
}
function mapLogX(freq, x0, w, logMin, logSpan) {
const clamped = Math.max(5, freq);
const frac = (Math.log10(clamped) - logMin) / (logSpan || 1);