move stats related javascript code to its own file
This commit is contained in:
parent
458c2a4820
commit
4c316b5cba
@ -21,4 +21,5 @@
|
|||||||
{{> footer}}
|
{{> footer}}
|
||||||
{{#extend "scripts"}}
|
{{#extend "scripts"}}
|
||||||
<script src="/libs/chart.min.js"></script>
|
<script src="/libs/chart.min.js"></script>
|
||||||
|
<script src="/scripts/stats.js"></script>
|
||||||
{{/extend}}
|
{{/extend}}
|
@ -269,452 +269,6 @@ function canSendVerificationEmail() {
|
|||||||
checkbox.classList.add('hidden');
|
checkbox.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
// create views chart label
|
|
||||||
function createViewsChartLabel(ctx) {
|
|
||||||
const period = ctx.dataset.period;
|
|
||||||
let labels = [];
|
|
||||||
|
|
||||||
if (period === "day") {
|
|
||||||
const nowHour = new Date().getHours();
|
|
||||||
for (let i = 23; i >= 0; --i) {
|
|
||||||
let h = nowHour - i;
|
|
||||||
if (h < 0) h = 24 + h;
|
|
||||||
labels.push(`${Math.floor(h)}:00`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (period === "week") {
|
|
||||||
const nowDay = new Date().getDate();
|
|
||||||
for (let i = 6; i >= 0; --i) {
|
|
||||||
const date = new Date(new Date().setDate(nowDay - i));
|
|
||||||
labels.push(`${date.getDate()} ${date.toLocaleString("default",{month:"short"})}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (period === "month") {
|
|
||||||
const nowDay = new Date().getDate();
|
|
||||||
for (let i = 29; i >= 0; --i) {
|
|
||||||
const date = new Date(new Date().setDate(nowDay - i));
|
|
||||||
labels.push(`${date.getDate()} ${date.toLocaleString("default",{month:"short"})}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (period === "year") {
|
|
||||||
const nowMonth = new Date().getMonth();
|
|
||||||
for (let i = 11; i >= 0; --i) {
|
|
||||||
const date = new Date(new Date().setMonth(nowMonth - i));
|
|
||||||
labels.push(`${date.toLocaleString("default",{month:"short"})} ${date.toLocaleString("default",{year:"numeric"})}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create views chart
|
|
||||||
function createViewsChart() {
|
|
||||||
const canvases = document.querySelectorAll("canvas.visits");
|
|
||||||
if (!canvases || !canvases.length) return;
|
|
||||||
|
|
||||||
canvases.forEach(ctx => {
|
|
||||||
const data = JSON.parse(ctx.dataset.data);
|
|
||||||
const period = ctx.dataset.period;
|
|
||||||
|
|
||||||
const labels = createViewsChartLabel(ctx);
|
|
||||||
const maxTicksLimitX = period === "year" ? 6 : period === "month" ? 15 : 12;
|
|
||||||
|
|
||||||
const gradient = ctx.getContext("2d").createLinearGradient(0, 0, 0, 300);
|
|
||||||
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
|
||||||
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
|
||||||
type: "line",
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
label: "Views",
|
|
||||||
data,
|
|
||||||
tension: 0.3,
|
|
||||||
|
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
pointRadius: 0,
|
|
||||||
pointHoverRadius: 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
target: "start",
|
|
||||||
},
|
|
||||||
backgroundColor: gradient,
|
|
||||||
borderColor: "rgb(179, 157, 219)",
|
|
||||||
borderWidth: 1,
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
||||||
titleColor: "#333",
|
|
||||||
titleFont: { weight: "normal", size: 15 },
|
|
||||||
bodyFont: { weight: "normal", size: 16 },
|
|
||||||
bodyColor: "rgb(179, 157, 219)",
|
|
||||||
padding: 12,
|
|
||||||
cornerRadius: 2,
|
|
||||||
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
displayColors: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responsive: true,
|
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
usePointStyle: true,
|
|
||||||
mode: "index",
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
grace: "10%",
|
|
||||||
beginAtZero: true,
|
|
||||||
ticks: {
|
|
||||||
maxTicksLimit: 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x: {
|
|
||||||
ticks: {
|
|
||||||
maxTicksLimit: maxTicksLimitX,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset the display: block style that chart.js applies automatically
|
|
||||||
ctx.style.display = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// beautify browser lables
|
|
||||||
function beautifyBrowserName(name) {
|
|
||||||
if (name === "firefox") return "Firefox";
|
|
||||||
if (name === "chrome") return "Chrome";
|
|
||||||
if (name === "edge") return "Edge";
|
|
||||||
if (name === "opera") return "Opera";
|
|
||||||
if (name === "safari") return "Safari";
|
|
||||||
if (name === "other") return "Other";
|
|
||||||
if (name === "ie") return "IE";
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create browsers chart
|
|
||||||
function createBrowsersChart() {
|
|
||||||
const canvases = document.querySelectorAll("canvas.browsers");
|
|
||||||
if (!canvases || !canvases.length) return;
|
|
||||||
|
|
||||||
canvases.forEach(ctx => {
|
|
||||||
const data = JSON.parse(ctx.dataset.data);
|
|
||||||
const period = ctx.dataset.period;
|
|
||||||
|
|
||||||
const gradient = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
||||||
const gradientHover = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
||||||
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
|
||||||
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
|
||||||
gradientHover.addColorStop(0, "rgba(179, 157, 219, 0.9)");
|
|
||||||
gradientHover.addColorStop(1, "rgba(179, 157, 219, 0.4)");
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
|
||||||
type: "bar",
|
|
||||||
data: {
|
|
||||||
labels: data.map(d => beautifyBrowserName(d.name)),
|
|
||||||
datasets: [{
|
|
||||||
label: "Views",
|
|
||||||
data: data.map(d => d.value),
|
|
||||||
backgroundColor: gradient,
|
|
||||||
borderColor: "rgba(179, 157, 219, 1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
hoverBackgroundColor: gradientHover,
|
|
||||||
hoverBorderWidth: 2
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
indexAxis: "y",
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
||||||
titleColor: "#333",
|
|
||||||
titleFont: { weight: "normal", size: 15 },
|
|
||||||
bodyFont: { weight: "normal", size: 16 },
|
|
||||||
bodyColor: "rgb(179, 157, 219)",
|
|
||||||
padding: 12,
|
|
||||||
cornerRadius: 2,
|
|
||||||
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
displayColors: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responsive: true,
|
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
mode: "index",
|
|
||||||
axis: "y"
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
grace: "5%",
|
|
||||||
beginAtZero: true,
|
|
||||||
ticks: {
|
|
||||||
maxTicksLimit: 6,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset the display: block style that chart.js applies automatically
|
|
||||||
ctx.style.display = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// create referrers chart
|
|
||||||
function createReferrersChart() {
|
|
||||||
const canvases = document.querySelectorAll("canvas.referrers");
|
|
||||||
if (!canvases || !canvases.length) return;
|
|
||||||
|
|
||||||
canvases.forEach(ctx => {
|
|
||||||
const data = JSON.parse(ctx.dataset.data);
|
|
||||||
const period = ctx.dataset.period;
|
|
||||||
let max = Array.from(data).sort((a, b) => a.value > b.value ? -1 : 1)[0];
|
|
||||||
|
|
||||||
let tooltipEnabled = true;
|
|
||||||
let hoverBackgroundColor = "rgba(179, 157, 219, 1)";
|
|
||||||
let hoverBorderWidth = 2;
|
|
||||||
let borderColor = "rgba(179, 157, 219, 1)";
|
|
||||||
if (data.length === 0) {
|
|
||||||
data.push({ name: "No views.", value: 1 });
|
|
||||||
max = { value: 1000 };
|
|
||||||
tooltipEnabled = false;
|
|
||||||
hoverBackgroundColor = "rgba(179, 157, 219, 0.1)";
|
|
||||||
hoverBorderWidth = 1;
|
|
||||||
borderColor = "rgba(179, 157, 219, 0.2)";
|
|
||||||
}
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
|
||||||
type: "doughnut",
|
|
||||||
data: {
|
|
||||||
labels: data.map(d => d.name.replace(/\[dot\]/g, ".")),
|
|
||||||
datasets: [{
|
|
||||||
label: "Views",
|
|
||||||
data: data.map(d => d.value),
|
|
||||||
backgroundColor: data.map(d => `rgba(179, 157, 219, ${Math.max((d.value / max.value) - 0.2, 0.1).toFixed(2)})`),
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor,
|
|
||||||
hoverBackgroundColor,
|
|
||||||
hoverBorderWidth,
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
position: "left",
|
|
||||||
labels: {
|
|
||||||
boxWidth: 25,
|
|
||||||
font: { size: 11 }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: tooltipEnabled,
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
||||||
titleColor: "#333",
|
|
||||||
titleFont: { weight: "normal", size: 15 },
|
|
||||||
bodyFont: { weight: "normal", size: 16 },
|
|
||||||
bodyColor: "rgb(179, 157, 219)",
|
|
||||||
padding: 12,
|
|
||||||
cornerRadius: 2,
|
|
||||||
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
displayColors: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responsive: false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset the display: block style that chart.js applies automatically
|
|
||||||
ctx.style.display = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// beautify browser lables
|
|
||||||
function beautifyOsName(name) {
|
|
||||||
if (name === "android") return "Android";
|
|
||||||
if (name === "ios") return "iOS";
|
|
||||||
if (name === "linux") return "Linux";
|
|
||||||
if (name === "macos") return "macOS";
|
|
||||||
if (name === "windows") return "Windows";
|
|
||||||
if (name === "other") return "Other";
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// create operation systems chart
|
|
||||||
function createOsChart() {
|
|
||||||
const canvases = document.querySelectorAll("canvas.os");
|
|
||||||
if (!canvases || !canvases.length) return;
|
|
||||||
|
|
||||||
canvases.forEach(ctx => {
|
|
||||||
const data = JSON.parse(ctx.dataset.data);
|
|
||||||
const period = ctx.dataset.period;
|
|
||||||
|
|
||||||
const gradient = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
||||||
const gradientHover = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
||||||
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
|
||||||
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
|
||||||
gradientHover.addColorStop(0, "rgba(179, 157, 219, 0.9)");
|
|
||||||
gradientHover.addColorStop(1, "rgba(179, 157, 219, 0.4)");
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
|
||||||
type: "bar",
|
|
||||||
data: {
|
|
||||||
labels: data.map(d => beautifyOsName(d.name)),
|
|
||||||
datasets: [{
|
|
||||||
label: "Views",
|
|
||||||
data: data.map(d => d.value),
|
|
||||||
backgroundColor: gradient,
|
|
||||||
borderColor: "rgba(179, 157, 219, 1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
hoverBackgroundColor: gradientHover,
|
|
||||||
hoverBorderWidth: 2
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
indexAxis: "y",
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
||||||
titleColor: "#333",
|
|
||||||
titleFont: { weight: "normal", size: 15 },
|
|
||||||
bodyFont: { weight: "normal", size: 16 },
|
|
||||||
bodyColor: "rgb(179, 157, 219)",
|
|
||||||
padding: 12,
|
|
||||||
cornerRadius: 2,
|
|
||||||
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
displayColors: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
responsive: true,
|
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
mode: "index",
|
|
||||||
axis: "y"
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
grace:"5%",
|
|
||||||
beginAtZero: true,
|
|
||||||
ticks: {
|
|
||||||
maxTicksLimit: 6,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset the display: block style that chart.js applies automatically
|
|
||||||
ctx.style.display = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add data to the map
|
|
||||||
function feedMapData(period) {
|
|
||||||
const map = document.querySelector("svg.map");
|
|
||||||
const paths = map.querySelectorAll("path");
|
|
||||||
if (!map || !paths || !paths.length) return;
|
|
||||||
|
|
||||||
let data = JSON.parse(map.dataset[period || "day"]);
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
let max = data.sort((a, b) => a.value > b.value ? -1 : 1)[0];
|
|
||||||
|
|
||||||
if (!max) max = { value: 1 }
|
|
||||||
|
|
||||||
data = data.reduce((a, c) => ({ ...a, [c.name]: c.value }), {});
|
|
||||||
|
|
||||||
for (let i = 0; i < paths.length; ++i) {
|
|
||||||
const id = paths[i].dataset.id;
|
|
||||||
const views = data[id] || 0;
|
|
||||||
paths[i].dataset.views = views;
|
|
||||||
const colorLevel = Math.ceil((views / max.value) * 6);
|
|
||||||
const classList = paths[i].classList;
|
|
||||||
for (let j = 1; j < 7; j++) {
|
|
||||||
paths[i].classList.remove(`color-${j}`);
|
|
||||||
}
|
|
||||||
paths[i].classList.add(`color-${colorLevel}`)
|
|
||||||
paths[i].dataset.views = views;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle map tooltip hover
|
|
||||||
function mapTooltipHoverOver() {
|
|
||||||
const tooltip = document.querySelector("#map-tooltip");
|
|
||||||
if (!tooltip) return;
|
|
||||||
if (!event.target.dataset.id) return mapTooltipHoverOut();
|
|
||||||
if (!tooltip.classList.contains("active")) {
|
|
||||||
tooltip.classList.add("visible");
|
|
||||||
}
|
|
||||||
tooltip.dataset.tooltip = `${event.target.ariaLabel}: ${event.target.dataset.views || 0}`;
|
|
||||||
const rect = event.target.getBoundingClientRect();
|
|
||||||
tooltip.style.top = rect.top + (rect.height / 2) + "px";
|
|
||||||
tooltip.style.left = rect.left + (rect.width / 2) + "px";
|
|
||||||
event.target.classList.add("active");
|
|
||||||
}
|
|
||||||
function mapTooltipHoverOut() {
|
|
||||||
const tooltip = document.querySelector("#map-tooltip");
|
|
||||||
const map = document.querySelector("svg.map");
|
|
||||||
const paths = map.querySelectorAll("path");
|
|
||||||
if (!tooltip || !map) return;
|
|
||||||
tooltip.classList.remove("visible");
|
|
||||||
for (let i = 0; i < paths.length; ++i) {
|
|
||||||
paths[i].classList.remove("active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create stats charts
|
|
||||||
function createCharts() {
|
|
||||||
createViewsChart();
|
|
||||||
createBrowsersChart();
|
|
||||||
createReferrersChart();
|
|
||||||
createOsChart();
|
|
||||||
feedMapData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// change stats period for showing charts and data
|
|
||||||
function changeStatsPeriod(event) {
|
|
||||||
const period = event.target.dataset.period;
|
|
||||||
if (!period) return;
|
|
||||||
const canvases = document.querySelector("#stats").querySelectorAll("[data-period]");
|
|
||||||
const buttons = document.querySelector("#stats").querySelectorAll(".nav");
|
|
||||||
if (!buttons || !canvases) return;
|
|
||||||
buttons.forEach(b => b.disabled = false);
|
|
||||||
event.target.disabled = true;
|
|
||||||
canvases.forEach(canvas => {
|
|
||||||
if (canvas.dataset.period === period) {
|
|
||||||
canvas.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
canvas.classList.add("hidden");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
feedMapData(period);
|
|
||||||
}
|
|
||||||
|
|
||||||
// htmx prefetch extension
|
// htmx prefetch extension
|
||||||
// https://github.com/bigskysoftware/htmx-extensions/blob/main/src/preload/README.md
|
// https://github.com/bigskysoftware/htmx-extensions/blob/main/src/preload/README.md
|
||||||
htmx.defineExtension('preload', {
|
htmx.defineExtension('preload', {
|
||||||
|
450
static/scripts/stats.js
Normal file
450
static/scripts/stats.js
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
// create views chart label
|
||||||
|
function createViewsChartLabel(ctx) {
|
||||||
|
const period = ctx.dataset.period;
|
||||||
|
let labels = [];
|
||||||
|
|
||||||
|
if (period === "day") {
|
||||||
|
const nowHour = new Date().getHours();
|
||||||
|
for (let i = 23; i >= 0; --i) {
|
||||||
|
let h = nowHour - i;
|
||||||
|
if (h < 0) h = 24 + h;
|
||||||
|
labels.push(`${Math.floor(h)}:00`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period === "week") {
|
||||||
|
const nowDay = new Date().getDate();
|
||||||
|
for (let i = 6; i >= 0; --i) {
|
||||||
|
const date = new Date(new Date().setDate(nowDay - i));
|
||||||
|
labels.push(`${date.getDate()} ${date.toLocaleString("default",{month:"short"})}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period === "month") {
|
||||||
|
const nowDay = new Date().getDate();
|
||||||
|
for (let i = 29; i >= 0; --i) {
|
||||||
|
const date = new Date(new Date().setDate(nowDay - i));
|
||||||
|
labels.push(`${date.getDate()} ${date.toLocaleString("default",{month:"short"})}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period === "year") {
|
||||||
|
const nowMonth = new Date().getMonth();
|
||||||
|
for (let i = 11; i >= 0; --i) {
|
||||||
|
const date = new Date(new Date().setMonth(nowMonth - i));
|
||||||
|
labels.push(`${date.toLocaleString("default",{month:"short"})} ${date.toLocaleString("default",{year:"numeric"})}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change stats period for showing charts and data
|
||||||
|
function changeStatsPeriod(event) {
|
||||||
|
const period = event.target.dataset.period;
|
||||||
|
if (!period) return;
|
||||||
|
const canvases = document.querySelector("#stats").querySelectorAll("[data-period]");
|
||||||
|
const buttons = document.querySelector("#stats").querySelectorAll(".nav");
|
||||||
|
if (!buttons || !canvases) return;
|
||||||
|
buttons.forEach(b => b.disabled = false);
|
||||||
|
event.target.disabled = true;
|
||||||
|
canvases.forEach(canvas => {
|
||||||
|
if (canvas.dataset.period === period) {
|
||||||
|
canvas.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
canvas.classList.add("hidden");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
feedMapData(period);
|
||||||
|
}
|
||||||
|
|
||||||
|
// beautify browser lables
|
||||||
|
function beautifyBrowserName(name) {
|
||||||
|
if (name === "firefox") return "Firefox";
|
||||||
|
if (name === "chrome") return "Chrome";
|
||||||
|
if (name === "edge") return "Edge";
|
||||||
|
if (name === "opera") return "Opera";
|
||||||
|
if (name === "safari") return "Safari";
|
||||||
|
if (name === "other") return "Other";
|
||||||
|
if (name === "ie") return "IE";
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create views chart
|
||||||
|
function createViewsChart() {
|
||||||
|
const canvases = document.querySelectorAll("canvas.visits");
|
||||||
|
if (!canvases || !canvases.length) return;
|
||||||
|
|
||||||
|
canvases.forEach(ctx => {
|
||||||
|
const data = JSON.parse(ctx.dataset.data);
|
||||||
|
const period = ctx.dataset.period;
|
||||||
|
|
||||||
|
const labels = createViewsChartLabel(ctx);
|
||||||
|
const maxTicksLimitX = period === "year" ? 6 : period === "month" ? 15 : 12;
|
||||||
|
|
||||||
|
const gradient = ctx.getContext("2d").createLinearGradient(0, 0, 0, 300);
|
||||||
|
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
||||||
|
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: "Views",
|
||||||
|
data,
|
||||||
|
tension: 0.3,
|
||||||
|
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHoverRadius: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
target: "start",
|
||||||
|
},
|
||||||
|
backgroundColor: gradient,
|
||||||
|
borderColor: "rgb(179, 157, 219)",
|
||||||
|
borderWidth: 1,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
||||||
|
titleColor: "#333",
|
||||||
|
titleFont: { weight: "normal", size: 15 },
|
||||||
|
bodyFont: { weight: "normal", size: 16 },
|
||||||
|
bodyColor: "rgb(179, 157, 219)",
|
||||||
|
padding: 12,
|
||||||
|
cornerRadius: 2,
|
||||||
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
||||||
|
borderWidth: 1,
|
||||||
|
displayColors: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
usePointStyle: true,
|
||||||
|
mode: "index",
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
grace: "10%",
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
maxTicksLimit: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
maxTicksLimit: maxTicksLimitX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// reset the display: block style that chart.js applies automatically
|
||||||
|
ctx.style.display = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// create browsers chart
|
||||||
|
function createBrowsersChart() {
|
||||||
|
const canvases = document.querySelectorAll("canvas.browsers");
|
||||||
|
if (!canvases || !canvases.length) return;
|
||||||
|
|
||||||
|
canvases.forEach(ctx => {
|
||||||
|
const data = JSON.parse(ctx.dataset.data);
|
||||||
|
const period = ctx.dataset.period;
|
||||||
|
|
||||||
|
const gradient = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
||||||
|
const gradientHover = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
||||||
|
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
||||||
|
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
||||||
|
gradientHover.addColorStop(0, "rgba(179, 157, 219, 0.9)");
|
||||||
|
gradientHover.addColorStop(1, "rgba(179, 157, 219, 0.4)");
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: "bar",
|
||||||
|
data: {
|
||||||
|
labels: data.map(d => beautifyBrowserName(d.name)),
|
||||||
|
datasets: [{
|
||||||
|
label: "Views",
|
||||||
|
data: data.map(d => d.value),
|
||||||
|
backgroundColor: gradient,
|
||||||
|
borderColor: "rgba(179, 157, 219, 1)",
|
||||||
|
borderWidth: 1,
|
||||||
|
hoverBackgroundColor: gradientHover,
|
||||||
|
hoverBorderWidth: 2
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
indexAxis: "y",
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
||||||
|
titleColor: "#333",
|
||||||
|
titleFont: { weight: "normal", size: 15 },
|
||||||
|
bodyFont: { weight: "normal", size: 16 },
|
||||||
|
bodyColor: "rgb(179, 157, 219)",
|
||||||
|
padding: 12,
|
||||||
|
cornerRadius: 2,
|
||||||
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
||||||
|
borderWidth: 1,
|
||||||
|
displayColors: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: "index",
|
||||||
|
axis: "y"
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grace: "5%",
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
maxTicksLimit: 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// reset the display: block style that chart.js applies automatically
|
||||||
|
ctx.style.display = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// create referrers chart
|
||||||
|
function createReferrersChart() {
|
||||||
|
const canvases = document.querySelectorAll("canvas.referrers");
|
||||||
|
if (!canvases || !canvases.length) return;
|
||||||
|
|
||||||
|
canvases.forEach(ctx => {
|
||||||
|
const data = JSON.parse(ctx.dataset.data);
|
||||||
|
const period = ctx.dataset.period;
|
||||||
|
let max = Array.from(data).sort((a, b) => a.value > b.value ? -1 : 1)[0];
|
||||||
|
|
||||||
|
let tooltipEnabled = true;
|
||||||
|
let hoverBackgroundColor = "rgba(179, 157, 219, 1)";
|
||||||
|
let hoverBorderWidth = 2;
|
||||||
|
let borderColor = "rgba(179, 157, 219, 1)";
|
||||||
|
if (data.length === 0) {
|
||||||
|
data.push({ name: "No views.", value: 1 });
|
||||||
|
max = { value: 1000 };
|
||||||
|
tooltipEnabled = false;
|
||||||
|
hoverBackgroundColor = "rgba(179, 157, 219, 0.1)";
|
||||||
|
hoverBorderWidth = 1;
|
||||||
|
borderColor = "rgba(179, 157, 219, 0.2)";
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: "doughnut",
|
||||||
|
data: {
|
||||||
|
labels: data.map(d => d.name.replace(/\[dot\]/g, ".")),
|
||||||
|
datasets: [{
|
||||||
|
label: "Views",
|
||||||
|
data: data.map(d => d.value),
|
||||||
|
backgroundColor: data.map(d => `rgba(179, 157, 219, ${Math.max((d.value / max.value) - 0.2, 0.1).toFixed(2)})`),
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor,
|
||||||
|
hoverBackgroundColor,
|
||||||
|
hoverBorderWidth,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "left",
|
||||||
|
labels: {
|
||||||
|
boxWidth: 25,
|
||||||
|
font: { size: 11 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: tooltipEnabled,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
||||||
|
titleColor: "#333",
|
||||||
|
titleFont: { weight: "normal", size: 15 },
|
||||||
|
bodyFont: { weight: "normal", size: 16 },
|
||||||
|
bodyColor: "rgb(179, 157, 219)",
|
||||||
|
padding: 12,
|
||||||
|
cornerRadius: 2,
|
||||||
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
||||||
|
borderWidth: 1,
|
||||||
|
displayColors: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// reset the display: block style that chart.js applies automatically
|
||||||
|
ctx.style.display = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// beautify browser lables
|
||||||
|
function beautifyOsName(name) {
|
||||||
|
if (name === "android") return "Android";
|
||||||
|
if (name === "ios") return "iOS";
|
||||||
|
if (name === "linux") return "Linux";
|
||||||
|
if (name === "macos") return "macOS";
|
||||||
|
if (name === "windows") return "Windows";
|
||||||
|
if (name === "other") return "Other";
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create operation systems chart
|
||||||
|
function createOsChart() {
|
||||||
|
const canvases = document.querySelectorAll("canvas.os");
|
||||||
|
if (!canvases || !canvases.length) return;
|
||||||
|
|
||||||
|
canvases.forEach(ctx => {
|
||||||
|
const data = JSON.parse(ctx.dataset.data);
|
||||||
|
const period = ctx.dataset.period;
|
||||||
|
|
||||||
|
const gradient = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
||||||
|
const gradientHover = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
||||||
|
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
||||||
|
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
||||||
|
gradientHover.addColorStop(0, "rgba(179, 157, 219, 0.9)");
|
||||||
|
gradientHover.addColorStop(1, "rgba(179, 157, 219, 0.4)");
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: "bar",
|
||||||
|
data: {
|
||||||
|
labels: data.map(d => beautifyOsName(d.name)),
|
||||||
|
datasets: [{
|
||||||
|
label: "Views",
|
||||||
|
data: data.map(d => d.value),
|
||||||
|
backgroundColor: gradient,
|
||||||
|
borderColor: "rgba(179, 157, 219, 1)",
|
||||||
|
borderWidth: 1,
|
||||||
|
hoverBackgroundColor: gradientHover,
|
||||||
|
hoverBorderWidth: 2
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
indexAxis: "y",
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
||||||
|
titleColor: "#333",
|
||||||
|
titleFont: { weight: "normal", size: 15 },
|
||||||
|
bodyFont: { weight: "normal", size: 16 },
|
||||||
|
bodyColor: "rgb(179, 157, 219)",
|
||||||
|
padding: 12,
|
||||||
|
cornerRadius: 2,
|
||||||
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
||||||
|
borderWidth: 1,
|
||||||
|
displayColors: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: "index",
|
||||||
|
axis: "y"
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
grace:"5%",
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
maxTicksLimit: 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// reset the display: block style that chart.js applies automatically
|
||||||
|
ctx.style.display = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// add data to the map
|
||||||
|
function feedMapData(period) {
|
||||||
|
const map = document.querySelector("svg.map");
|
||||||
|
const paths = map.querySelectorAll("path");
|
||||||
|
if (!map || !paths || !paths.length) return;
|
||||||
|
|
||||||
|
let data = JSON.parse(map.dataset[period || "day"]);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
let max = data.sort((a, b) => a.value > b.value ? -1 : 1)[0];
|
||||||
|
|
||||||
|
if (!max) max = { value: 1 }
|
||||||
|
|
||||||
|
data = data.reduce((a, c) => ({ ...a, [c.name]: c.value }), {});
|
||||||
|
|
||||||
|
for (let i = 0; i < paths.length; ++i) {
|
||||||
|
const id = paths[i].dataset.id;
|
||||||
|
const views = data[id] || 0;
|
||||||
|
paths[i].dataset.views = views;
|
||||||
|
const colorLevel = Math.ceil((views / max.value) * 6);
|
||||||
|
const classList = paths[i].classList;
|
||||||
|
for (let j = 1; j < 7; j++) {
|
||||||
|
paths[i].classList.remove(`color-${j}`);
|
||||||
|
}
|
||||||
|
paths[i].classList.add(`color-${colorLevel}`)
|
||||||
|
paths[i].dataset.views = views;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle map tooltip hover
|
||||||
|
function mapTooltipHoverOver() {
|
||||||
|
const tooltip = document.querySelector("#map-tooltip");
|
||||||
|
if (!tooltip) return;
|
||||||
|
if (!event.target.dataset.id) return mapTooltipHoverOut();
|
||||||
|
if (!tooltip.classList.contains("active")) {
|
||||||
|
tooltip.classList.add("visible");
|
||||||
|
}
|
||||||
|
tooltip.dataset.tooltip = `${event.target.ariaLabel}: ${event.target.dataset.views || 0}`;
|
||||||
|
const rect = event.target.getBoundingClientRect();
|
||||||
|
tooltip.style.top = rect.top + (rect.height / 2) + "px";
|
||||||
|
tooltip.style.left = rect.left + (rect.width / 2) + "px";
|
||||||
|
event.target.classList.add("active");
|
||||||
|
}
|
||||||
|
function mapTooltipHoverOut() {
|
||||||
|
const tooltip = document.querySelector("#map-tooltip");
|
||||||
|
const map = document.querySelector("svg.map");
|
||||||
|
const paths = map.querySelectorAll("path");
|
||||||
|
if (!tooltip || !map) return;
|
||||||
|
tooltip.classList.remove("visible");
|
||||||
|
for (let i = 0; i < paths.length; ++i) {
|
||||||
|
paths[i].classList.remove("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create stats charts
|
||||||
|
function createCharts() {
|
||||||
|
if (Chart === undefined) {
|
||||||
|
setTimeout(function() { createCharts() }, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createViewsChart();
|
||||||
|
createBrowsersChart();
|
||||||
|
createReferrersChart();
|
||||||
|
createOsChart();
|
||||||
|
feedMapData();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user