/* ============================================================================ * 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:'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',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'; } function stars(n){ n = Math.max(0, Math.min(5, Math.round(n))); return ''+'★'.repeat(n)+''+'★'.repeat(5-n)+''; } function bar(pct, cls){ pct = Math.max(0, Math.min(100, Math.round(pct))); return ''; } function money(v){ var n = Math.round(v), cls = n<0?'ivm-r':'ivm-g'; return ''+n.toLocaleString('de-AT')+' €'; } function num(v, u){ return ''+(typeof v==='number'?v.toLocaleString('de-AT'):esc(v))+(u?' '+u+'':'')+''; } 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 ''+'★'.repeat(n)+''+'★'.repeat(5-n)+''+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 ''+Math.round(v)+'%'+bar(v, ampPct(v)); case 'pctInv': return ''+Math.round(v)+'%'+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 ''+v+'/ '+tot+''+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 ''+t.toLocaleString('de-AT')+' °C'; } case 'bool': return v?'✓ ja':'✗ nein'; case 'boolInv': return v?'✗ nein':'✓ ja'; case 'text': return ''+esc(v)+''; 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('