feat : graphique x3 hauteur + multi-lignes par article
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
if (!trendEl || !totals.length) { return; }
|
||||
|
||||
var n = totals.length;
|
||||
var VW = 900, VH = 160;
|
||||
var VW = 900, VH = 480;
|
||||
var ml = 44, mr = 12, mt = 12, mb = 28; // marges pour axes
|
||||
var W = VW - ml - mr;
|
||||
var H = VH - mt - mb;
|
||||
@@ -142,7 +142,7 @@
|
||||
trendEl.innerHTML =
|
||||
'<p class="small text-muted mb-2 fw-semibold">Trafic total — 14 derniers jours</p>'
|
||||
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + VW + ' ' + VH + '"'
|
||||
+ ' style="width:100%;height:160px;display:block;overflow:visible">'
|
||||
+ ' style="width:100%;height:480px;display:block;overflow:visible">'
|
||||
+ '<defs>'
|
||||
+ '<linearGradient id="area-grad" x1="0" y1="0" x2="0" y2="1">'
|
||||
+ '<stop offset="0%" stop-color="var(--bs-primary,#0d6efd)" stop-opacity="0.2"/>'
|
||||
@@ -159,6 +159,111 @@
|
||||
+ '</svg>';
|
||||
}
|
||||
|
||||
function multiLineChart(pagesByDay, rssRows) {
|
||||
var el = document.getElementById('stats-multiline-container');
|
||||
if (!el) { return; }
|
||||
|
||||
var COLORS = ['#0d6efd','#198754','#dc3545','#fd7e14','#6f42c1',
|
||||
'#20c997','#0dcaf0','#e63946','#f4a261','#457b9d'];
|
||||
var n = 14;
|
||||
var VW = 900, VH = 480;
|
||||
var ml = 44, mr = 12, mt = 12, mb = 28;
|
||||
var W = VW - ml - mr;
|
||||
var H = VH - mt - mb;
|
||||
|
||||
// Top articles par total (max 10), dans l'ordre du RSS
|
||||
var series = [];
|
||||
rssRows.forEach(function (row) {
|
||||
var pm = row.link.match(/\/post\/[^?#]*/);
|
||||
var data = pm ? (pagesByDay[pm[0]] || null) : null;
|
||||
if (data && series.length < 10) {
|
||||
series.push({ title: row.title || row.slug, data: data });
|
||||
}
|
||||
});
|
||||
if (!series.length) { return; }
|
||||
|
||||
var allVals = series.reduce(function (acc, s) { return acc.concat(s.data); }, []);
|
||||
var rawMax = Math.max.apply(null, allVals) || 1;
|
||||
var mag = Math.pow(10, Math.floor(Math.log(rawMax) / Math.LN10));
|
||||
var maxV = Math.ceil(rawMax / mag) * mag;
|
||||
var nTicks = 4;
|
||||
|
||||
var now = new Date();
|
||||
var labels = series[0].data.map(function (_, i) {
|
||||
var d = new Date(now);
|
||||
d.setDate(d.getDate() - (n - 1 - i));
|
||||
return d.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
|
||||
});
|
||||
|
||||
function smoothPath(pts) {
|
||||
var d = 'M ' + pts[0].x.toFixed(1) + ' ' + pts[0].y.toFixed(1);
|
||||
for (var i = 0; i < pts.length - 1; i++) {
|
||||
var p0 = pts[i > 0 ? i - 1 : i];
|
||||
var p1 = pts[i], p2 = pts[i + 1];
|
||||
var p3 = pts[i + 2 < pts.length ? i + 2 : i + 1];
|
||||
var t = 0.35;
|
||||
var cp1x = p1.x + t * (p2.x - p0.x) / 2, cp1y = p1.y + t * (p2.y - p0.y) / 2;
|
||||
var cp2x = p2.x - t * (p3.x - p1.x) / 2, cp2y = p2.y - t * (p3.y - p1.y) / 2;
|
||||
d += ' C ' + cp1x.toFixed(1) + ' ' + cp1y.toFixed(1)
|
||||
+ ', ' + cp2x.toFixed(1) + ' ' + cp2y.toFixed(1)
|
||||
+ ', ' + p2.x.toFixed(1) + ' ' + p2.y.toFixed(1);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
var grid = '', yLabels = '';
|
||||
for (var t = 0; t <= nTicks; t++) {
|
||||
var val = Math.round(maxV * t / nTicks);
|
||||
var gy = (mt + H - (val / maxV) * H).toFixed(1);
|
||||
grid += '<line x1="' + ml + '" y1="' + gy + '" x2="' + (VW - mr) + '" y2="' + gy
|
||||
+ '" stroke="#e9ecef" stroke-width="1"/>';
|
||||
yLabels += '<text x="' + (ml - 6) + '" y="' + gy + '" text-anchor="end" dominant-baseline="middle"'
|
||||
+ ' font-size="11" fill="#adb5bd">' + val + '</text>';
|
||||
}
|
||||
|
||||
var xLabels = '';
|
||||
labels.forEach(function (lbl, i) {
|
||||
if (i % 2 === 0 || i === n - 1) {
|
||||
var x = (ml + i * W / (n - 1)).toFixed(1);
|
||||
xLabels += '<text x="' + x + '" y="' + (VH - 4) + '" text-anchor="middle"'
|
||||
+ ' font-size="11" fill="#adb5bd">' + lbl + '</text>';
|
||||
}
|
||||
});
|
||||
|
||||
var lines = series.map(function (s, si) {
|
||||
var color = COLORS[si % COLORS.length];
|
||||
var pts = s.data.map(function (v, i) {
|
||||
return { x: ml + i * W / (n - 1), y: mt + H - (v / maxV) * H, v: v, l: labels[i] };
|
||||
});
|
||||
var dots = pts.map(function (p) {
|
||||
return '<circle cx="' + p.x.toFixed(1) + '" cy="' + p.y.toFixed(1) + '" r="14"'
|
||||
+ ' fill="transparent"><title>' + esc(p.l) + ' — ' + esc(s.title) + ' : ' + p.v + ' vis.</title></circle>'
|
||||
+ '<circle cx="' + p.x.toFixed(1) + '" cy="' + p.y.toFixed(1) + '" r="2.5"'
|
||||
+ ' fill="' + color + '" stroke="#fff" stroke-width="1" pointer-events="none"/>';
|
||||
}).join('');
|
||||
return '<path d="' + smoothPath(pts) + '" fill="none" stroke="' + color
|
||||
+ '" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/>' + dots;
|
||||
}).join('');
|
||||
|
||||
// Légende
|
||||
var legend = series.map(function (s, si) {
|
||||
var color = COLORS[si % COLORS.length];
|
||||
var short = s.title.length > 32 ? s.title.slice(0, 32) + '…' : s.title;
|
||||
return '<span class="d-inline-flex align-items-center gap-1 me-3 mb-1 small">'
|
||||
+ '<svg width="16" height="3" style="flex-shrink:0"><line x1="0" y1="1.5" x2="16" y2="1.5"'
|
||||
+ ' stroke="' + color + '" stroke-width="2.5" stroke-linecap="round"/></svg>'
|
||||
+ '<span class="text-truncate" style="max-width:160px" title="' + esc(s.title) + '">'
|
||||
+ esc(short) + '</span></span>';
|
||||
}).join('');
|
||||
|
||||
el.innerHTML =
|
||||
'<p class="small text-muted mb-2 fw-semibold">Par article — 14 derniers jours</p>'
|
||||
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + VW + ' ' + VH + '"'
|
||||
+ ' style="width:100%;height:480px;display:block;overflow:visible">'
|
||||
+ grid + lines + yLabels + xLabels + '</svg>'
|
||||
+ '<div class="d-flex flex-wrap mt-2">' + legend + '</div>';
|
||||
}
|
||||
|
||||
fetch('/trending?period=14d')
|
||||
.then(function (r) { return r.ok ? r.text() : Promise.reject(); })
|
||||
.then(function (xml) {
|
||||
@@ -186,6 +291,7 @@
|
||||
arr.forEach(function (v, i) { if (i < nDays) { totals[i] += v; } });
|
||||
});
|
||||
trendChart(totals);
|
||||
multiLineChart(pagesByDay, rows);
|
||||
|
||||
var html = '<div class="table-responsive"><table class="table table-sm table-hover mb-0 small w-100"><tbody>';
|
||||
rows.forEach(function (row, i) {
|
||||
|
||||
@@ -36,6 +36,7 @@ $_activeGroup = trim($_GET['group'] ?? '');
|
||||
<p class="text-muted p-3 mb-0">Chargement…</p>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-top px-3 pt-3 pb-2" id="stats-trend-container"></div>
|
||||
<div class="card-footer bg-transparent border-top px-3 pt-3 pb-2" id="stats-multiline-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
Reference in New Issue
Block a user