.
The Animated 3D Pie Chart is a free, copy-paste ready interactive data visualization
built with pure SVG and vanilla JavaScript — zero external libraries, no Chart.js, no D3.
It renders a 6-slice 3D pie chart displaying budget data: Rent 38%,
Groceries 18%, Utilities 10%, Transport 9%, Savings 15%, Entertainment 10%.
The 3D effect is achieved with CSS transform: rotateX(50deg) rotateZ(-15deg)
on the SVG inside a container with perspective: 1000px and
transform-style: preserve-3d. Each slice has a
3D side depth panel drawn as a second SVG path at
cy + depth * 0.82 offset, and a linearGradient fill on the top face.
Slices animate in with a staggered entrance: opacity transitions from 0 to 1
starting at 200ms with i * 120ms delay per slice. Hovering a slice adds the
.pulled CSS class which applies
transform: translate3d(var(--tx), var(--ty), 0) — the --tx and
--ty values are calculated from the slice midpoint angle to push it
18px radially outward. Clicking locks the pull-out state.
Percentage labels are SVG <text> elements positioned at 66% of the radius.
A live data update fires after 3.8 seconds via setTimeout,
calling draw() with new values and re-animating the entire chart.
Each slice is an SVG <path>. The polar() function converts
degree angles to SVG x/y coordinates: a = (a-90) * Math.PI/180 (offset so 0°
points up), then x = cx + r * Math.cos(a), y = cy + r * Math.sin(a).
The arc path string is: M cx,cy L startX,startY A radius,radius 0 large-arc-flag 0 endX,endY Z.
The large-arc-flag is 0 when the slice is ≤ 180° and 1
for larger slices — this is the key SVG rule for rendering the correct arc direction.
Replace the hardcoded data array with values from your API. Call
draw(yourData) whenever data changes — on fetch() response,
WebSocket message, or user filter. Each call wipes svg.innerHTML and
rebuilds with fresh animations. To add more or fewer slices, simply add/remove objects
from the array. The HSL color generator automatically spreads colors across 320 degrees
of the color wheel. Change the CSS --size variable from 520px
to resize. Change depth from 28 to increase or decrease the
3D extrusion thickness.
This component is ideal for: analytics dashboards, budget and finance reports, admin panels, data journalism visuals, presentation slides, SaaS product metrics pages, portfolio data sections, and any project needing a premium chart without Chart.js or D3 dependencies. Replace the budget categories with sales data, traffic sources, survey results, or any proportional dataset.
Released under Creative Commons Attribution 4.0 International (CC BY 4.0). Free for personal and commercial use with attribution to Coodeverse.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Animated 3D Pie Chart</title>
<style>
:root{--size:520px;--depth:28;--bg:#0f1724}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;display:grid;place-items:center;background:linear-gradient(180deg,var(--bg) 0%,#071025 100%);color:#e6eef8}
.card{width:calc(var(--size)+240px);max-width:100%;background:linear-gradient(180deg,rgba(255,255,255,.02),rgba(255,255,255,.01));border-radius:14px;padding:20px;box-shadow:0 10px 30px rgba(2,6,23,.6);display:flex;gap:20px;align-items:center}
.chart-wrap{width:var(--size);height:var(--size);display:grid;place-items:center;perspective:1000px}
.scene{width:var(--size);height:var(--size);transform-style:preserve-3d;transition:transform .6s cubic-bezier(.2,.9,.3,1)}
svg{width:100%;height:100%;transform:rotateX(50deg) rotateZ(-15deg)}
.slices{transform-origin:50% 50%}
.chart-wrap:hover .scene{transform:translateY(-8px)}
.slice-top{transition:transform .32s cubic-bezier(.2,.9,.3,1),filter .22s;cursor:pointer}
.slice-top:hover{filter:brightness(1.08) saturate(1.06)}
.pulled{transform:translate3d(var(--tx),var(--ty),0)!important}
.legend{width:200px;display:flex;flex-direction:column;gap:10px;padding:8px 14px}
.row{display:flex;align-items:center;gap:10px}
.swatch{width:14px;height:14px;border-radius:3px;box-shadow:0 2px 6px rgba(0,0,0,.6)}
.lbl{font-size:13px;color:#cfe6ff}
.callout{font-weight:600;font-size:13px;fill:#fff;text-shadow:0 2px 10px rgba(0,17,29,.5)}
@media(max-width:860px){.card{flex-direction:column;padding:14px}.legend{width:100%;flex-direction:row;flex-wrap:wrap;justify-content:center;gap:8px}}
</style>
</head>
<body>
<div class="card">
<div class="chart-wrap" role="img" aria-label="3D animated pie chart">
<div class="scene">
<svg id="pieSvg" viewBox="0 0 420 420" xmlns="http://www.w3.org/2000/svg"></svg>
</div>
</div>
<div class="legend" id="legend"></div>
</div>
<script>
const data=[{label:"Rent",value:38},{label:"Groceries",value:18},{label:"Utilities",value:10},{label:"Transport",value:9},{label:"Savings",value:15},{label:"Entertainment",value:10}];
const size=420,cx=size/2,cy=size/2,radius=120,depth=28;
const svg=document.getElementById("pieSvg"),legend=document.getElementById("legend");
function hue(i,n){return Math.round((i/n)*320+20)}
function niceColor(i,n){return`hsl(${hue(i,n)}deg 78% 58%)`}
function polar(cx,cy,r,a){a=(a-90)*Math.PI/180;return{x:cx+r*Math.cos(a),y:cy+r*Math.sin(a)}}
function arc(cx,cy,r,s,e){const S=polar(cx,cy,r,e),E=polar(cx,cy,r,s),large=e-s<=180?0:1;
return`M${cx},${cy} L${E.x},${E.y} A${r},${r} 0 ${large} 0 ${S.x},${S.y} Z`}
function gradient(id,color){
let defs=svg.querySelector("defs")||svg.appendChild(document.createElementNS(svg.namespaceURI,"defs"));
const g=document.createElementNS(svg.namespaceURI,"linearGradient");g.id=id;
g.innerHTML=`<stop offset="0%" stop-color="${color}"/><stop offset="100%" stop-color="rgba(0,0,0,.12)"/>`;
defs.appendChild(g);return`url(#${id})`}
function slice(i,start,end,color,pct){
const mid=(start+end)/2,id=`s${i}`;
const top=document.createElementNS(svg.namespaceURI,"path");
top.setAttribute("d",arc(cx,cy,radius,start,end));
top.classList.add("slice-top");top.style.fill=gradient(id+"g",color);
const oy=depth*0.82;
const p1=polar(cx,cy,radius,start),p2=polar(cx,cy,radius,end);
const side=document.createElementNS(svg.namespaceURI,"path");
side.setAttribute("d",`M${p1.x},${p1.y} A${radius},${radius} 0 ${end-start<=180?0:1} 0 ${p2.x},${p2.y} L${p2.x},${p2.y+oy} A${radius},${radius} 0 ${end-start<=180?0:1} 1 ${p1.x},${p1.y+oy} Z`);
side.style.fill="rgba(0,0,0,.12)";
const label=document.createElementNS(svg.namespaceURI,"text");
const lp=polar(cx,cy,radius*0.66,mid);
label.setAttribute("x",lp.x);label.setAttribute("y",lp.y+oy*0.2);
label.setAttribute("text-anchor","middle");label.classList.add("callout");
label.textContent=Math.round(pct)+"%";
const g=document.createElementNS(svg.namespaceURI,"g");
g.append(side,top,label);svg.appendChild(g);
g.style.opacity=0;
setTimeout(()=>{g.style.transition="transform .52s cubic-bezier(.2,.9,.3,1), opacity .42s";g.style.opacity=1;label.style.opacity=1},200+i*120);
let pulled=false;
const pull=()=>{const rad=(mid-90)*Math.PI/180;
g.style.setProperty("--tx",Math.cos(rad)*18+"px");
g.style.setProperty("--ty",Math.sin(rad)*18*0.6+"px");
g.classList.toggle("pulled");pulled=!pulled;}
top.addEventListener("mouseenter",()=>g.classList.add("pulled"));
top.addEventListener("mouseleave",()=>g.classList.remove("pulled"));
top.addEventListener("click",pull);
}
function draw(d){
svg.innerHTML="";legend.innerHTML="";
const total=d.reduce((a,b)=>a+b.value,0);
let angle=0;
const shadow=document.createElementNS(svg.namespaceURI,"ellipse");
shadow.setAttribute("cx",cx);shadow.setAttribute("cy",cy+depth+36);
shadow.setAttribute("rx",radius*1.25);shadow.setAttribute("ry",16);
shadow.style.fill="rgba(0,0,0,.22)";shadow.style.filter="blur(8px)";
svg.appendChild(shadow);
d.forEach((item,i)=>{
const seg=(item.value/total)*360;
slice(i,angle,angle+seg,niceColor(i,d.length),(item.value/total)*100);
const row=document.createElement("div");row.className="row";
const sw=document.createElement("div");sw.className="swatch";sw.style.background=niceColor(i,d.length);
const txt=document.createElement("div");txt.className="lbl";txt.textContent=`${item.label} — ${item.value}`;
row.append(sw,txt);legend.appendChild(row);
angle+=seg;
});
}
draw(data);
setTimeout(()=>{draw([
{label:"Rent",value:35},{label:"Groceries",value:20},{label:"Utilities",value:8},
{label:"Transport",value:7},{label:"Savings",value:20},{label:"Entertainment",value:10}
])},3800);
</script>
</body>
</html>
Common questions about the free Animated 3D Pie Chart SVG JavaScript component.
Yes — 100% free under Creative Commons Attribution 4.0. No sign-up, no payment. Click Download Project and use it in personal, commercial, or dashboard projects with attribution to Coodeverse.
The 3D effect uses transform: rotateX(50deg) rotateZ(-15deg) on the SVG inside a container with perspective: 1000px and transform-style: preserve-3d. Each slice has a shadow side panel drawn at cy + depth * 0.82 offset with fill: rgba(0,0,0,.12). Slice arcs use polar-to-Cartesian math: x = cx + r*Math.cos(a), y = cy + r*Math.sin(a) with a = (degrees-90) * Math.PI/180.
On mouseenter the .pulled class is added to the slice group; on mouseleave it's removed. On click, it's toggled for sticky state. .pulled applies transform: translate3d(var(--tx), var(--ty), 0). The --tx and --ty CSS variables are calculated from the slice midpoint angle: Math.cos(rad)*18+'px' and Math.sin(rad)*18*0.6+'px', pushing the slice 18px radially outward.
The path string is: M cx,cy L startX,startY A radius,radius 0 large-arc-flag 0 endX,endY Z. The large-arc-flag is 0 when the slice is ≤ 180° and 1 for larger slices — this is the critical SVG rule. Start/end points come from the polar() function: a = (degrees-90) * Math.PI/180 offsets so 0° points up, then standard cos/sin conversion.
Call draw(newData) with a new array of {label, value} objects. It clears svg.innerHTML and legend.innerHTML then rebuilds everything with fresh staggered animations. In the demo, setTimeout(()=>draw([...]), 3800) triggers after 3.8s. For real dashboards, call draw() on fetch() responses or WebSocket messages.
Edit the data array — each object has label and value. Add or remove objects freely. Colors are auto-generated with hsl(hue, 78%, 58%) spread across 320° of the color wheel. To use custom colors, add a color field to each object and pass it to slice(). Change radius (default 120) or depth (default 28) to resize or adjust 3D thickness.
Browse the full collection at coodeverse.com/projects — VORTEX Interactive Canvas, Realistic Cursor Dragon, NEXUS 3D Pure CSS, Holographic Login and more. All 100% free with complete HTML CSS JavaScript source code.