雷达图canvas

手写雷达图

项目中用到了一个雷达图; 本来想用插件来实现的;找了一圈只有五边的;有了前面的 canvas 使用经验 想了想直接手写一个

UI给的效果  :

实现的效果图 自适应数据  6边的 5边的 4边的 3边的

 

 但是上面的 文字不同的边对齐方式 是有差异性的,需要使用时候调整

 

 实现的核心逻辑就是求 圆圈上的一个点: 

  •  一共画了四个圆环分别求圆上对应角度的点 坐标画成 六边形  五边形 四边形 三边形
    // 对应角度 对应半径 求圆上的点
    angleToPositon(angle, radius) {
      let endAngle = ((2 * Math.PI) / 360) * angle + this.startAngle;
      // console.log("anele:", angle)
      // console.log("endAngle:", endAngle)
      return {
        x: parseFloat(this.center.x) + Math.cos(endAngle) * (radius),
        y: parseFloat(this.center.y) + Math.sin(endAngle) * (radius),
      }
    },
    
  • 最后再根据数据的 百分百 显示对应轴的上距离 求出点的位置。
    // 数据百分百 对应的坐标
    drawDatum(datum, color, colorLine) {
      this.canvansContent.beginPath()
      for (let i = 0; i < this.radarLings.length; i++) {
        // console.log(datum[i].value)
        let positon = this.angleToPositon(i * this.childAngle, this.radius * datum[i].value / 100)
        this.canvansContent.lineTo(positon.x, positon.y)
      }
      this.canvansContent.closePath(); //回到起点 闭合路径
      this.canvansContent.fillStyle = color
      this.canvansContent.setStrokeStyle(colorLine);
      this.canvansContent.setLineWidth(2)
      this.canvansContent.fill()
      this.canvansContent.stroke()
    }
    

  • 链接这组数据上的点,并且填充颜色就得到下边的图了                       

源码如下:代码未整理只是验证实现原理

<template>
  <view class="radar-map">
    <canvas v-if="canvasId" class="canvas-content" :canvas-id="canvasId" :id="canvasId" :style="{
			width: boxSize + 'rpx',
			height: boxSize  + 'rpx',
			backgroundColor: '#FFFFFF'
		}" binderror="canvasIdErrorCallback" bindtap='CanvasTap'></canvas>
  </view>
</template>

<script>
  //https://uniapp.dcloud.io/api/canvas/CanvasContext.html
  export default {
    data() {
      return {
        canvasId: 'canvasId',
        startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向
        canvansContent: null
      }
    },
    props: {
      boxSize: {
        type: Number,
        default: 300,
      },
      borderWidth: {
        type: Number,
        default: uni.upx2px(2),
      },
      radarLings: {
        type: Array,
        default: () => {
          return []
        }
      },
      datumArray: {
        type: Array,
        default: () => {
          return []
        }
      }
    },
    computed: {
      canvasStyle() {
        return `{width: ${this.boxSize}px,
                 height: ${this.boxSize}px,
                 backgroundColor: #ffff00}`
      },
      radius() {
        return uni.upx2px(360 / 2)
      },
      childAngle() {
        return 360 / this.radarLings.length
      },
      center() {
        return {
          x: `${uni.upx2px( this.boxSize / 2)}`,
          y: `${uni.upx2px( this.boxSize / 2)}`,
        }
      }

    },
    mounted() {
      this.canvansContent = uni.createCanvasContext(this.canvasId, this)
      this.drawContent()
    },
    methods: {
      CanvasTap(e) {
        console.log('CanvasTap:', e)
      },
      drawContent() {
        // this.canvansContent.setLineWidth(1); // 设置圆环宽度
        // this.canvansContent.setStrokeStyle('#007AFF'); // 线条颜色

        // this.canvansContent.beginPath()
        // this.canvansContent.arc(this.radius, this.radius, this.borderWidth, this.startAngle, (2 * Math.PI), false);
        // this.canvansContent.setFillStyle('#ff0000');
        // this.canvansContent.fill()
        // this.canvansContent.beginPath()
        // this.canvansContent.arc(100, 75, 2, 0, 2 * Math.PI)
        // this.canvansContent.setFillStyle('lightgreen')
        // this.canvansContent.fill()

        // this.canvansContent.beginPath()
        // this.canvansContent.arc(100, 75, 2, 0, 2 * Math.PI)
        // this.canvansContent.setFillStyle('lightgreen')
        // this.canvansContent.fill()

        // this.canvansContent.beginPath()
        // this.canvansContent.arc(100, 25, 2, 0, 2 * Math.PI)
        // this.canvansContent.setFillStyle('blue')
        // this.canvansContent.fill()

        // this.canvansContent.beginPath()
        // this.canvansContent.arc(150, 75, 2, 0, 2 * Math.PI)
        // this.canvansContent.setFillStyle('red')
        // this.canvansContent.fill()

        // this.canvansContent.beginPath()
        // this.canvansContent.arc(this.center.x, this.center.y, 2, 0, 2 * Math.PI)
        // this.canvansContent.setFillStyle('#FF0000')
        // this.canvansContent.fill()


        // 画圆圈
        // this.canvansContent.beginPath()
        // this.canvansContent.arc(this.center.x, this.center.y, this.radius, this.startAngle, 2 * Math.PI, false)
        // this.canvansContent.setStrokeStyle('#333333')
        // this.canvansContent.stroke()
        
        // this.canvansContent.beginPath()
        // this.canvansContent.arc(this.center.x, this.center.y, this.radius*0.75, this.startAngle, 2 * Math.PI, false)
        // this.canvansContent.setStrokeStyle('#333333')
        // this.canvansContent.stroke()
        
        // this.canvansContent.beginPath()
        // this.canvansContent.arc(this.center.x, this.center.y, this.radius*0.5, this.startAngle, 2 * Math.PI, false)
        // this.canvansContent.setStrokeStyle('#333333')
        // this.canvansContent.stroke()
        
        // this.canvansContent.beginPath()
        // this.canvansContent.arc(this.center.x, this.center.y, this.radius*0.25, this.startAngle, 2 * Math.PI, false)
        // this.canvansContent.setStrokeStyle('#333333')
        // this.canvansContent.stroke()
        
 



        // this.canvansContent.beginPath()
        // this.canvansContent.moveTo(this.angleToPositon(0).x, this.angleToPositon(0).y);
        // this.canvansContent.lineTo(this.angleToPositon(60).x, this.angleToPositon(60).y); //终点 

        // this.canvansContent.moveTo(this.angleToPositon(60).x, this.angleToPositon(60).y);
        // this.canvansContent.lineTo(this.angleToPositon(120).x, this.angleToPositon(120).y); //终点 

        // this.canvansContent.moveTo(this.angleToPositon(120).x, this.angleToPositon(120).y);
        // this.canvansContent.lineTo(this.angleToPositon(180).x, this.angleToPositon(180).y); //终点 

        // this.canvansContent.moveTo(this.angleToPositon(180).x, this.angleToPositon(180).y);
        // this.canvansContent.lineTo(this.angleToPositon(240).x, this.angleToPositon(240).y); //终点 

        // this.canvansContent.moveTo(this.angleToPositon(240).x, this.angleToPositon(240).y);
        // this.canvansContent.lineTo(this.angleToPositon(300).x, this.angleToPositon(300).y); //终点 

        // this.canvansContent.moveTo(this.angleToPositon(300).x, this.angleToPositon(300).y);
        // this.canvansContent.lineTo(this.angleToPositon(360).x, this.angleToPositon(360).y); //终点 

        // this.canvansContent.closePath(); //回到起点
        // this.canvansContent.setStrokeStyle("#1300ee"); //颜色
        // this.canvansContent.setLineWidth(1.5) //宽度
        // this.canvansContent.stroke(); 


        // this.canvansContent.beginPath()
        // for (let i = 0; i < this.radarLings.length; i++) {

        //   let positonBeginn = this.angleToPositon(i * 60)
        //   let positonEnd = this.angleToPositon((i+1) * 60)

        //   this.canvansContent.moveTo(positonBeginn.x, positonBeginn.y);
        //   this.canvansContent.lineTo(positonEnd.x, positonEnd.y); 

        //   // positon = this.angleToPositon(i * 60)
        //   // console.log("positon:", positon)
        //   //this.canvansContent.moveTo(positonBeginn.x, positon.y); //起点 

        //   // this.canvansContent.beginPath()
        //   // this.canvansContent.arc(positonBeginn.x, positonBeginn.y, 5, 0, 2 * Math.PI)
        //   // this.canvansContent.setFillStyle('blue')
        //   // this.canvansContent.fill()
        // }
        // this.canvansContent.closePath(); //回到起点
        // this.canvansContent.setStrokeStyle("#00eeee"); //颜色
        // this.canvansContent.setLineWidth(1.5) //宽度
        // this.canvansContent.stroke(); 

        this.drawCenterLines()
        this.drawBoxLines(this.radius, 2);
        this.drawBoxLines(this.radius * 0.75);
        this.drawBoxLines(this.radius * 0.50);
        this.drawBoxLines(this.radius * 0.25);

        // this.canvansContent.draw()
        // return

        this.drawDatum(this.datumArray[0], "rgba(236, 253, 244,0.6)", '#46B77F')
        this.drawDatum(this.datumArray[1], "rgba(219, 239, 245, 0.6)", '#2168F9')
        this.canvansContent.draw()
      },
      angleToPositon(angle, radius) {
        let endAngle = ((2 * Math.PI) / 360) * angle + this.startAngle;
        // console.log("anele:", angle)
        // console.log("endAngle:", endAngle)
        return {
          x: parseFloat(this.center.x) + Math.cos(endAngle) * (radius),
          y: parseFloat(this.center.y) + Math.sin(endAngle) * (radius),
        }
      },
      // 画六边形外框
      drawBoxLines(radius, width = 1) {
        this.canvansContent.beginPath()
        for (let i = 0; i < this.radarLings.length; i++) {

          let positonBegin = this.angleToPositon(i * this.childAngle, radius)
          let positonEnd = this.angleToPositon((i + 1) * this.childAngle, radius)

          this.canvansContent.moveTo(positonBegin.x, positonBegin.y)
          this.canvansContent.lineTo(positonEnd.x, positonEnd.y)
        }
        this.canvansContent.closePath(); //回到起点 闭合路径
        this.canvansContent.setStrokeStyle("#E6E6E6") //颜色
        this.canvansContent.setLineWidth(width) //宽度
        this.canvansContent.stroke()

      },
      // 连接中心点到六边形
      drawCenterLines() {
        this.canvansContent.beginPath()
        for (let i = 0; i < this.radarLings.length; i++) {
          let positon = this.angleToPositon(i * this.childAngle, this.radius)
          this.canvansContent.moveTo(this.center.x, this.center.y)
          this.canvansContent.lineTo(positon.x, positon.y)

          this.drawTitle(positon, i)
        }
        this.canvansContent.closePath();
        this.canvansContent.setStrokeStyle("#E6E6E6") //颜色
        this.canvansContent.setLineWidth(1) //宽度
        this.canvansContent.stroke()
      },
      drawTitle(positon, i) {
        switch (i) {
          case 0:
            positon.y -= 12;
            this.canvansContent.textAlign = 'center'
            break
          case 1:
            positon.x += 12;
            this.canvansContent.textAlign = 'left'
            break
          case 2:
            positon.x += 12;
            this.canvansContent.textAlign = 'left'
            break
          case 3:
            positon.y += 20;
            this.canvansContent.textAlign = 'center'
            break
          case 4:
            positon.x -= 12;
            this.canvansContent.textAlign = 'right'
            break
          case 5:
            positon.x -= 12;
            this.canvansContent.textAlign = 'right'
            break

        }
        this.canvansContent.setFontSize(12)
        this.canvansContent.setFillStyle('#999999')
        // this.canvansContent.textAlign = 'center'
        this.canvansContent.fillText(this.radarLings[i], positon.x, positon.y)
      },

      drawDatum(datum, color, colorLine) {
        this.canvansContent.beginPath()
        for (let i = 0; i < this.radarLings.length; i++) {
          // console.log(datum[i].value)
          let positon = this.angleToPositon(i * this.childAngle, this.radius * datum[i].value / 100)
          this.canvansContent.lineTo(positon.x, positon.y)
        }
        this.canvansContent.closePath(); //回到起点 闭合路径
        this.canvansContent.fillStyle = color
        this.canvansContent.setStrokeStyle(colorLine);
        this.canvansContent.setLineWidth(2)
        this.canvansContent.fill()
        this.canvansContent.stroke()
      }
    }
  }
</script>

<style lang="scss" scoped>
  .radar-map {
    width: 100%;
    height: 100%;

    .canvas-content {
      width: 360rpx;
      height: 360rpx;
    }
  }
</style>

引入组件的数据

 ['综合积分', '巡检次数', '巡检完成率', '隐患发现个数', '巡更完成率', '巡更次数'],
<template>
  <view class="process">
<!--    <arprogress :width='284' :borderWidth='30' :percent="percent"><text>{
   
   {percent}}%</text></arprogress> -->
    <view class="parent-box">
      <radar-map :boxSize='boxSize' :radarLings='radarLings' :datumArray="datumArray"></radar-map>
    </view>
  </view>

</template>

<script>
  import arprogress from '@/components/circleProgress/index.vue'
  import radarMap from '@/components/radarMap/radarMap.vue'
  export default {
    data() {
      return {
        percent: 30,
        radarLings: ['综合积分', '巡检次数', '巡检完成率', '隐患发现个数', '巡更完成率', '巡更次数'],
        datumArray: [
          [{
            value: 80,
            min: 0,
            max: 100
          }, {
            value: 85,
            min: 0,
            max: 100
          }, {
            value: 75,
            min: 0,
            max: 100
          }, {
            value: 90,
            min: 0,
            max: 100
          }, {
            value: 70,
            min: 0,
            max: 100
          }, {
            value: 95,
            min: 0,
            max: 100
          }],
          [{
            value: 66.6,
            min: 0,
            max: 100
          }, {
            value: 40,
            min: 0,
            max: 100
          }, {
            value: 59,
            min: 0,
            max: 100
          }, {
            value: 30,
            min: 0,
            max: 100
          }, {
            value: 90,
            min: 0,
            max: 100
          }, {
            value: 100,
            min: 0,
            max: 100
          }]
        ],
        boxSize: 650
      }
    },
    components: {
      // arprogress,
      radarMap
    },
    computed: {

    },
    methods: {
      onChange(value) {
        // console.log(value.detail.value)
        this.percent = value.detail.value
      }
    }
  }
</script>

<style lang="scss" scoped>
  page {
    background-color: #F8F8F8;
  }

  .process {
    width: 100%;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;


    .parent-box {
      margin-top: 88rpx;
      width: 650rpx;
      height: 650rpx;
      background-color: #FFFFFF;
    }


  }
</style>

猜你喜欢

转载自blog.csdn.net/nicepainkiller/article/details/125372034
今日推荐