Files
LoraMapTester/server/static/quality-viz.js
T
2026-06-17 11:22:15 +03:00

97 lines
3.2 KiB
JavaScript

/** RX quality vs distance chart for TX/RX track compare. */
(function (global) {
'use strict';
function drawQualityDistChart(canvas, samples, qualityColor, highlightDist) {
if (!canvas) return;
const ctx = canvas.getContext('2d');
const w = canvas.clientWidth || 280;
const h = canvas.clientHeight || 140;
if (canvas.width !== w) canvas.width = w;
if (canvas.height !== h) canvas.height = h;
ctx.fillStyle = '#0a0a14';
ctx.fillRect(0, 0, w, h);
if (!samples?.length) {
ctx.fillStyle = '#888';
ctx.font = '11px system-ui';
ctx.fillText('нет данных качества', 12, h / 2);
return;
}
const dists = samples.map(s => s.distM);
const minD = Math.min(...dists);
const maxD = Math.max(...dists);
const span = Math.max(maxD - minD, 1);
const binCount = Math.min(20, Math.max(5, Math.ceil(span / 10)));
const binW = span / binCount;
const bins = Array.from({ length: binCount }, (_, i) => ({
min: minD + i * binW,
max: minD + (i + 1) * binW,
qualities: [],
}));
for (const s of samples) {
let idx = Math.floor((s.distM - minD) / binW);
if (idx >= binCount) idx = binCount - 1;
if (idx < 0) idx = 0;
bins[idx].qualities.push(s.quality);
}
const margin = { l: 36, r: 8, t: 16, b: 22 };
const plotW = w - margin.l - margin.r;
const plotH = h - margin.t - margin.b;
ctx.strokeStyle = '#333';
ctx.beginPath();
ctx.moveTo(margin.l, margin.t);
ctx.lineTo(margin.l, margin.t + plotH);
ctx.lineTo(margin.l + plotW, margin.t + plotH);
ctx.stroke();
ctx.fillStyle = '#888';
ctx.font = '9px system-ui';
ctx.fillText('0%', 2, margin.t + plotH);
ctx.fillText('100%', 2, margin.t + 8);
ctx.fillText(`${Math.round(minD)}m`, margin.l, h - 2);
ctx.fillText(`${Math.round(maxD)}m`, margin.l + plotW - 24, h - 2);
ctx.fillStyle = '#ccc';
ctx.font = '10px system-ui';
ctx.fillText('RX Quality vs расстояние', margin.l, margin.t - 4);
const barW = plotW / binCount * 0.75;
bins.forEach((b, i) => {
if (!b.qualities.length) return;
const avg = b.qualities.reduce((a, v) => a + v, 0) / b.qualities.length;
const cx = margin.l + (i + 0.5) / binCount * plotW;
const barH = (avg / 100) * plotH;
const x = cx - barW / 2;
const y = margin.t + plotH - barH;
const col = qualityColor ? qualityColor(avg) : '#888';
const highlight = highlightDist != null
&& highlightDist >= b.min && highlightDist < b.max;
ctx.fillStyle = col;
ctx.globalAlpha = highlight ? 1 : 0.75;
ctx.fillRect(x, y, barW, barH);
ctx.globalAlpha = 1;
if (highlight) {
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1.5;
ctx.strokeRect(x, y, barW, barH);
}
});
ctx.fillStyle = 'rgba(255,255,255,0.25)';
samples.forEach(s => {
const x = margin.l + ((s.distM - minD) / span) * plotW;
const y = margin.t + plotH - (s.quality / 100) * plotH;
ctx.beginPath();
ctx.arc(x, y, 1.5, 0, Math.PI * 2);
ctx.fill();
});
}
global.QualityViz = {
drawQualityDistChart,
};
})(typeof window !== 'undefined' ? window : globalThis);