This commit is contained in:
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user