diff --git a/assets/css/app.css b/assets/css/app.css index bef2604..5b6d3ee 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -378,6 +378,7 @@ button { .dashboard-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: start; } .chart-card--calendar, @@ -474,9 +475,12 @@ button { stroke-width: 1.1; } -.line-chart, +.line-chart { + min-height: 10.25rem; +} + .bar-chart { - min-height: 17rem; + min-height: 11rem; } .line-chart svg { @@ -499,16 +503,35 @@ button { stroke-width: 1.5; } +.line-point--solo { + stroke-width: 2; +} + +.line-point-glow { + opacity: 0.16; +} + .chart-axis { stroke: rgba(255, 255, 255, 0.1); stroke-width: 1; } +.chart-guide { + opacity: 0.22; + stroke-width: 2; +} + .chart-label { fill: rgba(239, 247, 255, 0.65); font-size: 11px; } +.chart-value { + fill: rgba(239, 247, 255, 0.9); + font-size: 15px; + font-weight: 700; +} + .bar-chart svg { width: 100%; height: auto; @@ -987,6 +1010,14 @@ input[type="range"] { flex-direction: column; align-items: flex-start; } + + .line-chart { + min-height: 9.5rem; + } + + .bar-chart { + min-height: 10rem; + } } @media (max-width: 640px) { @@ -1011,4 +1042,16 @@ input[type="range"] { .hero-score { font-size: 2.8rem; } + + .chart-card { + padding: 1rem; + } + + .line-chart { + min-height: 8.75rem; + } + + .bar-chart { + min-height: 9.5rem; + } } diff --git a/assets/js/app.js b/assets/js/app.js index e96f0e4..b45b75e 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,5 +1,6 @@ (function () { const textDecoder = new TextDecoder(); + let dashboardResizeTimer = null; function decodePayload(raw) { if (!raw) { @@ -282,11 +283,55 @@ return; } + const seriesName = container.dataset.series || ""; + const values = items.map(item => Number(item.value)); const width = 760; - const height = 220; - const padding = { top: 18, right: 18, bottom: 38, left: 14 }; - const maxValue = Math.max(...items.map(item => Number(item.value)), 10); - const minValue = 0; + const height = 196; + const padding = { top: 10, right: 18, bottom: 28, left: 14 }; + let minValue = Math.min(...values); + let maxValue = Math.max(...values); + + if (seriesName === "mood" || seriesName === "stress") { + minValue = Math.max(1, minValue - 1.5); + maxValue = Math.min(10, maxValue + 1.5); + } else { + minValue = Math.max(0, minValue - 1); + maxValue = maxValue + 1; + } + + if ((maxValue - minValue) < 3) { + const center = (maxValue + minValue) / 2; + if (seriesName === "mood" || seriesName === "stress") { + minValue = Math.max(1, center - 1.5); + maxValue = Math.min(10, center + 1.5); + } else { + minValue = Math.max(0, center - 1.5); + maxValue = center + 1.5; + } + } + + if (items.length === 1) { + const only = items[0]; + const chartTop = padding.top; + const chartBottom = height - padding.bottom; + const ratio = (Number(only.value) - minValue) / Math.max(maxValue - minValue, 1); + const cx = width / 2; + const cy = chartTop + ((1 - ratio) * (chartBottom - chartTop)); + + container.innerHTML = ` + + + + + + ${formatNumber(Number(only.value))} + ${formatDateLabel(only.date)} + ${formatDateLabel(only.date)}: ${formatNumber(Number(only.value))} + + `; + return; + } + const step = items.length > 1 ? (width - padding.left - padding.right) / (items.length - 1) : 0; const points = items.map((item, index) => { @@ -324,9 +369,9 @@ const recent = items.slice(-18); const maxValue = Math.max(...recent.map(item => Number(item.value)), 1); const width = Math.max(recent.length * 34, 520); - const height = 220; - const chartHeight = 150; - const baseY = 176; + const height = 184; + const chartHeight = 118; + const baseY = 146; const bars = recent.map((item, index) => { const sport = Number(item.sport || 0); @@ -349,7 +394,7 @@ ${formatDateLabel(item.date)} ยท Sport ${sport} min ${Math.round(total)} - ${formatDateLabel(item.date)} + ${formatDateLabel(item.date)} `; }).join(""); @@ -424,20 +469,27 @@ const totalWeeks = Math.floor((days.length - 1) / 7) + 1; const cellSize = 12; - const cellGap = 5; + const baseCellGap = 5; + const verticalGap = 5; const xOffset = 34; const yOffset = 22; - const gridHeight = (7 * cellSize) + (6 * cellGap); + const gridHeight = (7 * cellSize) + (6 * verticalGap); const height = yOffset + gridHeight + 8; - const width = xOffset + ((totalWeeks - 1) * (cellSize + cellGap)) + cellSize + 12; + const rightPadding = 4; + const naturalWidth = xOffset + (totalWeeks * cellSize) + ((totalWeeks - 1) * baseCellGap) + rightPadding; + const availableWidth = Math.floor(container.clientWidth || 0); + const width = Math.max(naturalWidth, availableWidth); + const horizontalGap = totalWeeks > 1 + ? (width - xOffset - rightPadding - (totalWeeks * cellSize)) / (totalWeeks - 1) + : 0; const monthLabels = []; let lastMonth = -1; const cells = days.map((item, index) => { const week = Math.floor(index / 7); const row = item.weekday === 0 ? 6 : item.weekday - 1; - const x = xOffset + (week * (cellSize + cellGap)); - const y = yOffset + (row * (cellSize + cellGap)); + const x = xOffset + (week * (cellSize + horizontalGap)); + const y = yOffset + (row * (cellSize + verticalGap)); const fill = calendarColor(item.entry); if (item.day <= 7 && item.month !== lastMonth && item.entry !== null) { @@ -511,6 +563,17 @@ }); } + window.addEventListener("resize", () => { + if (!document.querySelector("#calendar-heatmap")) { + return; + } + + window.clearTimeout(dashboardResizeTimer); + dashboardResizeTimer = window.setTimeout(() => { + initDashboardCharts(); + }, 120); + }); + updateRangeOutputs(); initHeaderDatePicker(); initTrackPreview();