2023-06-15 09:45:28 +02:00
|
|
|
// @author rubenwardy
|
|
|
|
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
|
|
|
|
|
2022-11-06 18:58:35 +01:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const labelColor = "#bbb";
|
2022-11-11 02:05:27 +01:00
|
|
|
const annotationColor = "#bbb";
|
|
|
|
const annotationLabelBgColor = "#444";
|
2022-11-06 18:58:35 +01:00
|
|
|
const gridColor = "#333";
|
|
|
|
|
|
|
|
|
|
|
|
const chartColors = [
|
|
|
|
"#7eb26d",
|
|
|
|
"#eab839",
|
|
|
|
"#6ed0e0",
|
|
|
|
"#e24d42",
|
|
|
|
"#1f78c1",
|
|
|
|
"#ba43a9",
|
|
|
|
];
|
|
|
|
|
|
|
|
|
2022-11-11 02:05:27 +01:00
|
|
|
const annotationNov5 = {
|
2022-11-11 02:18:31 +01:00
|
|
|
type: "line",
|
2022-11-11 02:05:27 +01:00
|
|
|
borderColor: annotationColor,
|
|
|
|
borderWidth: 1,
|
|
|
|
click: function({chart, element}) {
|
2023-10-08 15:56:12 +02:00
|
|
|
document.location = "https://blog.rubenwardy.com/2022/12/08/contentdb-youtuber-finds-minetest/";
|
2022-11-11 02:05:27 +01:00
|
|
|
},
|
|
|
|
label: {
|
|
|
|
backgroundColor: annotationLabelBgColor,
|
2023-10-08 15:56:12 +02:00
|
|
|
content: "YouTube Video 🡕",
|
2022-11-11 02:05:27 +01:00
|
|
|
display: true,
|
|
|
|
position: "end",
|
|
|
|
color: "#00bc8c",
|
2022-11-11 02:18:31 +01:00
|
|
|
rotation: "auto",
|
|
|
|
backgroundShadowColor: "rgba(0, 0, 0, 0.4)",
|
|
|
|
shadowBlur: 3,
|
2022-11-11 02:05:27 +01:00
|
|
|
},
|
2022-11-11 02:18:31 +01:00
|
|
|
scaleID: "x",
|
2022-11-11 02:05:27 +01:00
|
|
|
value: "2022-11-05",
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2022-11-06 18:58:35 +01:00
|
|
|
function hexToRgb(hex) {
|
|
|
|
var bigint = parseInt(hex, 16);
|
|
|
|
var r = (bigint >> 16) & 255;
|
|
|
|
var g = (bigint >> 8) & 255;
|
|
|
|
var b = bigint & 255;
|
|
|
|
|
|
|
|
return r + "," + g + "," + b;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-08 19:47:03 +01:00
|
|
|
function sum(list) {
|
|
|
|
return list.reduce((acc, x) => acc + x, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-06 18:58:35 +01:00
|
|
|
const chartColorsBg = chartColors.map(color => `rgba(${hexToRgb(color.slice(1))}, 0.2)`);
|
|
|
|
|
2022-11-09 19:46:11 +01:00
|
|
|
const SECONDS_IN_A_DAY = 1000 * 3600 * 24;
|
2022-11-06 18:58:35 +01:00
|
|
|
|
2023-06-15 09:45:28 +02:00
|
|
|
|
|
|
|
function format_message(id, values) {
|
|
|
|
let format = document.getElementById(id).textContent;
|
|
|
|
values.forEach((value, i) => {
|
|
|
|
format = format.replace("$" + (i + 1), value);
|
|
|
|
})
|
|
|
|
return format;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function add_summary_card(title, icon, value, extra) {
|
|
|
|
const ele = document.createElement("div");
|
|
|
|
ele.innerHTML = `
|
|
|
|
<div class="col-md-4">
|
|
|
|
<div class="card h-100">
|
|
|
|
<div class="card-body align-items-center text-center">
|
|
|
|
<div class="mt-0 mb-3">
|
2023-08-22 20:58:43 +02:00
|
|
|
<i class="fas fa-${icon} me-1"></i>
|
2023-06-15 09:45:28 +02:00
|
|
|
<span class="summary-title"></span>
|
|
|
|
</div>
|
|
|
|
<div class="my-0 h4">
|
|
|
|
<span class="summary-value"></span>
|
2023-08-22 20:58:43 +02:00
|
|
|
<small class="text-muted ms-2 summary-extra"></small>
|
2023-06-15 09:45:28 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
ele.querySelector(".summary-title").textContent = title;
|
|
|
|
ele.querySelector(".summary-value").textContent = value;
|
|
|
|
ele.querySelector(".summary-extra").textContent = extra;
|
|
|
|
|
|
|
|
document.getElementById("stats-summaries").appendChild(ele.children[0]);
|
|
|
|
}
|
|
|
|
|
2022-11-06 18:58:35 +01:00
|
|
|
async function load_data() {
|
|
|
|
const root = document.getElementById("stats-root");
|
|
|
|
const source = root.getAttribute("data-source");
|
2023-06-15 09:45:28 +02:00
|
|
|
const is_range = root.getAttribute("data-is-range") == "true";
|
2022-11-06 18:58:35 +01:00
|
|
|
const response = await fetch(source);
|
|
|
|
const json = await response.json();
|
|
|
|
|
2022-11-06 20:47:39 +01:00
|
|
|
document.getElementById("loading").style.display = "none";
|
|
|
|
|
|
|
|
if (json == null) {
|
|
|
|
document.getElementById("empty-view").style.display = "block";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-11-09 19:46:11 +01:00
|
|
|
const startDate = new Date(json.start);
|
|
|
|
const endDate = new Date(json.end);
|
|
|
|
const numberOfDays = Math.round((endDate.valueOf() - startDate.valueOf()) / SECONDS_IN_A_DAY) + 1;
|
|
|
|
const dates = [...Array(numberOfDays)].map((_, i) => {
|
|
|
|
const date = new Date(startDate.valueOf() + i*SECONDS_IN_A_DAY);
|
2022-11-11 02:18:31 +01:00
|
|
|
return date.toISOString().split("T")[0];
|
2022-11-09 19:46:11 +01:00
|
|
|
});
|
|
|
|
|
2023-06-15 09:45:28 +02:00
|
|
|
if (!is_range) {
|
|
|
|
if (json.platform_minetest.length >= 30) {
|
|
|
|
const total30 = sum(json.platform_minetest.slice(-30)) + sum(json.platform_other.slice(-30));
|
|
|
|
add_summary_card(format_message("downloads-30days", []), "download", total30,
|
|
|
|
format_message("downloads-per-day", [ (total30 / 30).toFixed(0) ]));
|
|
|
|
}
|
2022-11-08 19:47:03 +01:00
|
|
|
|
2023-06-15 09:45:28 +02:00
|
|
|
const total7 = sum(json.platform_minetest.slice(-7)) + sum(json.platform_other.slice(-7));
|
|
|
|
add_summary_card(format_message("downloads-7days", []), "download", total7,
|
|
|
|
format_message("downloads-per-day", [ (total7 / 7).toFixed(0) ]));
|
2022-11-08 19:47:03 +01:00
|
|
|
} else {
|
2023-06-15 09:45:28 +02:00
|
|
|
const total = sum(json.platform_minetest) + sum(json.platform_other);
|
|
|
|
const days = Math.max(json.platform_minetest.length, json.platform_other.length);
|
|
|
|
const title = format_message("downloads-range", [ json.start, json.end ]);
|
|
|
|
add_summary_card(title, "download", total,
|
|
|
|
format_message("downloads-per-day", [ (total / days).toFixed(0) ]));
|
2022-11-08 19:47:03 +01:00
|
|
|
}
|
|
|
|
|
2022-11-06 18:58:35 +01:00
|
|
|
const jsonOther = json.platform_minetest.map((value, i) =>
|
|
|
|
value + json.platform_other[i]
|
|
|
|
- json.reason_new[i] - json.reason_dependency[i]
|
|
|
|
- json.reason_update[i]);
|
|
|
|
|
2022-11-06 19:51:33 +01:00
|
|
|
root.style.display = "block";
|
2022-11-06 18:58:35 +01:00
|
|
|
|
|
|
|
function getData(list) {
|
2022-11-09 19:46:11 +01:00
|
|
|
return list.map((value, i) => ({ x: dates[i], y: value }));
|
2022-11-06 18:58:35 +01:00
|
|
|
}
|
|
|
|
|
2023-06-14 23:41:02 +02:00
|
|
|
const annotations = {};
|
|
|
|
if (new Date(json.start) < new Date("2022-11-05")) {
|
|
|
|
annotations.annotationNov5 = annotationNov5;
|
|
|
|
}
|
|
|
|
|
2022-11-09 20:45:25 +01:00
|
|
|
if (json.package_downloads) {
|
|
|
|
const packageRecentDownloads = Object.fromEntries(Object.entries(json.package_downloads)
|
|
|
|
.map(([label, values]) => [label, sum(values.slice(-30))]));
|
|
|
|
|
|
|
|
document.getElementById("downloads-by-package").classList.remove("d-none");
|
|
|
|
const ctx = document.getElementById("chart-packages").getContext("2d");
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
datasets: Object.entries(json.package_downloads)
|
|
|
|
.sort((a, b) => packageRecentDownloads[a[0]] - packageRecentDownloads[b[0]])
|
|
|
|
.map(([label, values]) => ({ label, data: getData(values) })),
|
|
|
|
};
|
2023-06-14 23:41:02 +02:00
|
|
|
setup_chart(ctx, data, annotations);
|
2022-11-09 20:45:25 +01:00
|
|
|
}
|
|
|
|
|
2022-11-06 18:58:35 +01:00
|
|
|
{
|
|
|
|
const ctx = document.getElementById("chart-platform").getContext("2d");
|
|
|
|
const data = {
|
|
|
|
datasets: [
|
|
|
|
{ label: "Web / other", data: getData(json.platform_other) },
|
|
|
|
{ label: "Minetest", data: getData(json.platform_minetest) },
|
|
|
|
],
|
|
|
|
};
|
2023-06-14 23:41:02 +02:00
|
|
|
setup_chart(ctx, data, annotations);
|
2022-11-06 18:58:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const ctx = document.getElementById("chart-reason").getContext("2d");
|
|
|
|
const data = {
|
|
|
|
datasets: [
|
|
|
|
{ label: "Other / Unknown", data: getData(jsonOther) },
|
|
|
|
{ label: "Update", data: getData(json.reason_update) },
|
|
|
|
{ label: "Dependency", data: getData(json.reason_dependency) },
|
|
|
|
{ label: "New Install", data: getData(json.reason_new) },
|
|
|
|
],
|
|
|
|
};
|
2023-06-14 23:41:02 +02:00
|
|
|
setup_chart(ctx, data, annotations);
|
2022-11-06 18:58:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const ctx = document.getElementById("chart-reason-pie").getContext("2d");
|
|
|
|
const data = {
|
|
|
|
labels: [
|
|
|
|
"New Install",
|
|
|
|
"Dependency",
|
|
|
|
"Update",
|
|
|
|
"Other / Unknown",
|
|
|
|
],
|
|
|
|
datasets: [{
|
|
|
|
label: "My First Dataset",
|
|
|
|
data: [
|
|
|
|
sum(json.reason_new),
|
|
|
|
sum(json.reason_dependency),
|
|
|
|
sum(json.reason_update),
|
|
|
|
sum(jsonOther),
|
|
|
|
],
|
|
|
|
backgroundColor: chartColors,
|
|
|
|
hoverOffset: 4,
|
|
|
|
borderWidth: 0,
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
const config = {
|
|
|
|
type: "doughnut",
|
|
|
|
data: data,
|
|
|
|
options: {
|
2022-11-07 01:53:30 +01:00
|
|
|
responsive: true,
|
|
|
|
plugins: {
|
|
|
|
legend: {
|
|
|
|
labels: {
|
|
|
|
color: labelColor,
|
|
|
|
},
|
2022-11-06 18:58:35 +01:00
|
|
|
},
|
|
|
|
},
|
2022-11-07 01:53:30 +01:00
|
|
|
}
|
2022-11-06 18:58:35 +01:00
|
|
|
};
|
|
|
|
new Chart(ctx, config);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-14 23:41:02 +02:00
|
|
|
function setup_chart(ctx, data, annotations) {
|
2022-11-06 18:58:35 +01:00
|
|
|
data.datasets = data.datasets.map((set, i) => {
|
2023-01-02 18:34:34 +01:00
|
|
|
const colorIdx = (data.datasets.length - i - 1) % chartColors.length;
|
2022-11-06 18:58:35 +01:00
|
|
|
return {
|
|
|
|
fill: true,
|
2023-01-02 18:34:34 +01:00
|
|
|
backgroundColor: chartColorsBg[colorIdx],
|
|
|
|
borderColor: chartColors[colorIdx],
|
|
|
|
pointBackgroundColor: chartColors[colorIdx],
|
2022-11-06 18:58:35 +01:00
|
|
|
...set,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
type: "line",
|
|
|
|
data: data,
|
|
|
|
options: {
|
|
|
|
responsive: true,
|
|
|
|
plugins: {
|
|
|
|
tooltip: {
|
|
|
|
mode: "index"
|
|
|
|
},
|
|
|
|
|
|
|
|
legend: {
|
|
|
|
reverse: true,
|
|
|
|
labels: {
|
|
|
|
color: labelColor,
|
|
|
|
}
|
2022-11-11 02:05:27 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
annotation: {
|
2023-06-14 23:41:02 +02:00
|
|
|
annotations,
|
2022-11-11 02:05:27 +01:00
|
|
|
},
|
2022-11-06 18:58:35 +01:00
|
|
|
},
|
|
|
|
interaction: {
|
|
|
|
mode: "nearest",
|
|
|
|
axis: "x",
|
|
|
|
intersect: false
|
|
|
|
},
|
|
|
|
scales: {
|
|
|
|
x: {
|
|
|
|
type: "time",
|
|
|
|
time: {
|
|
|
|
// min: start,
|
|
|
|
// max: end,
|
|
|
|
unit: "day",
|
|
|
|
},
|
|
|
|
ticks: {
|
|
|
|
color: labelColor,
|
|
|
|
},
|
|
|
|
grid: {
|
|
|
|
color: gridColor,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
y: {
|
|
|
|
stacked: true,
|
|
|
|
min: 0,
|
|
|
|
precision: 0,
|
|
|
|
ticks: {
|
|
|
|
color: labelColor,
|
|
|
|
},
|
|
|
|
grid: {
|
|
|
|
color: gridColor,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
new Chart(ctx, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-08-26 13:34:55 +02:00
|
|
|
window.addEventListener("load", load_data);
|