ce7bff3184
bar() faerbte ueber ivm-CSS-Klassen, die im Schueler-Drilldown fehlen -> Balken erschienen schwarz. Jetzt INLINE gefaerbt (ivmColor = _pctColor-Skala). starsBlock (Heli Start/Landung) zeigt jetzt %+Balken statt Sterne. pct/pctInv/ratio/money/temp/bool ebenfalls inline gefaerbt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
189 lines
9.5 KiB
JavaScript
189 lines
9.5 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',dir:'lo'},
|
||
{k:'final_budget',type:'num',l:'Budget',u:'Mio €'},
|
||
{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:'level',type:'num',l:'Level'},
|
||
{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',dir:'lo'}
|
||
],
|
||
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:'firstTryCount',type:'ratio',t:'total',l:'Auf Anhieb'},
|
||
{k:'completed',type:'ratio',t:'total',l:'Aufgaben gelöst'}
|
||
],
|
||
'eu-werkstatt': [
|
||
{k:'firstTryHits',type:'ratio',t:'stepsTotal',l:'Erste Wahl'},
|
||
{k:'muelleimerCount',type:'num',l:'verworfen',dir:'lo'},
|
||
{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',dir:'lo'},
|
||
{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',dir:'lo'},
|
||
{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',dir:'lo'},
|
||
{k:'hints_used_avg',type:'num',l:'Ø Hinweise',dir:'lo'}
|
||
],
|
||
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{'&':'&','<':'<','>':'>','"':'"'}[c];}); }
|
||
function ampPct(v){ return v>=75?'ivm-g':v>=55?'ivm-li':v>=40?'ivm-y':v>=25?'ivm-o':'ivm-r'; }
|
||
// 5-stufige Leistungs-Skala (= _pctColor). Balken/Werte werden INLINE gefärbt,
|
||
// damit sie überall farbig sind — auch wo die ivm-CSS-Klassen fehlen (Drilldown),
|
||
// wo der Balken sonst schwarz erschien.
|
||
function ivmColor(v){ v=Math.max(0,Math.min(100,v||0)); return v>=90?'#1f7a3d':v>=80?'#7cb342':v>=60?'#f9c33b':v>=50?'#ef8b2c':'#d8453a'; }
|
||
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, col){
|
||
pct = Math.max(0, Math.min(100, Math.round(pct)));
|
||
return '<span class="ivm-bar" style="display:block;height:5px;border-radius:99px;background:rgba(0,0,0,.09);overflow:hidden;margin-top:3px"><i style="display:block;height:100%;width:'+pct+'%;background:'+col+'"></i></span>';
|
||
}
|
||
function money(v){
|
||
var n = Math.round(v), col = n<0?'#d8453a':'#1f7a3d';
|
||
return '<span class="ivm-v" style="color:'+col+';font-weight:800">'+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){
|
||
// Sterne raus → Prozent + farbiger Balken (z. B. Heli Start/Landung, 0–5 → %).
|
||
var pct = Math.round(Math.max(0,Math.min(5,n))/5*100), col = ivmColor(pct);
|
||
return '<span class="ivm-v" style="color:'+col+';font-weight:800">'+pct+'<span class="ivm-u" style="font-weight:400"> %</span></span>'+bar(pct, col);
|
||
}
|
||
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" style="color:'+ivmColor(v)+';font-weight:800">'+Math.round(v)+'<span class="ivm-u" style="font-weight:400">%</span></span>'+bar(v, ivmColor(v));
|
||
case 'pctInv': return '<span class="ivm-v" style="color:'+ivmColor(100-v)+';font-weight:800">'+Math.round(v)+'<span class="ivm-u" style="font-weight:400">%</span></span>'+bar(v, ivmColor(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" style="color:'+ivmColor(pct)+';font-weight:800">'+v+'<span class="ivm-u" style="font-weight:400">/ '+tot+'</span></span>'+bar(pct, ivmColor(pct)); }
|
||
case 'money': return money(v);
|
||
case 'temp': { var t=Math.round(v*10)/10, col=t<=2?'#1f7a3d':t<=3?'#f9c33b':'#d8453a';
|
||
return '<span class="ivm-v" style="color:'+col+';font-weight:800">'+t.toLocaleString('de-AT')+' °C</span>'; }
|
||
case 'bool': return v?'<span class="ivm-v" style="color:#1f7a3d;font-weight:800">✓ ja</span>':'<span class="ivm-v" style="color:#d8453a;font-weight:800">✗ nein</span>';
|
||
case 'boolInv': return v?'<span class="ivm-v" style="color:#d8453a;font-weight:800">✗ nein</span>':'<span class="ivm-v" style="color:#1f7a3d;font-weight:800">✓ 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>';
|
||
}
|
||
|
||
// Plottbare (numerische) Kennzahlen einer Sim, dedupliziert nach Label.
|
||
var PLOTTABLE = {stars:1, starsSmall:1, pct:1, pctInv:1, ratio:1, temp:1, money:1, num:1};
|
||
function simPlotMetrics(simId){
|
||
var seen = {}, out = [];
|
||
(SIM_METRICS[simId] || []).forEach(function(f){
|
||
if (!PLOTTABLE[f.type] || seen[f.l]) return;
|
||
seen[f.l] = 1; out.push(f);
|
||
});
|
||
return out;
|
||
}
|
||
// true = höher ist besser (grün oben). temp/pctInv und dir:'lo' → niedriger ist besser.
|
||
function metricHigherBetter(f){
|
||
if (f.dir === 'lo') return false;
|
||
if (f.dir === 'hi') return true;
|
||
return !(f.type === 'pctInv' || f.type === 'temp');
|
||
}
|
||
|
||
root.SIM_METRICS = SIM_METRICS;
|
||
root.renderSimMetrics = renderSimMetrics;
|
||
root.ggsStarsHtml = stars;
|
||
root.simPlotMetrics = simPlotMetrics;
|
||
root.metricHigherBetter = metricHigherBetter;
|
||
})(typeof window !== 'undefined' ? window : this);
|