canvas繪製一個可以讓鼠標hover在點上的時候彈出小窗的折線圖

<template>
  <div>
    <Card :type="3" :performanceData='performanceData' />
    <div class="card-container">
      <div class="card-title">周正確率走勢</div>
      <div>
        <canvas v-on:mousemove="visible" @mouseleave="invisible" id="chart" width="360px" height="250px" style="width: 360px;height: 250px"></canvas>
      </div>
      <div v-show="seen" class="hover-item">
        <div class="time">{{performance.range}}</div>
        <div class="rate">
          周正確率
          <span class="big-number">{{performance.rate}}%</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Card from '@/components/stu/performance/card'
export default {
  props: {
    performanceData: {
      type: Object,
      required: true
    }
  },
  name: 'RateCard',
  components: {
    Card
  },
  data () {
    return {
      seen: false,
      performance: {
        range: '',
        rate: 0
      },
      points: [

      ]
    }
  },
  mounted () {

  },
  methods: {
    visible: function (e) {
      let canvas = document.getElementById('chart')
      var rect = canvas.getBoundingClientRect()
      let xy = {
        x: 2 * Math.round(e.clientX - rect.left),
        y: 2 * Math.round(e.clientY - rect.top)
      }
      let points = this.points
      let obj = {}
      obj = points.find((item) => {
        return xy.x >= item.xAxis - 7 && xy.x <= item.xAxis + 7 && xy.y >= item.yAxis - 7 && xy.y <= item.yAxis + 7
      })
      if (obj) {
        let temp1 = new Date(obj.date)
        temp1.setDate(temp1.getDate() - 7)

        let temp2 = new Date(obj.date)
        temp2.setDate(temp2.getDate() - 1)

        this.performance.range = temp1.getFullYear() + '-' + parseInt(temp1.getMonth() + 1) + '-' + temp1.getDate() + '~' + temp2.getFullYear() + '-' + parseInt(temp2.getMonth() + 1) + '-' + temp2.getDate()
        this.performance.rate = obj.rate
        this.seen = true
      }
    },
    invisible: function () {
      this.seen = false
    },
    goChart (dataArr) {
      // 聲明所需變量
      let canvas, ctx
      // 獲得canvas上下文
      canvas = document.getElementById('chart')
      if (canvas && canvas.getContext) {
        ctx = canvas.getContext('2d')
      }
      this.initChart(canvas, ctx, dataArr) // 圖表初始化
    },
    initChart (canvas, ctx, dataArr) {
      // 圖表信息
      let cMargin = 20
      let cSpace = 70
      /* 這裏是對高清屏幕的處理,
            方法:先將canvas的width 和height設置成本來的兩倍
            然後將style.height 和 style.width設置成本來的寬高
            這樣相當於把兩倍的東西縮放到原來的 1/2,這樣在高清屏幕上 一個像素的位置就可以有兩個像素的值
            這樣需要注意的是所有的寬高間距,文字大小等都得設置成原來的兩倍纔可以。
        */
      canvas.width = 720
      canvas.height = 400
      canvas.style.height = canvas.height / 2 + 'px'
      canvas.style.width = canvas.width / 2 + 'px'
      let cHeight = canvas.height - cMargin - cSpace
      let cWidth = canvas.width - cMargin - cSpace
      let originX = cMargin + cSpace
      let originY = cMargin + cHeight

      // 折線圖信息
      let tobalDots = dataArr.length
      let dotSpace = parseInt(cWidth / tobalDots)
      let maxValue = 0
      for (var i = 0; i < dataArr.length; i++) {
        var dotVal = parseInt(dataArr[i][1])
        if (dotVal > maxValue) {
          maxValue = dotVal
        }
      }
      maxValue = 100
      let totalYNomber = 4
      // 運動相關
      let ctr = 1
      let numctr = 100
      let speed = 6

      ctx.translate(0.5, 0.5) // 當只繪製1像素的線的時候,座標點需要偏移,這樣才能畫出1像素實線
      this.drawLineLabelMarkers(
        ctx,
        cHeight,
        cWidth,
        originX,
        originY,
        cMargin,
        cSpace,
        maxValue,
        totalYNomber,
        tobalDots,
        dataArr,
        dotSpace,
        canvas
      ) // 繪製圖表軸、標籤和標記
      this.drawLineAnimate(
        ctx,
        maxValue,
        tobalDots,
        dataArr,
        dotSpace,
        canvas,
        cHeight,
        ctr,
        numctr,
        originY,
        originX,
        speed,
        cWidth,
        cMargin,
        cSpace,
        totalYNomber
      ) // 繪製折線圖的動畫
    },
    drawLineLabelMarkers (
      ctx,
      cHeight,
      cWidth,
      originX,
      originY,
      cMargin,
      cSpace,
      maxValue,
      totalYNomber,
      tobalDots,
      dataArr,
      dotSpace,
      canvas
    ) {
      ctx.font = '24px Arial'
      ctx.lineWidth = 2
      ctx.fillStyle = '#566a80'
      ctx.strokeStyle = '#E3E3E3'
      // y軸
      // this.drawLine(ctx,originX, originY, originX, cMargin);
      // x軸
      this.drawLine(ctx, originX, originY, originX + cWidth, originY)

      // 繪製標記
      this.drawMarkers(
        ctx,
        cHeight,
        cWidth,
        originX,
        originY,
        cMargin,
        cSpace,
        maxValue,
        totalYNomber,
        tobalDots,
        dataArr,
        dotSpace,
        canvas
      )
    },
    drawLine (ctx, x, y, X, Y) {
      ctx.beginPath()
      ctx.moveTo(x, y)
      ctx.lineTo(X, Y)
      ctx.stroke()
      ctx.closePath()
    },
    drawMarkers (
      ctx,
      cHeight,
      cWidth,
      originX,
      originY,
      cMargin,
      cSpace,
      maxValue,
      totalYNomber,
      tobalDots,
      dataArr,
      dotSpace,
      canvas
    ) {
      ctx.strokeStyle = '#E0E0E0'
      // 繪製 y 軸 及中間橫線
      let oneVal = parseInt(maxValue / totalYNomber)
      ctx.textAlign = 'right'

      ctx.font = '32px Arial'
      ctx.fillStyle = '#333333'

      for (let i = 0; i <= totalYNomber; i++) {
        let markerVal = i * oneVal
        let xMarker = originX - 5
        let yMarker = parseInt(cHeight * (1 - markerVal / maxValue)) + cMargin

        ctx.fillText(markerVal + '%', xMarker, yMarker + 3, cSpace) // 文字
        if (i > 0) {
          this.drawLine(ctx, originX + 2, yMarker, originX + cWidth, yMarker)
        }
      }

      ctx.font = '24px Arial'
      ctx.fillStyle = '#999999'
      // 繪製 x 軸 及中間豎線
      ctx.textAlign = 'center'
      // 隱藏x軸座標
      // for (let i = 0; i < tobalDots; i++) {
      //   let markerVal = dataArr[i][0]
      //   let xMarker = originX + i * dotSpace
      //   let yMarker = originY + 30
      //   ctx.fillText(markerVal, xMarker, yMarker, cSpace) // 文字
      //   // if(i>0){
      //   //     this.drawLine(ctx, xMarker, originY-2, xMarker, cMargin);
      //   // }
      // }
      // 繪製標題 y
      ctx.save()
      ctx.rotate(-Math.PI / 2)
      // ctx.fillText("訪問量", -canvas.height/2, cSpace-10);
      ctx.restore()
      // 繪製標題 x
      // ctx.fillText("月份", originX+cWidth/2, originY+cSpace/2+20);
    },
    drawLineAnimate (
      ctx,
      maxValue,
      tobalDots,
      dataArr,
      dotSpace,
      canvas,
      cHeight,
      ctr,
      numctr,
      originY,
      originX,
      speed,
      cWidth,
      cMargin,
      cSpace,
      totalYNomber
    ) {
      ctx.strokeStyle = '#3B80FC' // "#49FE79";
      ctx.lineWidth = 4
      // 連線
      ctx.beginPath()
      for (let i = 0; i < tobalDots; i++) {
        let dotVal = dataArr[i][1]
        let barH = parseInt((((cHeight * dotVal) / maxValue) * ctr) / numctr) //
        let y = originY - barH
        let x = originX + dotSpace * i
        // 如果沒有數據就不繪製連線
        if (dotVal !== '') {
          if (i === 0) {
            ctx.moveTo(x, y)
          } else {
            ctx.lineTo(x, y)
          }
        }
      }
      ctx.stroke()

      // 背景
      ctx.lineTo(originX + dotSpace * (tobalDots - 1), originY)
      ctx.lineTo(originX, originY)
      // 背景漸變色
      // 柱狀圖漸變色
      // let gradient = ctx.createLinearGradient(0, 0, 0, 300);
      // gradient.addColorStop(0, 'rgba(133,171,212,0.6)');
      // gradient.addColorStop(1, 'rgba(133,171,212,0.1)');
      // ctx.fillStyle = gradient;
      // ctx.fill();
      // ctx.closePath();
      // ctx.fillStyle = "#566a80";

      // 繪製點
      for (let i = 0; i < tobalDots; i++) {
        let dotVal = dataArr[i][1]
        let barH = parseInt((((cHeight * dotVal) / maxValue) * ctr) / numctr)
        let y = originY - barH
        let x = originX + dotSpace * i
        // 沒有數據的話就不繪製
        if (dotVal !== '') {
          this.drawArc(ctx, x, y, dataArr[i][0], dotVal) // 繪製點
        }
        // ctx.fillText(parseInt(dotVal * ctr / numctr), x + 15, y - 8) // 交點位置文字
      }

      if (ctr < numctr) {
        ctr++
        let that = this
        setTimeout(function () {
          ctx.clearRect(0, 0, canvas.width, canvas.height)
          that.drawLineLabelMarkers(
            ctx,
            cHeight,
            cWidth,
            originX,
            originY,
            cMargin,
            cSpace,
            maxValue,
            totalYNomber,
            tobalDots,
            dataArr,
            dotSpace,
            canvas
          ) // 繪製圖表軸、標籤和標記
          that.drawLineAnimate(
            ctx,
            maxValue,
            tobalDots,
            dataArr,
            dotSpace,
            canvas,
            cHeight,
            ctr,
            numctr,
            originY,
            originX,
            speed,
            cWidth,
            cMargin,
            cSpace,
            totalYNomber
          ) // 繪製折線圖的動畫
        }, speed)
      }
    },
    drawArc (ctx, x, y, X, Y) {
      if (x && y) {
        let points = this.points

        let obj = {}
        obj = points.find((item) => {
          return item.xAxis === x && item.date === X
        })
        if (!obj) {
          this.points.push({
            xAxis: x,
            yAxis: y,
            date: X,
            rate: Y
          })
        } else {
          if (obj.yAxis > y) {
            // 刪掉大的加入小的
            // 找到已經被選中列表中的這一項的位置
            let position = points.indexOf(obj)
            // 去掉這個
            points.splice(position, 1)
            points.push({
              xAxis: x,
              yAxis: y,
              date: X,
              rate: Y
            })
            this.points = points
          }
        }
      }
      ctx.beginPath()
      ctx.arc(x, y, 7, 0, Math.PI * 2)
      ctx.fillStyle = '#3B80FC'
      ctx.fill()
      ctx.closePath()
    }
  },
  filters: {},
  created () {},
  watch: {
    performanceData (n, o) {
      let data = n['week'].reverse()
      let chartData = [
        ['']
      ].concat(data)
      this.goChart(chartData)
    }
  }
}
</script>

<style scoped>
.card-container {
  width: 100%;
  height: 368px;
  background: rgba(255, 255, 255, 1);
  margin-bottom: 24px;
  padding-top: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.card-title {
  font-size: 16px;
  font-weight: 400;
  color: rgba(51, 51, 51, 1);
  line-height: 22px;
  margin-bottom: 65px;
}
.hover-item {
  width: 200px;
  height: 60px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: #999999;
  border-radius: 3px;
  position: relative;
  left: 20%;
  top: -40%;
}
.time {
    color: #FFFFFF;
    width: 90%;
}
.rate {
    color: #FFFFFF;
    width: 90%;
}
.big-number {
    color: #FFFFFF;
    font-size: 20px
}
</style>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章