study-buddy / app /agents /d3 /templates /chart_pie.py
GitHub Actions
deploy d092bea3608b7a29952f16357fda39b7a29e399b
2e818da
Raw
History Blame Contribute Delete
4.06 kB
from __future__ import annotations
from pydantic import BaseModel, Field
from app.agents.d3.registry import D3Template, register
class PieSlice(BaseModel):
label: str
value: float = Field(ge=0)
class PieChartData(BaseModel):
title: str = ""
metric_label: str = "Value"
data: list[PieSlice] = Field(min_length=2, max_length=16)
_HTML = """<!DOCTYPE html><html><head><meta charset="utf-8">
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#FAF7F2;color:#1A3557;font-family:Inter,system-ui,sans-serif}*{box-sizing:border-box}
#root{width:100%;height:100%;position:relative}.title{position:absolute;left:24px;top:18px;font-family:Georgia,'Libre Caslon Text',serif;font-size:20px;font-weight:700;z-index:2}
svg{width:100%;height:100%}.slice{stroke:#FAF7F2;stroke-width:3;cursor:pointer;transition:opacity .15s}.slice:hover{opacity:.78}.label{font-size:13px;font-weight:650;fill:#1A3557}.value{font-size:11px;fill:#58708A}.leader{fill:none;stroke:#8DA4BA;stroke-width:1.2}
.tip{position:absolute;pointer-events:none;opacity:0;background:#fff;border:1px solid #C8D5E1;border-radius:8px;padding:8px 10px;box-shadow:0 8px 24px #1A355722;font-size:12px}.legend{position:absolute;right:18px;bottom:16px;display:flex;flex-wrap:wrap;justify-content:flex-end;gap:7px;max-width:42%;font-size:11px}.legend span{display:flex;align-items:center;gap:5px}.dot{width:9px;height:9px;border-radius:50%}
</style></head><body><div id="root"><div class="title"></div><div class="tip"></div><div class="legend"></div></div><script>
const spec=__DATA__,root=d3.select('#root'),tip=root.select('.tip');root.select('.title').text(spec.title);
const W=960,H=600,cx=400,cy=315,r=220,total=d3.sum(spec.data,d=>d.value),colors=['#1A3557','#D77932','#4A8C74','#8A5C9B','#D2A84A','#4A7FB5','#B95555','#6F879D','#A87652','#617A45'];
const color=d3.scaleOrdinal().domain(spec.data.map(d=>d.label)).range(colors),svg=root.append('svg').attr('viewBox',`0 0 ${W} ${H}`),g=svg.append('g').attr('transform',`translate(${cx},${cy})`),arc=d3.arc().innerRadius(0).outerRadius(r),outer=d3.arc().innerRadius(r*1.08).outerRadius(r*1.08),pie=d3.pie().sort(null).value(d=>d.value);
const arcs=pie(spec.data);g.selectAll('path.slice').data(arcs).join('path').attr('class','slice').attr('d',arc).attr('fill',d=>color(d.data.label)).on('mousemove',(e,d)=>tip.style('opacity',1).style('left',`${e.offsetX+14}px`).style('top',`${e.offsetY-10}px`).html(`<strong>${d.data.label}</strong><br>${spec.metric_label}: ${d3.format(',.4~g')(d.data.value)}<br>${d3.format('.1%')(d.data.value/total)}`)).on('mouseleave',()=>tip.style('opacity',0));
const side=d=>((d.startAngle+d.endAngle)/2)<Math.PI?1:-1;g.selectAll('path.leader').data(arcs).join('path').attr('class','leader').attr('d',d=>{const p=outer.centroid(d),q=[side(d)*295,p[1]];return `M${arc.centroid(d)}L${p}L${q}`});
const labels=g.selectAll('g.lab').data(arcs).join('g').attr('class','lab').attr('transform',d=>{const p=outer.centroid(d);return `translate(${side(d)*305},${p[1]})`}).attr('text-anchor',d=>side(d)>0?'start':'end');labels.append('text').attr('class','label').text(d=>d.data.label);labels.append('text').attr('class','value').attr('dy','1.35em').text(d=>`${d3.format(',.4~g')(d.data.value)} · ${d3.format('.1%')(d.data.value/total)}`);
root.select('.legend').selectAll('span').data(spec.data).join('span').html(d=>`<i class="dot" style="background:${color(d.label)}"></i>${d.label}`);
</script></body></html>"""
register(D3Template(id="pie_chart", family="chart", title="Pie chart", when_to_use="Show part-to-whole proportions for one explicitly selected metric.", data_requirements="Two to sixteen named non-negative values that form one meaningful whole.", schema=PieChartData, html_template=_HTML, golden_sample=PieChartData(title="Share by model", metric_label="Average score", data=[PieSlice(label="Qwen", value=22.6), PieSlice(label="DeepSeek", value=21.7), PieSlice(label="Base", value=15.0), PieSlice(label="SFT", value=23.1)])))