| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width,initial-scale=1" />
- <title>Mono — Force Graph, Dense</title>
- <script src="https://cdn.jsdelivr.net/npm/echarts@6/dist/echarts.min.js"></script>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
- <style>
- :root{--bg:#F0EFEB;--dark:#1C1C1A}
- *{margin:0;padding:0;box-sizing:border-box}
- body{background:var(--bg);font-family:'Inter',sans-serif;padding:40px;-webkit-font-smoothing:antialiased;
- display:flex;justify-content:center}
- .card{background:var(--dark);color:#F0EFEB;border-radius:24px;padding:34px 34px 24px;
- width:min(1100px,100%)}
- h2{font-weight:700;font-size:19px;letter-spacing:-.02em;margin-bottom:4px}
- .sub{font-size:12px;color:#8F8E88;margin-bottom:10px}
- .src{font-size:9.5px;color:#55554F;margin-top:12px;letter-spacing:.08em;font-weight:500}
- #ch{height:78vh;min-height:620px;cursor:grab}
- </style>
- </head>
- <body>
- <div class="card">
- <h2>A galaxy of one hundred eighty services</h2>
- <div class="sub">8 domains self-organize · node = call volume · drag anything, the galaxy fights back · click empty space to replay</div>
- <div id="ch"></div>
- <div class="src">FORCE GRAPH · DENSE · SOURCE · SERVICE MESH</div>
- </div>
- <script>
- const INK='#1C1C1A',PAPER='#F0EFEB';
- const rnd=(i,k)=>((i*73856093)^(k*19349663))%1000/1000; // deterministic
- // ── 8 domain hubs, each with a swarm of services; hubs interconnect ──
- const DOMAINS=[
- ['auth',26,'#F0EFEB'],['billing',24,'#DCDAD2'],['content',28,'#C9C7BD'],
- ['search',20,'#B3B0A4'],['notify',22,'#9C9A91'],['media',20,'#8F8E88'],
- ['analytics',18,'#7C7A72'],['edge',14,'#6A6963'],
- ];
- const nodes=[],links=[];
- DOMAINS.forEach(([dom,count,shade],di)=>{
- // the hub
- nodes.push({name:dom,value:60+Math.round(rnd(di+1,3)*50),hub:true,shade,dom:di});
- // satellites: long-tail sizes, a couple of mid-weights per domain
- for(let i=0;i<count;i++){
- const v=i<3?14+rnd(di+2,i+4)*18:2+rnd(di+5,i+9)*9;
- const name=dom+'-'+String(i+1).padStart(2,'0');
- nodes.push({name,value:Math.round(v),hub:false,shade,dom:di});
- // satellite -> its hub
- links.push({source:name,target:dom,
- lineStyle:{width:.5+v*.05,color:'#3F3E38',opacity:.5}});
- // occasional satellite-to-satellite shortcut inside the domain
- if(i>1&&rnd(di+3,i+11)<.22){
- links.push({source:name,target:dom+'-'+String(1+Math.floor(rnd(di+4,i+13)*i)).padStart(2,'0'),
- lineStyle:{width:.5,color:'#33322D',opacity:.4}});
- }
- }
- });
- // hub-to-hub backbone: every domain talks to 2-3 others
- const H=DOMAINS.length;
- for(let a=0;a<H;a++)for(let b=a+1;b<H;b++){
- if(rnd(a+7,b+9)<.42){
- links.push({source:DOMAINS[a][0],target:DOMAINS[b][0],
- lineStyle:{width:1.6+rnd(a+2,b+3)*2.2,color:'#55534B',opacity:.65,curveness:.08}});
- }
- }
- // a few cross-domain satellite shortcuts — the messy real-world wires
- for(let k=0;k<26;k++){
- const a=nodes[1+Math.floor(rnd(k+1,17)*(nodes.length-1))];
- const b=nodes[1+Math.floor(rnd(k+3,23)*(nodes.length-1))];
- if(a.name!==b.name&&a.dom!==b.dom&&!a.hub&&!b.hub){
- links.push({source:a.name,target:b.name,
- lineStyle:{width:.5,color:'#2E2D29',opacity:.38,curveness:.15}});
- }
- }
- const g=echarts.init(document.getElementById('ch'));
- const opt={
- animationDuration:300,
- tooltip:{backgroundColor:PAPER,borderWidth:0,
- textStyle:{color:INK,fontFamily:'Inter',fontSize:12},padding:[10,14],
- formatter:p=>p.dataType==='node'?p.name+' — '+p.value+'k calls/day':''},
- series:[{
- type:'graph',layout:'force',
- force:{repulsion:46,edgeLength:[10,42],gravity:.16,friction:.22,layoutAnimation:true},
- roam:true,draggable:true,
- left:6,right:6,top:6,bottom:6,
- data:nodes.map(n=>({
- name:n.name,value:n.value,
- symbolSize:n.hub?16+Math.sqrt(n.value)*1.6:2.5+Math.sqrt(n.value)*1.5,
- itemStyle:{color:n.shade,borderWidth:n.hub?2:0,borderColor:INK},
- label:{show:n.hub,position:'inside',
- color:INK,fontFamily:'Inter',fontSize:9.5,fontWeight:800},
- })),
- links,
- emphasis:{
- focus:'adjacency',
- lineStyle:{color:PAPER,opacity:.9,width:1.4},
- label:{show:true,color:PAPER,position:'right'},
- },
- blur:{itemStyle:{opacity:.1},lineStyle:{opacity:.03}},
- }],
- };
- const play=()=>{g.clear();g.setOption(opt)};
- play();
- // click on empty canvas replays; clicks on nodes are for dragging
- g.getZr().on('click',e=>{if(!e.target)play()});
- addEventListener('resize',()=>g.resize());
- </script>
- </body>
- </html>
|