1. 程式人生 > 其它 >typescript實現扇形餅狀圖功能

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();
}



  
}