手写雷达图
项目中用到了一个雷达图; 本来想用插件来实现的;找了一圈只有五边的;有了前面的 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>