Files
foodlinkk-command-center/cockpit/static/js/viz-engine.js
T
2026-06-09 10:41:13 +00:00

219 lines
8.3 KiB
JavaScript

(function (global) {
var MODES = [
{ id: 'neo-bars', label: 'Neo staafdiagram' },
{ id: 'neo-rings', label: 'Neo ringen' },
{ id: 'neo-equalizer', label: 'Neo equalizer' },
{ id: 'neo-cards', label: 'Neo kaarten' },
{ id: 'neo-table', label: 'Neo tabel' },
];
var charts = {};
function destroy(key) {
if (charts[key]) {
charts[key].destroy();
charts[key] = null;
}
}
function colors() {
return {
gold: 'rgba(252, 211, 77, 0.9)', cyan: 'rgba(56, 189, 248, 0.9)',
green: 'rgba(74, 222, 128, 0.9)', red: 'rgba(251, 113, 133, 0.9)',
purple: 'rgba(168, 85, 247, 0.9)', gray: 'rgba(159, 176, 196, 0.85)',
grid: 'rgba(159, 176, 196, 0.15)', text: '#c8d4e0',
};
}
function eqBars(counts, cls) {
var max = Math.max.apply(null, counts.concat([1]));
return '<div class="hm-eq viz-eq ' + (cls || '') + '">' +
counts.map(function (v) {
var h = Math.max(18, Math.round((v / max) * 92));
return '<span style="height:' + h + '%"></span>';
}).join('') + '</div>';
}
function findPanel(canvas, alt) {
var el = (canvas && canvas.closest('.page-viz-panel')) || (alt && alt.closest('.page-viz-panel'));
return el || null;
}
function applyPanelLayout(canvas, alt, mode) {
var panel = findPanel(canvas, alt);
if (!panel) return;
var isCanvas = mode === 'neo-bars' || mode === 'neo-rings';
panel.classList.toggle('viz-mode-chart', isCanvas);
panel.classList.toggle('viz-mode-alt', !isCanvas);
panel.classList.toggle('viz-mode-rings', mode === 'neo-rings');
}
function useHorizontal(labels, opts) {
if (opts && opts.horizontal !== undefined) return opts.horizontal;
return (labels || []).length >= 6;
}
function renderBars(canvas, labels, values, opts) {
if (!canvas || typeof Chart === 'undefined') return;
opts = opts || { key: 'chart' };
var c = colors();
var horiz = useHorizontal(labels, opts);
destroy(opts.key);
charts[opts.key] = new Chart(canvas, {
type: 'bar',
data: {
labels: labels,
datasets: [{ data: values, backgroundColor: opts.color || c.cyan, borderRadius: 6, barThickness: horiz ? 'flex' : undefined, maxBarThickness: horiz ? 28 : 48 }],
},
options: {
indexAxis: horiz ? 'y' : 'x',
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 4, right: 8, bottom: 4, left: 4 } },
plugins: { legend: { display: false }, title: { display: !!opts.title, text: opts.title, color: c.text, padding: { bottom: 8 } } },
scales: {
y: horiz
? { ticks: { color: c.text, autoSkip: false, font: { size: 11 } }, grid: { color: c.grid } }
: { ticks: { color: c.text }, grid: { color: c.grid }, beginAtZero: true },
x: horiz
? { ticks: { color: c.text }, grid: { display: false }, beginAtZero: true }
: { ticks: { color: c.text, maxRotation: 45, minRotation: 0, autoSkip: false, font: { size: 10 } }, grid: { display: false } },
},
},
});
requestAnimationFrame(function () {
if (charts[opts.key]) charts[opts.key].resize();
});
}
function renderRings(canvas, labels, values, key, title) {
if (!canvas || typeof Chart === 'undefined') return;
var c = colors();
destroy(key);
charts[key] = new Chart(canvas, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{ data: values, backgroundColor: [c.green, c.gray, c.red, c.gold, c.cyan, c.purple], borderWidth: 0 }],
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '62%',
layout: { padding: 4 },
plugins: {
legend: { position: 'bottom', labels: { color: c.text, boxWidth: 10, padding: 8, font: { size: 10 } } },
title: { display: !!title, text: title, color: c.text, font: { size: 11 } },
},
},
});
requestAnimationFrame(function () {
if (charts[key]) charts[key].resize();
});
}
function renderCards(container, rows) {
if (!container) return;
container.innerHTML = '<div class="viz-cards">' + rows.map(function (r) {
return '<div class="viz-card"><span class="viz-card-label">' + r.label + '</span><strong class="viz-card-val">' + r.value + '</strong></div>';
}).join('') + '</div>';
}
function renderTable(container, rows) {
if (!container) return;
container.innerHTML = '<table class="data-table viz-table"><thead><tr><th>Item</th><th>Waarde</th></tr></thead><tbody>' +
rows.map(function (r) { return '<tr><td>' + r.label + '</td><td>' + r.value + '</td></tr>'; }).join('') +
'</tbody></table>';
}
function renderEqualizer(container, rows, cls) {
if (!container) return;
container.innerHTML = rows.map(function (r) {
return '<div class="viz-eq-row"><span>' + r.label + '</span><strong>' + r.value + '</strong></div>';
}).join('') + eqBars(rows.map(function (r) { return Number(r.raw) || 0; }), cls);
}
function renderWidget(mode, spec) {
var canvas = spec.canvas;
var alt = spec.altContainer;
applyPanelLayout(canvas, alt, mode);
if (mode === 'neo-bars' && canvas) {
if (alt) alt.innerHTML = '';
canvas.style.display = '';
renderBars(canvas, spec.labels, spec.values, spec.chartOpts || { key: spec.key });
return;
}
if (mode === 'neo-rings' && canvas) {
if (alt) alt.innerHTML = '';
canvas.style.display = '';
renderRings(canvas, spec.labels, spec.values, spec.key, spec.title);
return;
}
if (canvas) canvas.style.display = 'none';
if (!alt) return;
if (mode === 'neo-equalizer') renderEqualizer(alt, spec.rows, spec.eqClass);
else if (mode === 'neo-cards') renderCards(alt, spec.rows);
else if (mode === 'neo-table') renderTable(alt, spec.rows);
else {
if (canvas) { canvas.style.display = ''; renderBars(canvas, spec.labels, spec.values, spec.chartOpts || { key: spec.key }); }
}
}
global.VizEngine = {
MODES: MODES,
renderWidget: renderWidget,
destroyAll: function () { Object.keys(charts).forEach(destroy); },
renderSentiment: function (mode, canvas, alt, stats, widgetMode) {
var m = widgetMode || mode;
var files = stats.nas_files || [];
var counts = { positive: 0, neutral: 0, negative: 0 };
files.forEach(function (f) {
var s = (f.sentiment_label || 'neutral').toLowerCase();
if (counts[s] !== undefined) counts[s]++;
});
if (!files.length) counts.neutral = 1;
renderWidget(m, {
canvas: canvas, altContainer: alt, key: 'sentiment',
labels: ['Positief', 'Neutraal', 'Negatief'],
values: [counts.positive, counts.neutral, counts.negative],
title: 'NAS sentiment',
rows: [
{ label: 'Positief', value: counts.positive, raw: counts.positive },
{ label: 'Neutraal', value: counts.neutral, raw: counts.neutral },
{ label: 'Negatief', value: counts.negative, raw: counts.negative },
],
eqClass: 'hm-eq-purple',
});
},
renderAgents: function (mode, canvas, alt, stats, widgetMode) {
var m = widgetMode || mode;
var map = {};
(stats.recent_events || []).forEach(function (e) {
var a = e.agent_name || 'other';
map[a] = (map[a] || 0) + 1;
});
var labels = Object.keys(map);
var values = labels.map(function (k) { return map[k]; });
if (!labels.length) { labels = ['geen']; values = [0]; }
renderWidget(m, {
canvas: canvas, altContainer: alt, key: 'agents',
labels: labels, values: values, title: 'Agent activiteit',
chartOpts: { key: 'agents', color: colors().gold },
rows: labels.map(function (l, i) { return { label: l, value: values[i], raw: values[i] }; }),
eqClass: 'hm-eq-lime',
});
},
renderFromRows: function (mode, canvas, alt, key, title, rows, eqClass, chartColor) {
var labels = rows.map(function (r) { return r.label; });
var values = rows.map(function (r) { return Number(r.raw) || 0; });
renderWidget(mode, {
canvas: canvas, altContainer: alt, key: key,
labels: labels, values: values, title: title,
chartOpts: { key: key, color: chartColor || colors().cyan },
rows: rows,
eqClass: eqClass || 'hm-eq-cyan',
});
},
};
})(window);