hub-flow / source /src /components.jsx
mindchain's picture
Upload source/src/components.jsx with huggingface_hub
8490831 verified
Raw History Blame Contribute Delete
9.41 kB
import React, { useEffect, useMemo, useRef, useState } from 'react'
export const nf = new Intl.NumberFormat('en-US')
export const fmt = (n) => (n == null || isNaN(n) ? '—' : nf.format(Math.round(n)))
export const pct = (g) => (g == null || isNaN(g) ? '—' : (g > 0 ? '+' : '') + (g * 100).toFixed(0) + '%')
export function useCountUp(target, dur = 900) {
const [v, setV] = useState(0)
useEffect(() => {
let raf
const t0 = performance.now()
const tick = (t) => {
const p = Math.min(1, (t - t0) / dur)
setV(Math.round(target * (1 - Math.pow(1 - p, 3))))
if (p < 1) raf = requestAnimationFrame(tick)
}
raf = requestAnimationFrame(tick)
return () => cancelAnimationFrame(raf)
}, [target, dur])
return v
}
export function Stat({ k, v, d, dCls = '', sub }) {
const n = useCountUp(v)
return (
<div className="min-w-[128px] flex-1 px-4 py-3.5 sm:px-5">
<div className="text-[10.5px] font-semibold uppercase tracking-[0.08em] text-[var(--faint)]">{k}</div>
<div className="tnum mt-0.5 text-[22px] font-bold tracking-tight sm:text-2xl">{fmt(n)}</div>
{d != null && <div className={'mt-0.5 font-mono text-[11.5px] ' + dCls}>{d}</div>}
{sub && <div className="text-[11px] text-[var(--faint)]">{sub}</div>}
</div>
)
}
export function Sparkline({ data, w = 90, h = 26, color = 'var(--accent)', area = true }) {
if (!data || data.length < 2) return null
const max = Math.max(1, ...data)
const min = Math.min(...data)
const span = Math.max(1e-9, max - min)
const pts = data.map((v, i) => [
(i / (data.length - 1)) * (w - 4) + 2,
h - 3 - ((v - min) / span) * (h - 8),
])
const line = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ')
return (
<svg width={w} height={h} viewBox={'0 0 ' + w + ' ' + h} className="overflow-visible" aria-hidden="true">
{area && <path d={line + ' L' + pts[pts.length - 1][0].toFixed(1) + ' ' + (h - 1) + ' L' + pts[0][0].toFixed(1) + ' ' + (h - 1) + ' Z'} fill={color} opacity="0.13" />}
<path d={line} fill="none" stroke={color} strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />
</svg>
)
}
// columnar series: {day:[], dl_all:[], dl30:[], likes:[]}
// bars = daily downloads (diff of dl_all), line = cumulative
export function BigChart({ series, height = 300 }) {
const wrapRef = useRef(null)
const [hover, setHover] = useState(null)
const { days, daily, cum } = useMemo(() => {
const out = { days: [], daily: [], cum: [] }
if (!series || !series.day || series.day.length < 2) return out
for (let i = 1; i < series.day.length; i++) {
out.days.push(series.day[i])
out.daily.push(Math.max(0, series.dl_all[i] - series.dl_all[i - 1]))
out.cum.push(series.dl_all[i])
}
return out
}, [series])
if (days.length < 2) {
return <p className="py-10 text-center text-sm text-[var(--dim)]">Not enough history for this model yet.</p>
}
const W = 1000, H = height, pad = { l: 56, r: 14, t: 16, b: 26 }
const iw = W - pad.l - pad.r, ih = H - pad.t - pad.b
const dmax = Math.max(1, ...daily)
const cmax = Math.max(1, ...cum)
const x = (i) => pad.l + (i / (daily.length - 1)) * iw
const yd = (v) => pad.t + ih - (v / dmax) * ih
const yc = (v) => pad.t + ih - (v / cmax) * ih
const bw = Math.max(1, iw / daily.length - 1.2)
const bars = daily.map((v, i) => (
<rect key={i} x={x(i) - bw / 2} y={yd(v)} width={bw} height={pad.t + ih - yd(v)} rx={bw > 3 ? 1.5 : 0}
fill={hover === i ? 'var(--accent)' : 'var(--bar)'} opacity={hover == null || hover === i ? 1 : 0.85} />
))
const cpts = cum.map((v, i) => x(i).toFixed(1) + ',' + yc(v).toFixed(1)).join(' ')
const grid = [0, 1, 2, 3, 4].map((g) => {
const gy = pad.t + ih - (g / 4) * ih
return (
<g key={g}>
<line x1={pad.l} y1={gy} x2={W - pad.r} y2={gy} stroke="var(--line2)" />
<text x={pad.l - 8} y={gy + 4} fontSize="10.5" fill="var(--faint)" textAnchor="end" className="tnum">{fmt((dmax * g) / 4)}</text>
</g>
)
})
const onMove = (e) => {
const rect = wrapRef.current.getBoundingClientRect()
const rel = (e.clientX - rect.left) / rect.width
setHover(Math.max(0, Math.min(daily.length - 1, Math.round(rel * (daily.length - 1)))))
}
return (
<div ref={wrapRef} className="relative mt-2" onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
<svg viewBox={'0 0 ' + W + ' ' + H} className="w-full" role="img" aria-label="download history">
<defs>
<linearGradient id="cumfade2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="var(--accent)" stopOpacity="0.22" />
<stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
</linearGradient>
</defs>
{grid}
{bars}
<polygon points={x(0) + ',' + (pad.t + ih) + ' ' + cpts + ' ' + x(daily.length - 1) + ',' + (pad.t + ih)} fill="url(#cumfade2)" />
<polyline points={cpts} fill="none" stroke="var(--accent)" strokeWidth="2.2" strokeLinejoin="round" />
{hover != null && (
<g>
<line x1={x(hover)} y1={pad.t} x2={x(hover)} y2={pad.t + ih} stroke="var(--faint)" strokeDasharray="3 3" />
<circle cx={x(hover)} cy={yc(cum[hover])} r="3.6" fill="var(--accent)" stroke="var(--bg)" strokeWidth="2" />
</g>
)}
{[days[0], days[Math.floor(days.length / 2)], days[days.length - 1]].map((t, i) => (
<text key={i} x={i === 0 ? pad.l : i === 1 ? W / 2 : W - pad.r} y={H - 8} fontSize="10.5" fill="var(--faint)" textAnchor={i === 0 ? 'start' : i === 1 ? 'middle' : 'end'}>{t}</text>
))}
</svg>
{hover != null && (
<div className="pointer-events-none absolute top-1 z-10 rounded-lg border border-[var(--line)] bg-[var(--panel)] px-3 py-2 text-xs shadow-xl"
style={{ left: 'min(max(' + ((hover / Math.max(1, daily.length - 1)) * 100) + '% - 70px, 0px), calc(100% - 160px))' }}>
<div className="font-semibold text-[var(--ink)]">{days[hover]}</div>
<div className="tnum mt-0.5 text-[var(--dim)]">{fmt(daily[hover])} downloads/day</div>
<div className="tnum text-[var(--accent)]">{fmt(cum[hover])} all time</div>
</div>
)}
</div>
)
}
// simple area/line chart from (days[], vals[]) — used for the hub-wide pulse
export function AreaChart({ days, vals, height = 240 }) {
const wrapRef = useRef(null)
const [hover, setHover] = useState(null)
if (!days || days.length < 2) return null
const W = 1000, H = height, pad = { l: 56, r: 14, t: 14, b: 26 }
const iw = W - pad.l - pad.r, ih = H - pad.t - pad.b
const max = Math.max(1, ...vals)
const x = (i) => pad.l + (i / (days.length - 1)) * iw
const y = (v) => pad.t + ih - (v / max) * ih
const pts = vals.map((v, i) => x(i).toFixed(1) + ',' + y(v).toFixed(1)).join(' ')
const grid = [0, 1, 2, 3, 4].map((g) => {
const gy = pad.t + ih - (g / 4) * ih
return (
<g key={g}>
<line x1={pad.l} y1={gy} x2={W - pad.r} y2={gy} stroke="var(--line2)" />
<text x={pad.l - 8} y={gy + 4} fontSize="10.5" fill="var(--faint)" textAnchor="end" className="tnum">{fmt((max * g) / 4)}</text>
</g>
)
})
const onMove = (e) => {
const rect = wrapRef.current.getBoundingClientRect()
const rel = (e.clientX - rect.left) / rect.width
setHover(Math.max(0, Math.min(days.length - 1, Math.round(rel * (days.length - 1)))))
}
return (
<div ref={wrapRef} className="relative" onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
<svg viewBox={'0 0 ' + W + ' ' + H} className="w-full" role="img" aria-label="hub downloads per day">
<defs>
<linearGradient id="hubfade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="var(--accent)" stopOpacity="0.3" />
<stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
</linearGradient>
</defs>
{grid}
<polygon points={x(0) + ',' + (pad.t + ih) + ' ' + pts + ' ' + x(days.length - 1) + ',' + (pad.t + ih)} fill="url(#hubfade)" />
<polyline points={pts} fill="none" stroke="var(--accent)" strokeWidth="2.2" strokeLinejoin="round" />
{hover != null && (
<g>
<line x1={x(hover)} y1={pad.t} x2={x(hover)} y2={pad.t + ih} stroke="var(--faint)" strokeDasharray="3 3" />
<circle cx={x(hover)} cy={y(vals[hover])} r="3.6" fill="var(--accent)" stroke="var(--bg)" strokeWidth="2" />
</g>
)}
{[days[0], days[Math.floor(days.length / 2)], days[days.length - 1]].map((t, i) => (
<text key={i} x={i === 0 ? pad.l : i === 1 ? W / 2 : W - pad.r} y={H - 8} fontSize="10.5" fill="var(--faint)" textAnchor={i === 0 ? 'start' : i === 1 ? 'middle' : 'end'}>{t}</text>
))}
</svg>
{hover != null && (
<div className="pointer-events-none absolute top-1 z-10 rounded-lg border border-[var(--line)] bg-[var(--panel)] px-3 py-2 text-xs shadow-xl"
style={{ left: 'min(max(' + ((hover / Math.max(1, days.length - 1)) * 100) + '% - 70px, 0px), calc(100% - 170px))' }}>
<div className="font-semibold text-[var(--ink)]">{days[hover]}</div>
<div className="tnum mt-0.5 text-[var(--dim)]">{fmt(vals[hover])} downloads</div>
</div>
)}
</div>
)
}