This commit is contained in:
2026-02-28 08:49:54 +03:00
parent 0feade8cfd
commit ccbedbfcc5

View File

@@ -1,12 +1,241 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fymio</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Роза показателей</title>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.2/babel.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0d0d1a; }
</style>
</head>
<body>
<h1>Hello World</h1>
<p>This page is automatically deployed from git.fymio.us</p>
<div id="root"></div>
<script type="text/babel">
const RAW_DATA = {
N1: { интер: 3, поток: 1, чист: 0, сеть: 1 },
N7: { интер: 0, поток: 1, чист: 0, сеть: 0 },
N9: { интер: 1, поток: 3, чист: 1, сеть: 0 },
N6: { интер: 2, поток: 3, чист: 2, сеть: 1 },
N3: { интер: 1, поток: 1, чист: 3, сеть: 0 },
N8: { интер: 0, поток: 2, чист: 2, сеть: 1 },
N2: { интер: 1, поток: 1, чист: 2, сеть: 0 },
N11: { интер: 1, поток: 1, чист: 2, сеть: 1 },
N5: { интер: 1, поток: 1, чист: 1, сеть: 2 },
N10: { интер: 2, поток: 1, чист: 0, сеть: 1 },
};
const CATEGORIES = ["интер", "поток", "чист", "сеть"];
const CAT_LABELS = { интер: "Интер", поток: "Поток", чист: "Чист трод", сеть: "Сеть" };
const CAT_COLORS = { интер: "#ff6b6b", поток: "#ffd93d", чист: "#6bcb77", сеть: "#4d96ff" };
const PARTICIPANTS = Object.keys(RAW_DATA);
function polarToXY(angle, r, cx, cy) {
const rad = (angle - 90) * (Math.PI / 180);
return [cx + r * Math.cos(rad), cy + r * Math.sin(rad)];
}
function RadarChart({ highlighted, onSelect }) {
const size = 340, cx = 170, cy = 170, maxR = 122, maxVal = 3;
const angleStep = 90;
const rings = [1, 2, 3];
const axes = CATEGORIES.map((cat, i) => {
const [x2, y2] = polarToXY(i * angleStep, maxR, cx, cy);
return { cat, i, x2, y2 };
});
return (
<svg width={size} height={size} style={{display:"block", cursor:"pointer"}}>
{rings.map(ring => {
const pts = CATEGORIES.map((_, i) => polarToXY(i * angleStep, (ring/maxVal)*maxR, cx, cy));
const d = pts.map((pt,i) => (i===0?`M${pt[0]},${pt[1]}`:`L${pt[0]},${pt[1]}`)).join(" ")+" Z";
return <path key={ring} d={d} fill="none" stroke="rgba(255,255,255,0.1)" strokeWidth={1}/>;
})}
{axes.map(({cat,x2,y2}) => (
<line key={cat} x1={cx} y1={cy} x2={x2} y2={y2} stroke="rgba(255,255,255,0.15)" strokeWidth={1}/>
))}
{PARTICIPANTS.map(p => {
const isHl = highlighted === p;
const dominant = CATEGORIES.reduce((a,b) => RAW_DATA[p][a]>=RAW_DATA[p][b]?a:b);
const color = CAT_COLORS[dominant];
const pts = CATEGORIES.map((cat,i) => {
const r = (RAW_DATA[p][cat]/maxVal)*maxR;
return polarToXY(i*angleStep, r, cx, cy);
});
const d = pts.map((pt,i)=>(i===0?`M${pt[0]},${pt[1]}`:`L${pt[0]},${pt[1]}`)).join(" ")+" Z";
return (
<path key={p} d={d}
fill={color} fillOpacity={isHl?0.55:highlighted?0.04:0.18}
stroke={color} strokeWidth={isHl?2.5:highlighted?0.5:1}
strokeOpacity={isHl?1:highlighted?0.25:0.6}
onClick={()=>onSelect(isHl?null:p)}
style={{transition:"all 0.25s"}}
/>
);
})}
{highlighted && CATEGORIES.map((cat,i) => {
const r = (RAW_DATA[highlighted][cat]/maxVal)*maxR;
const [x,y] = polarToXY(i*angleStep, r, cx, cy);
return <circle key={i} cx={x} cy={y} r={5} fill={CAT_COLORS[cat]} stroke="#0d0d1a" strokeWidth={1.5}/>;
})}
{axes.map(({cat,i}) => {
const [lx,ly] = polarToXY(i*angleStep, maxR+26, cx, cy);
return (
<text key={cat} x={lx} y={ly} textAnchor="middle" dominantBaseline="middle"
fill={CAT_COLORS[cat]} fontSize={12} fontFamily="'Courier New',monospace" fontWeight="bold">
{CAT_LABELS[cat]}
</text>
);
})}
{rings.map(ring => (
<text key={ring} x={cx+4} y={cy-(ring/maxVal)*maxR+4}
fill="rgba(255,255,255,0.3)" fontSize={9} fontFamily="monospace">{ring}</text>
))}
</svg>
);
}
function App() {
const [highlighted, setHighlighted] = React.useState(null);
const [view, setView] = React.useState("radar");
const totals = CATEGORIES.map(cat => ({
cat, val: PARTICIPANTS.reduce((s,p)=>s+RAW_DATA[p][cat],0)
}));
const maxTotal = Math.max(...totals.map(t=>t.val));
return (
<div style={{minHeight:"100vh",background:"#0d0d1a",fontFamily:"'Courier New',monospace",
color:"#e0e0f0",display:"flex",flexDirection:"column",alignItems:"center",padding:"32px 16px"}}>
<div style={{textAlign:"center",marginBottom:28}}>
<div style={{fontSize:11,letterSpacing:6,color:"#4d96ff",textTransform:"uppercase",marginBottom:6}}>
Анализ активности
</div>
<h1 style={{margin:0,fontSize:28,fontWeight:900,letterSpacing:2,color:"#fff"}}>
РОЗА ПОКАЗАТЕЛЕЙ
</h1>
<div style={{marginTop:8,fontSize:12,color:"rgba(255,255,255,0.4)"}}>N1 N11 · 4 категории</div>
</div>
<div style={{display:"flex",gap:0,marginBottom:28,border:"1px solid rgba(255,255,255,0.15)",
borderRadius:8,overflow:"hidden"}}>
{["radar","bar"].map(v => (
<button key={v} onClick={()=>setView(v)} style={{
padding:"8px 24px",background:view===v?"#4d96ff":"transparent",
border:"none",color:view===v?"#fff":"rgba(255,255,255,0.5)",
cursor:"pointer",fontFamily:"monospace",fontSize:13,
fontWeight:view===v?700:400,transition:"all 0.2s"}}>
{v==="radar"?"🕸 Радар":"📊 Столбцы"}
</button>
))}
</div>
<div style={{display:"flex",flexWrap:"wrap",gap:32,justifyContent:"center",
alignItems:"flex-start",width:"100%",maxWidth:900}}>
<div style={{background:"rgba(255,255,255,0.03)",borderRadius:20,
border:"1px solid rgba(255,255,255,0.08)",padding:20,
display:"flex",flexDirection:"column",alignItems:"center"}}>
{view==="radar" ? (
<RadarChart highlighted={highlighted} onSelect={setHighlighted}/>
) : (
<div style={{width:340,padding:"20px 10px"}}>
{PARTICIPANTS.map(p => {
const total = CATEGORIES.reduce((s,c)=>s+RAW_DATA[p][c],0);
const isHl = highlighted===p;
return (
<div key={p} onClick={()=>setHighlighted(isHl?null:p)}
style={{display:"flex",alignItems:"center",gap:10,marginBottom:10,
cursor:"pointer",opacity:highlighted&&!isHl?0.35:1,transition:"opacity 0.2s"}}>
<div style={{width:30,fontSize:11,color:isHl?"#fff":"rgba(255,255,255,0.6)",fontWeight:isHl?700:400}}>{p}</div>
<div style={{flex:1,display:"flex",height:18,borderRadius:4,overflow:"hidden"}}>
{CATEGORIES.map(cat => {
const w = total>0?(RAW_DATA[p][cat]/total)*100:0;
return <div key={cat} style={{width:`${w}%`,background:CAT_COLORS[cat],transition:"width 0.3s"}}
title={`${CAT_LABELS[cat]}: ${RAW_DATA[p][cat]}`}/>;
})}
</div>
<div style={{width:18,fontSize:11,color:"rgba(255,255,255,0.4)",textAlign:"right"}}>{total}</div>
</div>
);
})}
</div>
)}
<div style={{display:"flex",gap:14,marginTop:12,flexWrap:"wrap",justifyContent:"center"}}>
{CATEGORIES.map(cat => (
<div key={cat} style={{display:"flex",alignItems:"center",gap:5,fontSize:11}}>
<div style={{width:10,height:10,borderRadius:"50%",background:CAT_COLORS[cat]}}/>
<span style={{color:"rgba(255,255,255,0.6)"}}>{CAT_LABELS[cat]}</span>
</div>
))}
</div>
</div>
<div style={{display:"flex",flexDirection:"column",gap:6,minWidth:200}}>
<div style={{fontSize:10,letterSpacing:4,color:"rgba(255,255,255,0.3)",marginBottom:8,textTransform:"uppercase"}}>
Участники
</div>
{PARTICIPANTS.map(p => {
const total = CATEGORIES.reduce((s,c)=>s+RAW_DATA[p][c],0);
const dominant = CATEGORIES.reduce((a,b)=>RAW_DATA[p][a]>=RAW_DATA[p][b]?a:b);
const isHl = highlighted===p;
const col = CAT_COLORS[dominant];
return (
<div key={p} onClick={()=>setHighlighted(isHl?null:p)} style={{
display:"flex",alignItems:"center",gap:12,padding:"8px 14px",
borderRadius:10,cursor:"pointer",
background:isHl?`rgba(${parseInt(col.slice(1,3),16)},${parseInt(col.slice(3,5),16)},${parseInt(col.slice(5,7),16)},0.2)`:"rgba(255,255,255,0.03)",
border:`1px solid ${isHl?col:"rgba(255,255,255,0.06)"}`,
transition:"all 0.2s",opacity:highlighted&&!isHl?0.4:1}}>
<div style={{width:8,height:8,borderRadius:"50%",background:col,
boxShadow:isHl?`0 0 8px ${col}`:"none"}}/>
<span style={{fontWeight:isHl?700:400,fontSize:14,flex:1}}>{p}</span>
<div style={{display:"flex",gap:3,alignItems:"flex-end",height:28}}>
{CATEGORIES.map(cat => (
<div key={cat} style={{width:8,height:Math.max((RAW_DATA[p][cat]/3)*28,2),
background:CAT_COLORS[cat],borderRadius:2}}
title={`${CAT_LABELS[cat]}: ${RAW_DATA[p][cat]}`}/>
))}
</div>
<span style={{fontSize:11,color:"rgba(255,255,255,0.35)",minWidth:16,textAlign:"right"}}>{total}</span>
</div>
);
})}
<div style={{marginTop:16,padding:"12px 14px",borderRadius:10,
background:"rgba(255,255,255,0.04)",border:"1px solid rgba(255,255,255,0.08)"}}>
<div style={{fontSize:10,letterSpacing:3,color:"rgba(255,255,255,0.3)",marginBottom:8,textTransform:"uppercase"}}>
Итого по категориям
</div>
{totals.map(({cat,val}) => (
<div key={cat} style={{display:"flex",alignItems:"center",gap:8,marginBottom:6}}>
<div style={{width:8,height:8,borderRadius:"50%",background:CAT_COLORS[cat]}}/>
<span style={{fontSize:12,flex:1,color:"rgba(255,255,255,0.7)"}}>{CAT_LABELS[cat]}</span>
<div style={{width:80,height:6,background:"rgba(255,255,255,0.08)",borderRadius:3,overflow:"hidden"}}>
<div style={{width:`${(val/maxTotal)*100}%`,height:"100%",background:CAT_COLORS[cat],borderRadius:3}}/>
</div>
<span style={{fontSize:12,color:CAT_COLORS[cat],fontWeight:700,minWidth:20,textAlign:"right"}}>{val}</span>
</div>
))}
</div>
</div>
</div>
<div style={{marginTop:32,fontSize:11,color:"rgba(255,255,255,0.2)",textAlign:"center"}}>
Нажми на участника чтобы выделить · Переключай вид выше
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
</script>
</body>
</html>