// @author rubenwardy // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later "use strict"; const labelColor = "#bbb"; const annotationColor = "#bbb"; const annotationLabelBgColor = "#444"; const gridColor = "#333"; const chartColors = [ "#7eb26d", "#eab839", "#6ed0e0", "#e24d42", "#1f78c1", "#ba43a9", ]; const annotationNov5 = { type: "line", borderColor: annotationColor, borderWidth: 1, click: function({chart, element}) { document.location = "https://blog.rubenwardy.com/2022/12/08/contentdb-youtuber-finds-minetest/"; }, label: { backgroundColor: annotationLabelBgColor, content: "YouTube Video 🡕", display: true, position: "end", color: "#00bc8c", rotation: "auto", backgroundShadowColor: "rgba(0, 0, 0, 0.4)", shadowBlur: 3, }, scaleID: "x", value: "2022-11-05", }; 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; } function sum(list) { return list.reduce((acc, x) => acc + x, 0); } const chartColorsBg = chartColors.map(color => `rgba(${hexToRgb(color.slice(1))}, 0.2)`); const SECONDS_IN_A_DAY = 1000 * 3600 * 24; 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 = `
`; 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]); } async function load_data() { const root = document.getElementById("stats-root"); const source = root.getAttribute("data-source"); const is_range = root.getAttribute("data-is-range") == "true"; const response = await fetch(source); const json = await response.json(); document.getElementById("loading").style.display = "none"; if (json == null) { document.getElementById("empty-view").style.display = "block"; return; } 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); return date.toISOString().split("T")[0]; }); 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) ])); } 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) ])); } else { 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) ])); } 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]); root.style.display = "block"; function getData(list) { return list.map((value, i) => ({ x: dates[i], y: value })); } const annotations = {}; if (new Date(json.start) < new Date("2022-11-05")) { annotations.annotationNov5 = annotationNov5; } 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) })), }; setup_chart(ctx, data, annotations); } { 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) }, ], }; setup_chart(ctx, data, annotations); } { 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) }, ], }; setup_chart(ctx, data, annotations); } { 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: { responsive: true, plugins: { legend: { labels: { color: labelColor, }, }, }, } }; new Chart(ctx, config); } } function setup_chart(ctx, data, annotations) { data.datasets = data.datasets.map((set, i) => { const colorIdx = (data.datasets.length - i - 1) % chartColors.length; return { fill: true, backgroundColor: chartColorsBg[colorIdx], borderColor: chartColors[colorIdx], pointBackgroundColor: chartColors[colorIdx], ...set, }; }); const config = { type: "line", data: data, options: { responsive: true, plugins: { tooltip: { mode: "index" }, legend: { reverse: true, labels: { color: labelColor, } }, annotation: { annotations, }, }, 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); } window.addEventListener("load", load_data);