Single-page Chart.js 4 dashboard (CDN, zero build step): - 4 responsive pie charts (Arena Wien / Scharmüller: Sentiment + Plattform-Mix) - Percentage datalabels on slices > 4%, dark theme, 2x2 grid - Summary comparison table - README (MIT) and .gitignore
317 lines
9.6 KiB
HTML
317 lines
9.6 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>joni – Sentiment & Plattform-Übersicht</title>
|
||
|
||
<!-- Chart.js 4 + Datalabels-Plugin (CDN, kein Build-Step) -->
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0/dist/chartjs-plugin-datalabels.min.js"></script>
|
||
|
||
<style>
|
||
:root {
|
||
/* Dark theme – Farb-Variablen */
|
||
--bg: #0f1620;
|
||
--bg-card: #182230;
|
||
--bg-card-2: #1c2836;
|
||
--border: #2a3646;
|
||
--text: #e7edf4;
|
||
--text-muted: #9fb0c3;
|
||
--accent: #5b9dff;
|
||
|
||
/* Sentiment */
|
||
--pos: #4CAF50;
|
||
--neu: #90A4A0;
|
||
--neg: #EF5C53;
|
||
|
||
/* Status */
|
||
--ok: #4CAF50;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
body {
|
||
margin: 0;
|
||
padding: 32px 24px 56px;
|
||
font-family: "Segoe UI", Roboto, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
|
||
background:
|
||
radial-gradient(1200px 600px at 15% -10%, #1a2a3f 0%, transparent 60%),
|
||
var(--bg);
|
||
background-color: var(--bg);
|
||
color: var(--text);
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
.wrap { max-width: 1160px; margin: 0 auto; }
|
||
|
||
header.page {
|
||
margin-bottom: 28px;
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 18px;
|
||
}
|
||
header.page h1 {
|
||
margin: 0 0 4px;
|
||
font-size: 1.6rem;
|
||
letter-spacing: .2px;
|
||
}
|
||
header.page p {
|
||
margin: 0;
|
||
color: var(--text-muted);
|
||
font-size: .95rem;
|
||
}
|
||
header.page .brand { color: var(--accent); font-weight: 600; }
|
||
|
||
/* 2×2 Grid */
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20px;
|
||
}
|
||
@media (max-width: 760px) {
|
||
.grid { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.card {
|
||
background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-card-2) 100%);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 18px 18px 12px;
|
||
box-shadow: 0 8px 24px rgba(0,0,0,.25);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.card h2 {
|
||
margin: 0;
|
||
font-size: 1.08rem;
|
||
font-weight: 650;
|
||
}
|
||
.card .subtitle {
|
||
margin: 3px 0 10px;
|
||
color: var(--text-muted);
|
||
font-size: .84rem;
|
||
}
|
||
.chart-box {
|
||
position: relative;
|
||
flex: 1;
|
||
min-height: 300px;
|
||
}
|
||
|
||
/* Summary-Tabelle */
|
||
.summary {
|
||
margin-top: 34px;
|
||
background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-card-2) 100%);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 20px 22px;
|
||
box-shadow: 0 8px 24px rgba(0,0,0,.25);
|
||
}
|
||
.summary h2 { margin: 0 0 14px; font-size: 1.15rem; }
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: .93rem;
|
||
}
|
||
th, td {
|
||
text-align: left;
|
||
padding: 9px 14px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
thead th {
|
||
color: var(--text-muted);
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
font-size: .74rem;
|
||
letter-spacing: .6px;
|
||
}
|
||
tbody th {
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
}
|
||
tbody tr:last-child td, tbody tr:last-child th { border-bottom: none; }
|
||
td.num { font-variant-numeric: tabular-nums; }
|
||
tbody tr:hover { background: rgba(91,157,255,.06); }
|
||
|
||
footer.page {
|
||
margin-top: 30px;
|
||
color: var(--text-muted);
|
||
font-size: .8rem;
|
||
text-align: center;
|
||
}
|
||
footer.page code { color: var(--accent); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<header class="page">
|
||
<h1>Sentiment & Plattform-Übersicht</h1>
|
||
<p>Social-Media-Monitoring · Datenquelle <span class="brand">joni</span> · Arena Wien vs. Scharmüller</p>
|
||
</header>
|
||
|
||
<div class="grid">
|
||
<!-- Chart A: Arena Wien – Sentiment -->
|
||
<section class="card">
|
||
<h2>Arena Wien – Sentiment</h2>
|
||
<div class="subtitle">380 Beiträge · Status: Ruhig</div>
|
||
<div class="chart-box"><canvas id="chartA"></canvas></div>
|
||
</section>
|
||
|
||
<!-- Chart B: Scharmüller – Sentiment -->
|
||
<section class="card">
|
||
<h2>Scharmüller – Sentiment</h2>
|
||
<div class="subtitle">360 Beiträge · Status: Ruhig · 10 Krisen-Alerts</div>
|
||
<div class="chart-box"><canvas id="chartB"></canvas></div>
|
||
</section>
|
||
|
||
<!-- Chart C: Arena Wien – Plattform-Mix -->
|
||
<section class="card">
|
||
<h2>Arena Wien – Plattform-Mix</h2>
|
||
<div class="subtitle">590 Beiträge über 9 Plattformen</div>
|
||
<div class="chart-box"><canvas id="chartC"></canvas></div>
|
||
</section>
|
||
|
||
<!-- Chart D: Scharmüller – Plattform-Mix -->
|
||
<section class="card">
|
||
<h2>Scharmüller – Plattform-Mix</h2>
|
||
<div class="subtitle">706 Beiträge über 6 Plattformen</div>
|
||
<div class="chart-box"><canvas id="chartD"></canvas></div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- Summary-Tabelle -->
|
||
<div class="summary">
|
||
<h2>Kennzahlen im Vergleich</h2>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th></th>
|
||
<th>Arena Wien</th>
|
||
<th>Scharmüller</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><th>Status</th> <td>🟢 Ruhig</td> <td>🟢 Ruhig (inaktiv)</td></tr>
|
||
<tr><th>Beiträge</th> <td class="num">380</td> <td class="num">360</td></tr>
|
||
<tr><th>Positiv</th> <td class="num">208 (54.7%)</td> <td class="num">97 (26.9%)</td></tr>
|
||
<tr><th>Neutral</th> <td class="num">60 (15.8%)</td> <td class="num">72 (20.0%)</td></tr>
|
||
<tr><th>Negativ</th> <td class="num">112 (29.5%)</td> <td class="num">191 (53.1%)</td></tr>
|
||
<tr><th>Reichweite</th> <td class="num">8.228.828</td> <td class="num">1.120.193</td></tr>
|
||
<tr><th>30T-AVE</th> <td class="num">€58.295</td> <td class="num">€89.100</td></tr>
|
||
<tr><th>Krisen-Alerts</th> <td class="num">3</td> <td class="num">10</td></tr>
|
||
<tr><th>Top-Plattform</th> <td>Forum</td> <td>Forum</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<footer class="page">
|
||
Statisches Dashboard · <code>index.html</code> im Browser öffnen · Chart.js 4 via CDN · keine Build-Tools
|
||
</footer>
|
||
</div>
|
||
|
||
<script>
|
||
// Datalabels-Plugin global registrieren
|
||
Chart.register(ChartDataLabels);
|
||
|
||
// Gemeinsame Farben / Defaults
|
||
Chart.defaults.color = '#9fb0c3';
|
||
Chart.defaults.font.family = '"Segoe UI", Roboto, sans-serif';
|
||
Chart.defaults.borderColor = 'rgba(255,255,255,0.06)';
|
||
|
||
const SENTIMENT_COLORS = ['#4CAF50', '#90A4A0', '#EF5C53'];
|
||
|
||
// Professionelle Plattform-Palette (bis zu 9 Kategorien)
|
||
const PLATFORM_COLORS = [
|
||
'#5B9DFF', // Forum
|
||
'#00C2A8', // Web
|
||
'#3B5998', // Facebook
|
||
'#7C5CFF', // Bluesky
|
||
'#FF7043', // Reddit
|
||
'#B0BEC5', // X
|
||
'#E53935', // YouTube
|
||
'#26C6DA', // TikTok
|
||
'#EC4899' // Instagram
|
||
];
|
||
|
||
// Prozent-Label-Formatter: nur Slices > 4 % beschriften
|
||
function pctFormatter(value, ctx) {
|
||
const data = ctx.chart.data.datasets[0].data;
|
||
const total = data.reduce((a, b) => a + b, 0);
|
||
const pct = (value / total) * 100;
|
||
if (pct <= 4) return null; // kleine Slices ausblenden
|
||
return pct.toFixed(1) + ' %';
|
||
}
|
||
|
||
const commonPieOptions = (labelUnit) => ({
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
layout: { padding: 4 },
|
||
plugins: {
|
||
legend: {
|
||
position: 'bottom',
|
||
labels: { boxWidth: 12, boxHeight: 12, padding: 12, usePointStyle: true }
|
||
},
|
||
tooltip: {
|
||
callbacks: {
|
||
label(ctx) {
|
||
const data = ctx.dataset.data;
|
||
const total = data.reduce((a, b) => a + b, 0);
|
||
const pct = ((ctx.parsed / total) * 100).toFixed(1);
|
||
return ` ${ctx.label}: ${ctx.parsed} ${labelUnit} (${pct} %)`;
|
||
}
|
||
}
|
||
},
|
||
datalabels: {
|
||
formatter: pctFormatter,
|
||
color: '#0f1620',
|
||
font: { weight: 'bold', size: 12 },
|
||
textStrokeColor: 'rgba(255,255,255,0.85)',
|
||
textStrokeWidth: 3
|
||
}
|
||
}
|
||
});
|
||
|
||
function makePie(id, labels, data, colors, labelUnit) {
|
||
return new Chart(document.getElementById(id), {
|
||
type: 'pie',
|
||
data: {
|
||
labels,
|
||
datasets: [{
|
||
data,
|
||
backgroundColor: colors,
|
||
borderColor: '#182230',
|
||
borderWidth: 2,
|
||
hoverOffset: 6
|
||
}]
|
||
},
|
||
options: commonPieOptions(labelUnit)
|
||
});
|
||
}
|
||
|
||
// Chart A – Arena Wien Sentiment
|
||
makePie('chartA',
|
||
['Positiv', 'Neutral', 'Negativ'],
|
||
[208, 60, 112],
|
||
SENTIMENT_COLORS, 'Beiträge');
|
||
|
||
// Chart B – Scharmüller Sentiment
|
||
makePie('chartB',
|
||
['Positiv', 'Neutral', 'Negativ'],
|
||
[97, 72, 191],
|
||
SENTIMENT_COLORS, 'Beiträge');
|
||
|
||
// Chart C – Arena Wien Plattform-Mix
|
||
makePie('chartC',
|
||
['Forum', 'Web', 'Facebook', 'Bluesky', 'Reddit', 'X', 'YouTube', 'TikTok', 'Instagram'],
|
||
[341, 119, 38, 35, 19, 17, 11, 6, 4],
|
||
PLATFORM_COLORS, 'Beiträge');
|
||
|
||
// Chart D – Scharmüller Plattform-Mix
|
||
makePie('chartD',
|
||
['Forum', 'Web', 'YouTube', 'X', 'Facebook', 'Bluesky'],
|
||
[516, 68, 36, 37, 34, 15],
|
||
['#5B9DFF', '#00C2A8', '#E53935', '#B0BEC5', '#3B5998', '#7C5CFF'], 'Beiträge');
|
||
</script>
|
||
</body>
|
||
</html>
|