Analytics layout: larger charts with natural-width cap, one-line totals

- Charts grow to a larger intrinsic size (1052x174) and never upscale
  past it; centered with equal side margins above the cap, full width
  below, svg always within page bounds; overflow visible so wider fonts
  don't clip at the viewBox edge
- Totals row aligns its left edge with the charts and shrinks (gap first,
  then font) via container units to always stay on one line
This commit is contained in:
2026-08-24 18:21:58 +00:00
parent e3e29251ca
commit 4a48d08a19
3 changed files with 30 additions and 7 deletions
+14 -4
View File
@@ -26,6 +26,11 @@ import TrailLink from './TrailLink.vue'
import VisitorCell from './VisitorCell.vue'
import TransitionGraph from './TransitionGraph.vue'
import VisitorCharts from './VisitorCharts.vue'
import { VIEW_W } from './analytics/chart.js'
// Same centering margin as the charts, so the totals row's left edge
// aligns with the chart svg above the natural width.
const CHART_MARGIN = `max(0px, calc(50% - ${VIEW_W / 2}px))`
const ABUSE_MAX_LINES = 5
@@ -148,7 +153,7 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
<p v-if="error" class="error"> {{ error }}</p>
<p v-else-if="!data" class="loading">loading</p>
<template v-else>
<section class="totals">
<section class="totals" :style="{ marginLeft: CHART_MARGIN }">
<div><strong :title="String(visits.length)">{{ formatCount(visits.length) }}</strong> visits</div>
<div><strong :title="String(totalViews)">{{ formatCount(totalViews) }}</strong> page views</div>
<div><strong>{{ readStats.avgMinPerVisit }}</strong> min/visit</div>
@@ -288,6 +293,8 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
width: 100%;
/* Same 1.25rem side spacing as main's article padding. */
padding: 1.5rem 1.25rem 4rem;
/* Container for cqw-based shrink-to-fit (see .totals). */
container-type: inline-size;
}
.analytics-panel header {
@@ -354,12 +361,15 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
.analytics-view :deep(.muted) { color: var(--muted); }
.analytics-view :deep(.small) { font-size: 0.75em; }
/* One line at any width: the gap shrinks first, then the font (the number
scales along in em), both following the panel's container width. */
.totals {
display: flex;
gap: 2rem;
font-size: 1.1rem;
gap: clamp(0.5rem, 3cqw, 2rem);
font-size: clamp(0.6rem, 2.2cqw, 1.1rem);
white-space: nowrap;
}
.totals strong { font-size: 1.5rem; }
.totals strong { font-size: 1.36em; }
.visit-table-wrap {
overflow-x: auto;
+14 -1
View File
@@ -16,6 +16,11 @@ import {
const DAY_REFRESH_MS = 15000
// Keep the whole svg within page bounds: full width below the natural
// size, centered with equal side margins above it (max() clamps the
// centering margin to 0 at the breakpoint, so the rule is continuous).
const CHART_MARGIN = `max(0px, calc(50% - ${VIEW_W / 2}px))`
const props = defineProps({
data: { type: Object, default: null },
range: { type: String, required: true },
@@ -68,6 +73,7 @@ const viewChart = computed(() => buildChart(viewSeries.value, now.value))
]" :key="c.ylabel">
<template v-if="c.chart">
<svg class="chart" :viewBox="`${-MARGIN_L} 0 ${VIEW_W} ${VIEW_H}`"
:style="{ maxWidth: `${VIEW_W}px`, marginLeft: CHART_MARGIN }"
role="img" :aria-label="axisLabel(c.chart.unit, c.ylabel)">
<line v-for="g in c.chart.majors.slice(1)" :key="'j' + g.value"
:x1="0" :x2="CHART_W" :y1="g.y" :y2="g.y" class="major" />
@@ -116,11 +122,18 @@ const viewChart = computed(() => buildChart(viewSeries.value, now.value))
<style scoped>
/* Each chart is a self-contained SVG: the viewBox includes the axis label
margins, so nothing is positioned with HTML overlays. */
margins, so nothing is positioned with HTML overlays. Never upscale past
the natural size (1 viewBox unit = 1 px, max-width set inline) — that
would blow up the constant-size text; smaller panels still scale the
chart down to fit. The margin-left (set inline) centers the chart above
its natural width; the svg always stays within page bounds.
overflow: visible lets wider fonts extend past the viewBox instead of
clipping. */
.chart {
display: block;
width: 100%;
height: auto;
overflow: visible;
}
.chart .ylab,
+2 -2
View File
@@ -9,8 +9,8 @@
import { DAY, HOUR, MIN5, WEEK, mondayUTC } from './time.js'
import { formatCount } from './format.js'
export const CHART_W = 720
export const CHART_H = 126
export const CHART_W = 1000
export const CHART_H = 150
export const PAD_TOP = 14 // room above the highest point
export const MARGIN_L = 40 // y tick labels + vertical axis label
export const MARGIN_B = 24 // x tick labels