Refactor GUI rendering and align RTA axis
This commit is contained in:
+57
-31
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user