now shows sectors
Some checks are pending
Deployment / deploy-docker (push) Waiting to run

This commit is contained in:
Melchior Reimers
2026-02-03 14:11:48 +01:00
parent 740999b68e
commit f0acbf39bc

View File

@@ -185,6 +185,20 @@
<label class="block text-sm font-bold text-slate-400 mb-2">Exchanges (optional, komma-separiert)</label> <label class="block text-sm font-bold text-slate-400 mb-2">Exchanges (optional, komma-separiert)</label>
<input type="text" id="customExchanges" class="input-glass" placeholder="z.B. EIX,LS,XETRA,FRA,GETTEX,STU,QUOTRIX,DUSA,HAMA,HANA" onchange="updateCustomGraph(); updateUrlParams()"> <input type="text" id="customExchanges" class="input-glass" placeholder="z.B. EIX,LS,XETRA,FRA,GETTEX,STU,QUOTRIX,DUSA,HAMA,HANA" onchange="updateCustomGraph(); updateUrlParams()">
</div> </div>
<div>
<label class="block text-sm font-bold text-slate-400 mb-2">Chart-Typ</label>
<select id="customChartType" class="input-glass" onchange="updateCustomGraph(); updateUrlParams()">
<option value="line">Linien</option>
<option value="stacked">Stacked Area (Sektorrotation)</option>
<option value="percent">Prozentual (100% Stacked)</option>
</select>
</div>
<div class="flex items-center gap-4 mt-4">
<label class="flex items-center gap-2 text-sm text-slate-400 cursor-pointer">
<input type="checkbox" id="hideUnknown" class="w-4 h-4" checked onchange="updateCustomGraph(); updateUrlParams()">
"Unbekannt" ausblenden
</label>
</div>
</div> </div>
<button onclick="updateCustomGraph()" class="glass px-6 py-3 bg-sky-500 hover:bg-sky-600 text-white font-bold rounded-lg transition-colors cursor-pointer">Graph aktualisieren</button> <button onclick="updateCustomGraph()" class="glass px-6 py-3 bg-sky-500 hover:bg-sky-600 text-white font-bold rounded-lg transition-colors cursor-pointer">Graph aktualisieren</button>
@@ -750,6 +764,8 @@
const yAxis = document.getElementById('customYAxis').value; const yAxis = document.getElementById('customYAxis').value;
const groupBy = document.getElementById('customGroupBy').value; const groupBy = document.getElementById('customGroupBy').value;
const exchanges = document.getElementById('customExchanges').value; const exchanges = document.getElementById('customExchanges').value;
const chartType = document.getElementById('customChartType')?.value || 'line';
const hideUnknown = document.getElementById('hideUnknown')?.checked ?? true;
if (!dateFrom || !dateTo) { if (!dateFrom || !dateTo) {
console.log('Bitte wählen Sie Start- und Enddatum'); console.log('Bitte wählen Sie Start- und Enddatum');
@@ -778,7 +794,11 @@
const groupIdx = columns.findIndex(c => c.name === 'group_value'); const groupIdx = columns.findIndex(c => c.name === 'group_value');
const yIdx = columns.findIndex(c => c.name === 'y_value'); const yIdx = columns.findIndex(c => c.name === 'y_value');
const groups = [...new Set(data.map(r => r[groupIdx]).filter(g => g && g !== '' && g !== 'NONE'))]; // Filtere Gruppen - entferne "Unbekannt" wenn Checkbox aktiviert
let groups = [...new Set(data.map(r => r[groupIdx]).filter(g => g && g !== '' && g !== 'NONE'))];
if (hideUnknown) {
groups = groups.filter(g => g !== 'Unbekannt' && g !== 'Unknown' && g !== '');
}
const dates = [...new Set(data.map(r => r[xIdx]))].sort(); const dates = [...new Set(data.map(r => r[xIdx]))].sort();
if (groups.length === 0) { if (groups.length === 0) {
@@ -786,19 +806,56 @@
return; return;
} }
// Erweiterte Farben für mehr Exchanges (EIX, LS, XETRA, FRA, GETTEX, STU, QUOTRIX) // Berechne Rohdaten für jede Gruppe
const colors = ['#38bdf8', '#f43f5e', '#10b981', '#fbbf24', '#8b5cf6', '#f97316', '#ec4899', '#14b8a6', '#84cc16', '#a855f7']; const rawData = {};
const datasets = groups.map((group, idx) => ({ groups.forEach(group => {
label: group || 'Unknown', rawData[group] = dates.map(d => {
data: dates.map(d => {
const row = data.find(r => r[xIdx] === d && r[groupIdx] === group); const row = data.find(r => r[xIdx] === d && r[groupIdx] === group);
return row ? (row[yIdx] || 0) : 0; return row ? (row[yIdx] || 0) : 0;
}), });
borderColor: colors[idx % colors.length], });
backgroundColor: colors[idx % colors.length] + '33',
borderWidth: 2, // Für prozentuale Darstellung: Berechne Summen pro Tag
let processedData = rawData;
if (chartType === 'percent') {
const dailyTotals = dates.map((_, i) =>
groups.reduce((sum, g) => sum + rawData[g][i], 0)
);
processedData = {};
groups.forEach(group => {
processedData[group] = rawData[group].map((val, i) =>
dailyTotals[i] > 0 ? (val / dailyTotals[i]) * 100 : 0
);
});
}
// Sektoren-Farben (konsistent für bekannte Sektoren)
const sectorColors = {
'Technology': '#38bdf8',
'Healthcare': '#10b981',
'Financial Services': '#fbbf24',
'Consumer Cyclical': '#f43f5e',
'Industrials': '#8b5cf6',
'Communication Services': '#f97316',
'Consumer Defensive': '#ec4899',
'Energy': '#14b8a6',
'Basic Materials': '#84cc16',
'Real Estate': '#a855f7',
'Utilities': '#06b6d4'
};
const defaultColors = ['#38bdf8', '#f43f5e', '#10b981', '#fbbf24', '#8b5cf6', '#f97316', '#ec4899', '#14b8a6', '#84cc16', '#a855f7', '#06b6d4', '#eab308', '#ef4444', '#22c55e'];
const isStacked = chartType === 'stacked' || chartType === 'percent';
const datasets = groups.map((group, idx) => ({
label: group || 'Unknown',
data: processedData[group],
borderColor: sectorColors[group] || defaultColors[idx % defaultColors.length],
backgroundColor: (sectorColors[group] || defaultColors[idx % defaultColors.length]) + (isStacked ? 'aa' : '33'),
borderWidth: isStacked ? 1 : 2,
tension: 0.3, tension: 0.3,
fill: false fill: isStacked ? 'origin' : false,
pointRadius: isStacked ? 0 : 3
})); }));
// Nutze Config für Y-Achsen-Label (falls verfügbar) // Nutze Config für Y-Achsen-Label (falls verfügbar)
@@ -807,7 +864,6 @@
const config = ANALYTICS_CONFIG.yAxis[yAxis]; const config = ANALYTICS_CONFIG.yAxis[yAxis];
yAxisLabel = `${config.label}${config.unit ? ' (' + config.unit + ')' : ''}`; yAxisLabel = `${config.label}${config.unit ? ' (' + config.unit + ')' : ''}`;
} else { } else {
// Fallback
const labels = { const labels = {
'volume': 'Volumen (€)', 'volume': 'Volumen (€)',
'trade_count': 'Anzahl Trades', 'trade_count': 'Anzahl Trades',
@@ -816,10 +872,20 @@
yAxisLabel = labels[yAxis] || 'Wert'; yAxisLabel = labels[yAxis] || 'Wert';
} }
if (chartType === 'percent') {
yAxisLabel = 'Anteil (%)';
}
const chartTypeLabels = {
'line': '',
'stacked': ' - Stacked Area',
'percent': ' - Prozentuale Verteilung'
};
charts.customAnalytics = new Chart(ctx, { charts.customAnalytics = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
labels: dates.map(d => new Date(d).toLocaleDateString()), labels: dates.map(d => new Date(d).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' })),
datasets: datasets datasets: datasets
}, },
options: { options: {
@@ -830,35 +896,49 @@
y: { y: {
type: 'linear', type: 'linear',
display: true, display: true,
stacked: isStacked,
title: { display: true, text: yAxisLabel, color: '#94a3b8' }, title: { display: true, text: yAxisLabel, color: '#94a3b8' },
grid: { color: 'rgba(255,255,255,0.05)' }, grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#64748b' } ticks: {
color: '#64748b',
callback: chartType === 'percent' ? (v) => v + '%' : undefined
},
max: chartType === 'percent' ? 100 : undefined
}, },
x: { x: {
title: { display: true, text: 'Datum', color: '#94a3b8' }, title: { display: true, text: 'Datum', color: '#94a3b8' },
grid: { display: false }, grid: { display: false },
ticks: { color: '#64748b' } ticks: { color: '#64748b', maxRotation: 45 }
} }
}, },
plugins: { plugins: {
legend: { legend: {
display: true, display: true,
position: 'top', position: 'top',
labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 15 } labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 10 }
}, },
tooltip: { tooltip: {
backgroundColor: '#1e293b', backgroundColor: '#1e293b',
titleColor: '#38bdf8', titleColor: '#38bdf8',
bodyColor: '#e2e8f0', bodyColor: '#e2e8f0',
borderColor: 'rgba(255,255,255,0.1)', borderColor: 'rgba(255,255,255,0.1)',
borderWidth: 1 borderWidth: 1,
callbacks: {
label: (ctx) => {
const val = ctx.parsed.y;
if (chartType === 'percent') {
return `${ctx.dataset.label}: ${val.toFixed(1)}%`;
}
return `${ctx.dataset.label}: ${val.toLocaleString()}`;
}
}
} }
} }
} }
}); });
document.getElementById('customGraphTitle').innerText = document.getElementById('customGraphTitle').innerText =
`${yAxisLabel} nach ${groupBy === 'exchange' ? 'Exchange' : groupBy === 'sector' ? 'Sektor' : 'Datum'} (${dateFrom} bis ${dateTo})`; `${yAxisLabel} nach ${groupBy === 'exchange' ? 'Exchange' : groupBy === 'sector' ? 'Sektor' : 'Datum'}${chartTypeLabels[chartType]} (${dateFrom} bis ${dateTo})`;
} catch (err) { } catch (err) {
console.error('Error loading custom analytics:', err); console.error('Error loading custom analytics:', err);
} }
@@ -871,6 +951,8 @@
params.set('date_to', document.getElementById('customDateTo').value); params.set('date_to', document.getElementById('customDateTo').value);
params.set('y_axis', document.getElementById('customYAxis').value); params.set('y_axis', document.getElementById('customYAxis').value);
params.set('group_by', document.getElementById('customGroupBy').value); params.set('group_by', document.getElementById('customGroupBy').value);
params.set('chart_type', document.getElementById('customChartType')?.value || 'line');
params.set('hide_unknown', document.getElementById('hideUnknown')?.checked ? '1' : '0');
const exchanges = document.getElementById('customExchanges').value; const exchanges = document.getElementById('customExchanges').value;
if (exchanges) params.set('exchanges', exchanges); if (exchanges) params.set('exchanges', exchanges);
@@ -892,6 +974,8 @@
params.set('date_to', document.getElementById('customDateTo').value || ''); params.set('date_to', document.getElementById('customDateTo').value || '');
params.set('y_axis', document.getElementById('customYAxis').value || ''); params.set('y_axis', document.getElementById('customYAxis').value || '');
params.set('group_by', document.getElementById('customGroupBy').value || ''); params.set('group_by', document.getElementById('customGroupBy').value || '');
params.set('chart_type', document.getElementById('customChartType')?.value || 'line');
params.set('hide_unknown', document.getElementById('hideUnknown')?.checked ? '1' : '0');
const exchanges = document.getElementById('customExchanges').value; const exchanges = document.getElementById('customExchanges').value;
if (exchanges) params.set('exchanges', exchanges); if (exchanges) params.set('exchanges', exchanges);
else params.delete('exchanges'); else params.delete('exchanges');
@@ -915,12 +999,18 @@
const yAxis = params.get('y_axis'); const yAxis = params.get('y_axis');
const groupBy = params.get('group_by'); const groupBy = params.get('group_by');
const exchanges = params.get('exchanges'); const exchanges = params.get('exchanges');
const chartType = params.get('chart_type');
const hideUnknown = params.get('hide_unknown');
if (dateFrom) document.getElementById('customDateFrom').value = dateFrom; if (dateFrom) document.getElementById('customDateFrom').value = dateFrom;
if (dateTo) document.getElementById('customDateTo').value = dateTo; if (dateTo) document.getElementById('customDateTo').value = dateTo;
if (yAxis) document.getElementById('customYAxis').value = yAxis; if (yAxis) document.getElementById('customYAxis').value = yAxis;
if (groupBy) document.getElementById('customGroupBy').value = groupBy; if (groupBy) document.getElementById('customGroupBy').value = groupBy;
if (exchanges) document.getElementById('customExchanges').value = exchanges; if (exchanges) document.getElementById('customExchanges').value = exchanges;
if (chartType) document.getElementById('customChartType').value = chartType;
if (hideUnknown !== null) {
document.getElementById('hideUnknown').checked = hideUnknown === '1';
}
if (dateFrom && dateTo) { if (dateFrom && dateTo) {
setTimeout(() => updateCustomGraph(), 500); setTimeout(() => updateCustomGraph(), 500);