Verlauf-Grafik aus Sim-Schema (mehrere Linien) + Ampelbalken in allen Spalten
- renderRunsLineChart nutzt wieder SIM_METRICS statt highlights -> deckt auch Module ohne Highlight-Def ab (weltkueche/fluss zeigten sonst nur Sterne); Snapshot bei einem Durchgang via renderSimMetrics-Kacheln - Modul-Tabelle: Ampelbalken auch fuer Durchgaenge, Spielzeit, Zuletzt (mehr/aktueller = gruen) -> alle Spalten konsistent grafisch Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+28
-17
@@ -3579,6 +3579,15 @@ function renderModDetail() {
|
|||||||
var frac = (+v - st.min) / (st.max - st.min);
|
var frac = (+v - st.min) / (st.max - st.min);
|
||||||
return h.agg === 'min' ? (1 - frac) : frac;
|
return h.agg === 'min' ? (1 - frac) : frac;
|
||||||
}
|
}
|
||||||
|
// Engagement-Spalten (Durchgänge, Spielzeit, Zuletzt): mehr/aktueller = grün
|
||||||
|
var maxPlays = Math.max.apply(null, [1].concat(d.students.map(function(s){ return s.plays || 0; })));
|
||||||
|
var maxDur = Math.max.apply(null, [1].concat(d.students.map(function(s){ return s.total_duration_ms || 0; })));
|
||||||
|
var NOW = Date.now();
|
||||||
|
function recencyGood(lp){
|
||||||
|
if (!lp) return null;
|
||||||
|
var days = (NOW - new Date(lp.replace(' ','T')+'Z').getTime()) / 86400000;
|
||||||
|
return Math.max(0, Math.min(1, 1 - days/21)); // heute = 1, ab ~3 Wochen = 0
|
||||||
|
}
|
||||||
|
|
||||||
html += '<table class="result-matrix" style="font-size:.72rem">';
|
html += '<table class="result-matrix" style="font-size:.72rem">';
|
||||||
html += '<thead><tr>'
|
html += '<thead><tr>'
|
||||||
@@ -3604,7 +3613,7 @@ function renderModDetail() {
|
|||||||
html += '<tr>'
|
html += '<tr>'
|
||||||
+ '<td>'+avatar+' '+esc(s.displayName)+'</td>'
|
+ '<td>'+avatar+' '+esc(s.displayName)+'</td>'
|
||||||
+ '<td style="color:#a37800">'+starStr+(s.plays>0?ampBar(stars/5):'')+'</td>'
|
+ '<td style="color:#a37800">'+starStr+(s.plays>0?ampBar(stars/5):'')+'</td>'
|
||||||
+ '<td>'+s.plays+'</td>';
|
+ '<td>'+s.plays+(s.plays>0?ampBar(s.plays/maxPlays):'')+'</td>';
|
||||||
hl.forEach(function(h) {
|
hl.forEach(function(h) {
|
||||||
var v = (s.highlights || {})[h.key];
|
var v = (s.highlights || {})[h.key];
|
||||||
var isChamp = (champs[h.key] === s.id);
|
var isChamp = (champs[h.key] === s.id);
|
||||||
@@ -3613,8 +3622,8 @@ function renderModDetail() {
|
|||||||
+ (isChamp && v !== null && v !== undefined ? '🏆 ' : '')
|
+ (isChamp && v !== null && v !== undefined ? '🏆 ' : '')
|
||||||
+ text + ampBar(hlGoodness(h, v)) + '</td>';
|
+ text + ampBar(hlGoodness(h, v)) + '</td>';
|
||||||
});
|
});
|
||||||
html += '<td>'+lastDate+'</td>'
|
html += '<td>'+lastDate+(s.plays>0?ampBar(recencyGood(s.last_played)):'')+'</td>'
|
||||||
+ '<td>'+dur+'</td>'
|
+ '<td>'+dur+(s.total_duration_ms?ampBar(s.total_duration_ms/maxDur):'')+'</td>'
|
||||||
+ '<td>'+(s.plays > 0 ? '<button class="btn btn-outline" style="font-size:.6rem;padding:.2rem .5rem" onclick="openStudentRuns('+s.id+')">Verlauf</button>' : '')+'</td>'
|
+ '<td>'+(s.plays > 0 ? '<button class="btn btn-outline" style="font-size:.6rem;padding:.2rem .5rem" onclick="openStudentRuns('+s.id+')">Verlauf</button>' : '')+'</td>'
|
||||||
+ '</tr>';
|
+ '</tr>';
|
||||||
});
|
});
|
||||||
@@ -3655,7 +3664,7 @@ async function openStudentRuns(studentId) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Alle übrigen: Liniengrafik über die wichtigsten Kennzahlen (statt JSON)
|
// Alle übrigen: Liniengrafik über die wichtigsten Kennzahlen (statt JSON)
|
||||||
html += renderRunsLineChart(_modDetail.data.highlights || [], r.runs);
|
html += renderRunsLineChart(_modDetail.moduleId, r.runs);
|
||||||
}
|
}
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
box.innerHTML = html;
|
box.innerHTML = html;
|
||||||
@@ -3921,34 +3930,36 @@ function _hlGet(obj, path){
|
|||||||
for (var i = 0; i < parts.length; i++) { if (v == null) return null; v = v[parts[i]]; }
|
for (var i = 0; i < parts.length; i++) { if (v == null) return null; v = v[parts[i]]; }
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
function renderRunsLineChart(highlights, runs) {
|
function renderRunsLineChart(simId, runs) {
|
||||||
var rs = runs.slice().filter(function(r){ return r.submitted_at; })
|
var rs = runs.slice().filter(function(r){ return r.submitted_at; })
|
||||||
.sort(function(a,b){ return a.submitted_at < b.submitted_at ? -1 : a.submitted_at > b.submitted_at ? 1 : 0; });
|
.sort(function(a,b){ return a.submitted_at < b.submitted_at ? -1 : a.submitted_at > b.submitted_at ? 1 : 0; });
|
||||||
// Kennzahlen: Sterne (falls vorhanden) + alle Highlight-Spalten
|
function runData(run){ return (run.results && run.results.data) ? run.results.data : (run.results || {}); }
|
||||||
var metrics = [{ key:'stars', label:'Sterne', hi:true, star:true }];
|
// Kennzahlen aus dem gemeinsamen Sim-Schema (deckt alle Chart-Module ab)
|
||||||
(highlights || []).forEach(function(h){
|
var schema = (typeof simPlotMetrics === 'function') ? simPlotMetrics(simId) : [];
|
||||||
metrics.push({ key:h.key, label:h.label, unit:h.unit, format:h.format, hi:(h.agg !== 'min') });
|
var metrics = schema.map(function(f){
|
||||||
|
return { key:f.k, t:f.t, type:f.type, label:f.l, unit:f.u,
|
||||||
|
hi:(typeof metricHigherBetter === 'function' ? metricHigherBetter(f) : true) };
|
||||||
});
|
});
|
||||||
function rawVal(run, m){
|
function rawVal(run, m){
|
||||||
var v = m.star ? _hlGet(run.results, 'stars') : _hlGet(run.results, m.key);
|
var d = runData(run), v = d[m.key];
|
||||||
if (v === null || v === undefined || isNaN(+v)) return null;
|
if (v === null || v === undefined || isNaN(+v)) return null;
|
||||||
|
if (m.type === 'ratio') { var t = d[m.t]; return (t > 0) ? (+v / t * 100) : null; }
|
||||||
return +v;
|
return +v;
|
||||||
}
|
}
|
||||||
function fmtMetric(m, v){
|
function fmtMetric(m, v){
|
||||||
if (v === null) return '–';
|
if (v === null) return '–';
|
||||||
if (m.star) return Math.round(v) + '★';
|
if (m.type === 'stars' || m.type === 'starsSmall') return Math.round(v) + '★';
|
||||||
if (m.format === 'percent') return Math.round(v * 100) + ' %';
|
if (m.type === 'pct' || m.type === 'pctInv' || m.type === 'ratio') return Math.round(v) + ' %';
|
||||||
|
if (m.type === 'temp') return (Math.round(v*10)/10).toLocaleString('de-AT') + ' °C';
|
||||||
var n = Math.abs(v) >= 100 ? Math.round(v) : Math.round(v * 10) / 10;
|
var n = Math.abs(v) >= 100 ? Math.round(v) : Math.round(v * 10) / 10;
|
||||||
return n.toLocaleString('de-AT') + (m.unit ? ' ' + m.unit : '');
|
return n.toLocaleString('de-AT') + (m.unit ? ' ' + m.unit : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot statt Linie bei nur einem Durchgang
|
// Snapshot (grafische Kacheln) bei nur einem Durchgang
|
||||||
if (rs.length < 2) {
|
if (rs.length < 2) {
|
||||||
var only = (rs[0] || runs[0] || {}).results || {};
|
var only = runData(rs[0] || runs[0] || {});
|
||||||
var chips = metrics.map(function(m){ var v = rawVal(rs[0]||runs[0]||{}, m); return v===null ? '' :
|
|
||||||
'<div class="ivm" style="background:#f4f1e8;border-radius:8px;padding:6px 9px"><span style="display:block;font-size:.58rem;color:#8a8a8a;text-transform:uppercase">'+esc(m.label)+'</span><b style="font-size:.9rem">'+fmtMetric(m,v)+'</b></div>'; }).join('');
|
|
||||||
return '<div style="margin-top:.5rem"><p style="font-size:.72rem;color:#8a8a8a;margin:.2rem 0 .5rem">Erst ein Durchgang – die Liniengrafik erscheint ab dem zweiten. Aktueller Stand:</p>'
|
return '<div style="margin-top:.5rem"><p style="font-size:.72rem;color:#8a8a8a;margin:.2rem 0 .5rem">Erst ein Durchgang – die Liniengrafik erscheint ab dem zweiten. Aktueller Stand:</p>'
|
||||||
+ '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:7px">'+chips+'</div></div>';
|
+ ((typeof renderSimMetrics === 'function') ? renderSimMetrics(simId, only) : '') + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
var COLORS = ['#3f7a45','#c65a49','#3a6a8a','#c9913a','#7a5a9a','#4a9a8a','#b0743a'];
|
var COLORS = ['#3f7a45','#c65a49','#3a6a8a','#c9913a','#7a5a9a','#4a9a8a','#b0743a'];
|
||||||
|
|||||||
Reference in New Issue
Block a user