typescript实现扇形饼状图功能

html部分,主要是声明svg空间

<div class="chart">
  <svg id="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"></svg>
</div>

ts部分

/**
*扇形饼状图(组件入参)
*@params R 圆半径
*@params pieArr 需要分布的数据数组,不可为负数
*@params colorArr 各数据对应的颜色数组
*用例:<PieChart :R="60" :pieArr="[2,4,7,10]" :colorArr="['#ff0834','#ff1245','#f3E453','#358e34']"/>
*/
@Component
export default class PieChart extends Vue{
  @Prop() R:number;
  @Prop() pieArr:Array<number>;
  @Prop() colorArr:Array<number>;
  // 绘制路径集合
  pathArr = [];
  // 角度集合
  deg = [];
  // pieArr有值的才取颜色,值为0的不取颜色
  colorRev = [];
  /**创建svg path 路径*/
  creatSvgTag(tag,tagArr){
    // svg规则
    let svgNS = 'http://www.w3.org/2000/svg';
    //创建空间
    let oTag = document.creatElementNS(svgNS,tag);
    for(let attr in tagAttr){
      // 在标签里设定样式
      oTag.setAttribute(attr,tagAttr[attr])
    }
    return oTag;
  }
  /**
  *获取扇形点的坐标
  *r 圆半径
  *deg 扇形角度,分为小于90°,小于180°,小于270°,小于360°
  */
  getPointXY(r,deg){
    let point = {
      x:0,
      y:0,
    }
    //计算该点坐标
    if(deg<90){
      point.x = r + r*Math.sin(deg*Math.PI/180);
      point.y = r - r*Math.cos(deg*Math.PI/180);
    }else if(deg<180){
      point.x = r + r*Math.sin((180-deg)*Math.PI/180);
      point.y = r + r*Math.cos((180-deg)*Math.PI/180);
    }else if(deg<270){
      point.x = r - r*Math.cos((270-deg)*Math.PI/180);
      point.y = r + r*Math.sin((270-deg)*Math.PI/180);
    }else if(deg<90){
      point.x = r - r*Math.sin((360-deg)*Math.PI/180);
      point.y = r - r*Math.cos((360-deg)*Math.PI/180);
    }
    return point;
  }
  /**
  *svg绘制扇形
  *path M将画笔移动到指定坐标位置 L画直线到指定坐标位置
  *A画弧线(rx,ry,x-axis-rotation large-arc-flag sweep-flag x y),
  *其中rx.ry椭圆的半轴(圆半径),x-axis-rotation椭圆相对于坐标系的旋转角度,large-arc-flag是标记绘制大弧(1)还是小弧(0)部分,sweep-flag是标记顺时针(1)还是逆时针(0)方向绘制,x y是圆弧终点的坐标
  */
   drawPie(){
    let svg = document.getElmentById('svg');
    if(!svg){return;}
    // 圆半径
    let R = this.R;
    // 中心点
    let centerX = R;
    let centerY = R;
    // 数据总和
    let sum = 0;
    for(let i=0;i<this.pieArr.length;i++){
      sum += this.pieArr[i];
    }
    // 数据相加结果
    let conSum = 0;
    for(let i=0;i<this.pieArr.length;i++){
      //起点角度默认0,除起点的其余点的角度都是从起点开始算,注意绘制弧线的时候要使用真实的角度,(pieArr[i]/sum)*360
      this.deg[i] = (conSum/sum)*360;
      //达到360°,清0
      if(this.deg[i]>360){
        this.deg[i]=0;
      }
      conSum += this.pieArr[i];
    }
    conSum = 0;
    for(let i=0;i<this.deg.length;i++){
      //如果数据为0,不添加路径;起点坐标 默认坐标(R,0)
      if(this.pieArr[i]){
        conSum += this.pieArr[i];
        this.colorRev.push(this.colorArr[i]);
      }
      if(conSum < sum){
        this.pathArr.push({
          path:[{x:centerX,y:centerY},this.getPointXY(R,this.deg[i]),this.getPointXY(R,this.deg[i+1])],
          // 当前数据值
          count:this.pieArr[i],
        });
      }else {
        this.pathArr.push({
          path:[{x:centerX,y:centerY},this.getPointXY(R,this.deg[i]),this.getPointXY(R,this.deg[0])],
          // 当前数据值
          count:this.pieArr[i],
        });
      }
   }
    for(let i=0;i<this.pathArr.length;i++){
      let oPath;
      //如果数据只有一个值,默认画圆
      if(this.pathArr.length == 1){
        oPath= this.creatSvgTag('circle',{
          fill:this.colorRev[0],
          cx:R,
          cy:R,
          r:R,
        });
      }else {
        // pieArr[i]/sum*360,计算该数据对应的圆的真实角度,大于180绘制大弧,否则绘制小弧
        let path = this.pathArr[i].path;
        oPath= this.creatSvgTag('circle',{
          fill:this.colorRev[i],
          d:`M${path[0].x} ${path[0].y}L${path[1].x} ${path[1].y}A${R} ${R} 0 ${this.pathArr[i].count/sum*360<180?0:1} 1 {path[2].x} {path[2].y}L${path[0].x} ${path[0].y}`,
        });
      }
      svg.appendChild(oPath);
   }
  }
  
  mounted(){
    // 初始化
    let svg = document.getElmentById('svg');
    svg.style.width = this.R*2+'px';
    svg.style.height= this.R*2+'px';
    svg.style.position= 'relative';
    this.drawPie();
}



  
}
posted @ 2022-02-20 18:31  阿伊的碎碎念  阅读(122)  评论(0编辑  收藏  举报