Files
geograsim/App/assets/js/sim-metrics.js
T
Adminator 5bf8f0fe03 Drilldown: volle Modalbreite + durchgaengige Ampelfarben (gruen->rot)
- Modal max-width 640->1120px, Kachel-Grid dichter -> Kennzahlen einzeilig
- Werte UND Balken ampelgefaerbt (pct/pctInv/ratio), Sterne mit Ampelbalken
  je n/5 -> Vergleich auf einen Blick

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:51:33 +02:00

165 lines
7.7 KiB
JavaScript

/* ============================================================================
* GeoGraSim — gemeinsames Sim-Kennzahlen-Schema
* ----------------------------------------------------------------------------
* Pro Simulation eine KURATIERTE, menschlich benannte Auswahl der wirklich
* relevanten Ergebnis-/Live-Felder mit Anzeigetyp (Sterne, Prozentbalken,
* Verhältnis, Geld, Temperatur, Zahl, Text, Ja/Nein). Alles NICHT Gelistete
* (technische Felder wie activeMs, phase, dishId …) wird ausgeblendet.
*
* renderSimMetrics(simId, params) → HTML-Grid mit grafischen Kacheln.
* Wiederverwendbar für Drilldown UND Ergebnis-Tabellen.
*
* Typen: stars | pct | pctInv | ratio(+t) | money | temp | num(+u) | text | bool | boolInv
* ==========================================================================*/
(function (root) {
'use strict';
var SIM_METRICS = {
klima: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'final_temperature',type:'temp',l:'Temperatur'},
{k:'final_flooded_pct',type:'pctInv',l:'Überflutung'},
{k:'final_co2',type:'num',l:'CO₂',u:'ppm'},
{k:'final_budget',type:'money',l:'Budget'},
{k:'final_population',type:'num',l:'Bevölkerung'},
{k:'measures_bought',type:'num',l:'Maßnahmen'}
],
heli: [
{k:'sterneGesamt',type:'stars',l:'Sterne'},
{k:'stars',type:'stars',l:'Sterne'},
{k:'planPct',type:'pct',l:'Planung'},
{k:'startScore',type:'starsSmall',l:'Start'},
{k:'landingScore',type:'starsSmall',l:'Landung'},
{k:'missionTitle',type:'text',l:'Mission'},
{k:'failed',type:'boolInv',l:'geschafft'}
],
busfahrt: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'wonOrders',type:'ratio',t:'totalOrders',l:'Aufträge gewonnen'},
{k:'quizFirstTry',type:'ratio',t:'totalOrders',l:'Quiz Erstversuch'},
{k:'avgTapKm',type:'num',l:'Ø Abweichung',u:'km'}
],
farmer: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'hit_rate',type:'pct',l:'Trefferquote'},
{k:'year',type:'num',l:'Jahr erreicht'},
{k:'crops_used',type:'num',l:'Sorten'}
],
sonnensystem: [
{k:'completed',type:'ratio',t:'total',l:'Aufgaben gelöst'},
{k:'firstTry',type:'bool',l:'Erstversuch'}
],
'eu-werkstatt': [
{k:'firstTryHits',type:'ratio',t:'stepsTotal',l:'Erste Wahl'},
{k:'muelleimerCount',type:'num',l:'verworfen'},
{k:'endType',type:'text',l:'Ergebnis'}
],
energiemanager: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'blocksBalanced',type:'ratio',t:'blocksTotal',l:'Blöcke ausbalanciert'},
{k:'daysPlayed',type:'ratio',t:'daysTotal',l:'Tage gespielt'}
],
weltkueche: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'completedDishes',type:'ratio',t:'totalDishes',l:'Gerichte'},
{k:'mistakesInDish',type:'num',l:'Fehlklicks'},
{k:'dishName',type:'text',l:'Aktuelles Gericht'},
{k:'ingSolved',type:'ratio',t:'ingTotal',l:'Zutaten gelöst'},
{k:'progressPct',type:'pct',l:'Fortschritt'}
],
logistik: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'ordersDelivered',type:'ratio',t:'totalOrders',l:'Ausgeliefert'},
{k:'ordersLate',type:'num',l:'verspätet'},
{k:'finalBalance',type:'money',l:'Bilanz'},
{k:'balance',type:'money',l:'Konto'}
],
fluss: [
{k:'stars',type:'stars',l:'Sterne'},
{k:'biodiv',type:'pct',l:'Biodiversität'},
{k:'economy',type:'pct',l:'Wirtschaft'},
{k:'flood',type:'pctInv',l:'Hochwasser'},
{k:'food',type:'pct',l:'Ernährung'}
],
kofferdetektiv: [
{k:'cases_solved',type:'ratio',t:'cases_total',l:'Fälle gelöst'},
{k:'correct_first_try',type:'num',l:'Erstversuch'},
{k:'wrong_guesses',type:'num',l:'Fehlversuche'}
],
tourismustal: [
{k:'goalsDone',type:'ratio',t:'goalsTotal',l:'Ziele erreicht'},
{k:'satisfaction',type:'pct',l:'Zufriedenheit'},
{k:'nature',type:'pct',l:'Naturwert'},
{k:'money',type:'money',l:'Konto'},
{k:'year',type:'num',l:'Jahr'},
{k:'buildings',type:'num',l:'Gebäude'},
{k:'maxGuests',type:'num',l:'Gäste (max)'}
],
tourismusregion: [
{k:'score',type:'pct',l:'Gesamtscore'},
{k:'goalsDone',type:'ratio',t:'goalsTotal',l:'Ziele erreicht'}
]
};
SIM_METRICS['klima-3d'] = SIM_METRICS.klima;
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
function ampPct(v){ return v>=75?'ivm-g':v>=55?'ivm-li':v>=40?'ivm-y':v>=25?'ivm-o':'ivm-r'; }
function stars(n){
n = Math.max(0, Math.min(5, Math.round(n)));
return '<span class="ivm-stars">'+'★'.repeat(n)+'<span class="ivm-star-off">'+'★'.repeat(5-n)+'</span></span>';
}
function bar(pct, cls){
pct = Math.max(0, Math.min(100, Math.round(pct)));
return '<span class="ivm-bar"><i class="'+cls+'" style="width:'+pct+'%"></i></span>';
}
function money(v){
var n = Math.round(v), cls = n<0?'ivm-r':'ivm-g';
return '<span class="ivm-v '+cls+'">'+n.toLocaleString('de-AT')+' €</span>';
}
function num(v, u){ return '<span class="ivm-v">'+(typeof v==='number'?v.toLocaleString('de-AT'):esc(v))+(u?' <span class="ivm-u">'+u+'</span>':'')+'</span>'; }
function starsBlock(n, small){
n = Math.max(0, Math.min(5, Math.round(n)));
var c = ampPct(n/5*100);
var cls = 'ivm-stars'+(small?' sm':'')+' '+c;
return '<span class="'+cls+'">'+'★'.repeat(n)+'<span class="ivm-star-off">'+'★'.repeat(5-n)+'</span></span>'+bar(n/5*100, c);
}
function renderVal(f, v, src){
switch (f.type) {
case 'stars': return starsBlock(v, false);
case 'starsSmall': return starsBlock(v, true);
case 'pct': return '<span class="ivm-v '+ampPct(v)+'">'+Math.round(v)+'<span class="ivm-u">%</span></span>'+bar(v, ampPct(v));
case 'pctInv': return '<span class="ivm-v '+ampPct(100-v)+'">'+Math.round(v)+'<span class="ivm-u">%</span></span>'+bar(v, ampPct(100-v));
case 'ratio': { var tot=src[f.t]; if(tot==null) return num(v); var pct=tot>0?(v/tot*100):0;
return '<span class="ivm-v '+ampPct(pct)+'">'+v+'<span class="ivm-u">/ '+tot+'</span></span>'+bar(pct, ampPct(pct)); }
case 'money': return money(v);
case 'temp': { var t=Math.round(v*10)/10, c=t<=2?'ivm-g':t<=3?'ivm-y':'ivm-r';
return '<span class="ivm-v '+c+'">'+t.toLocaleString('de-AT')+' °C</span>'; }
case 'bool': return v?'<span class="ivm-v ivm-g">✓ ja</span>':'<span class="ivm-v ivm-r">✗ nein</span>';
case 'boolInv': return v?'<span class="ivm-v ivm-r">✗ nein</span>':'<span class="ivm-v ivm-g">✓ ja</span>';
case 'text': return '<span class="ivm-v ivm-txt">'+esc(v)+'</span>';
default: return num(v, f.u);
}
}
function renderSimMetrics(simId, params){
if (!params || typeof params!=='object') return '';
var src = (params.results && typeof params.results==='object') ? params.results : params;
var schema = SIM_METRICS[simId] || [];
var seen = {}, out = [];
schema.forEach(function(f){
if (seen[f.l]) return; // Doppel-Label (z. B. stars/sterneGesamt) nur einmal
var v = src[f.k];
if (v==null || v==='') return;
seen[f.l] = 1;
out.push('<div class="ivm"><span class="ivm-l">'+esc(f.l)+'</span>'+renderVal(f, v, src)+'</div>');
});
return out.length ? '<div class="ivm-grid">'+out.join('')+'</div>'
: '<span style="font-size:.72rem;color:#8a8a8a">keine auswertbaren Parameter</span>';
}
root.SIM_METRICS = SIM_METRICS;
root.renderSimMetrics = renderSimMetrics;
root.ggsStarsHtml = stars;
})(typeof window !== 'undefined' ? window : this);