Files
Adminator ce7bff3184 Drilldown-Metriken: schwarze Balken -> farbig + Sterne raus (sim-metrics.js)
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>
2026-08-25 11:34:25 +02:00

189 lines
9.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================================
* 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{'&':'&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'; }
// 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, 05 → %).
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);