| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135 |
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width,initial-scale=1" />
- <title>Lieflat Charts — Glance Gallery · 22 张参考实现</title>
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
- <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;--ink:#1C1C1A;--muted:#8F8E88;--faint:#C6C5BF;--grid:#DEDDD6}
- *{margin:0;padding:0;box-sizing:border-box}
- body{background:var(--bg);font-family:'Inter',sans-serif;color:var(--ink);padding:40px;-webkit-font-smoothing:antialiased}
- .grid2{display:grid;grid-template-columns:1fr 1fr;gap:22px;max-width:1400px;margin:0 auto}
- .card{background:var(--bg);border-radius:24px;padding:28px 28px 20px}
- .card.dark{background:var(--dark);color:#F0EFEB}
- .card.dark .sub{color:#8F8E88}
- .card.dark .src{color:#55554F}
- h2{font-weight:700;font-size:16.5px;letter-spacing:-.02em;margin-bottom:3px}
- .sub{font-size:11.5px;color:var(--muted);margin-bottom:14px}
- .src{font-size:9.5px;color:var(--faint);margin-top:10px;letter-spacing:.08em;font-weight:500}
- .ch{height:320px}
- .wrap{position:relative;height:320px}
- svg text{font-family:'Inter',sans-serif}
- .pop{transform-box:fill-box;transform-origin:center;animation:pop .5s cubic-bezier(.2,.7,.3,1.3) both}
- @keyframes pop{from{transform:scale(0)}to{transform:none}}
- .pagehead{max-width:1400px;margin:0 auto 26px;padding:0 4px}
- .pagehead h1{font-size:22px;letter-spacing:-.02em;font-weight:800}
- .pagehead p{font-size:12px;color:#8F8E88;margin-top:5px;line-height:1.7}
- .pagehead .badge{margin:0 2px}
- .badge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.08em;
- padding:3px 10px;border-radius:99px;margin-bottom:10px}
- .badge.del{background:#1C1C1A;color:#F0EFEB}
- .badge.pick{border:1.5px solid #1C1C1A;color:#1C1C1A}
- .badge.pair{border:1px dashed #B0AFA9;color:#8F8E88}
- .card.dark .badge.del{background:#F0EFEB;color:#1C1C1A}
- .card.dark .badge.pick{border-color:#F0EFEB;color:#F0EFEB}
- .card.dark .badge.pair{border-color:#55554F;color:#8F8E88}
- </style>
- </head>
- <body>
- <div class="pagehead">
- <h1>Glance 系(快读)· 22 张参考实现</h1>
- <p>粗笔画 · 提前聚合 · 3 秒读完。每张卡是一个图型的参考实现;代码在页内 <code>// ════ 图型名 ════</code> 注释块里,按 catalog.md 的卡内标题检索。</p>
- </div>
- <div class="grid2">
- <!-- ══ mono-demo ══ -->
- <div class="card">
- <h2>Daily active range</h2>
- <div class="sub">min – max concurrent users · 1–14 Feb</div>
- <div class="wrap"><canvas id="c1"></canvas></div>
- <div class="src">RANGE CAPSULES · MONO-DEMO · LIVE OPS</div>
- </div>
- <div class="card dark">
- <h2>How releases made us feel</h2>
- <div class="sub">reactions tagged in retro · H1 2026</div>
- <div class="ch" id="rose"></div>
- <div class="src">PETAL ROSE · MONO-DEMO · TEAM RETRO BOARD</div>
- </div>
- <div class="card">
- <h2>Revenue by plan</h2>
- <div class="sub">Q2 2026 · MRR, $K</div>
- <div class="wrap"><canvas id="c3"></canvas></div>
- <div class="src">CHUNKY BARS · MONO-DEMO · BILLING</div>
- </div>
- <!-- ══ mono-redesign · dot waffle ══ -->
- <div class="card">
- <h2>Where sign-ups come from</h2>
- <div class="sub">Q2 2026 · every dot = 1% of new accounts</div>
- <svg id="waffle" viewBox="0 0 380 300" width="100%" style="max-height:320px"></svg>
- <div class="src">DONUT, REDESIGNED · MONO-REDESIGN · ATTRIBUTION</div>
- </div>
- <!-- ══ mono-fancy ══ -->
- <div class="card">
- <h2>Trees planted, year by year</h2>
- <div class="sub">reforestation program · thousands of trees</div>
- <div class="ch" id="forest"></div>
- <div class="src">PICTORIAL BAR · MONO-FANCY · GREEN OPS</div>
- </div>
- <div class="card dark">
- <h2>Who works with whom</h2>
- <div class="sub">cross-team collaborations · Q2 2026 · line = shared project</div>
- <div class="ch" id="circular"></div>
- <div class="src">CIRCULAR GRAPH · MONO-FANCY · PROJECT DB</div>
- </div>
- <div class="card">
- <h2>Everything the platform ships</h2>
- <div class="sub">product areas and capabilities · Jun 2026</div>
- <div class="ch" id="tree"></div>
- <div class="src">TREE · MONO-FANCY · PRODUCT CATALOG</div>
- </div>
- <div class="card">
- <h2>Campaigns rain down, sign-ups flow</h2>
- <div class="sub">daily ad spend (from top, $K) vs sign-ups (flow, hundreds) · 30 days</div>
- <div class="ch" id="rainfall"></div>
- <div class="src">DUAL AREA · MONO-FANCY · GROWTH + ADS</div>
- </div>
- <div class="card">
- <h2>One dataset, three views</h2>
- <div class="sub">watch the same 12 products fly between views · every 3s</div>
- <div class="ch" id="morph"></div>
- <div class="src">SCATTER MORPH · MONO-FANCY · COHORTS</div>
- </div>
- <!-- ══ mono-fancy2 ══ -->
- <div class="card">
- <h2>Where we gained, where we bled</h2>
- <div class="sub">net accounts per segment · Q2 2026 · churn left, growth right</div>
- <div class="ch" id="negbar"></div>
- <div class="src">DIVERGING BAR · MONO-FANCY2 · CRM</div>
- </div>
- <div class="card dark">
- <h2>Integrations, pulled into orbit</h2>
- <div class="sub">force layout · node = monthly syncs · drag a node, the web fights back</div>
- <div class="ch" id="force"></div>
- <div class="src">FORCE GRAPH · NEW · INTEGRATION HUB</div>
- </div>
- <div class="card">
- <h2>Fifty markets, one wave</h2>
- <div class="sub">sign-ups per market · bars cascade in · click to replay</div>
- <div class="ch" id="wave"></div>
- <div class="src">STAGGER DELAY · MONO-FANCY2 · GROWTH</div>
- </div>
- <!-- ══ mono-fancy3 ══ -->
- <div class="card">
- <h2>Big slice, deep engagement</h2>
- <div class="sub">angle = share of users · radius = minutes per day · dotted rings at 15 / 30 / 45 min</div>
- <div class="ch" id="custompie"></div>
- <div class="src">CUSTOM PIE · MONO-FANCY3 · PRODUCT ANALYTICS</div>
- </div>
- <div class="card">
- <h2>Support load, day by day</h2>
- <div class="sub">tickets per hour · each row is its own axis · dot = volume</div>
- <div class="ch" id="singleaxis"></div>
- <div class="src">SINGLE AXIS · MONO-FANCY3 · SUPPORT DESK</div>
- </div>
- <div class="card">
- <h2>Response times, spread out</h2>
- <div class="sub">every dot = one ticket · jitter reveals the pile-up · by priority</div>
- <div class="ch" id="jitter"></div>
- <div class="src">JITTER STRIP · MONO-FANCY3 · SUPPORT DESK</div>
- </div>
- <!-- ══ mono-fancy4 ══ -->
- <div class="card">
- <h2>Eight products race for revenue</h2>
- <div class="sub">MRR by product, $K · 2019 → 2026 · leader wears black · click to replay</div>
- <div class="ch" id="race"></div>
- <div class="src">BAR RACE · MONO-FANCY4 · BILLING</div>
- </div>
- <div class="card">
- <h2>Concurrent users, streaming</h2>
- <div class="sub">one new reading every 300ms · window of 50 · click to restart</div>
- <div class="ch" id="stream"></div>
- <div class="src">DYNAMIC DATA · MONO-FANCY4 · LIVE OPS</div>
- </div>
- <div class="card">
- <h2>H1 revenue, drawn in one stroke</h2>
- <div class="sub">cumulative ARR · the line draws, the number counts · click to replay</div>
- <div class="ch" id="stroke"></div>
- <div class="src">DRAW-IN + COUNTER · MONO-FANCY4 · FINANCE</div>
- </div>
- <!-- G19 · violin -->
- <div class="card">
- <span class="badge">GLANCE 分布型 · 小提琴</span>
- <h2>How fast each plan gets an answer</h2>
- <div class="sub">hours to first reply · silhouette = where tickets pile up · paper tick = median</div>
- <svg id="violin" viewBox="0 0 400 320" preserveAspectRatio="xMidYMid meet"></svg>
- <div class="src">VIOLIN · MONO-GLANCE · SUPPORT DESK</div>
- </div>
- <!-- G20 · matrix heat -->
- <div class="card">
- <span class="badge">GLANCE 热力型 · 通用矩阵</span>
- <h2>Adoption runs hot on the new versions</h2>
- <div class="sub">5 versions × 6 features · shade = % of accounts using it · every cell carries its number</div>
- <svg id="gheat" viewBox="0 0 400 320" preserveAspectRatio="xMidYMid meet"></svg>
- <div class="src">MATRIX HEAT (GLANCE) · MONO-GLANCE · PRODUCT ANALYTICS</div>
- </div>
- <!-- G21 · rank strip -->
- <div class="card">
- <span class="badge">GLANCE 排名型 · RANK STRIP</span>
- <h2>Flows climbs to the top</h2>
- <div class="sub">one row per product, one cell per quarter · darker = higher rank · a bar race you can print</div>
- <svg id="rankstrip" viewBox="0 0 400 320" preserveAspectRatio="xMidYMid meet"></svg>
- <div class="src">RANK STRIP · MONO-GLANCE · BILLING</div>
- </div>
- <!-- G22 · aggregate sankey -->
- <div class="card">
- <span class="badge">GLANCE 流向型 · 聚合桑基</span>
- <h2>Channels pour into plans</h2>
- <div class="sub">ribbon width = accounts · channels on the left, the plans they land in on the right</div>
- <svg id="sankey" viewBox="0 0 400 320" preserveAspectRatio="xMidYMid meet"></svg>
- <div class="src">AGGREGATE SANKEY · MONO-GLANCE · ATTRIBUTION</div>
- </div>
- </div>
- <script>
- // ── shared Mono tokens ──
- const INK='#1C1C1A',PAPER='#F0EFEB',MUTED='#8F8E88',GRID='#DEDDD6';
- const L=['#1C1C1A','#4A4944','#6A6963','#8F8E88','#B0AFA9','#C6C5BF'];
- const LAD=['#1C1C1A','#4A4944','#8F8E88','#B0AFA9','#D8D7D1'];
- const tipLight={backgroundColor:INK,borderWidth:0,textStyle:{color:PAPER,fontFamily:'Inter',fontSize:12},padding:[10,14]};
- const tipDark={backgroundColor:PAPER,borderWidth:0,textStyle:{color:INK,fontFamily:'Inter',fontSize:12},padding:[10,14]};
- const rnd=(i,k)=>((i*73856093)^(k*19349663))%1000/1000; // deterministic
- // ── unified reveal: every chart waits to be scrolled into view; click replays.
- // fn charts manage their own timers via keep(); they're cleared on replay.
- const timers={};
- const obsReveal=(id,fn)=>{
- const el=document.getElementById(id);
- const go=()=>{(timers[id]||[]).forEach(clearInterval);timers[id]=[];fn(el)};
- const io=new IntersectionObserver(es=>{
- if(es[0].isIntersecting){go();io.disconnect()}
- },{threshold:.35});
- io.observe(el);
- el.style.cursor='pointer';
- el.addEventListener('click',go);
- };
- const keep=(id,t)=>{(timers[id]=timers[id]||[]).push(t)};
- const eReveal=(id,opt)=>obsReveal(id,el=>{
- const g=echarts.getInstanceByDom(el)||echarts.init(el);
- g.clear();g.setOption(opt);
- });
- const cjs={};
- const cReveal=(id,mkCfg)=>obsReveal(id,el=>{
- cjs[id]?.destroy();
- cjs[id]=new Chart(el,mkCfg());
- });
- Chart.defaults.color=MUTED;
- Chart.defaults.font.family='Inter';
- Chart.defaults.font.size=10.5;
- Chart.defaults.borderColor=GRID;
- Chart.defaults.plugins.legend.display=false;
- Chart.defaults.plugins.tooltip.backgroundColor=INK;
- Chart.defaults.plugins.tooltip.titleColor='#F0EFEB';
- Chart.defaults.plugins.tooltip.bodyColor='#A5A49E';
- Chart.defaults.plugins.tooltip.padding=12;
- Chart.defaults.plugins.tooltip.cornerRadius=12;
- Chart.defaults.plugins.tooltip.displayColors=false;
- // ════ mono-demo · 1: floating range capsules ════
- (()=>{
- const ranges=[[195,235],[165,192],[150,232],[73,168],[122,202],[182,192],[138,178],[162,227],[195,235],[228,305],[218,232],[165,232],[118,210],[195,232]];
- cReveal('c1',()=>({
- type:'bar',
- data:{labels:ranges.map((_,i)=>i+1),
- datasets:[{data:ranges,backgroundColor:INK,borderRadius:99,borderSkipped:false,barPercentage:.32}]},
- options:{
- maintainAspectRatio:false,
- animation:{duration:800,easing:'easeOutQuart',delay:c=>c.type==='data'?c.dataIndex*40:0},
- plugins:{tooltip:{callbacks:{label:i=>i.raw[0]+'K – '+i.raw[1]+'K users'}}},
- scales:{
- x:{grid:{display:false},border:{display:false},
- ticks:{padding:6,font:{size:9},callback:v=>[0,6,13].includes(v)?(v+1)+' FEB':''}},
- y:{min:50,max:320,grid:{display:false},border:{display:false},
- ticks:{padding:8,maxTicksLimit:6,callback:v=>v+'K',font:{size:9.5}}},
- },
- },
- }));
- })();
- // ════ mono-demo · 2: petal rose on dark ════
- (()=>{
- const EMO=[
- ['Happiness',12],['Awe',10],['Admiration',5],['Surprise',12],
- ['Sadness',6],['Fear',4],['Anger',2],['Anticipation',5],
- ];
- const MAXV=12;
- const petal=v=>{const t=v/MAXV;return t>.8?'#F2F1ED':t>.6?'#E4E3DD':t>.35?'#C9C8C1':'#A8A7A0'};
- eReveal('rose',{
- animationDuration:1100,animationEasing:'quarticOut',
- tooltip:{...tipDark,formatter:p=>p.name+' — '+EMO[p.dataIndex][1]},
- series:[
- {type:'pie',silent:false,radius:['14%','92%'],center:['50%','50%'],
- itemStyle:{color:'#32312D',borderRadius:16,borderColor:'#1C1C1A',borderWidth:5},
- label:{show:false},emphasis:{scale:false},
- data:EMO.map(([n])=>({name:n,value:1})),z:1},
- {type:'pie',silent:true,radius:['14%','92%'],center:['50%','50%'],
- itemStyle:{color:'transparent'},label:{position:'inside'},
- data:EMO.map(([n,v])=>({name:n,value:1,
- label:{formatter:`{n|${v}}\n{l|${n}}`,rich:{
- n:{fontSize:17,fontWeight:700,fontFamily:'Inter',lineHeight:20,color:v>=8?'#1C1C1A':'#C8C7C1'},
- l:{fontSize:9.5,fontFamily:'Inter',color:v>=8?'#55554F':'#8F8E88'},
- }}})),z:3},
- {type:'pie',roseType:'area',silent:true,radius:['14%','88%'],center:['50%','50%'],
- itemStyle:{borderRadius:14,borderColor:'#1C1C1A',borderWidth:5},label:{show:false},
- data:EMO.map(([n,v])=>({name:n,value:v,itemStyle:{color:petal(v)}})),z:2},
- ],
- });
- })();
- // ════ mono-demo · 3: chunky bars, values on top ════
- (()=>{
- cReveal('c3',()=>({
- type:'bar',
- data:{labels:['STARTER','PRO','TEAM','ENT'],
- datasets:[{data:[182,486,391,274],
- backgroundColor:['#C6C5BF',INK,'#8F8E88','#B0AFA9'],
- borderRadius:{topLeft:99,topRight:99},borderSkipped:false,barPercentage:.52}]},
- options:{
- maintainAspectRatio:false,
- animation:{duration:900,easing:'easeOutQuart',delay:c=>c.type==='data'?c.dataIndex*110:0},
- plugins:{tooltip:{callbacks:{label:i=>'$'+i.raw+'K MRR'}}},
- scales:{
- x:{grid:{display:false},border:{display:false},ticks:{padding:8,font:{size:9,weight:600}}},
- y:{display:false,suggestedMax:540},
- },
- },
- plugins:[{
- id:'topValues',
- afterDatasetsDraw(chart){
- const {ctx}=chart;
- chart.getDatasetMeta(0).data.forEach((bar,i)=>{
- ctx.save();ctx.font='700 13px Inter';ctx.fillStyle=INK;ctx.textAlign='center';
- ctx.fillText('$'+chart.data.datasets[0].data[i]+'K',bar.x,bar.y-10);ctx.restore();
- });
- },
- }],
- }));
- })();
- // ════ mono-redesign · donut → waffle of dots ════
- (()=>{
- const NS='http://www.w3.org/2000/svg';
- const mkEl=(svg,t,a)=>{const n=document.createElementNS(NS,t);for(const k in a)n.setAttribute(k,a[k]);svg.appendChild(n);return n};
- const D=[['Search',34],['Referral',27],['Social',18],['Partners',12],['Paid',9]];
- obsReveal('waffle',s=>{
- s.innerHTML='';
- const COLS=10,CELL=21,R=7.5,X0=8,Y0=10;
- let idx=0;
- D.forEach(([name,v],g)=>{
- for(let k=0;k<v;k++){
- const c=idx+k,row=Math.floor(c/COLS),col=c%COLS;
- mkEl(s,'circle',{cx:X0+col*CELL+R,cy:Y0+row*CELL+R,r:R,fill:LAD[g],
- class:'pop',style:`animation-delay:${c*.008+g*.05}s`});
- }
- idx+=v;
- });
- D.forEach(([name,v],g)=>{
- const y=26+g*40;
- mkEl(s,'circle',{cx:246,cy:y,r:6,fill:LAD[g]});
- const n=mkEl(s,'text',{x:260,y:y-1,'font-size':10.5,'font-weight':600,fill:INK});
- n.textContent=name;
- const val=mkEl(s,'text',{x:260,y:y+13,'font-size':15,'font-weight':800,fill:g<2?INK:'#8F8E88'});
- val.textContent=v+'%';
- });
- });
- })();
- // ════ mono-fancy · 1: pictorial bar forest ════
- (()=>{
- const TREE='path://M20,0 L38,28 L29,28 L40,46 L26,46 L26,58 L14,58 L14,46 L0,46 L11,28 L2,28 Z';
- const years=['2022','2023','2024','2025','2026'];
- const vals=[26,41,63,88,117];
- eReveal('forest',{
- animationDuration:900,animationEasing:'quarticOut',animationDelay:i=>i*120,
- tooltip:{...tipLight,formatter:p=>p.name+' — '+vals[p.dataIndex]+'k trees'},
- grid:{left:44,right:56,top:10,bottom:8},
- yAxis:{type:'category',data:years,inverse:true,
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:10.5,fontWeight:700}},
- xAxis:{show:false,max:13},
- series:[{
- type:'pictorialBar',symbol:TREE,
- symbolRepeat:true,symbolSize:[17,24],symbolMargin:3,symbolClip:true,
- data:vals.map((v,i)=>({name:years[i],value:v/10,itemStyle:{color:L[Math.max(0,4-i)]}})),
- label:{show:true,position:'right',offset:[8,0],
- color:INK,fontFamily:'Inter',fontSize:12,fontWeight:700,
- formatter:p=>vals[p.dataIndex]+'k'},
- z:10,
- },{
- type:'pictorialBar',symbol:TREE,
- symbolRepeat:'fixed',symbolSize:[17,24],symbolMargin:3,symbolClip:true,
- itemStyle:{color:'#DEDDD6'},data:years.map(()=>13),silent:true,z:5,
- }],
- });
- })();
- // ════ mono-fancy · 2: circular graph on dark ════
- (()=>{
- const teams=[
- ['Product',52],['Design',38],['Frontend',46],['Backend',44],
- ['Data',30],['Growth',34],['Support',22],['Ops',18],['Legal',9],['Finance',12],
- ];
- const links=[
- [0,1,9],[0,2,8],[0,3,7],[1,2,9],[2,3,6],[3,4,7],[0,5,6],[5,4,5],
- [5,6,4],[6,2,3],[7,3,4],[7,9,3],[8,9,2],[0,8,2],[4,2,4],[1,5,3],
- ];
- eReveal('circular',{
- animationDuration:1200,animationEasing:'quarticOut',
- tooltip:tipDark,
- series:[{
- type:'graph',layout:'circular',circular:{rotateLabel:false},
- left:52,right:52,top:34,bottom:34,
- data:teams.map(([n,v],i)=>({
- name:n,value:v,symbolSize:v*.62,
- itemStyle:{color:i<4?PAPER:i<8?'#8F8E88':'#4A4944',borderWidth:0},
- label:{show:true,position:'right',distance:7,color:'#B3B0A4',fontFamily:'Inter',fontSize:9.5,fontWeight:600},
- })),
- links:links.map(([s,t,w])=>({
- source:s,target:t,
- lineStyle:{width:w*.7,color:'#4A4840',opacity:.55,curveness:.28},
- })),
- emphasis:{focus:'adjacency',lineStyle:{color:PAPER,opacity:.8}},
- }],
- });
- })();
- // ════ mono-fancy · 3: LR tree ════
- (()=>{
- const b=(name,kids,shade)=>({name,itemStyle:{color:shade},lineStyle:{color:shade},
- children:kids.map(k=>({name:k,itemStyle:{color:shade},lineStyle:{color:shade}}))});
- eReveal('tree',{
- animationDuration:1100,animationEasing:'quarticOut',
- tooltip:tipLight,
- series:[{
- type:'tree',layout:'orthogonal',orient:'LR',
- left:64,right:96,top:8,bottom:8,
- symbol:'circle',symbolSize:7,
- initialTreeDepth:2,expandAndCollapse:false,roam:false,
- itemStyle:{borderWidth:0},
- lineStyle:{width:1.4,curveness:.5},
- label:{fontFamily:'Inter',fontSize:10,fontWeight:600,color:'#6A6963',position:'left'},
- leaves:{label:{position:'right',color:MUTED,fontWeight:500}},
- data:[{
- name:'Platform',itemStyle:{color:INK},lineStyle:{color:'#C6C5BF'},
- label:{fontSize:11.5,color:INK},
- children:[
- b('Editor',['Blocks','Tables','Comments','History'],L[0]),
- b('Automate',['Workflows','Triggers','Webhooks'],L[2]),
- b('Collaborate',['Spaces','Guests','Mentions'],L[3]),
- b('Integrate',['API','Slack','GitHub'],L[4]),
- ],
- }],
- }],
- });
- })();
- // ════ mono-fancy · 4: rainfall dual area ════
- (()=>{
- const days=Array.from({length:30},(_,i)=>i+1);
- const flow=[42,45,44,48,52,55,53,58,64,62,66,71,69,75,82,79,84,80,86,92,88,95,91,97,104,101,108,105,112,118];
- const rain=[2,0,4,8,3,0,6,12,7,3,9,15,6,2,11,5,8,14,4,9,6,13,3,7,10,5,12,8,15,6];
- eReveal('rainfall',{
- animationDuration:1200,animationEasing:'quarticOut',
- tooltip:{...tipLight,trigger:'axis',
- formatter:p=>`Day ${p[0].axisValue} — spend $${rain[p[0].dataIndex]}K · ${p[0].value} sign-ups`},
- axisPointer:{link:[{xAxisIndex:'all'}],lineStyle:{color:'#B0AFA9'}},
- grid:[
- {left:44,right:14,top:8,height:'26%'},
- {left:44,right:14,top:'42%',bottom:26},
- ],
- xAxis:[
- {gridIndex:0,type:'category',data:days,axisLine:{show:false},axisTick:{show:false},axisLabel:{show:false},position:'bottom'},
- {gridIndex:1,type:'category',data:days,axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:9.5,interval:6,formatter:v=>'D'+v}},
- ],
- yAxis:[
- {gridIndex:0,inverse:true,max:18,splitLine:{show:false},
- axisLabel:{color:'#B0AFA9',fontFamily:'Inter',fontSize:9,formatter:'${value}K'},
- axisLine:{show:false},axisTick:{show:false}},
- {gridIndex:1,splitLine:{lineStyle:{color:'#DEDDD6'}},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:9.5},
- axisLine:{show:false},axisTick:{show:false}},
- ],
- series:[
- {name:'spend',type:'bar',xAxisIndex:0,yAxisIndex:0,data:rain,
- itemStyle:{color:'#B0AFA9',borderRadius:[0,0,4,4]},barWidth:'55%'},
- {name:'sign-ups',type:'line',xAxisIndex:1,yAxisIndex:1,data:flow,
- smooth:.4,symbol:'none',lineStyle:{color:INK,width:2.2},
- areaStyle:{color:{type:'linear',x:0,y:0,x2:0,y2:1,
- colorStops:[{offset:0,color:'rgba(28,28,26,.22)'},{offset:1,color:'rgba(28,28,26,0)'}]}}},
- ],
- });
- })();
- // ════ mono-fancy · 5: universal transition morph ════
- (()=>{
- const P12=[
- ['Editor',12,9.1,486],['Boards',18,8.4,391],['Forms',9,8.8,274],
- ['Docs',15,8.0,318],['Chat',7,7.2,182],['Vault',24,7.8,226],
- ['Flows',21,8.6,352],['Views',11,7.5,198],['Sync',16,6.9,141],
- ['Pages',8,8.2,243],['Grid',19,7.1,167],['Hub',13,6.6,118],
- ];
- const shade=i=>L[Math.min(5,Math.floor(i/2))];
- const axis={splitLine:{lineStyle:{color:'#DEDDD6'}},axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:9.5}};
- const VIEWS=[
- {opt:{
- grid:{left:38,right:16,top:30,bottom:30},
- xAxis:{...axis,type:'value',min:5,max:26,name:'PRICE $',nameTextStyle:{color:'#C6C5BF',fontSize:8.5}},
- yAxis:{...axis,type:'value',min:6,max:9.6,name:'CSAT',nameTextStyle:{color:'#C6C5BF',fontSize:8.5}},
- series:[{
- id:'p',type:'scatter',universalTransition:true,
- symbolSize:d=>Math.sqrt(d[3])*1.35,
- data:P12.map((d,i)=>({name:d[0],value:[d[1],d[2],d[0],d[3]],
- groupId:d[0],itemStyle:{color:shade(i)}})),
- label:{show:true,position:'top',color:MUTED,fontFamily:'Inter',fontSize:8.5,
- formatter:p=>p.name},
- }],
- }},
- {opt:{
- grid:{left:38,right:16,top:30,bottom:44},
- xAxis:{...axis,type:'category',
- data:[...P12].sort((a,b)=>b[3]-a[3]).map(d=>d[0]),
- axisLabel:{...axis.axisLabel,rotate:38,fontSize:8.5}},
- yAxis:{...axis,type:'value',max:520},
- series:[{
- id:'p',type:'bar',universalTransition:true,
- data:[...P12].sort((a,b)=>b[3]-a[3]).map((d,i)=>({name:d[0],value:d[3],
- groupId:d[0],itemStyle:{color:shade(i),borderRadius:[6,6,0,0]}})),
- barCategoryGap:'32%',
- }],
- }},
- {opt:{
- grid:{left:0,right:0,top:0,bottom:0},
- xAxis:{show:false,type:'value'},yAxis:{show:false,type:'value'},
- series:[{
- id:'p',type:'pie',universalTransition:true,
- radius:['26%','72%'],center:['50%','50%'],
- itemStyle:{borderColor:'#F0EFEB',borderWidth:2,borderRadius:6},
- label:{color:'#6A6963',fontFamily:'Inter',fontSize:9,formatter:'{b}'},
- labelLine:{lineStyle:{color:'#C6C5BF'}},
- data:[...P12].sort((a,b)=>b[3]-a[3]).map((d,i)=>({name:d[0],value:d[3],
- groupId:d[0],itemStyle:{color:shade(i)}})),
- }],
- }},
- ];
- const base={animationDurationUpdate:1100,animationEasingUpdate:'cubicInOut',tooltip:{...tipLight}};
- obsReveal('morph',el=>{
- const g=echarts.getInstanceByDom(el)||echarts.init(el);
- g.clear();
- let vi=0;
- g.setOption({...base,...VIEWS[0].opt});
- keep('morph',setInterval(()=>{
- vi=(vi+1)%VIEWS.length;
- g.setOption({...base,...VIEWS[vi].opt},{replaceMerge:['xAxis','yAxis','series']});
- },3000));
- });
- })();
- // ════ mono-fancy2 · 2: diverging bar ════
- (()=>{
- const D=[
- ['Enterprise',86],['Team',54],['Pro',31],['Starter',12],
- ['Legacy Basic',-18],['Trial expired',-42],['Free dormant',-67],
- ];
- eReveal('negbar',{
- animationDuration:900,animationEasing:'quarticOut',animationDelay:i=>i*80,
- tooltip:{...tipLight,formatter:p=>p.name+' — '+(p.value>0?'+':'')+p.value+' accounts'},
- grid:{left:96,right:52,top:8,bottom:8},
- xAxis:{type:'value',
- splitLine:{lineStyle:{color:'#DEDDD6'}},
- axisLine:{show:false},axisTick:{show:false},axisLabel:{show:false}},
- yAxis:{type:'category',data:D.map(d=>d[0]),inverse:true,
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:'#6A6963',fontFamily:'Inter',fontSize:9.5,fontWeight:600}},
- series:[{
- type:'bar',barWidth:16,
- data:D.map(([n,v])=>({name:n,value:v,
- itemStyle:{color:v>0?INK:'#B0AFA9',
- borderRadius:v>0?[0,9,9,0]:[9,0,0,9]}})),
- label:{show:true,fontFamily:'Inter',fontSize:11,fontWeight:700,
- position:'outside',
- formatter:p=>(p.value>0?'+':'')+p.value,color:INK},
- markLine:{symbol:'none',silent:true,label:{show:false},
- lineStyle:{color:'#8F8E88',width:1.5},
- data:[{xAxis:0}]},
- }],
- });
- })();
- // ════ force graph on dark: the network shakes itself into place ════
- // (replaced the axis-break bar — bars must never lie about length; house rule)
- (()=>{
- const hubs=[['Core API',52,0]];
- const apps=[
- ['Slack',34,1],['GitHub',30,1],['Figma',22,1],['Notion',26,1],
- ['Linear',18,1],['Drive',16,1],['Zoom',10,2],['Jira',14,2],
- ['Sheets',12,2],['Intercom',8,2],['Stripe',20,1],['Segment',9,2],
- ];
- const nodes=[...hubs,...apps];
- const links=[
- ...apps.map((a,i)=>({source:a[0],target:'Core API',
- lineStyle:{width:Math.max(.8,a[1]*.09),color:'#4A4840',opacity:.6}})),
- // a few app-to-app side roads
- {source:'Slack',target:'GitHub',lineStyle:{width:1.2,color:'#3A3934',opacity:.5}},
- {source:'GitHub',target:'Linear',lineStyle:{width:1.2,color:'#3A3934',opacity:.5}},
- {source:'Figma',target:'Notion',lineStyle:{width:1,color:'#3A3934',opacity:.5}},
- {source:'Stripe',target:'Sheets',lineStyle:{width:1,color:'#3A3934',opacity:.5}},
- ];
- eReveal('force',{
- animationDuration:1200,animationEasing:'quarticOut',
- tooltip:{...tipDark,formatter:p=>p.dataType==='node'?p.name+' — '+p.value+'k syncs/mo':''},
- series:[{
- type:'graph',layout:'force',
- force:{repulsion:220,edgeLength:[36,110],gravity:.12,friction:.18},
- roam:false,draggable:true,
- left:14,right:14,top:14,bottom:14,
- data:nodes.map(([n,v,tier])=>({
- name:n,value:v,symbolSize:8+v*.75,
- itemStyle:{color:tier===0?PAPER:tier===1?'#B3B0A4':'#6A6963'},
- label:{show:true,position:'right',distance:6,
- color:tier===0?PAPER:'#8F8E88',fontFamily:'Inter',
- fontSize:tier===0?10.5:9,fontWeight:tier===0?800:600},
- })),
- links,
- emphasis:{focus:'adjacency',lineStyle:{color:PAPER,opacity:.85}},
- }],
- });
- })();
- // ════ mono-fancy2 · 4: stagger wave ════
- (()=>{
- const n=50;
- const vals=Array.from({length:n},(_,i)=>
- Math.round(30+55*Math.sin(i/7.5)+18*Math.sin(i/2.6)+(i%5)*3+28));
- eReveal('wave',{
- tooltip:{...tipLight,formatter:p=>'Market '+(p.dataIndex+1)+' — '+p.value},
- grid:{left:36,right:10,top:14,bottom:24},
- xAxis:{type:'category',data:vals.map((_,i)=>i+1),
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:9,interval:9}},
- yAxis:{type:'value',
- splitLine:{lineStyle:{color:'#DEDDD6'}},
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:9.5}},
- series:[{
- type:'bar',barCategoryGap:'26%',
- data:vals.map((v,i)=>({value:v,
- itemStyle:{color:L[Math.min(5,Math.floor((120-v)/16))],borderRadius:[4,4,0,0]}})),
- animationDelay:i=>i*36,
- animationDuration:640,animationEasing:'elasticOut',
- }],
- animationDurationUpdate:640,
- animationDelayUpdate:i=>i*36,
- });
- })();
- // ════ mono-fancy3 · 1: custom double-encoded pie ════
- (()=>{
- const D=[
- ['Editor',34,42],['Boards',22,31],['Docs',18,38],
- ['Chat',12,11],['Automations',8,24],['Other',6,7],
- ];
- const SCALE=45;
- const TAU=Math.PI*2;
- const byShare=[...D].sort((a,b)=>b[1]-a[1]);
- const minsDesc=D.map(d=>d[2]).sort((a,b)=>b-a);
- let acc=0;
- const slices=byShare.map(([n,share,mins])=>{
- const p0=-Math.PI/2+acc/100*TAU, p1=-Math.PI/2+(acc+share)/100*TAU;
- acc+=share;
- return {n,share,mins,p0,p1,color:L[minsDesc.indexOf(mins)]};
- });
- eReveal('custompie',{
- animationDuration:900,animationEasing:'cubicOut',animationDelay:i=>i*130,
- tooltip:{...tipLight,formatter:p=>{
- const s=slices[p.dataIndex];
- return s.n+' — '+s.share+'% of users · '+s.mins+' min/day';
- }},
- xAxis:{show:false,min:-1,max:1},
- yAxis:{show:false,min:-1,max:1},
- grid:{left:0,right:0,top:0,bottom:0},
- series:[{
- type:'custom',
- renderItem:(params,api)=>{
- const s=slices[params.dataIndex];
- const cx=api.getWidth()/2, cy=api.getHeight()/2+6;
- const R=Math.min(api.getWidth(),api.getHeight())*.37;
- const r0=R*.24;
- const rOf=m=>r0+(R-r0)*m/SCALE;
- const pm=(s.p0+s.p1)/2, lr=R+14;
- const cos=Math.cos(pm);
- const kids=[];
- if(params.dataIndex===0){
- [15,30,45].forEach(m=>kids.push({type:'circle',
- shape:{cx,cy,r:rOf(m)},
- style:{fill:'none',stroke:'#D8D6CE',lineWidth:1,lineDash:[3,4]}}));
- }
- kids.push({type:'sector',
- shape:{cx,cy,r0,r:rOf(s.mins),startAngle:s.p0,endAngle:s.p1,clockwise:true,cornerRadius:5},
- style:{fill:s.color,stroke:PAPER,lineWidth:3},
- enterFrom:{shape:{r:r0}}});
- kids.push({type:'text',style:{
- x:cx+cos*lr, y:cy+Math.sin(pm)*lr,
- text:s.n+' '+s.share+'% · '+s.mins+'m',
- font:'600 10px Inter',fill:'#55544E',
- align:Math.abs(cos)<.25?'center':(cos>0?'left':'right'),
- verticalAlign:'middle'},
- enterFrom:{style:{opacity:0}}});
- return {type:'group',children:kids};
- },
- data:slices.map(s=>s.share),
- }],
- });
- })();
- // ════ mono-fancy3 · 3: single-axis small multiples ════
- (()=>{
- const days=['MON','TUE','WED','THU','FRI','SAT','SUN'];
- const hours=Array.from({length:24},(_,i)=>i);
- const mkRow=di=>hours.map(h=>{
- const workday=di<5, core=h>=9&&h<=17;
- let v=Math.round((workday&&core?6+5*Math.sin((h-9)/8*Math.PI):0)+((h+di)%5===0?2:0)+(h>=22||h<=5?0:1));
- return [h,v];
- }).filter(d=>d[1]>0);
- eReveal('singleaxis',{
- animationDuration:500,animationEasing:'backOut',
- tooltip:{...tipLight,formatter:p=>days[p.seriesIndex]+' '+String(p.value[0]).padStart(2,'0')+':00 — '+p.value[1]+' tickets'},
- title:days.map((d,i)=>({
- text:d,textBaseline:'middle',top:(i+.5)*100/7-1+'%',left:0,
- textStyle:{fontSize:9,fontWeight:700,fontFamily:'Inter',color:MUTED},
- })),
- singleAxis:days.map((d,i)=>({
- left:52,right:14,type:'category',boundaryGap:false,
- data:hours.map(h=>String(h).padStart(2,'0')),
- top:i*100/7+6+'%',height:100/7-8+'%',
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{show:i===6,color:MUTED,fontFamily:'Inter',fontSize:8.5,interval:3},
- splitLine:{show:false},
- })),
- series:days.map((d,i)=>({
- singleAxisIndex:i,coordinateSystem:'singleAxis',
- type:'scatter',
- data:mkRow(i),
- symbolSize:dd=>dd[1]*2.6,
- itemStyle:{color:i<5?L[Math.min(4,i)]:'#C6C5BF'},
- animationDelay:di=>i*160+di*14,
- })),
- });
- })();
- // ════ mono-fancy3 · 4: jitter strip ════
- (()=>{
- const cats=['P0 CRITICAL','P1 HIGH','P2 NORMAL','P3 LOW'];
- const mkPts=(ci,n,median,spread,outliers)=>{
- const pts=[];
- for(let i=0;i<n;i++){
- const u=rnd(i+1,ci+1), v=rnd(i+7,ci+3);
- let x=median+ (u-.5)*spread*2 + (v>.5?u*spread*.6:0);
- if(i<outliers) x=median+spread*2.2+u*spread*3;
- const jy=ci+ (rnd(i+13,ci+5)-.5)*.58;
- pts.push([Math.max(.2,x),jy]);
- }
- return pts;
- };
- const groups=[
- mkPts(0,38,.8,.5,2),
- mkPts(1,64,2.4,1.1,3),
- mkPts(2,110,6.5,2.4,4),
- mkPts(3,72,14,4.5,3),
- ];
- const flat=groups.flat();
- eReveal('jitter',{
- animationDuration:450,animationEasing:'cubicOut',
- animationDelay:i=>i<flat.length?Math.round(flat[i][1])*260+(i%37)*9:0,
- tooltip:{...tipLight,formatter:p=>cats[Math.round(p.value[1])]+' — '+p.value[0].toFixed(1)+'h to resolve'},
- grid:{left:86,right:16,top:14,bottom:30},
- xAxis:{type:'value',name:'HOURS TO RESOLVE',nameTextStyle:{color:'#C6C5BF',fontSize:8.5},
- splitLine:{lineStyle:{color:'#DEDDD6'}},
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:MUTED,fontFamily:'Inter',fontSize:9.5,formatter:v=>v+'h'}},
- yAxis:{type:'value',min:-.6,max:3.6,inverse:true,
- splitLine:{show:false},axisLine:{show:false},axisTick:{show:false},
- axisLabel:{show:false}},
- series:[{
- type:'scatter',
- data:flat,
- symbolSize:7,
- itemStyle:{color:p=>L[Math.round(p.value[1])],opacity:.62},
- },{
- type:'scatter',silent:true,symbolSize:0,
- data:cats.map((c,i)=>({value:[0,i],label:{show:true,position:'left',offset:[-6,0],
- color:'#6A6963',fontFamily:'Inter',fontSize:9,fontWeight:700,formatter:c}})),
- }],
- });
- })();
- // ════ mono-fancy4 · 1: bar race ════
- (()=>{
- const P=['Editor','Boards','Docs','Chat','Flows','Vault','Pages','Sync'];
- const YEARS=[2019,2020,2021,2022,2023,2024,2025,2026];
- const vals=[P.map((_,i)=>[42,38,30,26,18,14,10,8][i])];
- for(let y=1;y<YEARS.length;y++)
- vals.push(vals[y-1].map((v,i)=>v*(1.04+rnd(i+1,y+1)*.5)));
- obsReveal('race',el=>{
- const g=echarts.getInstanceByDom(el)||echarts.init(el);
- g.clear();
- let step=0;
- const frame=()=>{
- const row=vals[step];
- const rank=[...row].map((v,i)=>[v,i]).sort((a,b)=>b[0]-a[0]).map(d=>d[1]);
- g.setOption({
- animationDuration:0,animationDurationUpdate:950,animationEasingUpdate:'linear',
- tooltip:{...tipLight,valueFormatter:v=>'$'+Math.round(v)+'K'},
- grid:{left:64,right:64,top:8,bottom:8},
- xAxis:{type:'value',max:'dataMax',
- splitLine:{show:false},axisLine:{show:false},axisTick:{show:false},axisLabel:{show:false}},
- yAxis:{type:'category',data:P,inverse:true,max:7,
- animationDuration:300,animationDurationUpdate:300,
- axisLine:{show:false},axisTick:{show:false},
- axisLabel:{color:'#6A6963',fontFamily:'Inter',fontSize:9.5,fontWeight:600}},
- series:[{
- type:'bar',realtimeSort:true,barCategoryGap:'32%',
- data:row.map((v,i)=>({value:v,
- itemStyle:{color:L[Math.min(5,rank.indexOf(i))],borderRadius:99}})),
- label:{show:true,position:'right',valueAnimation:true,
- fontFamily:'Inter',fontSize:12,fontWeight:800,color:INK,
- formatter:p=>'$'+Math.round(p.value)+'K'},
- }],
- graphic:[{id:'yr',type:'text',right:18,bottom:14,
- style:{text:YEARS[step],font:'800 44px Inter',fill:'#DEDDD6'}}],
- });
- step=(step+1)%YEARS.length;
- };
- frame();
- keep('race',setInterval(frame,1150));
- });
- })();
- // ════ mono-fancy4 · 2: dynamic data stream ════
- (()=>{
- const WIN=50;
- obsReveal('stream',el=>{
- const g=echarts.getInstanceByDom(el)||echarts.init(el);
- g.clear();
- let t=0,v=64;
- const data=[];
- for(;t<WIN;t++){v=Math.max(30,v+(rnd(t+1,7)-.48)*9);data.push(Math.round(v))}
- const draw=()=>g.setOption({
- animation:true,animationDuration:0,animationDurationUpdate:260,animationEasingUpdate:'linear',
- tooltip:{...tipLight,valueFormatter:x=>x+'k users'},
- grid:{left:14,right:58,top:44,bottom:20},
- xAxis:{type:'category',data:data.map((_,i)=>t-WIN+i),boundaryGap:false,
- axisLine:{show:false},axisTick:{show:false},axisLabel:{show:false},splitLine:{show:false}},
- yAxis:{show:false,min:20,max:130},
- series:[{
- type:'line',data:[...data],smooth:.45,symbol:'none',
- lineStyle:{color:INK,width:2.4},
- endLabel:{show:true,fontFamily:'Inter',fontSize:14,fontWeight:800,color:INK,
- formatter:p=>p.value+'k'},
- areaStyle:{color:{type:'linear',x:0,y:0,x2:0,y2:1,
- colorStops:[{offset:0,color:'rgba(28,28,26,.16)'},{offset:1,color:'rgba(28,28,26,0)'}]}},
- }],
- graphic:[{type:'group',left:16,top:10,children:[
- {type:'circle',shape:{cx:5,cy:5,r:4},style:{fill:'#4A4944'}},
- {type:'text',style:{text:'LIVE',x:16,y:0,font:'800 11px Inter',fill:INK}},
- ]}],
- });
- draw();
- keep('stream',setInterval(()=>{
- v=Math.max(30,Math.min(125,v+(rnd(t+1,7)-.48)*9));
- data.push(Math.round(v));data.shift();t++;
- draw();
- },300));
- });
- })();
- // ════ mono-fancy4 · 3: draw-in + counter ════
- (()=>{
- const days=Array.from({length:180},(_,i)=>i);
- const daily=days.map(i=>14+10*Math.sin(i/29)+i*.12+rnd(i+1,3)*6);
- const cum=[];daily.reduce((a,b,i)=>(cum[i]=a+b,cum[i]),0);
- const TOTAL=cum[cum.length-1];
- const DUR=2600;
- let gen=0; // replay guard: only the latest counter loop may write
- obsReveal('stroke',el=>{
- const g=echarts.getInstanceByDom(el)||echarts.init(el);
- g.clear();
- gen++;const my=gen;
- g.setOption({
- animationDuration:DUR,animationEasing:'cubicOut',
- tooltip:{show:false},
- grid:{left:14,right:20,top:64,bottom:24},
- xAxis:{type:'category',data:days,boundaryGap:false,
- axisLine:{show:false},axisTick:{show:false},splitLine:{show:false},
- axisLabel:{color:'#C6C5BF',fontFamily:'Inter',fontSize:9,fontWeight:600,
- interval:29,formatter:i=>['JAN','FEB','MAR','APR','MAY','JUN'][Math.floor(i/30)]||''}},
- yAxis:{show:false,max:TOTAL*1.06},
- series:[{
- type:'line',data:cum.map(x=>Math.round(x)),smooth:.2,symbol:'none',
- lineStyle:{color:INK,width:2.6},
- areaStyle:{color:{type:'linear',x:0,y:0,x2:0,y2:1,
- colorStops:[{offset:0,color:'rgba(28,28,26,.14)'},{offset:1,color:'rgba(28,28,26,0)'}]}},
- }],
- });
- const t0=performance.now();
- const tick=()=>{
- if(my!==gen)return;
- const p=Math.min(1,(performance.now()-t0)/DUR);
- const eased=1-Math.pow(1-p,3);
- const val=(TOTAL*eased/1000).toFixed(2);
- g.setOption({graphic:[{id:'kpi',type:'group',left:16,top:8,children:[
- {type:'text',style:{text:'$'+val+'M',font:'800 32px Inter',fill:INK}},
- {type:'text',style:{text:'ARR · H1 2026',y:38,font:'600 10px Inter',fill:'#8F8E88'}},
- ]}]});
- if(p<1)requestAnimationFrame(tick);
- };
- requestAnimationFrame(tick);
- });
- })();
- addEventListener('resize',()=>{
- ['rose','forest','circular','tree','rainfall','morph','negbar','force','wave',
- 'custompie','singleaxis','jitter','race','stream','stroke']
- .forEach(id=>echarts.getInstanceByDom(document.getElementById(id))?.resize());
- });
- // ── New chart-family helpers (scoped to the added templates) ──
- (()=>{
- const INK='#1C1C1A',PAPER='#F0EFEB',MUTED='#8F8E88',GRID='#DEDDD6';
- const NS='http://www.w3.org/2000/svg';
- const el=(p,t,a)=>{const n=document.createElementNS(NS,t);for(const k in a)n.setAttribute(k,a[k]);p.appendChild(n);return n};
- const txt=(p,a,s)=>{const n=el(p,'text',a);n.textContent=s;return n};
- const tip=(n,s)=>{const t=document.createElementNS(NS,'title');t.textContent=s;n.appendChild(t)};
- const rnd=(i,k)=>Math.abs(((i*73856093)^(k*19349663))%1000)/1000;
- // ════ G19 · violin ════
- // 小提琴:提前聚合的密度剪影,Glance 世界观(beeswarm 的姊妹)。
- // 四个 plan 各一把琴,宽度 = 该时长的工单密度(KDE),
- // 明度按中位数快慢排:越快越黑。纸色横档 = 中位数。
- (()=>{
- const G=[['ENT',1.6,.8],['PRO',3.4,1.6],['STARTER',6.2,2.6],['FREE',9.5,4.2]];
- const M=64;
- const samples=g=>Array.from({length:M},(_,i)=>{
- const [,c,s]=G[g];
- const v=c+s*((rnd(i+1,g*3+1)+rnd(i+7,g*3+2)+rnd(i+13,g*3+3))-1.5);
- return Math.max(.2,v);
- });
- obsReveal('violin',s=>{
- const TOP=40,BASE=262,HMAX=17;
- const mapY=h=>BASE-h/HMAX*(BASE-TOP);
- const colX=g=>76+g*84;
- // hour grid
- [0,4,8,12,16].forEach(h=>{
- el(s,'line',{x1:34,y1:mapY(h),x2:378,y2:mapY(h),stroke:'#E3E2DB','stroke-width':.8,
- class:'fade',style:`animation-delay:${h*.01}s`});
- txt(s,{x:28,y:mapY(h)+3,'font-size':7.5,'font-weight':600,fill:MUTED,'text-anchor':'end',
- class:'fade',style:`animation-delay:${h*.01}s`},h+'h');
- });
- const SHADE=[INK,'#4A4944','#8F8E88','#B0AFA9'];
- G.forEach(([name],g)=>{
- const vs=samples(g).sort((a,b)=>a-b);
- const med=vs[Math.floor(M/2)];
- // fuller bandwidth = the classic rounded violin belly, not a spindle
- const bw=Math.max(.9,G[g][2]*.72);
- // pad past the data by 1.6 bandwidths so the density decays to ~0 and the
- // ends taper naturally — clipping tighter gives flat-cut lantern ends
- const lo=Math.max(0,vs[0]-bw*1.6),hi=Math.min(HMAX,vs[M-1]+bw*1.6);
- const NPTS=44;
- const grid=Array.from({length:NPTS},(_,k)=>lo+k/(NPTS-1)*(hi-lo));
- const dens=grid.map(h=>vs.reduce((a,v)=>a+Math.exp(-((h-v)**2)/(2*bw*bw)),0));
- const dmax=Math.max(...dens);
- const W=30;
- const x=colX(g);
- // smooth closed outline through the mirrored density — quadratic
- // midpoint smoothing, same trick as almanac's blob
- const pts=[];
- grid.forEach((h,k)=>pts.push([x+dens[k]/dmax*W,mapY(h)]));
- for(let k=NPTS-1;k>=0;k--)pts.push([x-dens[k]/dmax*W,mapY(grid[k])]);
- let d=`M${pts[0][0].toFixed(1)} ${pts[0][1].toFixed(1)}`;
- for(let t=0;t<pts.length;t++){
- const p=pts[t],q=pts[(t+1)%pts.length];
- d+=` Q${p[0].toFixed(1)} ${p[1].toFixed(1)} ${((p[0]+q[0])/2).toFixed(1)} ${((p[1]+q[1])/2).toFixed(1)}`;
- }
- const body=el(s,'path',{d:d+' Z',fill:SHADE[g],class:'fade',style:`animation-delay:${g*.14}s`});
- tip(body,`${name} — median ${med.toFixed(1)}h to first reply`);
- // median: paper tick across the body, value parked right
- const mk=Math.round((med-lo)/(hi-lo)*(NPTS-1));
- const wAtMed=dens[Math.max(0,Math.min(NPTS-1,mk))]/dmax*W;
- el(s,'line',{x1:x-wAtMed,y1:mapY(med),x2:x+wAtMed,y2:mapY(med),
- stroke:PAPER,'stroke-width':2.2,class:'fade',style:`animation-delay:${.5+g*.14}s`});
- txt(s,{x:x+W+5,y:mapY(med)+3,'font-size':9.5,'font-weight':800,fill:INK,
- class:'fade',style:`animation-delay:${.6+g*.14}s`},med.toFixed(1)+'h');
- txt(s,{x,y:BASE+18,'font-size':7.5,'font-weight':700,fill:MUTED,'text-anchor':'middle',
- 'letter-spacing':'.08em',class:'fade',style:`animation-delay:${g*.14}s`},name);
- });
- txt(s,{x:200,y:306,'font-size':7,'font-weight':600,fill:'#B0AFA9','text-anchor':'middle',
- 'letter-spacing':'.12em',class:'fade',style:'animation-delay:1.1s'},
- 'WIDTH = HOW MANY TICKETS WAIT THAT LONG · PAPER TICK = MEDIAN · DARKEST = FASTEST');
- });
- })();
- // ════ G20 · matrix heat ════
- // 矩阵热力的 Glance 皮:粗笔画版本。大圆角色块、每格标数、3 秒读出
- // 「新版本跑得黑」的对角线。和 P1 Lupi 皮同数据形状,读者时间不同。
- (()=>{
- const FEAT=['EDITOR','BOARDS','DOCS','CHAT','FLOWS','VAULT'];
- const VER=['v2.0','v1.9','v1.8','v1.7','v1.6'];
- const BASEV=[88,74,61,42,35,27];
- const v=(r,c)=>Math.max(2,Math.round(BASEV[c]-r*(6+c*.8)+(rnd(r*6+c+1,c+3)-.5)*10));
- const shade=t=>t>64?INK:t>46?'#4A4944':t>30?'#8F8E88':t>15?'#B0AFA9':'#D8D7D1';
- obsReveal('gheat',s=>{
- const X0=92,Y0=58,PX=48,PY=42,W=42,H=36;
- FEAT.forEach((f,c)=>{
- txt(s,{x:X0+c*PX+W/2,y:Y0-12,'font-size':6.5,'font-weight':700,fill:MUTED,'text-anchor':'middle',
- 'letter-spacing':'.04em',class:'fade',style:`animation-delay:${c*.04}s`},f);
- });
- VER.forEach((ver,r)=>{
- txt(s,{x:X0-10,y:Y0+r*PY+H/2+3,'font-size':8.5,'font-weight':700,fill:'#6A6963','text-anchor':'end',
- class:'fade',style:`animation-delay:${r*.05}s`},ver);
- FEAT.forEach((f,c)=>{
- const t=v(r,c),x=X0+c*PX,y=Y0+r*PY,dark=t>46;
- const cell=el(s,'rect',{x,y,width:W,height:H,rx:9,fill:shade(t),
- class:'pop',style:`animation-delay:${(r*6+c)*.022}s`});
- tip(cell,`${f} on ${ver} — ${t}% of accounts`);
- txt(s,{x:x+W/2,y:y+H/2+4,'font-size':10.5,'font-weight':800,fill:dark?PAPER:INK,'text-anchor':'middle',
- class:'fade',style:`animation-delay:${.15+(r*6+c)*.022}s`},t);
- });
- });
- txt(s,{x:200,y:300,'font-size':7,'font-weight':600,fill:'#B0AFA9','text-anchor':'middle',
- 'letter-spacing':'.12em',class:'fade',style:'animation-delay:1s'},
- 'SHADE = ADOPTION · NEWER VERSIONS RUN DARKER');
- });
- })();
- // ════ G21 · rank strip ════
- // 排名随时间变的静态版,第二稿。第一稿是泳道 bump——灰线 X 交叉,
- // 和 slope 同一种丑(catalog 实测三连败那条),废弃。
- // 这版不画线:一行一个产品,一格一个季度,明度 = 名次(#1 最黑)。
- // 攀升者的行自己从浅变黑,故事读行内渐变,不读交叉。
- (()=>{
- const RANKS=[['Flows',[5,3,2,1,1,1]],['Editor',[1,1,1,2,2,2]],['Boards',[2,2,3,3,3,4]],
- ['Vault',[4,5,5,4,4,3]],['Docs',[3,4,4,5,5,5]]];
- const SHADE=[INK,'#4A4944','#8F8E88','#B0AFA9','#D8D7D1']; // rank 1 → 5
- obsReveal('rankstrip',s=>{
- // rows sorted by final rank, the climber lands on top
- const rows=[...RANKS].sort((a,b)=>a[1][5]-b[1][5]);
- const X0=110,PX=38,Y0=64,PY=42,C=32;
- ['Q1','Q2','Q3','Q4','Q5','Q6'].forEach((q,k)=>
- txt(s,{x:X0+k*PX+C/2,y:Y0-12,'font-size':7.5,'font-weight':600,fill:MUTED,'text-anchor':'middle',
- 'letter-spacing':'.08em',class:'fade',style:`animation-delay:${k*.04}s`},q));
- rows.forEach(([p,rk],i)=>{
- const y=Y0+i*PY,hero=p==='Flows';
- txt(s,{x:X0-12,y:y+C/2+3,'font-size':8.5,'font-weight':hero?800:600,fill:hero?INK:'#6A6963','text-anchor':'end',
- class:'fade',style:`animation-delay:${i*.07}s`},p);
- rk.forEach((r,q)=>{
- const dark=r<=2;
- const cell=el(s,'rect',{x:X0+q*PX,y,width:C,height:C,rx:8,fill:SHADE[r-1],
- stroke:'#CFCEC7','stroke-width':.5,
- class:'pop',style:`animation-delay:${i*.07+q*.035}s`});
- tip(cell,`${p} — #${r} in Q${q+1}`);
- txt(s,{x:X0+q*PX+C/2,y:y+C/2+3.5,'font-size':9.5,'font-weight':800,
- fill:dark?PAPER:'#55544E','text-anchor':'middle',
- class:'fade',style:`animation-delay:${.1+i*.07+q*.035}s`},r);
- });
- // arrival flag: climbed / fell / held, told at the row's end
- const d=rk[0]-rk[5];
- txt(s,{x:X0+6*PX+8,y:y+C/2+3,'font-size':8,'font-weight':800,
- fill:d>0?INK:d<0?'#B0AFA9':'#C6C5BF',
- class:'fade',style:`animation-delay:${.5+i*.07}s`},
- d>0?'▲'+d:d<0?'▼'+(-d):'—');
- });
- txt(s,{x:200,y:308,'font-size':7,'font-weight':600,fill:'#B0AFA9','text-anchor':'middle',
- 'letter-spacing':'.12em',class:'fade',style:'animation-delay:1.1s'},
- 'CELL = RANK THAT QUARTER · #1 WEARS BLACK · READ EACH ROW LIKE A FILMSTRIP');
- });
- })();
- // ════ G22 · aggregate sankey ════
- // B3 Threads 是逐条丝线(每线一条记录);这张是聚合版——
- // 缎带宽度∝账号数。左渠道右套餐,缎带按渠道明度分。
- // 交叉处必须读得清,缎带走 .5 透明度(同 convergence 连线的处理)。
- (()=>{
- const SRC=[['SEARCH',[20,10,4]],['REFERRAL',[12,9,6]],['SOCIAL',[12,4,2]],['PAID',[6,3,3]],['OTHER',[5,2,2]]];
- const DST=['FREE','PRO','TEAM'];
- const SH=[INK,'#4A4944','#8F8E88','#B0AFA9','#C6C5BF'];
- obsReveal('sankey',s=>{
- const XL=104,XR=298,Y0=34,SC=1.9,GAPL=8,GAPR=22,MID=(XL+XR)/2;
- const srcTot=SRC.map(d=>d[1].reduce((a,b)=>a+b,0));
- const dstTot=DST.map((_,j)=>SRC.reduce((a,d)=>a+d[1][j],0));
- const sy=[],dy=[];
- let y=Y0;srcTot.forEach((t,i)=>{sy[i]=y;y+=t*SC+GAPL});
- y=Y0+2;dstTot.forEach((t,j)=>{dy[j]=y;y+=t*SC+GAPR});
- const syc=[...sy],dyc=[...dy];
- SRC.forEach((d,i)=>{
- d[1].forEach((v,j)=>{
- if(!v)return;
- const sa=syc[i],sb=sa+v*SC,da=dyc[j],db=da+v*SC;
- syc[i]=sb;dyc[j]=db;
- const rib=el(s,'path',{d:`M${XL} ${sa} C${MID} ${sa} ${MID} ${da} ${XR} ${da} L${XR} ${db} C${MID} ${db} ${MID} ${sb} ${XL} ${sb} Z`,
- fill:SH[i],opacity:.5,class:'fade',style:`animation-delay:${.2+(i*3+j)*.06}s`});
- tip(rib,`${SRC[i][0]} → ${DST[j]} — ${v} accounts of 100`);
- });
- });
- SRC.forEach((d,i)=>{
- el(s,'rect',{x:XL-9,y:sy[i],width:9,height:srcTot[i]*SC,rx:4,fill:SH[i],
- class:'fade',style:`animation-delay:${i*.06}s`});
- txt(s,{x:XL-16,y:sy[i]+srcTot[i]*SC/2+2.5,'font-size':7.5,'font-weight':700,fill:'#4A4944','text-anchor':'end',
- 'letter-spacing':'.05em',class:'fade',style:`animation-delay:${i*.06}s`},`${d[0]} · ${srcTot[i]}`);
- });
- DST.forEach((n,j)=>{
- el(s,'rect',{x:XR,y:dy[j],width:9,height:dstTot[j]*SC,rx:4,fill:'#3A3934',
- class:'fade',style:`animation-delay:${.1+j*.06}s`});
- txt(s,{x:XR+16,y:dy[j]+dstTot[j]*SC/2-3,'font-size':7.5,'font-weight':700,fill:'#4A4944',
- 'letter-spacing':'.05em',class:'fade',style:`animation-delay:${.15+j*.06}s`},n);
- txt(s,{x:XR+16,y:dy[j]+dstTot[j]*SC/2+9,'font-size':10,'font-weight':800,fill:INK,
- class:'fade',style:`animation-delay:${.2+j*.06}s`},dstTot[j]);
- });
- txt(s,{x:200,y:302,'font-size':7,'font-weight':600,fill:'#B0AFA9','text-anchor':'middle',
- 'letter-spacing':'.12em',class:'fade',style:'animation-delay:1.1s'},
- 'RIBBON WIDTH = ACCOUNTS OF 100 · SHADE = CHANNEL');
- });
- })();
- })();
- </script>
- </body>
- </html>
|