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:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user