Neu: MyAnimeList Visualisierung
This commit is contained in:
BIN
anime_graph/icon.png
Normal file
BIN
anime_graph/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
586
anime_graph/index.html
Normal file
586
anime_graph/index.html
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>MyAnimeList Visualisierung | PonyWave Tools</title>
|
||||||
|
<link rel="icon" href="https://tools.ponywave.de/anime_graph/icon.png">
|
||||||
|
<meta name="description" content="Visualisiere deine MyAnimeList.net-Anime- und Manga-Listen als Grafiken und Statistiken.">
|
||||||
|
<meta property="og:title" content="MyAnimeList Visualisierung | PonyWave Tools">
|
||||||
|
<meta property="og:description" content="Visualisiere deine MyAnimeList.net-Anime- und Manga-Listen als Grafiken und Statistiken.">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://tools.ponywave.de/anime_graph/">
|
||||||
|
<meta property="og:image" content="https://tools.ponywave.de/anime_graph/icon.png">
|
||||||
|
<script defer src="https://stats.ponywave.de/script" data-website-id="9ef713d2-adb9-4906-9df5-708d8a8b9131" data-tag="anime_graph"></script>
|
||||||
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
color: #2e51a2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-section {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-box {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-btn-wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border: 2px solid #2e51a2;
|
||||||
|
color: #2e51a2;
|
||||||
|
background-color: white;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-btn-wrapper input[type=file] {
|
||||||
|
font-size: 100px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #2e51a2;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.charts-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>MyAnimeList Visualisierung</h1>
|
||||||
|
|
||||||
|
<div class="upload-section">
|
||||||
|
<h2>XML-Datei hochladen</h2>
|
||||||
|
<p>Lade deine exportierte MyAnimeList XML-Datei hoch, um detaillierte Grafiken zu deinen Anime- und Manga-Gewohnheiten zu sehen.</p>
|
||||||
|
|
||||||
|
<div class="upload-btn-wrapper">
|
||||||
|
<button class="btn">Datei auswählen</button>
|
||||||
|
<input type="file" id="xmlFileInput" accept=".xml" />
|
||||||
|
</div>
|
||||||
|
<span class="file-name" id="fileName"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-info" id="userInfoSection">
|
||||||
|
<h2>Benutzerinformationen</h2>
|
||||||
|
<div id="userStats"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="charts-container" id="chartsContainer">
|
||||||
|
<div class="chart-box">
|
||||||
|
<h2>Bewertungsverteilung</h2>
|
||||||
|
<canvas id="scoresChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-box">
|
||||||
|
<h2>Status-Verteilung</h2>
|
||||||
|
<canvas id="statusChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-box">
|
||||||
|
<h2>Typ-Verteilung</h2>
|
||||||
|
<canvas id="typeChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-box">
|
||||||
|
<h2>Chronologische Aktivität</h2>
|
||||||
|
<canvas id="timelineChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p><a href="https://tools.ponywave.de/">Zurück zur Startseite</a> | © <span id="current-year"></span> Akamaru | Made with <span class="heart">❤️</span> by Claude</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Setze das aktuelle Jahr im Footer
|
||||||
|
document.getElementById('current-year').textContent = new Date().getFullYear();
|
||||||
|
|
||||||
|
const fileInput = document.getElementById('xmlFileInput');
|
||||||
|
const fileName = document.getElementById('fileName');
|
||||||
|
const userInfoSection = document.getElementById('userInfoSection');
|
||||||
|
const chartsContainer = document.getElementById('chartsContainer');
|
||||||
|
const userStats = document.getElementById('userStats');
|
||||||
|
|
||||||
|
// Charts
|
||||||
|
let scoresChart, statusChart, typeChart, timelineChart;
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', function(e) {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
fileName.textContent = file.name;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
const xmlContent = e.target.result;
|
||||||
|
parseXML(xmlContent);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseXML(xmlContent) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(xmlContent, "text/xml");
|
||||||
|
|
||||||
|
// Bestimmen, ob es sich um eine Anime- oder Manga-Liste handelt
|
||||||
|
const isAnimeList = xmlDoc.querySelector('myanimelist myinfo user_export_type').textContent === "1";
|
||||||
|
const isMangaList = xmlDoc.querySelector('myanimelist myinfo user_export_type').textContent === "2";
|
||||||
|
|
||||||
|
// Benutzerdaten extrahieren
|
||||||
|
const userInfo = {
|
||||||
|
userId: xmlDoc.querySelector('myanimelist myinfo user_id').textContent,
|
||||||
|
userName: xmlDoc.querySelector('myanimelist myinfo user_name').textContent,
|
||||||
|
totalItems: isAnimeList
|
||||||
|
? xmlDoc.querySelector('myanimelist myinfo user_total_anime').textContent
|
||||||
|
: xmlDoc.querySelector('myanimelist myinfo user_total_manga').textContent,
|
||||||
|
watching: isAnimeList
|
||||||
|
? xmlDoc.querySelector('myanimelist myinfo user_total_watching').textContent
|
||||||
|
: xmlDoc.querySelector('myanimelist myinfo user_total_reading').textContent,
|
||||||
|
completed: xmlDoc.querySelector('myanimelist myinfo user_total_completed').textContent,
|
||||||
|
onHold: xmlDoc.querySelector('myanimelist myinfo user_total_onhold').textContent,
|
||||||
|
dropped: xmlDoc.querySelector('myanimelist myinfo user_total_dropped').textContent,
|
||||||
|
planTo: isAnimeList
|
||||||
|
? xmlDoc.querySelector('myanimelist myinfo user_total_plantowatch').textContent
|
||||||
|
: xmlDoc.querySelector('myanimelist myinfo user_total_plantoread').textContent
|
||||||
|
};
|
||||||
|
|
||||||
|
// Daten aus den Einträgen extrahieren
|
||||||
|
let entries = [];
|
||||||
|
|
||||||
|
if (isAnimeList) {
|
||||||
|
const animeEntries = xmlDoc.querySelectorAll('myanimelist anime');
|
||||||
|
animeEntries.forEach(entry => {
|
||||||
|
entries.push({
|
||||||
|
id: entry.querySelector('series_animedb_id').textContent,
|
||||||
|
title: entry.querySelector('series_title').textContent,
|
||||||
|
type: entry.querySelector('series_type').textContent,
|
||||||
|
episodes: parseInt(entry.querySelector('series_episodes').textContent) || 0,
|
||||||
|
watchedEpisodes: parseInt(entry.querySelector('my_watched_episodes').textContent) || 0,
|
||||||
|
score: parseInt(entry.querySelector('my_score').textContent) || 0,
|
||||||
|
status: entry.querySelector('my_status').textContent,
|
||||||
|
startDate: parseDate(entry.querySelector('my_start_date').textContent),
|
||||||
|
finishDate: parseDate(entry.querySelector('my_finish_date').textContent),
|
||||||
|
timesWatched: parseInt(entry.querySelector('my_times_watched').textContent) || 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (isMangaList) {
|
||||||
|
const mangaEntries = xmlDoc.querySelectorAll('myanimelist manga');
|
||||||
|
mangaEntries.forEach(entry => {
|
||||||
|
entries.push({
|
||||||
|
id: entry.querySelector('manga_mangadb_id').textContent,
|
||||||
|
title: entry.querySelector('manga_title').textContent,
|
||||||
|
volumes: parseInt(entry.querySelector('manga_volumes').textContent) || 0,
|
||||||
|
chapters: parseInt(entry.querySelector('manga_chapters').textContent) || 0,
|
||||||
|
readVolumes: parseInt(entry.querySelector('my_read_volumes').textContent) || 0,
|
||||||
|
readChapters: parseInt(entry.querySelector('my_read_chapters').textContent) || 0,
|
||||||
|
score: parseInt(entry.querySelector('my_score').textContent) || 0,
|
||||||
|
status: entry.querySelector('my_status').textContent,
|
||||||
|
startDate: parseDate(entry.querySelector('my_start_date').textContent),
|
||||||
|
finishDate: parseDate(entry.querySelector('my_finish_date').textContent),
|
||||||
|
timesRead: parseInt(entry.querySelector('my_times_read').textContent) || 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benutzerinfo anzeigen
|
||||||
|
displayUserInfo(userInfo, isAnimeList ? "Anime" : "Manga");
|
||||||
|
|
||||||
|
// Grafiken erstellen
|
||||||
|
createCharts(entries, isAnimeList);
|
||||||
|
|
||||||
|
// UI-Elemente anzeigen
|
||||||
|
userInfoSection.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDate(dateStr) {
|
||||||
|
if (!dateStr || dateStr === '0000-00-00') return null;
|
||||||
|
|
||||||
|
const [year, month, day] = dateStr.split('-').map(Number);
|
||||||
|
return new Date(year, month - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayUserInfo(userInfo, listType) {
|
||||||
|
userStats.innerHTML = `
|
||||||
|
<p><strong>Benutzername:</strong> ${userInfo.userName}</p>
|
||||||
|
<p><strong>Benutzer-ID:</strong> ${userInfo.userId}</p>
|
||||||
|
<p><strong>Gesamt ${listType}:</strong> ${userInfo.totalItems}</p>
|
||||||
|
<p><strong>${listType === 'Anime' ? 'Watching' : 'Reading'}:</strong> ${userInfo.watching}</p>
|
||||||
|
<p><strong>Completed:</strong> ${userInfo.completed}</p>
|
||||||
|
<p><strong>On-Hold:</strong> ${userInfo.onHold}</p>
|
||||||
|
<p><strong>Dropped:</strong> ${userInfo.dropped}</p>
|
||||||
|
<p><strong>${listType === 'Anime' ? 'Plan to Watch' : 'Plan to Read'}:</strong> ${userInfo.planTo}</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCharts(entries, isAnimeList) {
|
||||||
|
// Grafiken löschen, falls sie bereits existieren
|
||||||
|
if (scoresChart) scoresChart.destroy();
|
||||||
|
if (statusChart) statusChart.destroy();
|
||||||
|
if (typeChart) typeChart.destroy();
|
||||||
|
if (timelineChart) timelineChart.destroy();
|
||||||
|
|
||||||
|
// Bewertungsverteilung
|
||||||
|
createScoresChart(entries);
|
||||||
|
|
||||||
|
// Status-Verteilung
|
||||||
|
createStatusChart(entries);
|
||||||
|
|
||||||
|
// Typ-Verteilung (nur für Anime)
|
||||||
|
if (isAnimeList) {
|
||||||
|
createTypeChart(entries);
|
||||||
|
} else {
|
||||||
|
// Alternative für Manga anzeigen
|
||||||
|
createVolumesChart(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeitverlauf
|
||||||
|
createTimelineChart(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScoresChart(entries) {
|
||||||
|
const ctx = document.getElementById('scoresChart').getContext('2d');
|
||||||
|
|
||||||
|
// Bewertungen zählen (1-10)
|
||||||
|
const scoresCounts = Array(11).fill(0); // Index 0 wird nicht genutzt
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.score > 0 && entry.score <= 10) {
|
||||||
|
scoresCounts[entry.score]++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nicht verwendeten Index 0 entfernen
|
||||||
|
scoresCounts.shift();
|
||||||
|
|
||||||
|
scoresChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Anzahl',
|
||||||
|
data: scoresCounts,
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.6)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Anzahl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Bewertung'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStatusChart(entries) {
|
||||||
|
const ctx = document.getElementById('statusChart').getContext('2d');
|
||||||
|
|
||||||
|
// Status zählen
|
||||||
|
const statusMap = {};
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (!statusMap[entry.status]) {
|
||||||
|
statusMap[entry.status] = 0;
|
||||||
|
}
|
||||||
|
statusMap[entry.status]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusLabels = Object.keys(statusMap);
|
||||||
|
const statusData = Object.values(statusMap);
|
||||||
|
|
||||||
|
// Farben für die verschiedenen Status
|
||||||
|
const colors = [
|
||||||
|
'rgba(75, 192, 192, 0.6)', // Watching/Reading
|
||||||
|
'rgba(54, 162, 235, 0.6)', // Completed
|
||||||
|
'rgba(255, 206, 86, 0.6)', // On-Hold
|
||||||
|
'rgba(255, 99, 132, 0.6)', // Dropped
|
||||||
|
'rgba(153, 102, 255, 0.6)' // Plan to Watch/Read
|
||||||
|
];
|
||||||
|
|
||||||
|
statusChart = new Chart(ctx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: statusLabels,
|
||||||
|
datasets: [{
|
||||||
|
data: statusData,
|
||||||
|
backgroundColor: colors.slice(0, statusLabels.length),
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'right'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTypeChart(entries) {
|
||||||
|
const ctx = document.getElementById('typeChart').getContext('2d');
|
||||||
|
|
||||||
|
// Typen zählen (TV, Movie, OVA, etc.)
|
||||||
|
const typeMap = {};
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (!typeMap[entry.type]) {
|
||||||
|
typeMap[entry.type] = 0;
|
||||||
|
}
|
||||||
|
typeMap[entry.type]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeLabels = Object.keys(typeMap);
|
||||||
|
const typeData = Object.values(typeMap);
|
||||||
|
|
||||||
|
// Farben für die verschiedenen Typen
|
||||||
|
const colors = [
|
||||||
|
'rgba(75, 192, 192, 0.6)', // TV
|
||||||
|
'rgba(54, 162, 235, 0.6)', // Movie
|
||||||
|
'rgba(255, 206, 86, 0.6)', // OVA
|
||||||
|
'rgba(255, 99, 132, 0.6)', // Special
|
||||||
|
'rgba(153, 102, 255, 0.6)', // ONA
|
||||||
|
'rgba(255, 159, 64, 0.6)' // Music
|
||||||
|
];
|
||||||
|
|
||||||
|
typeChart = new Chart(ctx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: typeLabels,
|
||||||
|
datasets: [{
|
||||||
|
data: typeData,
|
||||||
|
backgroundColor: colors.slice(0, typeLabels.length),
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'right'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVolumesChart(entries) {
|
||||||
|
const ctx = document.getElementById('typeChart').getContext('2d');
|
||||||
|
document.querySelector('#typeChart').closest('.chart-box').querySelector('h2').textContent = 'Gelesene Bände/Kapitel';
|
||||||
|
|
||||||
|
// Berechne durchschnittliche Anzahl gelesener Bände/Kapitel pro Manga
|
||||||
|
const completedEntries = entries.filter(entry => entry.status === 'Completed');
|
||||||
|
const totalVolumes = completedEntries.reduce((sum, entry) => sum + entry.readVolumes, 0);
|
||||||
|
const totalChapters = completedEntries.reduce((sum, entry) => sum + entry.readChapters, 0);
|
||||||
|
|
||||||
|
// Einteilung in Kategorien für Bände
|
||||||
|
const volumeCategories = {
|
||||||
|
'1 Band': 0,
|
||||||
|
'2-5 Bände': 0,
|
||||||
|
'6-10 Bände': 0,
|
||||||
|
'11-20 Bände': 0,
|
||||||
|
'21+ Bände': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
completedEntries.forEach(entry => {
|
||||||
|
if (entry.readVolumes === 1) volumeCategories['1 Band']++;
|
||||||
|
else if (entry.readVolumes >= 2 && entry.readVolumes <= 5) volumeCategories['2-5 Bände']++;
|
||||||
|
else if (entry.readVolumes >= 6 && entry.readVolumes <= 10) volumeCategories['6-10 Bände']++;
|
||||||
|
else if (entry.readVolumes >= 11 && entry.readVolumes <= 20) volumeCategories['11-20 Bände']++;
|
||||||
|
else if (entry.readVolumes > 20) volumeCategories['21+ Bände']++;
|
||||||
|
});
|
||||||
|
|
||||||
|
const volumeLabels = Object.keys(volumeCategories);
|
||||||
|
const volumeData = Object.values(volumeCategories);
|
||||||
|
|
||||||
|
typeChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: volumeLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Anzahl Manga',
|
||||||
|
data: volumeData,
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.6)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Anzahl Manga'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTimelineChart(entries) {
|
||||||
|
const ctx = document.getElementById('timelineChart').getContext('2d');
|
||||||
|
|
||||||
|
// Nur Einträge mit gültigem Enddatum berücksichtigen
|
||||||
|
const entriesWithDate = entries.filter(entry => entry.finishDate);
|
||||||
|
|
||||||
|
// Nach Jahr und Monat gruppieren
|
||||||
|
const timeline = {};
|
||||||
|
|
||||||
|
entriesWithDate.forEach(entry => {
|
||||||
|
const year = entry.finishDate.getFullYear();
|
||||||
|
const month = entry.finishDate.getMonth();
|
||||||
|
|
||||||
|
const key = `${year}-${month + 1}`;
|
||||||
|
|
||||||
|
if (!timeline[key]) {
|
||||||
|
timeline[key] = 0;
|
||||||
|
}
|
||||||
|
timeline[key]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sortieren und in zwei Arrays für Labels und Daten aufteilen
|
||||||
|
const sortedKeys = Object.keys(timeline).sort();
|
||||||
|
const timelineLabels = [];
|
||||||
|
const timelineData = [];
|
||||||
|
|
||||||
|
const monthNames = ['Jan', 'Feb', 'März', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
|
||||||
|
|
||||||
|
sortedKeys.forEach(key => {
|
||||||
|
const [year, month] = key.split('-').map(Number);
|
||||||
|
timelineLabels.push(`${monthNames[month - 1]} ${year}`);
|
||||||
|
timelineData.push(timeline[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
timelineChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: timelineLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Beendete Titel',
|
||||||
|
data: timelineData,
|
||||||
|
fill: false,
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
tension: 0.1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
maxRotation: 90,
|
||||||
|
minRotation: 45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Anzahl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -181,6 +181,10 @@
|
|||||||
<div class="category-section">
|
<div class="category-section">
|
||||||
<h2 class="category-title">🛠️ Utilities</h2>
|
<h2 class="category-title">🛠️ Utilities</h2>
|
||||||
<div class="tools-grid">
|
<div class="tools-grid">
|
||||||
|
<a href="https://tools.ponywave.de/anime_graph" class="tool-bubble">
|
||||||
|
<h2 class="tool-title">MyAnimeList Visualisierung</h2>
|
||||||
|
<p class="tool-description">Visualisiere deine MyAnimeList.net-Anime- und Manga-Listen als Grafiken und Statistiken.</p>
|
||||||
|
</a>
|
||||||
<a href="https://tools.ponywave.de/sys_info" class="tool-bubble">
|
<a href="https://tools.ponywave.de/sys_info" class="tool-bubble">
|
||||||
<h2 class="tool-title">Systeminformationen</h2>
|
<h2 class="tool-title">Systeminformationen</h2>
|
||||||
<p class="tool-description">Zeigt Infos zu Browser, Gerät, Betriebssystem, User-Agent und IPs an.</p>
|
<p class="tool-description">Zeigt Infos zu Browser, Gerät, Betriebssystem, User-Agent und IPs an.</p>
|
||||||
|
@ -24,4 +24,5 @@ https://tools.ponywave.de/banana_run
|
|||||||
https://tools.ponywave.de/pokemon_quiz
|
https://tools.ponywave.de/pokemon_quiz
|
||||||
https://tools.ponywave.de/gronkh_games
|
https://tools.ponywave.de/gronkh_games
|
||||||
https://tools.ponywave.de/url_expander/
|
https://tools.ponywave.de/url_expander/
|
||||||
https://tools.ponywave.de/minesweeper/
|
https://tools.ponywave.de/minesweeper/
|
||||||
|
https://tools.ponywave.de/anime_graph/
|
Reference in New Issue
Block a user