Files
vrobbler/vrobbler/apps/trends/templates/trends/_mood_trajectory.html

108 lines
3.3 KiB
HTML

<div class="row">
<div class="col-12">
{% if data.trajectory %}
{{ data.trajectory|json_script:"mood-trajectory-data" }}
<div class="d-flex flex-column align-items-center">
<canvas id="moodTrajectoryChart" width="700" height="300" style="max-width:100%;"></canvas>
<div class="d-flex gap-4 mt-2 small text-muted">
<span>← Earlier</span>
<span>Later →</span>
</div>
</div>
<script>
(function() {
var el = document.getElementById('mood-trajectory-data');
if (!el) return;
var data = JSON.parse(el.textContent);
if (!data.length) return;
var canvas = document.getElementById('moodTrajectoryChart');
var ctx = canvas.getContext('2d');
var W = canvas.width, H = canvas.height;
var pad = { top: 20, right: 20, bottom: 30, left: 40 };
var plotW = W - pad.left - pad.right;
var plotH = H - pad.top - pad.bottom;
var yMin = 1, yMax = 7;
var yRange = yMax - yMin;
var maxCount = data.reduce(function(m, d) { return Math.max(m, d.count); }, 0);
function xPos(i) {
return pad.left + (i / (data.length - 1 || 1)) * plotW;
}
function yPos(val) {
return pad.top + (1 - (val - yMin) / yRange) * plotH;
}
// Background grid
ctx.strokeStyle = '#e5e7eb';
ctx.lineWidth = 0.5;
for (var q = 1; q <= 7; q++) {
var y = yPos(q);
ctx.beginPath();
ctx.moveTo(pad.left, y);
ctx.lineTo(W - pad.right, y);
ctx.stroke();
ctx.fillStyle = '#9ca3af';
ctx.font = '11px sans-serif';
ctx.textAlign = 'right';
ctx.fillText(q.toFixed(1), pad.left - 5, y + 4);
}
// Reference line at neutral (4)
var neutralY = yPos(4);
ctx.strokeStyle = '#d1d5db';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath();
ctx.moveTo(pad.left, neutralY);
ctx.lineTo(W - pad.right, neutralY);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#9ca3af';
ctx.font = '11px sans-serif';
ctx.textAlign = 'left';
ctx.fillText('neutral', W - pad.right + 4, neutralY + 4);
// Line chart
ctx.beginPath();
data.forEach(function(d, i) {
var x = xPos(i);
var y = yPos(d.avg_quality);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.strokeStyle = '#6366f1';
ctx.lineWidth = 2.5;
ctx.lineJoin = 'round';
ctx.stroke();
// Gradient fill below the line
var gradient = ctx.createLinearGradient(0, pad.top, 0, H - pad.bottom);
gradient.addColorStop(0, 'rgba(99, 102, 241, 0.25)');
gradient.addColorStop(1, 'rgba(99, 102, 241, 0.02)');
ctx.lineTo(xPos(data.length - 1), yPos(yMin));
ctx.lineTo(xPos(0), yPos(yMin));
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
// Dots
data.forEach(function(d, i) {
var x = xPos(i);
var y = yPos(d.avg_quality);
ctx.beginPath();
ctx.arc(x, y, 3.5, 0, 2 * Math.PI);
ctx.fillStyle = '#6366f1';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1.5;
ctx.stroke();
});
})();
</script>
{% else %}
<p class="text-muted">No mood check-in data found.</p>
{% endif %}
</div>
</div>