108 lines
3.3 KiB
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>
|