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:
parent
5797ceb733
commit
756859f6fe
3 changed files with 389 additions and 2 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.log
|
||||||
70
README.md
70
README.md
|
|
@ -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
317
index.html
Normal 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 & 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>
|
||||||
Loading…
Reference in a new issue