跳转至

G13 · 大切片图

双编码:占比(角)×强度(径)的 Glance 真实参考实现。示例结论为“大份额带来深参与”,副标题已经把图例、单位或时间口径写在图旁:角度代表用户占比 · 半径代表每日使用分钟数 · 虚线环标记 15、30、45 分钟

技术档案

项目 约定
数据形状 双编码:占比(角)×强度(径)
场合 产品分析
读者时间 ~30s
渲染引擎 ECharts custom
交互与动效 滚入视野初始化,点击图表重播;保留原 gallery 的 tooltip 与动画
示例来源行 CUSTOM PIE · MONO-FANCY3 · PRODUCT ANALYTICS

实现与使用边界

  • 页面直接复用 gallery 中 “Big slice, deep engagement” 的卡片结构和同名渲染块,没有按截图重新绘制。
  • 标题、副标题、图形、来源行四件套保持原样;数据编码、比例关系、灰阶和动画节奏由原实现决定。
  • 使用时只替换数据与文案,并保持“双编码:占比(角)×强度(径)”这一数据契约;若数据本体改变,应回到总目录重新选型。
  • 颜色限于 Mono 灰阶,面积编码继续使用平方根换算;柱长、位置与角度编码不得断轴或视觉夸大。

模板来源:Lieflat Charts G13 Big Slice (Custom Pie),真实骨架取自 templates/glance-gallery.html 的 “Big slice, deep engagement” 卡片与对应渲染块。

g13-big-slice.html —— 自包含单页源码
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Lieflat Charts — G13 大切片图</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="stylesheet" href="../../../../vendor/fonts/fonts.css">
<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}

  html,body{min-height:100%;margin:0}
  body{display:grid;place-items:center;padding:28px}
  .pagehead{display:none}
  .grid2{display:block;width:min(100%,920px);max-width:920px;margin:0 auto}
  .card,.card.wide{display:block;width:100%;min-height:520px;padding:38px 42px 24px}
  .card .split{grid-template-columns:minmax(190px,250px) 1fr}
  .wiki-code{display:inline-block;margin-bottom:12px;padding:4px 11px;border:1px dashed #B0AFA9;border-radius:99px;color:#8F8E88;font-size:10px;font-weight:700;letter-spacing:.08em}
  .card.dark .wiki-code{border-color:#55554F}
  @media(max-width:620px){
    body{padding:10px}
    .card,.card.wide{min-height:500px;padding:26px 18px 20px}
    .card .split{display:block}
    .card .note,.card .legend{display:none}
  }
</style>

</head>
<body>
<main class="grid2">
<div class="card">
    <span class="wiki-code">GLANCE 快读型 · G13</span>
    <h2>大份额带来深参与</h2>
    <div class="sub">角度代表用户占比 · 半径代表每日使用分钟数 · 虚线环标记 15、30、45 分钟</div>
    <div class="ch" id="custompie"></div>
    <div class="src">CUSTOM PIE · MONO-FANCY3 · PRODUCT ANALYTICS</div>
  </div>
</main>
<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-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),
  }],
});
})();
</script>
</body>
</html>