You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

586 lines
18 KiB

let combined_charts = [];
let player_charts = [];
const x_scale = {
display: true,
type: "linear",
stepSize: 1000 * 60 * 60 * 24,
ticks: {
callback: (value, index, ticks) => {
let tickdate = new Date(value);
return `${tickdate.getDate()}.${month_names[tickdate.getMonth()]}`;
}
}
}
const zoom_plugin = {
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true,
},
mode: "x",
onZoomComplete: function({chart}) {
const show_point_cutoff = 1000 * 60 * 60 * 24 * 3
const diff = parseInt(chart.scales.x.max) - parseInt(chart.scales.x.min);
const show_points = diff < show_point_cutoff;
const hide_points = diff >= show_point_cutoff;
let update_chart = false;
chart.data.datasets.forEach(dataset => {
const points_shown = dataset.pointRadius > 0
if (show_points && !points_shown) {
dataset.pointRadius = 3;
update_chart = true;
}
if (hide_points && points_shown) {
dataset.pointRadius = 0;
update_chart = true;
}
})
if (update_chart) chart.update();
}
},
pan: {
enabled: true,
mode: "x",
},
}
let hovered = false;
const trigger_legend_hover = function (legendItem, chart) {
if (chart.legend.legendItems[legendItem.datasetIndex].hidden || hovered) {
return;
}
hovered = true;
chart.data.datasets.forEach((dataset, i) => {
dataset.backgroundColor = (legendItem.datasetIndex === i || dataset.backgroundColor.length === 9) ? dataset.backgroundColor : dataset.backgroundColor + '3D';
dataset.borderColor = (legendItem.datasetIndex === i || dataset.borderColor.length === 9) ? dataset.borderColor : dataset.borderColor + '3D';
dataset.borderWidth = legendItem.datasetIndex === i ? 4 : 3;
dataset.pointRadius = dataset.pointRadius===0 ? 0 : 2;
});
chart.update("none");
}
const trigger_legend_leave = function (chart) {
chart.data.datasets.forEach((dataset, i) => {
dataset.backgroundColor = dataset.backgroundColor.length === 9 ? dataset.backgroundColor.slice(0, -2) : dataset.backgroundColor;
dataset.borderColor = dataset.borderColor.length === 9 ? dataset.borderColor.slice(0, -2) : dataset.borderColor;
dataset.borderWidth = 3;
});
chart.update("none");
hovered = false;
}
const trigger_legend_click = function (item) {
combined_charts.forEach(c_chart => {
if (c_chart.isDatasetVisible(item.datasetIndex)) {
trigger_legend_leave(c_chart)
c_chart.hide(item.datasetIndex);
c_chart.legend.legendItems[item.datasetIndex].hidden = true;
} else {
c_chart.show(item.datasetIndex);
c_chart.legend.legendItems[item.datasetIndex].hidden = false;
trigger_legend_hover(item, c_chart)
}
})
}
// break hover state when mouseout fails to trigger
window.onmousemove = (e) => {
if (hovered && (e.target.closest("ul") === null || !e.target.closest("ul").classList.contains("legend-list"))) {
console.log("breaking hover")
combined_charts.forEach(chart => {
trigger_legend_leave(chart);
})
}
}
const get_create_LegendList = (chart, id) => {
let created = false;
const legendContainer = document.getElementById(id);
let listContainer = legendContainer.querySelector("ul");
if (!listContainer) {
created = true;
listContainer = document.createElement("ul");
listContainer.classList.add('legend-list');
legendContainer.appendChild(listContainer);
}
return {list: listContainer, created: created};
}
const htmlLegendPlugin = {
id: 'htmlLegend',
afterUpdate(chart, args, options) {
function sort_legend(a,b) {
const a_values = chart.data.datasets[a.datasetIndex].data
const b_values = chart.data.datasets[b.datasetIndex].data
const a_value = a_values[a_values.length - 1].y;
const b_value = b_values[b_values.length - 1].y;
if (a_value > b_value) {
return -1;
} else if (a_value < b_value) {
return 1;
}
return 0;
}
//console.log("update")
const legendList = get_create_LegendList(chart, options.containerID);
const ul = legendList.list;
// Reuse the built-in legendItems generator
const items = chart.options.plugins.legend.labels.generateLabels(chart);
items.sort(sort_legend);
items.forEach((item, i) => {
if (legendList.created) {
const li = document.createElement('li');
li.classList.add("legend-element");
li.onmouseover = () => {trigger_legend_hover(item, chart)};
li.onmouseout = () => {trigger_legend_leave(chart)};
li.onclick = () => {trigger_legend_click(item)};
// Color box
const boxSpan = document.createElement('span');
boxSpan.style.background = item.fillStyle;
boxSpan.style.borderColor = item.strokeStyle;
boxSpan.style.borderWidth = item.lineWidth + 'px';
// Text
const textContainer = document.createElement('p');
textContainer.style.color = item.fontColor;
textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
const text = document.createTextNode(item.text);
textContainer.appendChild(text);
li.appendChild(boxSpan);
li.appendChild(textContainer);
ul.appendChild(li);
} else {
const list_element = ul.querySelector(`li.legend-element:nth-of-type(${i+1})`);
const boxSpan = list_element.querySelector("span");
const textContainer = list_element.querySelector("p");
boxSpan.style.background = item.fillStyle;
boxSpan.style.borderColor = item.strokeStyle;
boxSpan.style.borderWidth = item.lineWidth + 'px';
textContainer.style.color = item.fontColor;
textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
}
});
},
};
window.onload = create_charts;
async function create_charts() {
combined_charts.forEach(chart => chart.destroy());
combined_charts = [];
player_charts.forEach(chart => chart.destroy());
player_charts = [];
let player_rank_values = {};
let player_progress_values = {};
let player_entries = {}; // puuid zeigt auf player entries
let player_entries_byName = {}; // playername zeigt auf entries (damit im kombinierten Graphen die Tooltips korrekt gerendert werden können)
let player_accounts = {}; // puuid zeigt auf player account
const tooltip_plugin = {
callbacks: {
label: function (context) {
return format_rank(player_entries_byName[context.dataset.label][context.parsed.x / 1000]["tier"], player_entries_byName[context.dataset.label][context.parsed.x / 1000]["rank"], player_entries_byName[context.dataset.label][context.parsed.x / 1000]["points"])
},
title: items => {
let ms = items[0].raw.x;
let options = {
weekday: "short",
day: "numeric",
month: "short",
hour: "2-digit",
minute: "2-digit"
}
return new Date(ms).toLocaleDateString("de-DE", options);
},
beforeTitle: function (context) {
return context[0].dataset.label;
},
}
};
await fetch(`get.php`, {
method: "GET",
})
.then(res => res.json())
.then(result => {
player_entries = result["entries"];
player_accounts = result["accounts"];
let all_rank_yDatasets = []; // Datasets, also die einzelnen Linien im Graphen, beinhaltet player_rank_values
let all_progress_yDatasets = []; // Datasets, also die einzelnen Linien im Graphen, beinhaltet player_progress_values
let minval = -1;
let maxval = -1;
let color_counter = 0;
for (const puuid in player_entries) {
player_rank_values[puuid] = [];
player_progress_values[puuid] = [];
player_entries_byName[`${player_accounts[puuid]["gameName"]}#${player_accounts[puuid]["tagLine"]}`] = [];
let player_start_points = -1;
for (const timestamp in player_entries[puuid]) {
// Für alle Player Entries Punktzahl und Punktedifferenz berechnen
// Bei der Gelegenheit auch Entries in player_entries_byName eintragen
const current_points = rank_to_points(player_entries[puuid][timestamp]["tier"], player_entries[puuid][timestamp]["rank"], player_entries[puuid][timestamp]["points"]);
const adj_timestamp = parseInt(timestamp) * 1000;
player_rank_values[puuid].push({x: adj_timestamp, y: current_points});
player_entries_byName[`${player_accounts[puuid]["gameName"]}#${player_accounts[puuid]["tagLine"]}`][timestamp] = player_entries[puuid][timestamp];
if (player_start_points === -1) {
player_start_points = current_points;
player_progress_values[puuid].push({x: adj_timestamp, y: 0});
} else {
player_progress_values[puuid].push({
x: adj_timestamp,
y: current_points - player_start_points
});
}
if (minval === -1 || adj_timestamp < minval) {
minval = adj_timestamp;
}
if (maxval === -1 || adj_timestamp > maxval) {
maxval = adj_timestamp;
}
}
// Linie für den Spieler zu Datasets des Graphen hinzufügen
all_rank_yDatasets.push({
label: `${player_accounts[puuid]["gameName"]}#${player_accounts[puuid]["tagLine"]}`,
fill: false,
borderColor: getColor(color_counter),
backgroundColor: getColor(color_counter),
borderJoinStyle: "round",
data: player_rank_values[puuid],
spanGaps: true,
pointHitRadius: 16,
pointRadius: 0,
})
all_progress_yDatasets.push({
label: `${player_accounts[puuid]["gameName"]}#${player_accounts[puuid]["tagLine"]}`,
fill: false,
borderColor: getColor(color_counter),
backgroundColor: getColor(color_counter),
borderJoinStyle: "round",
data: player_progress_values[puuid],
spanGaps: true,
pointHitRadius: 16,
pointRadius: 0,
})
color_counter++;
}
// Graphen erstellen
combined_charts.push(new Chart(`progress-chart-combined`, {
type: "line",
data: {
datasets: all_rank_yDatasets,
},
options: {
plugins: {
tooltip: tooltip_plugin,
zoom: {...zoom_plugin},
legend: {
display: false,
},
htmlLegend: {
containerID: 'legend-rank-graph',
}
},
scales: {
x: x_scale,
y: {
display: true,
stepSize: 100,
ticks: {
callback: (value) => {
return points_to_rankstring(value, false);
}
}
},
},
responsive: true,
maintainAspectRatio: false,
},
plugins: [htmlLegendPlugin],
}));
combined_charts.push(new Chart(`progress-chart-combined-progress`, {
type: "line",
data: {
datasets: all_progress_yDatasets,
},
options: {
plugins: {
tooltip: tooltip_plugin,
zoom: {...zoom_plugin},
legend: {
display: false,
},
htmlLegend: {
containerID: 'legend-progress-graph',
}
},
scales: {
x: x_scale,
y: {
display: true,
},
},
responsive: true,
maintainAspectRatio: false,
},
plugins: [htmlLegendPlugin],
}));
minval -= 7200000;
maxval += 7200000;
combined_charts.forEach(combined_chart => {
combined_chart.options.plugins.zoom.limits = {
x: {min: minval, max: maxval},
}
})
})
.catch(e => console.error(e))
const charts = document.getElementsByClassName("progress-chart");
for (const chart of charts) {
let puuid = chart.id.split("-");
puuid.splice(0, 2);
puuid = puuid.join("-");
let values = [];
let minval = Object.keys(player_entries[puuid])[0] * 1000 - 7200000;
let maxval = Object.keys(player_entries[puuid])[Object.keys(player_entries[puuid]).length - 1] * 1000 + 7200000;
for (const entriesKey in player_entries[puuid]) {
let points = rank_to_points(player_entries[puuid][entriesKey]["tier"], player_entries[puuid][entriesKey]["rank"], player_entries[puuid][entriesKey]["points"]);
values.push({x: parseInt(entriesKey) * 1000, y: points});
}
const player_chart = new Chart(`progress-chart-${puuid}`, {
type: "line",
data: {
datasets: [{
label: `${player_accounts[puuid]["gameName"]}#${player_accounts[puuid]["tagLine"]}`,
fill: false,
borderColor: "rgba(150,150,175)",
backgroundColor: "rgba(150,150,175)",
borderJoinStyle: "round",
data: values,
pointHitRadius: 16,
pointRadius: 0,
}]
},
options: {
plugins: {
legend: {display: false},
tooltip: tooltip_plugin,
zoom: {...zoom_plugin},
},
scales: {
x: x_scale,
y: {
display: true,
stepSize: 100,
ticks: {
callback: (value) => {
return points_to_rankstring(value, false);
}
}
},
},
responsive: true,
maintainAspectRatio: false,
}
});
player_chart.options.plugins.zoom.limits = {
x: {min: minval, max: maxval},
}
player_charts.push(player_chart);
}
}
function rank_to_points(tier, rank, lp) {
const apex_tiers = (tier === "MASTER" || tier === "GRANDMASTER" || tier === "CHALLENGER");
const tiers = {
"DIAMOND": 2400,
"EMERALD": 2000,
"PLATINUM": 1600,
"GOLD": 1200,
"SILVER": 800,
"BRONZE": 400,
"IRON": 0,
};
const ranks = {
"I": 300,
"II": 200,
"III": 100,
"IV": 0,
};
if (apex_tiers) {
return 2800 + lp;
} else {
return tiers[tier] + ranks[rank] + lp;
}
}
function points_to_rankstring(points, include_LP = true) {
const apex_tiers = (points >= 2800);
let lp = (apex_tiers) ? points - 2800 : points % 100
let rank = (points - lp) % 400;
let tier = (points - lp - rank);
const tiers = {
2400: "Diamond",
2000: "Emerald",
1600: "Platinum",
1200: "Gold",
800: "Silver",
400: "Bronze",
0: "Iron",
};
const ranks = {
300: "I",
200: "II",
100: "III",
0: "IV",
};
let rank_string = (apex_tiers) ? "Master" : tiers[tier];
if (!apex_tiers) rank_string += ` ${ranks[rank]}`;
if (include_LP || apex_tiers) rank_string += ` ${lp} LP`;
return rank_string;
}
function format_rank(tier, rank, lp) {
tier = tier.charAt(0).toUpperCase() + tier.slice(1).toLowerCase();
const apex_tiers = (tier === "Master" || tier === "Grandmaster" || tier === "Challenger");
let rank_string = tier;
if (!apex_tiers) rank_string += ` ${rank}`;
rank_string += ` ${lp} LP`;
return rank_string;
}
function getColor(num) {
const colors = ["#33b1ff", "#d2a106", "#007d79", "#8a3ffc", "#ff7eb6", "#ba4e00", "#fa4d56", "#fff1f1", "#6fdc8c", "#4589ff", "#d12771", "#08bdba", "#bae6ff", "#d4bbff"];
return colors[num % 9];
}
async function toggle_combined_chart() {
const comb_charts = this.parentNode.parentNode.querySelectorAll(`.leaderboard>.graph-wrapper`);
const chart = this.parentNode.parentNode.querySelector(`.graph-wrapper.${this.id}`);
const buttons = document.querySelectorAll("button.open-general-graph");
const legend = document.querySelector(`#combined-chart-legends #legend-${this.id}`)
const all_legends = document.querySelectorAll(`#combined-chart-legends .chart-legend`);
all_legends.forEach(element => {element.classList.add("hidden")});
legend.classList.remove("hidden");
combined_charts.forEach(c_chart => {
c_chart.options.animation = false;
})
if (chart.classList.contains("closed")) {
comb_charts.forEach(element => element.classList.add("closed"));
buttons.forEach(element => element.classList.remove("dropdown-open"));
chart.classList.remove("closed");
this.classList.add("dropdown-open");
document.querySelector("#combined-chart-legends").classList.remove("hidden");
} else {
chart.classList.add("closed");
this.classList.remove("dropdown-open");
document.querySelector("#combined-chart-legends").classList.add("hidden");
}
await new Promise(r => setTimeout(r,400)); // auf höhen-animation warten
combined_charts.forEach(c_chart => {
c_chart.options.animation = true;
})
}
function toggle_leaderboard_chart(event) {
if (this.classList.contains("closed")) {
this.classList.remove("closed");
} else {
if (event.target.nodeName === "CANVAS") return;
this.classList.add("closed");
}
}
document.querySelectorAll("button.open-general-graph").forEach(element => element.addEventListener("click", toggle_combined_chart));
document.querySelectorAll("button.leaderboard-element").forEach(element => element.addEventListener("mousedown", toggle_leaderboard_chart));
async function update_leaderboard_entries() {
this.disabled = true;
this.classList.add("button-updating")
let eventSource = new EventSource("./update.php");
eventSource.addEventListener("progress", e => {
this.style.setProperty("--button-loading-bar-width", `${e.data * 100}%`);
})
eventSource.addEventListener("forbidden", e => {
eventSource.close();
let response = JSON.parse(e.data);
const currenttime = new Date();
const updatetime = new Date(response.last);
let timediff = new Date(currenttime - updatetime);
let resttime = new Date( 1000 * 60 * response.limit - (currenttime - updatetime));
window.alert(`Das letzte Update wurde vor ${format_time_minsec(timediff)} durchgeführt. Versuche es in ${format_time_minsec(resttime)} noch einmal`);
reset_button(this);
})
eventSource.onerror = e => {
eventSource.close();
window.alert(`Beim Update ist ein Fehler aufgetreten. Versuche es später noch einmal`)
reset_button(this);
}
eventSource.addEventListener("done", e => {
eventSource.close();
reset_button(this);
update_leaderboard_elements();
})
async function reset_button(button) {
await new Promise(r => setTimeout(r,400)); // warten bis ladebalken am ende angekommen ist
button.style.setProperty("--button-loading-bar-opacity", `0`);
await new Promise(r => setTimeout(r,400)); // warten bis ladebalken verblasst ist
button.style.setProperty("--button-loading-bar-width", `0`);
await new Promise(r => setTimeout(r,400)); // warten bis ladebalken wieder am anfang angekommen ist
button.style.setProperty("--button-loading-bar-opacity", `1`);
button.disabled = false;
button.classList.remove("button-updating");
}
}
document.querySelector("button.update-leaderboard").addEventListener("click", update_leaderboard_entries);
function format_time_minsec(date) {
let format, trenner = "", min = "", nullausgleich = "";
if (date.getMinutes() === 0) {
format = " Sekunden";
} else {
min = date.getMinutes();
format = " Minuten";
trenner = ":";
if (date.getSeconds() < 10) {
nullausgleich = "0";
}
}
return min + trenner + nullausgleich + date.getSeconds() + format;
}
const month_names = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
async function update_leaderboard_elements() {
fetch(`./leaderboard_list.php`, {
method: "GET",
})
.then(res => res.text())
.then(leaderboard_list => {
// replace updated Leaderboard
document.querySelector(".leaderboard-list").outerHTML = leaderboard_list;
// reapply EventListeners
document.querySelectorAll("button.leaderboard-element").forEach(element => element.addEventListener("mousedown", toggle_leaderboard_chart));
// recreate charts
create_charts();
})
.catch(e => console.error(e));
}