big-circular.html 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width,initial-scale=1" />
  6. <title>Mono — Circular Graph, Dense</title>
  7. <script src="https://cdn.jsdelivr.net/npm/echarts@6/dist/echarts.min.js"></script>
  8. <link rel="preconnect" href="https://fonts.googleapis.com">
  9. <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
  10. <style>
  11. :root{--bg:#F0EFEB;--dark:#1C1C1A}
  12. *{margin:0;padding:0;box-sizing:border-box}
  13. body{background:var(--bg);font-family:'Inter',sans-serif;padding:40px;-webkit-font-smoothing:antialiased;
  14. display:flex;justify-content:center}
  15. .card{background:var(--dark);color:#F0EFEB;border-radius:24px;padding:34px 34px 24px;
  16. width:min(1100px,100%)}
  17. h2{font-weight:700;font-size:19px;letter-spacing:-.02em;margin-bottom:4px}
  18. .sub{font-size:12px;color:#8F8E88;margin-bottom:10px}
  19. .src{font-size:9.5px;color:#55554F;margin-top:12px;letter-spacing:.08em;font-weight:500}
  20. #ch{height:78vh;min-height:620px;cursor:pointer}
  21. </style>
  22. </head>
  23. <body>
  24. <div class="card">
  25. <h2>Six hundred collaborations, one ring</h2>
  26. <div class="sub">60 repos · line = shared contributors · hover a repo to light up its world · click to replay</div>
  27. <div id="ch"></div>
  28. <div class="src">CIRCULAR GRAPH · DENSE · SOURCE · GIT LOG</div>
  29. </div>
  30. <script>
  31. const INK='#1C1C1A',PAPER='#F0EFEB';
  32. const rnd=(i,k)=>((i*73856093)^(k*19349663))%1000/1000; // deterministic
  33. // ── 60 repos in 5 org clusters; cluster = shade family ──
  34. const ORGS=[
  35. ['platform',16,'#F0EFEB'], // paper — the core
  36. ['product',14,'#C9C7BD'],
  37. ['data',12,'#A5A39A'],
  38. ['infra',10,'#8F8E88'],
  39. ['labs',8,'#6A6963'],
  40. ];
  41. const nodes=[];
  42. ORGS.forEach(([org,count],oi)=>{
  43. for(let i=0;i<count;i++){
  44. // activity: a few giants per cluster, long tail of small repos
  45. const heavy=i<2?30+rnd(oi+1,i+2)*45:4+rnd(oi+3,i+7)*16;
  46. nodes.push({
  47. name:org+'/'+org.slice(0,2)+'-'+String(i+1).padStart(2,'0'),
  48. org:oi,value:Math.round(heavy),
  49. });
  50. }
  51. });
  52. // links: dense inside a cluster, sparser across; weight = shared contributors
  53. const links=[];
  54. const idx={};nodes.forEach((n,i)=>idx[n.name]=i);
  55. nodes.forEach((a,i)=>{
  56. nodes.forEach((b,j)=>{
  57. if(j<=i)return;
  58. const same=a.org===b.org;
  59. const p=same?.16:.045;
  60. if(rnd(i+1,j+2)<p){
  61. links.push({source:i,target:j,
  62. w:1+Math.round(rnd(i+2,j+3)*6),same});
  63. }
  64. });
  65. });
  66. const g=echarts.init(document.getElementById('ch'));
  67. const opt={
  68. animationDuration:1800,animationEasing:'quarticOut',
  69. tooltip:{backgroundColor:PAPER,borderWidth:0,
  70. textStyle:{color:INK,fontFamily:'Inter',fontSize:12},padding:[10,14],
  71. formatter:p=>p.dataType==='node'
  72. ?p.name+' — '+p.value+' contributors'
  73. :p.data.rawS+' ↔ '+p.data.rawT+' — '+p.data.w+' shared'},
  74. series:[{
  75. type:'graph',layout:'circular',
  76. circular:{rotateLabel:true},
  77. left:24,right:24,top:24,bottom:24,
  78. data:nodes.map((n,i)=>({
  79. name:n.name,value:n.value,
  80. symbolSize:3.5+Math.sqrt(n.value)*1.7,
  81. itemStyle:{color:ORGS[n.org][2],borderWidth:0},
  82. // only the giants get a label — 60 names would be soup
  83. label:{show:n.value>=26,position:'right',distance:4,
  84. color:'#B3B0A4',fontFamily:'Inter',fontSize:8.5,fontWeight:600},
  85. })),
  86. links:links.map(l=>({
  87. source:l.source,target:l.target,
  88. rawS:nodes[l.source].name,rawT:nodes[l.target].name,w:l.w,
  89. lineStyle:{
  90. width:.4+l.w*.28,
  91. color:l.same?'#55534B':'#3A3934',
  92. opacity:l.same?.5:.32,
  93. curveness:.3,
  94. },
  95. })),
  96. emphasis:{
  97. focus:'adjacency',
  98. lineStyle:{color:PAPER,opacity:.9,width:1.6},
  99. label:{show:true,color:PAPER},
  100. },
  101. blur:{itemStyle:{opacity:.12},lineStyle:{opacity:.03}},
  102. }],
  103. };
  104. const play=()=>{g.clear();g.setOption(opt)};
  105. play();
  106. document.getElementById('ch').addEventListener('click',play);
  107. addEventListener('resize',()=>g.resize());
  108. </script>
  109. </body>
  110. </html>