SI formatting of large visitor numbers.
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
calcTotalViews,
|
||||
copyIp,
|
||||
copyList,
|
||||
formatCount,
|
||||
formatAbuseRows,
|
||||
formatCrawlerRows,
|
||||
formatVisitRows,
|
||||
@@ -123,8 +124,8 @@ function countryName(code) {
|
||||
<p v-else-if="!data" class="loading">loading…</p>
|
||||
<template v-else>
|
||||
<section class="totals">
|
||||
<div><strong>{{ visits.length }}</strong> visits</div>
|
||||
<div><strong>{{ totalViews }}</strong> page views</div>
|
||||
<div><strong :title="String(visits.length)">{{ formatCount(visits.length) }}</strong> visits</div>
|
||||
<div><strong :title="String(totalViews)">{{ formatCount(totalViews) }}</strong> page views</div>
|
||||
</section>
|
||||
|
||||
<VisitorCharts :data="data" :range="range" />
|
||||
@@ -207,7 +208,7 @@ function countryName(code) {
|
||||
<a v-for="(s, si) in c.pages" :key="si"
|
||||
:href="s.path" :title="`${s.title}${s.count > 1 ? ` (${s.count} hits)` : ''}`"
|
||||
@click="$emit('close')">
|
||||
<small v-if="s.count > 1" class="muted">{{ s.count }}×</small>{{ s.slug }}
|
||||
<small v-if="s.count > 1" class="muted">{{ formatCount(s.count) }}×</small>{{ s.slug }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="ip-ua-cell">
|
||||
@@ -257,7 +258,7 @@ function countryName(code) {
|
||||
@click="copyList(a.allUas, $event)">
|
||||
<div v-for="(u, ui) in a.uas.slice(0, ABUSE_MAX_LINES)" :key="ui"
|
||||
class="list-line">
|
||||
<small v-if="u.count > 1" class="muted">{{ u.count }}×</small>{{ u.ua }}
|
||||
<small v-if="u.count > 1" class="muted">{{ formatCount(u.count) }}×</small>{{ u.ua }}
|
||||
</div>
|
||||
<div v-if="a.uas.length > ABUSE_MAX_LINES" class="list-line">
|
||||
<small class="muted">+{{ a.uas.length - ABUSE_MAX_LINES }} more</small>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
import { computed, onBeforeUnmount, shallowRef, watch } from 'vue'
|
||||
import { rangeWindow, WEEK } from './analytics/time.js'
|
||||
import { formatCount } from './analytics/format.js'
|
||||
import {
|
||||
TNODE_R,
|
||||
BEAD_R,
|
||||
@@ -140,7 +141,7 @@ onBeforeUnmount(() => cancelAnimationFrame(rafId))
|
||||
<text :transform="`translate(${x.x}, ${x.y}) scale(${x.r - 4})`" class="tnodeslug" :style="{ '--node-r': x.r - 4 }">
|
||||
<textPath href="#tnode-label-arc" startOffset="50%" text-anchor="middle" side="right">{{ x.label }}</textPath>
|
||||
</text>
|
||||
<text :x="x.x" :y="x.y + 4" class="tnodecount">{{ x.count }}</text>
|
||||
<text :x="x.x" :y="x.y + 4" class="tnodecount">{{ formatCount(x.count) }}</text>
|
||||
</a>
|
||||
</g>
|
||||
<g v-for="n in graph.nodes" :key="n.path">
|
||||
@@ -150,7 +151,7 @@ onBeforeUnmount(() => cancelAnimationFrame(rafId))
|
||||
<textPath href="#tnode-label-arc" startOffset="50%" text-anchor="middle" side="right">{{ n.label }}</textPath>
|
||||
</text>
|
||||
<text :x="n.x" :y="n.y + 4" class="tnodecount">
|
||||
{{ n.readMin ? `${n.views}×${n.readMin}m` : n.views }}
|
||||
{{ n.readMin ? `${formatCount(n.views)}×${n.readMin}m` : formatCount(n.views) }}
|
||||
</text>
|
||||
</a>
|
||||
<template v-else>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { DAY, HOUR, MIN5, WEEK, mondayUTC } from './time.js'
|
||||
import { formatCount } from './format.js'
|
||||
|
||||
export const CHART_W = 720
|
||||
export const CHART_H = 180
|
||||
@@ -412,7 +413,7 @@ export function fmtTick(t, span) {
|
||||
return d.toLocaleDateString(undefined, { year: 'numeric', timeZone: 'UTC' })
|
||||
}
|
||||
|
||||
/** Y labels: integers when the step allows, one decimal for fractional steps. */
|
||||
/** Y labels use the same compact formatter as text labels. */
|
||||
export function fmtY(v) {
|
||||
return Number.isInteger(v) ? String(v) : v.toFixed(1)
|
||||
return formatCount(v)
|
||||
}
|
||||
|
||||
@@ -200,6 +200,17 @@ export function formatWhenIso(ts) {
|
||||
return `${new Date(ts).toISOString().split('.')[0]}Z`
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact visitor counts: plain below 1k, then 1.2k / 10k / 1.2M.
|
||||
* Truncated, not rounded.
|
||||
*/
|
||||
export function formatCount(n) {
|
||||
if (n < 1000) return String(n)
|
||||
if (n < 10000) return `${Math.trunc(n / 1000)}.${Math.trunc((n % 1000) / 100)}k`
|
||||
if (n < 1_000_000) return `${Math.trunc(n / 1000)}k`
|
||||
return `${Math.trunc(n / 1_000_000)}.${Math.trunc((n % 1_000_000) / 100_000)}M`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format recent visits for display, newest first. Each step is a linked slug
|
||||
* pointing to its article; external referers/origins are shown as their
|
||||
|
||||
Reference in New Issue
Block a user