最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用canvas仿Echarts實(shí)現(xiàn)金字塔圖的實(shí)例代碼

  發(fā)布時(shí)間:2021-11-09 17:08:26   作者:舒冬冬_   我要評論
本文主要介紹了使用canvas仿Echarts實(shí)現(xiàn)金字塔圖的實(shí)例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言

最近公司項(xiàng)目都偏向于數(shù)字化大屏展示🥱,而這次發(fā)給我的項(xiàng)目原型中出現(xiàn)了一個(gè)金字塔圖🤔️, 好巧不巧,由于我們的圖表都是使用Echarts,而Echarts中又不支持金字塔圖,作為一個(gè)喜歡造輪子的前端開發(fā),雖然自身技術(shù)不咋滴,但喜歡攻克難題的精神還是有的😁, 不斷地內(nèi)卷,才是我們這些普通前端開發(fā)的核心競爭力😂,所以就有了仿Echarts實(shí)現(xiàn)金字塔圖的想法。

不多說先上效果

ScreenFlow.gif

項(xiàng)目地址:(https://github.com/SHDjason/Pyramid.git)

正文

目前demo是基于vue2.x框架

項(xiàng)目實(shí)現(xiàn)可傳入配置有:主體圖位置(distance)、主體圖偏移度(offset)、數(shù)據(jù)排序(sort)、圖顏色(color)、數(shù)據(jù)文本回調(diào)(fontFormatter)、tooltip配置(tooltip)、數(shù)據(jù)展示樣式配置(infoStyle)等

image.png

初始化canvas基本信息 并實(shí)現(xiàn)大小自適應(yīng)

<template>
  <div id="canvas-warpper">
    <div id="canvas-tooltip"></div>
  </div>
</template>

先創(chuàng)建 canvas畫布

      // 創(chuàng)建canvas元素
      this.canvas = document.createElement('canvas')
      // 把canvas元素節(jié)點(diǎn)添加在el元素下
      el.appendChild(this.canvas)
      this.canvasWidth = el.offsetWidth
      this.canvasHeight = el.offsetHeight
      // 將canvas元素設(shè)置與父元素同寬
      this.canvas.setAttribute('width', this.canvasWidth)
      // 將canvas元素設(shè)置與父元素同高
      this.canvas.setAttribute('height', this.canvasHeight)

獲取畫布中心點(diǎn) 方便后面做自適應(yīng)和定點(diǎn)

 this.canvasCenter = [
        Math.round((this.canvasWidth - this.integration.distance[0] * 2) / 2) + this.integration.distance[0],
        Math.round((this.canvasHeight - this.integration.distance[1] * 2) / 2) + this.integration.distance[1]
      ]

監(jiān)聽傳來的數(shù)據(jù) 并計(jì)算數(shù)據(jù)占比

剛好在這編寫 數(shù)據(jù)排序(sort)的傳入配置

  watch: {
    data: {
      immediate: true,
      deep: true,
      handler(newValue) {
        // 數(shù)據(jù)總量
        let totalData = 0
        newValue.forEach(element => {
          totalData = totalData + Number(element.value)
        })
        this.dataInfo = newValue.map(item => {
          const accounted = (item.value / totalData) * 100
          return { ...item, accounted, title: this.integration.title }
        })
        if (this.integration.sort === 'max') {
          this.dataInfo.sort((a, b) => {
            return a.value - b.value
          })
        } else if (this.integration.sort === 'min') {
          this.dataInfo.sort((a, b) => {
            return b.value - a.value
          })
        }
      }
    }
  },

下面可以確定金字塔4個(gè)基本點(diǎn)的位置了

這幾個(gè)基本點(diǎn)的位置決定在后面金字塔展示的形狀 可以根據(jù)自己的審美進(jìn)行微調(diào)

 if (this.canvas.getContext) {
        this.ctx = this.canvas.getContext('2d')
        // 金字塔基本點(diǎn)位置
        this.point.top = [this.canvasCenter[0] - this.canvasWidth / 13, this.integration.distance[1]]
        this.point.left = [
          this.integration.distance[0] * 1.5,
          this.canvasHeight - this.integration.distance[1] - this.canvasHeight / 5
        ]
        this.point.right = [
          this.canvasWidth - this.integration.distance[0] * 1.9,
          this.canvasHeight - this.integration.distance[1] - this.canvasHeight / 5
        ]
        this.point.bottom = [
          this.canvasCenter[0] - this.canvasWidth / 13,
          this.canvasHeight - this.integration.distance[1]
        ]
        this.point.shadow = [
          this.integration.distance[0] - this.canvasCenter[0] / 5,
          this.canvasHeight / 1.2 - this.integration.distance[1]
        ]
        for (const key in this.point) {
          this.point[key][0] = this.point[key][0] + this.integration.offset[0]
          this.point[key][1] = this.point[key][1] + this.integration.offset[1]
        }
      } else {
        throw 'canvas下未找到 getContext方法'
      }

完整代碼

      let el = document.getElementById('canvas-warpper')
      // 創(chuàng)建canvas元素
      this.canvas = document.createElement('canvas')
      // 把canvas元素節(jié)點(diǎn)添加在el元素下
      el.appendChild(this.canvas)
      this.canvasWidth = el.offsetWidth
      this.canvasHeight = el.offsetHeight
      // 將canvas元素設(shè)置與父元素同寬
      this.canvas.setAttribute('width', this.canvasWidth)
      // 將canvas元素設(shè)置與父元素同高
      this.canvas.setAttribute('height', this.canvasHeight)
      this.canvasCenter = [
        Math.round((this.canvasWidth - this.integration.distance[0] * 2) / 2) + this.integration.distance[0],
        Math.round((this.canvasHeight - this.integration.distance[1] * 2) / 2) + this.integration.distance[1]
      ]
      if (this.canvas.getContext) {
        this.ctx = this.canvas.getContext('2d')
        // 金字塔基本點(diǎn)位置
        this.point.top = [this.canvasCenter[0] - this.canvasWidth / 13, this.integration.distance[1]]
        this.point.left = [
          this.integration.distance[0] * 1.5,
          this.canvasHeight - this.integration.distance[1] - this.canvasHeight / 5
        ]
        this.point.right = [
          this.canvasWidth - this.integration.distance[0] * 1.9,
          this.canvasHeight - this.integration.distance[1] - this.canvasHeight / 5
        ]
        this.point.bottom = [
          this.canvasCenter[0] - this.canvasWidth / 13,
          this.canvasHeight - this.integration.distance[1]
        ]
        this.point.shadow = [
          this.integration.distance[0] - this.canvasCenter[0] / 5,
          this.canvasHeight / 1.2 - this.integration.distance[1]
        ]
        for (const key in this.point) {
          this.point[key][0] = this.point[key][0] + this.integration.offset[0]
          this.point[key][1] = this.point[key][1] + this.integration.offset[1]
        }
      } else {
        throw 'canvas下未找到 getContext方法'
      }
      this.topAngle.LTB = this.angle(this.point.top, this.point.left, this.point.bottom)
      this.topAngle.RTB = this.angle(this.point.top, this.point.right, this.point.bottom)
      // 計(jì)算各數(shù)據(jù)點(diǎn)位置
      this.calculationPointPosition(this.dataInfo)
    },

計(jì)算金字塔每條邊的角度

為了后面給每個(gè)數(shù)據(jù)定點(diǎn) 但是 唉~ 奈何數(shù)學(xué)太差 所以我就想到了一個(gè)方法 :

每條數(shù)據(jù)的定點(diǎn)范圍肯定都是在 四個(gè)基本點(diǎn)的連線上。那我把每個(gè)基本點(diǎn)連線的角度求出來 ,到時(shí)候 在進(jìn)行角度翻轉(zhuǎn)到垂直后 再求每個(gè)條數(shù)據(jù)所占當(dāng)前基本點(diǎn)連線的占比不就行了?

 /**
   * @description: 求3點(diǎn)之間角度
   * @return {*} 點(diǎn) a 的角度
   * @author: 舒冬冬
   */
  angle(a, b, c) {
      const A = { X: a[0], Y: a[1] }
      const B = { X: b[0], Y: b[1] }
      const C = { X: c[0], Y: c[1] }
      const AB = Math.sqrt(Math.pow(A.X - B.X, 2) + Math.pow(A.Y - B.Y, 2))
      const AC = Math.sqrt(Math.pow(A.X - C.X, 2) + Math.pow(A.Y - C.Y, 2))
      const BC = Math.sqrt(Math.pow(B.X - C.X, 2) + Math.pow(B.Y - C.Y, 2))
      const cosA = (Math.pow(AB, 2) + Math.pow(AC, 2) - Math.pow(BC, 2)) / (2 * AB * AC)
      const angleA = Math.round((Math.acos(cosA) * 180) / Math.PI)
      return angleA
    }  

計(jì)算各個(gè)數(shù)據(jù)點(diǎn)的位置

接下來就是確定每條數(shù)據(jù)的 繪畫范圍了

我們先把金字塔左邊和有右邊旋轉(zhuǎn)垂直后的點(diǎn)的位置確定下來

/**
     * @description: 根據(jù)A點(diǎn)旋轉(zhuǎn)指定角度后B點(diǎn)的坐標(biāo)位置
     * @param {*} ptSrc 圓上某點(diǎn)(初始點(diǎn));
     * @param {*} ptRotationCenter 圓心點(diǎn)
     * @param {*} angle 旋轉(zhuǎn)角度°  -- [angle * M_PI / 180]:將角度換算為弧度
     * 【注意】angle 逆時(shí)針為正,順時(shí)針為負(fù)
     * @return {*}
     * @author: 舒冬冬
     */
    rotatePoint(ptSrc, ptRotationCenter, angle) {
      const a = ptRotationCenter[0]
      const b = ptRotationCenter[1]
      const x0 = ptSrc[0]
      const y0 = ptSrc[1]
      const rx = a + (x0 - a) * Math.cos((angle * Math.PI) / 180) - (y0 - b) * Math.sin((angle * Math.PI) / 180)
      const ry = b + (x0 - a) * Math.sin((angle * Math.PI) / 180) + (y0 - b) * Math.cos((angle * Math.PI) / 180)
      const point = [rx, ry]
      return point
    },
const LP = this.rotatePoint(this.point.left, this.point.top, this.topAngle.LTB * -1)
      const RP = this.rotatePoint(this.point.right, this.point.top, this.topAngle.RTB)

LP 為 TL 的邊 逆時(shí)針旋轉(zhuǎn) LTB 角度后的 點(diǎn)的位置

RP 為 TR 的邊 順時(shí)針旋轉(zhuǎn) RTB 角度后的 點(diǎn)的位置

image.png

這樣就可以確定 每個(gè)數(shù)據(jù)點(diǎn)在 三條邊上的各自所占長度了 完整代碼
每個(gè)點(diǎn)的長度計(jì)算思路, 以在TL邊上點(diǎn)為例:
拿到 LP (逆時(shí)針旋轉(zhuǎn) LTB角度后的位置)長度,根據(jù)數(shù)據(jù)所占總數(shù)據(jù)占比 求出該條數(shù)據(jù)的長度 再把角度轉(zhuǎn)回去還原該邊 就能拿到該條數(shù)據(jù)再 TL 邊的上的位置信息。
const vertical = [ this.point.top[0], (LP[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1] ]

  /**
    * @description: 計(jì)算數(shù)據(jù)的點(diǎn)位置
    * @param {*} val 點(diǎn)占比
    * @return {*}
    * @author: 舒冬冬
    */
   calculationPointPosition(val) {
     const LP = this.rotatePoint(this.point.left, this.point.top, this.topAngle.LTB * -1)
     const RP = this.rotatePoint(this.point.right, this.point.top, this.topAngle.RTB)
     let temporary = {
       left: [
         [0, 0],
         [0, 0],
         [0, 0]
       ],
       right: [
         [0, 0],
         [0, 0],
         [0, 0]
       ],
       middle: [
         [0, 0],
         [0, 0],
         [0, 0]
       ]
     }

     
     const dataInfo = val.map((item, index) => {
       if (index === 0) {
         for (const key in temporary) {
           if (key === 'left') {
             // 垂直后點(diǎn)的位置
             // 垂直后點(diǎn)點(diǎn)距離
             const vertical = [
               this.point.top[0],
               (LP[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
             ]
             // 還原后點(diǎn)的位置
             temporary.left = [this.point.top, this.rotatePoint(vertical, this.point.top, this.topAngle.LTB), vertical]
           } else if (key === 'right') {
             // 垂直后點(diǎn)點(diǎn)距離
             const vertical = [
               this.point.top[0],
               (RP[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
             ]
             // 還原后點(diǎn)的位置
             temporary.right = [
               this.point.top,
               this.rotatePoint(vertical, this.point.top, this.topAngle.RTB * -1),
               vertical
             ]
           } else if (key === 'middle') {
             // 垂直后點(diǎn)點(diǎn)距離
             temporary.middle = [
               this.point.top,
               [
                 this.point.top[0],
                 (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
               ],
               [
                 this.point.top[0],
                 (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
               ]
             ]
           }
         }
       } else {
         for (const key in temporary) {
           const vertical = JSON.parse(JSON.stringify(temporary[key][2]))
           if (key === 'left') {
             // 垂直后點(diǎn)點(diǎn)距離
             const vertical1 = [this.point.top[0], vertical[1] + (LP[1] - this.point.top[1]) * (item.accounted / 100)]
             // 還原后點(diǎn)的位置
             temporary.left = [
               this.point.top,
               this.rotatePoint(vertical1, this.point.top, this.topAngle.LTB),
               vertical1
             ]
           } else if (key === 'right') {
             // 垂直后點(diǎn)點(diǎn)距離
             const vertical1 = [this.point.top[0], vertical[1] + (RP[1] - this.point.top[1]) * (item.accounted / 100)]
             // 還原后點(diǎn)的位置
             temporary.right = [
               this.point.top,
               this.rotatePoint(vertical1, this.point.top, this.topAngle.RTB * -1),
               vertical1
             ]
           } else if (key === 'middle') {
             temporary.middle = [
               this.point.top,
               [this.point.top[0], (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + vertical[1]],
               [this.point.top[0], (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + vertical[1]]
             ]
           }
         }
       }

       return { ...item, temporary: JSON.parse(JSON.stringify(temporary)) }
     })
     this.dataInfo = dataInfo
   },

這樣就拿到了每個(gè)數(shù)據(jù)在每一條邊上所占長度的點(diǎn)位。

繪畫

數(shù)據(jù)圖層繪畫

我們雖然拿到了每個(gè)數(shù)據(jù)在每一條邊上所占長度的點(diǎn)位。 那怎么獲取這條數(shù)據(jù)在該邊上的所在的線段長度呢?
很簡單 因?yàn)?第一條數(shù)據(jù)的在該邊長度的第二個(gè)點(diǎn)的位置就是第二條數(shù)據(jù)的第一個(gè)點(diǎn)的位置
現(xiàn)在就可以進(jìn)行下一步。
數(shù)據(jù) 圖層的繪畫了

   /**
    * @description: 數(shù)據(jù)圖層繪畫
    * @param {*}
    * @return {*}
    * @author: 舒冬冬
    */
   paintDataInfo() {
     // let data = JSON.parse(JSON.stringify(this.dataInfo))
     // data.reverse()
     var index = -1
     this.dataInfo = this.dataInfo.map(item => {
       index++
       if (this.integration.color.length === index) {
         index = 0
       }
       return { ...item, color: this.integration.color[index] }
     })
     this.dataInfo = this.dataInfo.map((item, index) => {
       let drawingPoint = []
       this.ctx.fillStyle = item.color
       this.ctx.beginPath()
       let point1, point2, point3, point4, point5, point6
       if (index === 0) {
         [point1, point2, point3, point4, point5, point6] = [
           item.temporary.left[0],
           item.temporary.left[1],
           item.temporary.middle[1],
           item.temporary.right[1],
           item.temporary.right[0],
           item.temporary.middle[0]
         ]
       } else {
         [point1, point2, point3, point4, point5, point6] = [
           this.dataInfo[index - 1].temporary.left[1],
           item.temporary.left[1],
           item.temporary.middle[1],
           item.temporary.right[1],
           this.dataInfo[index - 1].temporary.right[1],
           this.dataInfo[index - 1].temporary.middle[1]
         ]
       }
       this.ctx.moveTo(...point1)
       this.ctx.lineTo(...point2)
       this.ctx.lineTo(...point3)
       this.ctx.lineTo(...point4)
       this.ctx.lineTo(...point5)
       this.ctx.lineTo(...point6)
       drawingPoint = [point1, point2, point3, point4, point5, point6]
       if (this.integration.infoStyle.stroke) {
         this.ctx.shadowOffsetX = 0
         this.ctx.shadowOffsetY = 0
         this.ctx.shadowBlur = 2
         this.ctx.shadowColor = this.integration.infoStyle.strokeColor
       }
       this.ctx.fill()
       return { ...item, drawingPoint }
     })
   }

以上就基本完成 金字塔圖的核心內(nèi)容了。

但是還是不夠, 想要達(dá)到Echarts的簡單的功能,單單有圖是不行的

文字的繪畫

字體繪畫就比較簡單了, 我們擁有每一個(gè)數(shù)據(jù)的點(diǎn)的位置,把每個(gè)數(shù)據(jù)點(diǎn)的 F C 兩個(gè)點(diǎn)的長度 除2 的點(diǎn)的位置設(shè)為起點(diǎn)就行了

image.png

 /**
    * @description: 繪畫字體
    * 此方法請?jiān)?paintDataInfo() 執(zhí)行后使用
    * @param {*}
    * @return {*}
    * @author: 舒冬冬
    */
   paintingText(lData) {
     this.ctx.shadowColor = 'rgba(90,90,90,0)'
     const color = this.integration.infoStyle.color ? this.integration.infoStyle.color : '#fff'
     const width = this.integration.infoStyle.width ? this.integration.infoStyle.width : 0
     const dotSize = this.integration.infoStyle.dotSize ? this.integration.infoStyle.dotSize : 4
     const offset = this.integration.infoStyle.offset ? this.integration.infoStyle.offset : [0, 0]
     let text = ''
     this.ctx.strokeStyle = color
     this.ctx.fillStyle = color
     this.dataInfo.forEach((item, index) => {
       if (item.drawingPoint) {
         let line = [
           [0, 0],
           [0, 0]
         ]
         this.ctx.font = `normal lighter ${
           this.integration.infoStyle.size ? this.integration.infoStyle.size : 14
         }px sans-serif `

         this.ctx.beginPath()
         if (lData && index + 1 === lData.l) {
           line = [
             [
               lData.obj.drawingPoint[2][0],
               (lData.obj.drawingPoint[2][1] - lData.obj.drawingPoint[5][1]) / 2 + lData.obj.drawingPoint[5][1]
             ],
             [
               lData.obj.drawingPoint[2][0] + lData.obj.drawingPoint[2][0] / 2 + width,
               (lData.obj.drawingPoint[2][1] - lData.obj.drawingPoint[5][1]) / 2 + lData.obj.drawingPoint[5][1]
             ]
           ]

           this.ctx.font = `normal lighter ${
             this.integration.infoStyle.size ? this.integration.infoStyle.size + 2 : 16
           }px sans-serif `
           text =
             this.integration.fontFormatter(item) !== 'default'
               ? this.integration.fontFormatter(item)
               : lData.obj.value + ' ---- ' + lData.obj.name
           this.ctx.setLineDash([0, 0])
           this.ctx.strokeText(
             text,
             line[1][0] + offset[0],
             line[1][1] + (this.integration.infoStyle.size ? this.integration.infoStyle.size + 2 : 14) / 3 + offset[1]
           )
         } else {
           line = [
             [
               item.drawingPoint[2][0],
               (item.drawingPoint[2][1] - item.drawingPoint[5][1]) / 2 + item.drawingPoint[5][1]
             ],
             [
               item.drawingPoint[2][0] + item.drawingPoint[2][0] / 2 + width,
               (item.drawingPoint[2][1] - item.drawingPoint[5][1]) / 2 + item.drawingPoint[5][1]
             ]
           ]
           text =
             this.integration.fontFormatter(item) !== 'default'
               ? this.integration.fontFormatter(item)
               : item.value + ' ----- ' + item.name
           this.ctx.setLineDash([0, 0])
           this.ctx.strokeText(
             text,
             line[1][0] + offset[0],
             line[1][1] + (this.integration.infoStyle.size ? this.integration.infoStyle.size + 2 : 16) / 3 + offset[1]
           )
         }
         this.ctx.setLineDash(this.integration.infoStyle.setLineDash)
         this.ctx.moveTo(...line[0])
         this.ctx.lineTo(...line[1])
         this.ctx.stroke()
         this.ctx.arc(...line[0], dotSize, 0, 360, false)
         this.ctx.fill() //畫實(shí)心圓
       } else {
         throw '未找到 drawingPoint 屬性'
       }
     })
   },

高亮圖層

高亮圖層無非就是監(jiān)聽鼠標(biāo)移入位置,并且判斷鼠標(biāo)移入位置是否存在圖層內(nèi),在哪個(gè)圖層內(nèi),然后重新繪畫當(dāng)前圖層

  /**
    * @description: 鼠標(biāo)事件注冊
    * @param {*}
    * @return {*}
    * @author: 舒冬冬
    */
   eventRegistered() {
     const canvasWarpper = document.getElementById('canvas-warpper')
     //注冊事件
     canvasWarpper.addEventListener('mousedown', this.doMouseDown, false)
     canvasWarpper.addEventListener('mouseup', this.doMouseUp, false)
     canvasWarpper.addEventListener('mousemove', this.doMouseMove, false)
     // //注冊事件
     // this.canvas.addEventListener('mousedown', this.doMouseDown, false)
     // this.canvas.addEventListener('mouseup', this.doMouseUp, false)
     // this.canvas.addEventListener('mousemove', this.doMouseMove, false)
   },
      /**
    * @description: 鼠標(biāo)移動
    * @param {*} e
    * @return {*}
    * @author: 舒冬冬
    */
   // eslint-disable-next-line no-unused-vars
   doMouseMove(e) {
     const x = e.pageX
     const y = e.pageY
     this.highlightCurrentRegion(this.determineDataMouse(this.getLocation(x, y)))
     if (this.integration.tooltip.show) {
       this.showTooltip(this.determineDataMouse(this.getLocation(x, y)), this.getLocation(x, y))
     }
   },
 /**
    * @description: 判斷鼠標(biāo)在哪層位置上
    * @param {*}
    * @return {*}
    * @author: 舒冬冬
    */
   determineDataMouse(mouseLocation) {
     let req = false
     for (let index = 0; index < this.dataInfo.length; index++) {
       if (this.insidePolygon(this.dataInfo[index].drawingPoint, mouseLocation)) {
         return (req = { l: index + 1, obj: this.dataInfo[index] })
       }
     }
     return req
   },
 /**
    * @description: 高亮某一層級
    * @param {*} lData 層級數(shù)據(jù)
    * @return {*}
    * @author: 舒冬冬
    */
   highlightCurrentRegion(lData) {
     // const width = this.canvas.width;
     // this.canvas.width = width;

     this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
     if (!lData) {
       this.paintDataInfo()
       this.ctx.shadowColor = 'rgba(90,90,90,0)'
       this.paintingBody()
       this.paintingText()
       return
     }
     this.paintDataInfo()
     this.ctx.shadowColor = 'rgba(90,90,90,0)'
     this.paintingBody()
     this.ctx.fillStyle = lData.obj.color
     //  this.ctx.scale(1.05, 1.05)
     this.ctx.beginPath()
     this.ctx.moveTo(lData.obj.drawingPoint[0][0], lData.obj.drawingPoint[0][1])
     this.ctx.lineTo(lData.obj.drawingPoint[1][0], lData.obj.drawingPoint[1][1])
     this.ctx.lineTo(lData.obj.drawingPoint[2][0], lData.obj.drawingPoint[2][1])
     this.ctx.lineTo(lData.obj.drawingPoint[3][0], lData.obj.drawingPoint[3][1])
     this.ctx.lineTo(lData.obj.drawingPoint[4][0], lData.obj.drawingPoint[4][1])
     this.ctx.lineTo(lData.obj.drawingPoint[5][0], lData.obj.drawingPoint[5][1])
     this.ctx.shadowOffsetX = 0
     this.ctx.shadowOffsetY = 0
     this.ctx.shadowBlur = 10
     this.ctx.shadowColor = this.integration.infoStyle.highlightedColor
     this.ctx.fill()
     // 陰影繪制
     this.ctx.beginPath()
     this.ctx.moveTo(lData.obj.drawingPoint[0][0], lData.obj.drawingPoint[0][1])
     this.ctx.lineTo(lData.obj.drawingPoint[1][0], lData.obj.drawingPoint[1][1])
     this.ctx.lineTo(lData.obj.drawingPoint[2][0], lData.obj.drawingPoint[2][1])
     this.ctx.lineTo(lData.obj.drawingPoint[5][0], lData.obj.drawingPoint[5][1])
     this.ctx.fillStyle = 'rgba(120,120,120,.15)'
     this.ctx.fill()
     this.paintingText(lData)
   }

顯示tooltip位置

可以先定義 tooltip 的渲染模板

image.png

然后在代碼上進(jìn)行渲染

 showTooltip(lData, coordinates) {
     let canvasWarpper = document.getElementById('canvas-warpper')
     let canvasTooltip = document.getElementById('canvas-tooltip')
     if (lData) {
       canvasTooltip.style.zIndex = this.integration.tooltip.z
       canvasTooltip.style.transition =
         ' opacity 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s, visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s,transform 0.15s'
       let html = JSON.parse(JSON.stringify(this.tooltipDiv))
       if (this.integration.tooltip.formatter) {
         html = this.integration.tooltip.formatter(lData)
       } else {
         const searchVal = [
           ['$[title]$', lData.obj.title],
           ['$[name]$', lData.obj.name],
           ['$[val]$', lData.obj.value],
           ['$[color]$', lData.obj.color],
           ['$[fontSize]$', this.integration.tooltip.fontSize],
           ['$[backgroundColor]$', this.integration.tooltip.backgroundColor],
           ['$[fontColor]$', this.integration.tooltip.fontColor]
         ]
         searchVal.forEach(el => {
           html = html.replaceAll(...el)
         })
       }
       canvasTooltip.innerHTML = html
       canvasWarpper.style.cursor = 'pointer'
       canvasTooltip.style.visibility = 'visible'
       canvasTooltip.style.opacity = 1
       let [x, y] = coordinates
       x = x + 20
       y = y + 20
       // 畫布高度
       // canvasHeight: 0,
       // 畫布寬度
       // canvasWidth: 0,
       // 判斷是否超出框架內(nèi)容
       if (x + canvasTooltip.clientWidth > this.canvasWidth) {
         x = x - canvasTooltip.clientWidth - 40
       }
       if (y + canvasTooltip.clientHeight > this.canvasHeight) {
         y = y - canvasTooltip.clientHeight - 40
       }
       canvasTooltip.style.transform = `translate3d(${x}px, ${y}px, 0px)`
     } else {
       canvasWarpper.style.cursor = 'default'
       canvasTooltip.style.visibility = 'hidden'
       canvasTooltip.style.opacity = 0
     }
   },

而一些其他的配置功能呢也是比較簡單的操作了,主要是太懶了😂,
直接上完整源碼吧! 源碼上注釋也比較全,不是很清楚的可以評論,我看到會回復(fù)的!

完整源碼

<template>
  <div id="canvas-warpper">
    <div id="canvas-tooltip"></div>
  </div>
</template>

<script>
export default {
  name: 'Pyramid',
  props: {
    options: {
      type: Object,
      default: () => {
        return {
          title: '',
          // 主體離邊框距離
          distance: [0, 0],
          // 主體偏移值 (x,y)
          offset: [0, 0],
          // 排序(max , min)優(yōu)先
          sort: '',
          // 顏色
          color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
          // 格式化字體輸出
          fontFormatter: () => {
            return 'default'
          },
          // tooltip信息配置
          tooltip: {
            show: true, // 是否顯示
            fontColor: '#000', //  字體內(nèi)部顏色
            fontSize: 14, // 字體大小
            backgroundColor: '#fff', // tooltip背景
            formatter: null, // 回調(diào)方法
            z: 999999 // tooltip z-index層級
          },
          // 樣式
          infoStyle: {
            stroke: false, // 是否描邊
            strokeColor: '#fff', //描邊顏色
            size: null, // 字體大小
            color: null, //顏色
            highlightedColor: '#fff', // 高亮顏色
            setLineDash: [0, 0], // 虛線值
            width: -10, // 設(shè)置多少 就會在基礎(chǔ)上加上設(shè)置的值
            offset: [0, 0], // 字體x,y的偏移度
            dotSize: 4 //點(diǎn)大小
          }
        }
      }
    },

    // 渲染數(shù)據(jù)
    data: {
      type: Array,
      default: () => {
        return [
          { name: 'name1', value: 11 },
          { name: 'name2', value: 11 },
          { name: 'name3', value: 11 },
          { name: 'name4', value: 77 },
          { name: 'name5', value: 55 },
          { name: 'name6', value: 66 }
        ]
      }
    }
  },
  watch: {
    data: {
      immediate: true,
      deep: true,
      handler(newValue) {
        // 數(shù)據(jù)總量
        let totalData = 0
        newValue.forEach(element => {
          totalData = totalData + Number(element.value)
        })
        this.dataInfo = newValue.map(item => {
          const accounted = (item.value / totalData) * 100
          return { ...item, accounted, title: this.integration.title }
        })
        if (this.integration.sort === 'max') {
          this.dataInfo.sort((a, b) => {
            return a.value - b.value
          })
        } else if (this.integration.sort === 'min') {
          this.dataInfo.sort((a, b) => {
            return b.value - a.value
          })
        }
      }
    }
  },
  computed: {
    integration() {
      return {
        title: this.options.title ? this.options.title : '',
        // 主體離邊框距離
        distance: this.options.distance ? this.options.distance : [0, 0],
        // 主體偏移值 (x,y)
        offset: this.options.offset ? this.options.offset : [0, 0],
        // 排序(max , min)優(yōu)先
        sort: this.options.sort ? this.options.sort : '',
        // 顏色
        color: this.options.color ? this.options.color : ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
        // 格式化字體輸出
        fontFormatter: this.options.fontFormatter
          ? this.options.fontFormatter
          : () => {
              return 'default'
            },
        // tooltip顯示
        tooltip: {
          show: this.options.tooltip ? (this.options.tooltip.show ? this.options.tooltip.show : true) : true, // 是否顯示
          fontColor: this.options.tooltip
            ? this.options.tooltip.fontColor
              ? this.options.tooltip.fontColor
              : '#000'
            : '#000', //  字體內(nèi)部顏色
          fontSize: this.options.tooltip ? (this.options.tooltip.fontSize ? this.options.tooltip.fontSize : 14) : 14, // 字體大小
          backgroundColor: this.options.tooltip
            ? this.options.tooltip.backgroundColor
              ? this.options.tooltip.backgroundColor
              : '#fff'
            : '#fff', // tooltip背景
          formatter: this.options.tooltip
            ? this.options.tooltip.formatter
              ? this.options.tooltip.formatter
              : null
            : null, // 返回方法
          z: this.options.tooltip ? (this.options.tooltip.z ? this.options.tooltip.z : 999999) : 999999 // tooltip z-index層級
        },
        // 樣式
        infoStyle: {
          stroke: this.options.infoStyle
            ? this.options.infoStyle.stroke
              ? this.options.infoStyle.stroke
              : false
            : false, //是否描邊
          strokeColor: this.options.infoStyle
            ? this.options.infoStyle.strokeColor
              ? this.options.infoStyle.strokeColor
              : '#fff'
            : '#fff', // 描邊顏色
          size: this.options.infoStyle ? (this.options.infoStyle.size ? this.options.infoStyle.size : null) : null, // 字體大小
          color: this.options.infoStyle ? (this.options.infoStyle.color ? this.options.infoStyle.color : null) : null, //顏色
          width: this.options.infoStyle
            ? this.options.infoStyle.width || this.options.infoStyle.width !== 0
              ? this.options.infoStyle.width
              : -10
            : -10, // 設(shè)置多少 就會在基礎(chǔ)上加上設(shè)置的值
          offset: this.options.infoStyle
            ? this.options.infoStyle.offset
              ? this.options.infoStyle.offset
              : [0, 0]
            : [0, 0], // 字體x,y的偏移度
          setLineDash: this.options.infoStyle
            ? this.options.infoStyle.setLineDash
              ? this.options.infoStyle.setLineDash
              : [0, 0]
            : [0, 0], //虛線值
          highlightedColor: this.options.infoStyle
            ? this.options.infoStyle.highlightedColor
              ? this.options.infoStyle.highlightedColor
              : '#fff'
            : '#fff', //高亮顏色
          dotSize: this.options.infoStyle
            ? this.options.infoStyle.dotSize || this.options.infoStyle.dotSize !== 0
              ? this.options.infoStyle.dotSize
              : 4
            : 4 //點(diǎn)大小
        }
      }
    }
  },
  data() {
    return {
      // canvas 主體
      canvas: null,
      // 圖像渲染內(nèi)容
      ctx: null,
      // 畫布高度
      canvasHeight: 0,
      // 畫布寬度
      canvasWidth: 0,
      // 畫布中心點(diǎn) [x,y]
      canvasCenter: [0, 0],
      // 金字塔四個(gè)點(diǎn)位置
      point: {
        top: [0, 0],
        left: [0, 0],
        right: [0, 0],
        bottom: [0, 0],
        shadow: [0, 0]
      },
      // 數(shù)據(jù)信息
      dataInfo: [],
      // 金字塔頂端角度信息
      topAngle: {
        LTB: 0,
        RTB: 0
      },
      // tooltip 模板
      tooltipDiv: `<div  style="margin: 0px 0 0; line-height: 1;border-color: $[backgroundColor]$ ;background-color: $[backgroundColor]$;color: $[fontColor]$;
    border-width: 1px;border-radius: 4px;padding: 10px;pointer-events: none;box-shadow: rgb(0 0 0 / 20%) 1px 2px 10px;border-style: solid;white-space: nowrap;">
        <div style="margin: 0px 0 0; line-height: 1">
          <div style="font-size: $[fontSize]$px; color: $[fontColor]$; font-weight: 400; line-height: 1"> $[title]$ </div>
          <div style="margin: 10px 0 0; line-height: 1">
            <div style="margin: 0px 0 0; line-height: 1">
              <div style="margin: 0px 0 0; line-height: 1">
                <span
                  style="
                    display: inline-block;
                    margin-right: 4px;
                    border-radius: 10px;
                    width: 10px;
                    height: 10px;
                    background-color: $[color]$;
                  "
                ></span>
                <span style="font-size: $[fontSize]$px; color: $[fontColor]$; font-weight: 400; margin-left: 2px">$[name]$</span>
                <span style="float: right; margin-left: 20px; font-size: $[fontSize]$px; color: $[fontColor]$; font-weight: 900">$[val]$</span>
                <div style="clear: both"></div>
              </div>
              <div style="clear: both"></div>
            </div>
            <div style="clear: both"></div>
          </div>
          <div style="clear: both"></div>
        </div>
        <div style="clear: both"></div>
      </div>`
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      this.initCanvasBaseInfo()
      this.paintDataInfo()
      this.paintingText()
      this.paintingBody()
      this.eventRegistered()
    },
    /**
     * @description: 初始化canvas基本信息
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    initCanvasBaseInfo() {
      let el = document.getElementById('canvas-warpper')
      // 創(chuàng)建canvas元素
      this.canvas = document.createElement('canvas')
      // 把canvas元素節(jié)點(diǎn)添加在el元素下
      el.appendChild(this.canvas)
      this.canvasWidth = el.offsetWidth
      this.canvasHeight = el.offsetHeight
      // 將canvas元素設(shè)置與父元素同寬
      this.canvas.setAttribute('width', this.canvasWidth)
      // 將canvas元素設(shè)置與父元素同高
      this.canvas.setAttribute('height', this.canvasHeight)
      this.canvasCenter = [
        Math.round((this.canvasWidth - this.integration.distance[0] * 2) / 2) + this.integration.distance[0],
        Math.round((this.canvasHeight - this.integration.distance[1] * 2) / 2) + this.integration.distance[1]
      ]
      if (this.canvas.getContext) {
        this.ctx = this.canvas.getContext('2d')
        // 金字塔基本點(diǎn)位置
        this.point.top = [this.canvasCenter[0] - this.canvasWidth / 13, this.integration.distance[1]]
        this.point.left = [
          this.integration.distance[0] * 1.5,
          this.canvasHeight - this.integration.distance[1] - this.canvasHeight / 5
        ]
        this.point.right = [
          this.canvasWidth - this.integration.distance[0] * 1.9,
          this.canvasHeight - this.integration.distance[1] - this.canvasHeight / 5
        ]
        this.point.bottom = [
          this.canvasCenter[0] - this.canvasWidth / 13,
          this.canvasHeight - this.integration.distance[1]
        ]
        this.point.shadow = [
          this.integration.distance[0] - this.canvasCenter[0] / 5,
          this.canvasHeight / 1.2 - this.integration.distance[1]
        ]
        for (const key in this.point) {
          this.point[key][0] = this.point[key][0] + this.integration.offset[0]
          this.point[key][1] = this.point[key][1] + this.integration.offset[1]
        }
      } else {
        throw 'canvas下未找到 getContext方法'
      }
      this.topAngle.LTB = this.angle(this.point.top, this.point.left, this.point.bottom)
      this.topAngle.RTB = this.angle(this.point.top, this.point.right, this.point.bottom)
      // 計(jì)算各數(shù)據(jù)點(diǎn)位置
      this.calculationPointPosition(this.dataInfo)
    },
    // ======================================事件==========================================
    /**
     * @description: 鼠標(biāo)事件注冊
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    eventRegistered() {
      const canvasWarpper = document.getElementById('canvas-warpper')
      //注冊事件
      canvasWarpper.addEventListener('mousedown', this.doMouseDown, false)
      canvasWarpper.addEventListener('mouseup', this.doMouseUp, false)
      canvasWarpper.addEventListener('mousemove', this.doMouseMove, false)
      // //注冊事件
      // this.canvas.addEventListener('mousedown', this.doMouseDown, false)
      // this.canvas.addEventListener('mouseup', this.doMouseUp, false)
      // this.canvas.addEventListener('mousemove', this.doMouseMove, false)
    },
    /**
     * @description: 鼠標(biāo)按下
     * @param {*} e
     * @return {*}
     * @author: 舒冬冬
     */
    // eslint-disable-next-line no-unused-vars
    doMouseDown(e) {},
    /**
     * @description: 鼠標(biāo)彈起
     * @param {*} e
     * @return {*}
     * @author: 舒冬冬
     */
    // eslint-disable-next-line no-unused-vars
    doMouseUp(e) {},
    /**
     * @description: 鼠標(biāo)移動
     * @param {*} e
     * @return {*}
     * @author: 舒冬冬
     */
    // eslint-disable-next-line no-unused-vars
    doMouseMove(e) {
      const x = e.pageX
      const y = e.pageY
      this.highlightCurrentRegion(this.determineDataMouse(this.getLocation(x, y)))
      if (this.integration.tooltip.show) {
        this.showTooltip(this.determineDataMouse(this.getLocation(x, y)), this.getLocation(x, y))
      }
    },

    /**
     *  @description 判斷一個(gè)點(diǎn)是否在多邊形內(nèi)部
     *  @param points 多邊形坐標(biāo)集合
     *  @param testPoint 測試點(diǎn)坐標(biāo)
     *  @author: 舒冬冬
     *  返回true為真,false為假
     */
    insidePolygon(points, testPoint) {
      const x = testPoint[0],
        y = testPoint[1]
      let inside = false
      for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
        const xi = points[i][0],
          yi = points[i][1]
        const xj = points[j][0],
          yj = points[j][1]

        const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
        if (intersect) inside = !inside
      }
      return inside
    },
    /**
     * @description: 獲取當(dāng)前鼠標(biāo)坐標(biāo)
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    getLocation(x, y) {
      const bbox = this.canvas.getBoundingClientRect()
      return [(x - bbox.left) * (this.canvas.width / bbox.width), (y - bbox.top) * (this.canvas.height / bbox.height)]
    },
    // ======================================算法==========================================

    /**
     * @description: 根據(jù)A點(diǎn)旋轉(zhuǎn)指定角度后B點(diǎn)的坐標(biāo)位置
     * @param {*} ptSrc 圓上某點(diǎn)(初始點(diǎn));
     * @param {*} ptRotationCenter 圓心點(diǎn)
     * @param {*} angle 旋轉(zhuǎn)角度°  -- [angle * M_PI / 180]:將角度換算為弧度
     * 【注意】angle 逆時(shí)針為正,順時(shí)針為負(fù)
     * @return {*}
     * @author: 舒冬冬
     */
    rotatePoint(ptSrc, ptRotationCenter, angle) {
      const a = ptRotationCenter[0]
      const b = ptRotationCenter[1]
      const x0 = ptSrc[0]
      const y0 = ptSrc[1]
      const rx = a + (x0 - a) * Math.cos((angle * Math.PI) / 180) - (y0 - b) * Math.sin((angle * Math.PI) / 180)
      const ry = b + (x0 - a) * Math.sin((angle * Math.PI) / 180) + (y0 - b) * Math.cos((angle * Math.PI) / 180)
      const point = [rx, ry]
      return point
    },

    /**
     * @description: 求3點(diǎn)之間角度
     * @return {*} 點(diǎn) a 的角度
     * @author: 舒冬冬
     */
    angle(a, b, c) {
      const A = { X: a[0], Y: a[1] }
      const B = { X: b[0], Y: b[1] }
      const C = { X: c[0], Y: c[1] }
      const AB = Math.sqrt(Math.pow(A.X - B.X, 2) + Math.pow(A.Y - B.Y, 2))
      const AC = Math.sqrt(Math.pow(A.X - C.X, 2) + Math.pow(A.Y - C.Y, 2))
      const BC = Math.sqrt(Math.pow(B.X - C.X, 2) + Math.pow(B.Y - C.Y, 2))
      const cosA = (Math.pow(AB, 2) + Math.pow(AC, 2) - Math.pow(BC, 2)) / (2 * AB * AC)
      const angleA = Math.round((Math.acos(cosA) * 180) / Math.PI)
      return angleA
    },
    /**
     * @description: 計(jì)算兩點(diǎn)之間距離
     * @return {*}
     * @author: 舒冬冬
     */
    getDistanceBetweenTwoPoints(a, b) {
      const A = a[0] - b[0]
      const B = a[1] - b[1]
      const result = Math.sqrt(Math.pow(A, 2) + Math.pow(B, 2))
      return result
    },
    /**
     * @description: 計(jì)算數(shù)據(jù)的點(diǎn)位置
     * @param {*} val 點(diǎn)占比
     * @return {*}
     * @author: 舒冬冬
     */
    calculationPointPosition(val) {
      const LP = this.rotatePoint(this.point.left, this.point.top, this.topAngle.LTB * -1)
      const RP = this.rotatePoint(this.point.right, this.point.top, this.topAngle.RTB)
      let temporary = {
        left: [
          [0, 0],
          [0, 0],
          [0, 0]
        ],
        right: [
          [0, 0],
          [0, 0],
          [0, 0]
        ],
        middle: [
          [0, 0],
          [0, 0],
          [0, 0]
        ]
      }

      
      const dataInfo = val.map((item, index) => {
        if (index === 0) {
          for (const key in temporary) {
            if (key === 'left') {
              // 垂直后點(diǎn)的位置
              // 垂直后點(diǎn)點(diǎn)距離
              const vertical = [
                this.point.top[0],
                (LP[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
              ]
              // 還原后點(diǎn)的位置
              temporary.left = [this.point.top, this.rotatePoint(vertical, this.point.top, this.topAngle.LTB), vertical]
            } else if (key === 'right') {
              // 垂直后點(diǎn)點(diǎn)距離
              const vertical = [
                this.point.top[0],
                (RP[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
              ]
              // 還原后點(diǎn)的位置
              temporary.right = [
                this.point.top,
                this.rotatePoint(vertical, this.point.top, this.topAngle.RTB * -1),
                vertical
              ]
            } else if (key === 'middle') {
              // 垂直后點(diǎn)點(diǎn)距離
              temporary.middle = [
                this.point.top,
                [
                  this.point.top[0],
                  (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
                ],
                [
                  this.point.top[0],
                  (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + this.point.top[1]
                ]
              ]
            }
          }
        } else {
          for (const key in temporary) {
            const vertical = JSON.parse(JSON.stringify(temporary[key][2]))
            if (key === 'left') {
              // 垂直后點(diǎn)點(diǎn)距離
              const vertical1 = [this.point.top[0], vertical[1] + (LP[1] - this.point.top[1]) * (item.accounted / 100)]
              // 還原后點(diǎn)的位置
              temporary.left = [
                this.point.top,
                this.rotatePoint(vertical1, this.point.top, this.topAngle.LTB),
                vertical1
              ]
            } else if (key === 'right') {
              // 垂直后點(diǎn)點(diǎn)距離
              const vertical1 = [this.point.top[0], vertical[1] + (RP[1] - this.point.top[1]) * (item.accounted / 100)]
              // 還原后點(diǎn)的位置
              temporary.right = [
                this.point.top,
                this.rotatePoint(vertical1, this.point.top, this.topAngle.RTB * -1),
                vertical1
              ]
            } else if (key === 'middle') {
              temporary.middle = [
                this.point.top,
                [this.point.top[0], (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + vertical[1]],
                [this.point.top[0], (this.point.bottom[1] - this.point.top[1]) * (item.accounted / 100) + vertical[1]]
              ]
            }
          }
        }

        return { ...item, temporary: JSON.parse(JSON.stringify(temporary)) }
      })
      this.dataInfo = dataInfo
    },
    /**
     * @description: 判斷鼠標(biāo)在哪層位置上
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    determineDataMouse(mouseLocation) {
      let req = false
      for (let index = 0; index < this.dataInfo.length; index++) {
        if (this.insidePolygon(this.dataInfo[index].drawingPoint, mouseLocation)) {
          return (req = { l: index + 1, obj: this.dataInfo[index] })
        }
      }
      return req
    },
    // ======================================繪圖==========================================
    /**
     * @description: 繪畫主體
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    paintingBody() {
      // 左半邊金字塔陰影
      this.ctx.fillStyle = 'rgba(120,120,120,.15)'
      this.ctx.beginPath()
      this.ctx.moveTo(...this.point.top)
      this.ctx.lineTo(...this.point.bottom)
      this.ctx.lineTo(...this.point.left)
      this.ctx.fill()

      this.ctx.fill()
    },
    /**
     * @description: 數(shù)據(jù)圖層繪畫
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    paintDataInfo() {
      var index = -1
      this.dataInfo = this.dataInfo.map(item => {
        index++
        if (this.integration.color.length === index) {
          index = 0
        }
        return { ...item, color: this.integration.color[index] }
      })
      this.dataInfo = this.dataInfo.map((item, index) => {
        let drawingPoint = []
        this.ctx.fillStyle = item.color
        this.ctx.beginPath()
        let point1, point2, point3, point4, point5, point6
        if (index === 0) {
          [point1, point2, point3, point4, point5, point6] = [
            item.temporary.left[0],
            item.temporary.left[1],
            item.temporary.middle[1],
            item.temporary.right[1],
            item.temporary.right[0],
            item.temporary.middle[0]
          ]
        } else {
          [point1, point2, point3, point4, point5, point6] = [
            this.dataInfo[index - 1].temporary.left[1],
            item.temporary.left[1],
            item.temporary.middle[1],
            item.temporary.right[1],
            this.dataInfo[index - 1].temporary.right[1],
            this.dataInfo[index - 1].temporary.middle[1]
          ]
        }
        this.ctx.moveTo(...point1)
        this.ctx.lineTo(...point2)
        this.ctx.lineTo(...point3)
        this.ctx.lineTo(...point4)
        this.ctx.lineTo(...point5)
        this.ctx.lineTo(...point6)
        drawingPoint = [point1, point2, point3, point4, point5, point6]
        if (this.integration.infoStyle.stroke) {
          this.ctx.shadowOffsetX = 0
          this.ctx.shadowOffsetY = 0
          this.ctx.shadowBlur = 2
          this.ctx.shadowColor = this.integration.infoStyle.strokeColor
        }
        this.ctx.fill()
        return { ...item, drawingPoint }
      })
    },
    /**
     * @description: 繪畫字體
     * 此方法請?jiān)?paintDataInfo() 執(zhí)行后使用
     * @param {*}
     * @return {*}
     * @author: 舒冬冬
     */
    paintingText(lData) {
      this.ctx.shadowColor = 'rgba(90,90,90,0)'
      const color = this.integration.infoStyle.color ? this.integration.infoStyle.color : '#fff'
      const width = this.integration.infoStyle.width ? this.integration.infoStyle.width : 0
      const dotSize = this.integration.infoStyle.dotSize ? this.integration.infoStyle.dotSize : 4
      const offset = this.integration.infoStyle.offset ? this.integration.infoStyle.offset : [0, 0]
      let text = ''
      this.ctx.strokeStyle = color
      this.ctx.fillStyle = color
      this.dataInfo.forEach((item, index) => {
        if (item.drawingPoint) {
          let line = [
            [0, 0],
            [0, 0]
          ]
          this.ctx.font = `normal lighter ${
            this.integration.infoStyle.size ? this.integration.infoStyle.size : 14
          }px sans-serif `

          this.ctx.beginPath()
          if (lData && index + 1 === lData.l) {
            line = [
              [
                lData.obj.drawingPoint[2][0],
                (lData.obj.drawingPoint[2][1] - lData.obj.drawingPoint[5][1]) / 2 + lData.obj.drawingPoint[5][1]
              ],
              [
                lData.obj.drawingPoint[2][0] + lData.obj.drawingPoint[2][0] / 2 + width,
                (lData.obj.drawingPoint[2][1] - lData.obj.drawingPoint[5][1]) / 2 + lData.obj.drawingPoint[5][1]
              ]
            ]

            this.ctx.font = `normal lighter ${
              this.integration.infoStyle.size ? this.integration.infoStyle.size + 2 : 16
            }px sans-serif `
            text =
              this.integration.fontFormatter(item) !== 'default'
                ? this.integration.fontFormatter(item)
                : lData.obj.value + ' ---- ' + lData.obj.name
            this.ctx.setLineDash([0, 0])
            this.ctx.strokeText(
              text,
              line[1][0] + offset[0],
              line[1][1] + (this.integration.infoStyle.size ? this.integration.infoStyle.size + 2 : 14) / 3 + offset[1]
            )
          } else {
            line = [
              [
                item.drawingPoint[2][0],
                (item.drawingPoint[2][1] - item.drawingPoint[5][1]) / 2 + item.drawingPoint[5][1]
              ],
              [
                item.drawingPoint[2][0] + item.drawingPoint[2][0] / 2 + width,
                (item.drawingPoint[2][1] - item.drawingPoint[5][1]) / 2 + item.drawingPoint[5][1]
              ]
            ]
            text =
              this.integration.fontFormatter(item) !== 'default'
                ? this.integration.fontFormatter(item)
                : item.value + ' ----- ' + item.name
            this.ctx.setLineDash([0, 0])
            this.ctx.strokeText(
              text,
              line[1][0] + offset[0],
              line[1][1] + (this.integration.infoStyle.size ? this.integration.infoStyle.size + 2 : 16) / 3 + offset[1]
            )
          }
          this.ctx.setLineDash(this.integration.infoStyle.setLineDash)
          this.ctx.moveTo(...line[0])
          this.ctx.lineTo(...line[1])
          this.ctx.stroke()
          this.ctx.arc(...line[0], dotSize, 0, 360, false)
          this.ctx.fill() //畫實(shí)心圓
        } else {
          throw '未找到 drawingPoint 屬性'
        }
      })
    },
    /**
     * @description: 顯示tooltip位置
     * @param {*} lData 當(dāng)前層級
     * @param {*} coordinates 鼠標(biāo)位置
     * @return {*}
     * @author: 舒冬冬
     */
    showTooltip(lData, coordinates) {
      let canvasWarpper = document.getElementById('canvas-warpper')
      let canvasTooltip = document.getElementById('canvas-tooltip')
      if (lData) {
        canvasTooltip.style.zIndex = this.integration.tooltip.z
        canvasTooltip.style.transition =
          ' opacity 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s, visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s,transform 0.15s'
        let html = JSON.parse(JSON.stringify(this.tooltipDiv))
        if (this.integration.tooltip.formatter) {
          html = this.integration.tooltip.formatter(lData)
        } else {
          const searchVal = [
            ['$[title]$', lData.obj.title],
            ['$[name]$', lData.obj.name],
            ['$[val]$', lData.obj.value],
            ['$[color]$', lData.obj.color],
            ['$[fontSize]$', this.integration.tooltip.fontSize],
            ['$[backgroundColor]$', this.integration.tooltip.backgroundColor],
            ['$[fontColor]$', this.integration.tooltip.fontColor]
          ]
          searchVal.forEach(el => {
            html = html.replaceAll(...el)
          })
        }
        canvasTooltip.innerHTML = html
        canvasWarpper.style.cursor = 'pointer'
        canvasTooltip.style.visibility = 'visible'
        canvasTooltip.style.opacity = 1
        let [x, y] = coordinates
        x = x + 20
        y = y + 20
        // 畫布高度
        // canvasHeight: 0,
        // 畫布寬度
        // canvasWidth: 0,
        // 判斷是否超出框架內(nèi)容
        if (x + canvasTooltip.clientWidth > this.canvasWidth) {
          x = x - canvasTooltip.clientWidth - 40
        }
        if (y + canvasTooltip.clientHeight > this.canvasHeight) {
          y = y - canvasTooltip.clientHeight - 40
        }
        canvasTooltip.style.transform = `translate3d(${x}px, ${y}px, 0px)`
      } else {
        canvasWarpper.style.cursor = 'default'
        canvasTooltip.style.visibility = 'hidden'
        canvasTooltip.style.opacity = 0
      }
    },
    /**
     * @description: 高亮某一層級
     * @param {*} lData 層級數(shù)據(jù)
     * @return {*}
     * @author: 舒冬冬
     */
    highlightCurrentRegion(lData) {

      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
      if (!lData) {
        this.paintDataInfo()
        this.ctx.shadowColor = 'rgba(90,90,90,0)'
        this.paintingBody()
        this.paintingText()
        return
      }
      this.paintDataInfo()
      this.ctx.shadowColor = 'rgba(90,90,90,0)'
      this.paintingBody()
      this.ctx.fillStyle = lData.obj.color
      //  this.ctx.scale(1.05, 1.05)
      this.ctx.beginPath()
      this.ctx.moveTo(lData.obj.drawingPoint[0][0], lData.obj.drawingPoint[0][1])
      this.ctx.lineTo(lData.obj.drawingPoint[1][0], lData.obj.drawingPoint[1][1])
      this.ctx.lineTo(lData.obj.drawingPoint[2][0], lData.obj.drawingPoint[2][1])
      this.ctx.lineTo(lData.obj.drawingPoint[3][0], lData.obj.drawingPoint[3][1])
      this.ctx.lineTo(lData.obj.drawingPoint[4][0], lData.obj.drawingPoint[4][1])
      this.ctx.lineTo(lData.obj.drawingPoint[5][0], lData.obj.drawingPoint[5][1])
      this.ctx.shadowOffsetX = 0
      this.ctx.shadowOffsetY = 0
      this.ctx.shadowBlur = 10
      this.ctx.shadowColor = this.integration.infoStyle.highlightedColor
      this.ctx.fill()
      // 陰影繪制
      this.ctx.beginPath()
      this.ctx.moveTo(lData.obj.drawingPoint[0][0], lData.obj.drawingPoint[0][1])
      this.ctx.lineTo(lData.obj.drawingPoint[1][0], lData.obj.drawingPoint[1][1])
      this.ctx.lineTo(lData.obj.drawingPoint[2][0], lData.obj.drawingPoint[2][1])
      this.ctx.lineTo(lData.obj.drawingPoint[5][0], lData.obj.drawingPoint[5][1])
      this.ctx.fillStyle = 'rgba(120,120,120,.15)'
      this.ctx.fill()
      this.paintingText(lData)
    }
  }
}
</script>


結(jié)尾

項(xiàng)目地址:(https://github.com/SHDjason/Pyramid.git)

到此這篇關(guān)于使用canvas仿Echarts實(shí)現(xiàn)金字塔圖的實(shí)例代碼的文章就介紹到這了,更多相關(guān)canvas仿Echarts金字塔圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

河东区| 东乡| 元谋县| 色达县| 满洲里市| 亚东县| 西安市| 林口县| 双牌县| 遵义市| 化州市| 乃东县| 永年县| 英山县| 雅江县| 英超| 尖扎县| 平谷区| 九江县| 淄博市| 剑阁县| 汪清县| 大港区| 宝应县| 昌图县| 北海市| 青冈县| 卫辉市| 土默特右旗| 江安县| 银川市| 绥芬河市| 绩溪县| 淮滨县| 黑水县| 梁山县| 黄平县| 襄汾县| 东明县| 扎鲁特旗| 凉城县|