图表
1、饼图
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>饼图</title>
<style>
body{margin: 0;overflow: hidden}
#canvas{background: antiquewhite;}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas=document.getElementById('canvas');
const [width,height]=[window.innerWidth,window.innerHeight];
canvas.width=width;
canvas.height=height;
const ctx=canvas.getContext('2d');
const seriesLabel=['T恤','夹克','护甲背心','迷彩服','防弹衣'];
const seriesData=[11121,7355,6277,4253,2425];
const pos={x:width/2,y:height/2};
const radius=200;
const colorSpace=Math.floor(150/seriesLabel.length);
const labelSum=eval(seriesData.join('+'));
const seriesAngle=[];
const seriesColor=[];
const seriesRadius=[];
seriesData.forEach((ele,ind)=>{
const dataScalar=ele/labelSum;
const angleLen=dataScalar*Math.PI*2;
let startAngle=ind===0?0:seriesAngle[ind-1][1];
const endAngle=startAngle+angleLen;
seriesAngle.push([startAngle,endAngle]);
const gamut=80+ind*colorSpace;
const color=`rgba(255,${gamut},${gamut},1)`;
seriesColor.push(color);
const itemRadius=radius-ind*10;
seriesRadius.push(itemRadius);
});
class Sector{
constructor(radius,start,end,color='chocolate'){
this.radius=0;
this.realRad=radius;
this.expandRad=radius+20;
this.startAngle=start;
this.endAngle=end;
this.color=color;
this.x=0;
this.x=0;
this.text='';
this.data=0;
this.textAlign='left';
this.p1={x:0,y:0};
this.p2={x:0,y:0};
this.p3={x:0,y:0};
this.p4={x:0,y:0};
this.vr=0;
this.ar=0.03;
this.bounce=0.6;
this.state=0;
}
updatePoints(){
const {realRad,startAngle,endAngle,x,y,p1,p2,p3,p4}=this;
const dir=startAngle+(endAngle-startAngle)/2;
const p1Len=realRad+22;
p1.x=Math.cos(dir)*p1Len+x;
p1.y=Math.sin(dir)*p1Len+y;
const p2Len=realRad+70;
p2.x=Math.cos(dir)*p2Len+x;
p2.y=Math.sin(dir)*p2Len+y;
let d=1;
if(Math.cos(dir)<0){
d=-1;
this.textAlign='right';
}
p3.x=p2.x+20*d;
p3.y=p2.y;
p4.x=p3.x+10*d;
p4.y=p2.y;
}
updateRad(diff){
const {state,realRad,expandRad}=this;
switch (state) {
case 0:
this.expand(diff,realRad);
break;
case 1:
this.expand(diff,expandRad);
break;
case 2:
this.shrink(diff,realRad);
break;
}
}
expand(diff,endR){
const {ar,bounce}=this;
this.vr+=ar;
this.radius+=this.vr*diff;
if(this.radius>endR){
this.radius=endR;
this.vr*=-bounce;
}
}
shrink(diff,endR){
const {ar,bounce}=this;
this.vr-=ar;
this.radius+=this.vr*diff;
if(this.radius<endR){
this.radius=endR;
this.vr*=-bounce;
}
}
draw(ctx){
const {x,y,radius,startAngle,endAngle,color,p1,p2,p3,p4,text,textAlign}=this;
ctx.save();
ctx.beginPath();
ctx.moveTo(x,y);
ctx.arc(x,y,radius,startAngle,endAngle);
ctx.fillStyle=color;
ctx.fill();
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.lineTo(p3.x,p3.y);
ctx.stroke();
ctx.fillStyle='#000';
ctx.textBaseline='middle';
ctx.textAlign=textAlign;
ctx.font='14px Arial';
ctx.fillText(text,p4.x,p4.y);
ctx.restore();
}
}
const series=[];
seriesAngle.forEach((ele,ind)=>{
const item=new Sector(seriesRadius[ind],ele[0],ele[1],seriesColor[ind]);
item.x=pos.x;
item.y=pos.y;
item.text=seriesLabel[ind];
item.data=seriesData[ind];
item.updatePoints();
series.push(item);
});
class Tip{
constructor() {
this.text='';
this.fontSize=14;
this.x=0;
this.y=0;
this.visible=false;
this.xn=0;
this.yn=0;
this.rate=0.1;
this.state=0;
}
updatePos(){
const {x,y,xn,yn,rate,state,visible}=this;
if(!this.visible||!state){return}
const subX=xn-x;
const subY=yn-y;
const len=Math.sqrt(subX*subX+subY*subY);
if(len<1){
this.state=0;
}else{
this.x+=subX*rate;
this.y+=subY*rate;
}
}
draw(ctx){
const {x,y,fontSize,visible,text}=this;
const [padW,padH]=[15,8];
if(!visible){return}
ctx.save();
ctx.font=`${fontSize}px arial`;
const {width}=ctx.measureText(text);
ctx.fillStyle='rgba(0,0,0,0.6)';
ctx.fillRect(x,y,width+padW*2,fontSize+padH*2);
ctx.textBaseline='top';
ctx.fillStyle='#fff';
ctx.fillText(text,x+padW,y+padH);
ctx.restore();
}
}
const tip=new Tip();
tip.x=pos.x;
tip.y=pos.y;
canvas.addEventListener('mousemove',mousemoveFn);
function mousemoveFn(event){
const mousePos=getMousePos(event);
let hoveredItem=null;
for (let i=0;i<series.length;i++){
if(containPoint(series[i],mousePos)){
series[i].state=1;
hoveredItem=series[i];
}else if(series[i].state===1){
series[i].state=2;
}
}
if(hoveredItem){
tip.visible=true;
tip.xn=mousePos.x+10;
tip.yn=mousePos.y+20;
tip.state=1;
tip.text=hoveredItem.data;
}else{
tip.visible=false;
}
}
let time=new Date();
render();
function render(){
const now=new Date();
const diff=now-time;
time=now;
ctx.clearRect(0,0,width,height);
series.forEach((item)=>{
item.updateRad(diff);
item.draw(ctx);
});
tip.updatePos();
tip.draw(ctx);
requestAnimationFrame(render);
}
function containPoint(obj,mousePos){
const {x,y,radius,startAngle,endAngle}=obj;
const [subX,subY]=[mousePos.x-x,mousePos.y-y];
const len=Math.sqrt(subX*subX+subY*subY);
const b1=len<radius;
let dir=Math.atan2(subY,subX);
if(dir<0){dir+=Math.PI*2}
const b2=dir>startAngle&&dir<endAngle;
return b1&&b2;
}
function getMousePos(event){
const {clientX,clientY}=event;
const {top,left}=canvas.getBoundingClientRect();
const x=clientX-left;
const y=clientY-top;
return {x,y};
}
</script>
</body>
</html>
