Analyse: Zeitraum = abgeschlossene Tage, "Heute bisher" getrennt, Feinschliff
Final-Review-Fixwelle der Cockpit-Übersicht:
- F1 Collector: Der Zeitraum sind die range Kalendertage bis gestern (Berliner Tag),
der Vorzeitraum die range Tage davor; heute steht getrennt in today { day, pv, visitors,
contacts }. Kein Einbruch mehr am rechten Rand von Diagramm und Mini-Verläufen,
kein falsches "x % weniger" tagsüber. Portal: weekdayStats bekommt die ganze Reihe
(completedDays entfällt), "Vergleich ab" = firstDay + 2 x range, Kopfzeile
"Analyse aktiv · Heute bisher: N Aufrufe · Stand: HH:MM Uhr" (alter Collector ohne today
bleibt ohne "Heute bisher").
- F2 "direkt kontaktiert" zählt nur Telefon, E-Mail und WhatsApp (auch im Vorzeitraum),
Links nach außen nicht.
- F3 Mini-Verlauf "Ø Zeit pro Seite": Tage ohne Messung sind Lücken statt 0-Tälern.
- F4 Fehlerfarbe beim Ausschalten rot, --an-faint auf 55 %, Achsenschrift per
Container-Abfragen auf 11-12 px (y-Rand wächst mit der Stellenzahl, x-Beschriftungen mit
Mindestabstand), Ranglisten-Anteile an allen Seitenaufrufen, Tippflächen bis 880 px
mindestens 40 px hoch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eadb768d1a
commit
0db59d38e9
11 files changed
+376
-135
No files matched your search
+18
-21
@@ -55,8 +55,8 @@ export function shortDay(day) {
|
||||
return m ? `${m[2]}.${m[1]}.` : String(day || '')
|
||||
}
|
||||
|
||||
/** Koordinaten fuer das Liniendiagramm. series: [{day, pv}] => Punkte, Achsenbeschriftung, Pfad. Mit compare ([{pv}], Zeitraum davor) gemeinsame Skala und gestrichelte Vergleichslinie (nach Tagesindex ausgerichtet). */
|
||||
export function chartModel(series, { width = 640, height = 240, pad = { l: 40, r: 12, t: 12, b: 28 }, maxLabels = 6, compare } = {}) {
|
||||
/** Koordinaten fuer das Liniendiagramm. series: [{day, pv}] => Punkte, Achsenbeschriftung, Pfad. Mit compare ([{pv}], Zeitraum davor) gemeinsame Skala und gestrichelte Vergleichslinie (nach Tagesindex ausgerichtet). labelGap: Mindestabstand (Einheiten) zwischen der letzten und der vorletzten Beschriftung der x-Achse. */
|
||||
export function chartModel(series, { width = 640, height = 240, pad = { l: 40, r: 12, t: 12, b: 28 }, maxLabels = 6, labelGap = 36, compare } = {}) {
|
||||
const data = Array.isArray(series) ? series : []
|
||||
const before = Array.isArray(compare) ? compare : []
|
||||
const prevOf = (i) => {
|
||||
@@ -70,7 +70,7 @@ export function chartModel(series, { width = 640, height = 240, pad = { l: 40, r
|
||||
const y = (v) => pad.t + innerH - (innerH * v) / top
|
||||
const points = data.map((d, i) => ({ x: round(x(i)), y: round(y(Number(d.pv) || 0)), day: d.day, pv: Number(d.pv) || 0, prev: prevOf(i) }))
|
||||
const step = Math.max(1, Math.ceil(data.length / maxLabels))
|
||||
const xLabels = points.filter((_, i) => i % step === 0 || i === data.length - 1).filter((_, i, a) => i === a.length - 1 || a[a.length - 1].x - a[i].x > 36).map((p) => ({ x: p.x, label: shortDay(p.day) }))
|
||||
const xLabels = points.filter((_, i) => i % step === 0 || i === data.length - 1).filter((_, i, a) => i === a.length - 1 || a[a.length - 1].x - a[i].x > labelGap).map((p) => ({ x: p.x, label: shortDay(p.day) }))
|
||||
const yTicks = [0, 0.5, 1].map((f) => ({ y: round(y(top * f)), label: String(Math.round(top * f)) }))
|
||||
const line = points.map((p, i) => `${i ? 'L' : 'M'}${p.x} ${p.y}`).join(' ')
|
||||
const baseY = round(pad.t + innerH)
|
||||
@@ -165,13 +165,14 @@ export function periodLabel(from, to) {
|
||||
return a && b ? `${a.d}. ${MONTHS[a.m - 1]} – ${b.d}. ${MONTHS[b.m - 1]}` : ''
|
||||
}
|
||||
|
||||
/** Vergleich moeglich, wenn der Vorzeitraum vollstaendig ist und mindestens 20 Aufrufe hat. Sonst from = "TT.MM." ab dem der Vergleich geht (nur bei Vorzeitraum, der noch nicht voll ist). */
|
||||
/** Vergleich moeglich, wenn der Vorzeitraum vollstaendig ist und mindestens 20 Aufrufe hat. Sonst from = "TT.MM." ab dem der Vergleich geht (nur bei Vorzeitraum, der noch nicht voll ist).
|
||||
Der Zeitraum endet gestern: der Vorzeitraum beginnt heute - 2 x range, also ist er ab firstDay + 2 x range voll. */
|
||||
export function compareFrom(stats) {
|
||||
const prev = stats?.previous
|
||||
if (prev?.complete && Number(prev.totals?.pv) >= 20) return { ok: true, from: '' }
|
||||
const range = Number(stats?.range)
|
||||
if (prev && !prev.complete && stats.firstDay && range > 0) {
|
||||
const from = addDays(stats.firstDay, 2 * range - 1)
|
||||
const from = addDays(stats.firstDay, 2 * range)
|
||||
return { ok: false, from: from ? shortDay(from) : '' }
|
||||
}
|
||||
return { ok: false, from: '' }
|
||||
@@ -232,7 +233,7 @@ export function refName(host) {
|
||||
return hit ? hit[1] : h
|
||||
}
|
||||
|
||||
/** Staerkster Wochentag und Spitzentag ueber die Tage ab firstDay ('' = alle). null bei weniger als 7 Tagen. */
|
||||
/** Staerkster Wochentag und Spitzentag ueber die (abgeschlossenen) Tage der Reihe ab firstDay ('' = alle). null bei weniger als 7 Tagen. */
|
||||
export function weekdayStats(series, firstDay) {
|
||||
const rows = (Array.isArray(series) ? series : [])
|
||||
.filter((d) => parseDay(d?.day) && (!firstDay || d.day >= firstDay))
|
||||
@@ -255,7 +256,8 @@ export function weekdayStats(series, firstDay) {
|
||||
return { best, avg, peak }
|
||||
}
|
||||
|
||||
const CONTACT_WAYS = { tel: 'per Telefon', mail: 'per E-Mail', wa: 'per WhatsApp', out: 'über einen Link nach außen' }
|
||||
// Direkte Kontaktwege; Klicks auf Links nach außen ('out') sind kein Kontakt zu dir und zählen im Satz nicht mit
|
||||
const CONTACT_WAYS = { tel: 'per Telefon', mail: 'per E-Mail', wa: 'per WhatsApp' }
|
||||
const DEVICE_WORDS = { m: 'vom Handy', d: 'vom Computer' }
|
||||
|
||||
// Satz ueber dem Hinweis-Block; nur hier steht fett (<b>), alle Zahlen sind fmtNum
|
||||
@@ -280,17 +282,19 @@ function leadOf(stats, cmp) {
|
||||
}
|
||||
|
||||
function contactItem(stats, cmp) {
|
||||
const total = Number(stats.totals.contacts) || 0
|
||||
if (!Array.isArray(stats.contacts)) return null // Collector ohne Kontaktliste: nichts behaupten
|
||||
const direct = (rows) => (Array.isArray(rows) ? rows : []).filter((c) => Object.hasOwn(CONTACT_WAYS, c?.key) && c.count > 0)
|
||||
const sum = (rows) => rows.reduce((a, c) => a + c.count, 0)
|
||||
const cur = direct(stats.contacts)
|
||||
const total = sum(cur)
|
||||
if (total < 1) {
|
||||
return Number(stats.totals.visitors) >= 30
|
||||
? 'Von den Besuchern mit Zustimmung hat noch niemand über die Website angerufen oder geschrieben.'
|
||||
: null
|
||||
}
|
||||
const main = (Array.isArray(stats.contacts) ? stats.contacts : [])
|
||||
.filter((c) => CONTACT_WAYS[c.key] && c.count > 0)
|
||||
.reduce((m, c) => (!m || c.count > m.count ? c : m), null)
|
||||
let text = `${fmtNum(total)}-mal hat dich jemand direkt kontaktiert${main ? `, meist ${CONTACT_WAYS[main.key]} (${fmtNum(main.count)})` : ''}.`
|
||||
const before = cmp.ok ? Number(stats.previous.totals.contacts) : 0
|
||||
const main = cur.reduce((m, c) => (!m || c.count > m.count ? c : m), null)
|
||||
let text = `${fmtNum(total)}-mal hat dich jemand direkt kontaktiert, meist ${CONTACT_WAYS[main.key]} (${fmtNum(main.count)}).`
|
||||
const before = cmp.ok ? sum(direct(stats.previous.contacts)) : 0
|
||||
if (before >= 1) {
|
||||
const diff = total - before
|
||||
text += diff === 0 ? ' Genauso oft wie zuvor.' : ` Das sind ${fmtNum(Math.abs(diff))} ${diff > 0 ? 'mehr' : 'weniger'} als zuvor.`
|
||||
@@ -298,16 +302,9 @@ function contactItem(stats, cmp) {
|
||||
return text
|
||||
}
|
||||
|
||||
// Der letzte Tag der Reihe ist heute und noch nicht zu Ende: er zaehlt beim Wochentag-Hinweis nicht mit
|
||||
function completedDays(stats) {
|
||||
const series = Array.isArray(stats.series) ? stats.series : []
|
||||
const last = series[series.length - 1]
|
||||
return last && (!stats.to || last.day === stats.to) ? series.slice(0, -1) : series
|
||||
}
|
||||
|
||||
function dayItem(stats) {
|
||||
if (!(Number(stats.totals.pv) >= 70)) return null
|
||||
const w = weekdayStats(completedDays(stats), stats.firstDay || '')
|
||||
const w = weekdayStats(stats.series, stats.firstDay || '')
|
||||
if (!w) return null
|
||||
const parts = []
|
||||
if (w.best) parts.push(`${w.best.name} ist im Schnitt dein stärkster Tag.`)
|
||||
|
||||
Reference in new issue
Block a user