Add joni Sentiment & Plattform-Übersicht dashboard

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
This commit is contained in:
Sokrates 2026-07-10 18:51:17 +02:00
parent 5797ceb733
commit 756859f6fe
3 changed files with 389 additions and 2 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules/
.DS_Store
Thumbs.db
*.log

View file

@ -1,3 +1,69 @@
# piedash
# 📊 piedash — Sentiment & Plattform-Übersicht
Von Sokrates erzeugtes Projekt (Coding-Delegation).
Ein schlankes, in sich geschlossenes **Single-Page-Dashboard**, das die
Social-Media-Monitoring-Kennzahlen von **joni** als vier Kreisdiagramme
visualisiert. Kein Server, kein Build-Step — einfach `index.html` im Browser
öffnen. ✨
## 🔍 Was zeigt das Dashboard?
Zwei beobachtete Entitäten im direkten Vergleich:
| # | Chart | Inhalt |
|---|-------|--------|
| A | **Arena Wien Sentiment** | Positiv / Neutral / Negativ (380 Beiträge) |
| B | **Scharmüller Sentiment** | Positiv / Neutral / Negativ (360 Beiträge, 10 Krisen-Alerts) |
| C | **Arena Wien Plattform-Mix** | Verteilung über 9 Plattformen (Forum, Web, Facebook, …) |
| D | **Scharmüller Plattform-Mix** | Verteilung über 6 Plattformen |
Darunter eine kompakte **Kennzahlen-Tabelle** (Status, Reichweite, 30-Tage-AVE,
Krisen-Alerts, Top-Plattform) für beide Entitäten nebeneinander.
## 🛰️ Datenquelle
Die Zahlen stammen aus **joni** — dem Social-Media-Monitoring-System, das
Beiträge über Foren, Web, Facebook, Bluesky, Reddit, X, YouTube, TikTok und
Instagram sammelt, klassifiziert und Sentiment- sowie Reichweiten-Kennzahlen
berechnet. Das Dashboard bildet einen statischen Snapshot dieser Daten ab.
## 🧱 Tech-Stack
- **HTML5** + **CSS3** (CSS Grid, CSS-Variablen, Dark-Theme)
- **[Chart.js 4](https://www.chartjs.org/)** via CDN
- **[chartjs-plugin-datalabels 2](https://chartjs-plugin-datalabels.netlify.app/)** für Prozent-Labels auf den Slices
- **Zero Build Step** — kein npm, kein Webpack, keine Toolchain
## ▶️ Ausführen
Es wird **kein Server benötigt**:
```bash
# Repository klonen …
git clone https://git.peterscharmueller.com/pch/piedash.git
cd piedash
# … und index.html direkt im Browser öffnen
xdg-open index.html # Linux
# open index.html # macOS
# start index.html # Windows
```
Oder die Datei einfach per Doppelklick öffnen. Chart.js und das Datalabels-Plugin
werden zur Laufzeit vom CDN geladen — dafür wird eine Internetverbindung benötigt.
## ✨ Features
- 🎨 Professionelles Dark-Theme mit CSS-Variablen
- 🧩 Sauberes 2×2-Grid-Layout, responsiv (bricht auf schmalen Screens auf eine Spalte um)
- 🏷️ Prozent-Labels nur auf Slices > 4 % — kleine Segmente bleiben lesbar
- 🖱️ Interaktive Tooltips mit Absolutwert und Prozentanteil
- 📱 `responsive: true` — passt sich der Container-Größe an
## 📄 Lizenz
Veröffentlicht unter der **MIT-Lizenz**. Frei nutzbar, veränderbar und
verteilbar.
```
MIT License — © 2026 Peter Scharmüller (pch)
```

317
index.html Normal file
View file

@ -0,0 +1,317 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>joni Sentiment &amp; 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 &amp; 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>