372 lines
8.4 KiB
Vue
372 lines
8.4 KiB
Vue
<template>
|
|
<view class="tui-charts__radar-box" :style="{width:radar_w+'px'}">
|
|
<view class="tui-radar__legend" v-if="legend.show">
|
|
<view class="tui-radar__legend-item" v-for="(item,index) in dataset" :key="index">
|
|
<view class="tui-legend__circle" :style="{background:item.color}"></view>
|
|
<text
|
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
|
</view>
|
|
</view>
|
|
<view class="tui-charts-radar" :class="{'tui-radar__mrgin':label.show}"
|
|
:style="{width:radar_w+'px',height:radar_w+'px'}">
|
|
<view class="tui-radar__radius" v-for="(item,index) in indicators" :key="index"
|
|
:style="{height:radar_w/2+'px',transform:`rotate(${item.angle}deg)`,background:axisLineColor,width:lineBold?'2px':'1px'}">
|
|
<view class="tui-radar__name" v-if="label.show"
|
|
:style="{color:label.color || '#bbb',fontSize:(label.size || 24) +'rpx'}">{{item.name}}</view>
|
|
</view>
|
|
<view class="tui-radar__center" :style="{width:lineBold?'2px':'1px',height:lineBold?'2px':'1px'}">
|
|
<view class="tui-radar__hypotenuse" v-for="(l,idx) in hypotenuse" :key="l.id"
|
|
:style="[{bottom: l.y+'px', left: l.x+'px',width: l.width+'px',transform: `rotate(${l.angle}deg)`,background:splitLineColor,height:lineBold?'2px':'1px'}]">
|
|
</view>
|
|
<view v-for="(item,index) in dataset" :key="index" @tap.stop="onDotTap(index)">
|
|
<view class="tui-radar__dataset" v-for="(d,i) in item.lines" :key="d.id"
|
|
:style="{bottom: d.y+'px', left: d.x+'px',width: d.width+'px',transform: `rotate(${d.angle}deg)`,background:item.color,height:lineBold?'2px':'1px'}">
|
|
<view class="tui-radar__dot" :style="{background:item.color}"></view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="tui-radar__tooltip" v-if="tooltip" :class="{'tui-radar__tooltip-show':tooltipShow}">
|
|
<view class="tui-tooltip__title">{{name}}</view>
|
|
<view class="tui-radar__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
|
<view class="tui-legend__circle" :style="{background:color}"></view>
|
|
<text class="tui-tooltip__val">{{item.name}}</text>
|
|
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.value}}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: "tui-charts-radar",
|
|
emits: ['click'],
|
|
props: {
|
|
//图例,说明
|
|
legend: {
|
|
type: Object,
|
|
default () {
|
|
return {
|
|
show: true,
|
|
size: 24,
|
|
color: '#333'
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
width: {
|
|
type: [Number, String],
|
|
default: 480
|
|
},
|
|
splitNumber: {
|
|
type: [Number, String],
|
|
default: 5
|
|
},
|
|
indicator: {
|
|
type: Array,
|
|
default () {
|
|
return []
|
|
}
|
|
},
|
|
label: {
|
|
type: Object,
|
|
default () {
|
|
return {
|
|
show: true,
|
|
color: '#bbb',
|
|
size: 24
|
|
}
|
|
}
|
|
},
|
|
axisLineColor: {
|
|
type: String,
|
|
default: '#ddd'
|
|
},
|
|
splitLineColor: {
|
|
type: String,
|
|
default: '#eee'
|
|
},
|
|
lineBold:{
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
// #ifndef VUE3
|
|
beforeDestroy() {
|
|
this.clearTimer()
|
|
},
|
|
// #endif
|
|
// #ifdef VUE3
|
|
beforeUnmount() {
|
|
this.clearTimer()
|
|
},
|
|
// #endif
|
|
data() {
|
|
return {
|
|
radar_w: 200,
|
|
dataset: [],
|
|
indicators: [],
|
|
hypotenuse: [],
|
|
name: '',
|
|
color: '',
|
|
tooltips: [],
|
|
tooltipShow: false,
|
|
timer: null
|
|
};
|
|
},
|
|
created() {
|
|
this.radar_w = this.getPx(this.width)
|
|
this.draw(this.dataset)
|
|
},
|
|
methods: {
|
|
getPx(rpx) {
|
|
let px = parseInt(uni.upx2px(Number(rpx)))
|
|
return px % 2 === 0 ? px : px + 1
|
|
},
|
|
getDrawData(dots, idx, type = 'l') {
|
|
let data = [];
|
|
dots.map((dot, index) => {
|
|
let obj = !dots[index + 1] ? dots[0] : dots[index + 1]
|
|
const AB = {
|
|
x: obj.x - dot.x,
|
|
y: obj.y - dot.y,
|
|
y1: dot.y - obj.y
|
|
}
|
|
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
|
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
|
data.push({
|
|
id: type + idx + '_' + index,
|
|
x: dot.x + 1,
|
|
y: dot.y,
|
|
width: v,
|
|
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(angle, 2))
|
|
})
|
|
})
|
|
return data
|
|
},
|
|
initData(dataset, r, indicator, angle) {
|
|
let total = this.radar_w
|
|
dataset = JSON.parse(JSON.stringify(dataset))
|
|
dataset.map((item, index) => {
|
|
let lines = []
|
|
item.source.map((val, idx) => {
|
|
let dsR = val / indicator[idx].max * r
|
|
lines.push({
|
|
y: dsR * Math.cos(angle * idx * Math.PI / 180),
|
|
x: dsR * Math.sin(angle * idx * Math.PI / 180)
|
|
})
|
|
})
|
|
item.lines = this.getDrawData(lines, index, 'd')
|
|
})
|
|
this.dataset = dataset
|
|
},
|
|
draw(dataset) {
|
|
if (!dataset || !dataset[0] || !this.indicator) return
|
|
let len = this.indicator.length
|
|
let angle = 360 / len
|
|
let r = this.radar_w / 2
|
|
let indicator = JSON.parse(JSON.stringify(this.indicator))
|
|
indicator.map((item, i) => {
|
|
item.angle = angle * i
|
|
})
|
|
this.indicators = indicator
|
|
|
|
let dots = []
|
|
let sn = Number(this.splitNumber)
|
|
for (let i = 0; i < sn; i++) {
|
|
let dotArr = []
|
|
let dsDot = []
|
|
let radius = r - i * (r / sn)
|
|
for (let j = 0; j < len; j++) {
|
|
dotArr.push({
|
|
y: radius * Math.cos(angle * j * Math.PI / 180),
|
|
x: radius * Math.sin(angle * j * Math.PI / 180)
|
|
})
|
|
}
|
|
dots.push(dotArr)
|
|
}
|
|
|
|
let lineArr = [];
|
|
dots.map((dotArr, idx) => {
|
|
lineArr = lineArr.concat(this.getDrawData(dotArr, idx))
|
|
})
|
|
|
|
this.hypotenuse = lineArr
|
|
|
|
this.initData(dataset, r, indicator, angle)
|
|
},
|
|
clearTimer() {
|
|
clearTimeout(this.timer)
|
|
this.timer = null;
|
|
},
|
|
tooltipHandle(index) {
|
|
let data = this.dataset[index]
|
|
let tooltips = []
|
|
let indicator = JSON.parse(JSON.stringify(this.indicator))
|
|
indicator.map((item, idx) => {
|
|
item.value = data.source[idx]
|
|
})
|
|
this.name = data.name || ''
|
|
this.color = data.color || '#333'
|
|
this.tooltips = indicator;
|
|
this.clearTimer()
|
|
this.tooltipShow = true;
|
|
this.timer = setTimeout(() => {
|
|
this.tooltipShow = false
|
|
}, 5000)
|
|
},
|
|
onDotTap(index) {
|
|
this.tooltipHandle(index);
|
|
this.$emit('click', {
|
|
datasetIndex: index
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.tui-charts__radar-box {
|
|
position: relative;
|
|
}
|
|
|
|
.tui-charts-radar {
|
|
border-radius: 50%;
|
|
position: relative;
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
.tui-radar__mrgin {
|
|
margin-top: 32rpx;
|
|
margin-bottom: 32rpx;
|
|
}
|
|
|
|
.tui-radar__radius {
|
|
/* width: 1px; */
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 0;
|
|
transform: translateX(-50%);
|
|
transform-origin: 50% 100%;
|
|
}
|
|
|
|
.tui-radar__name {
|
|
min-width: 120rpx;
|
|
font-size: 24rpx;
|
|
position: absolute;
|
|
top: -20rpx;
|
|
left: 50%;
|
|
transform: translate(-50%, -100%);
|
|
text-align: center;
|
|
}
|
|
|
|
.tui-radar__center {
|
|
position: absolute;
|
|
/* width: 2px;
|
|
height: 2px; */
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(0, -50%);
|
|
}
|
|
|
|
.tui-radar__hypotenuse,
|
|
.tui-radar__dataset {
|
|
/* height: 1px; */
|
|
position: absolute;
|
|
transform-origin: 0 50%;
|
|
z-index: 2;
|
|
}
|
|
|
|
.tui-radar__dataset {
|
|
z-index: 3;
|
|
/* #ifdef H5 */
|
|
cursor: pointer;
|
|
/* #endif */
|
|
}
|
|
|
|
.tui-radar__dot {
|
|
height: 6px;
|
|
width: 6px;
|
|
border-radius: 50%;
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
z-index: 5;
|
|
transform: translate(-50%, -50%) rotate(0);
|
|
/* #ifdef H5 */
|
|
cursor: pointer;
|
|
/* #endif */
|
|
}
|
|
|
|
.tui-radar__legend {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
padding-bottom: 24rpx;
|
|
}
|
|
|
|
.tui-radar__legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-left: 24rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.tui-legend__circle {
|
|
height: 20rpx;
|
|
width: 20rpx;
|
|
border-radius: 50%;
|
|
margin-right: 8rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.tui-radar__tooltip {
|
|
padding: 30rpx;
|
|
border-radius: 12rpx;
|
|
background-color: rgba(0, 0, 0, .6);
|
|
display: inline-block;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
z-index: 20;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.tui-tooltip__title {
|
|
font-size: 30rpx;
|
|
color: #fff;
|
|
line-height: 30rpx;
|
|
}
|
|
|
|
.tui-radar__tooltip-show {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
|
|
.tui-radar__tooltip-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding-top: 24rpx;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.tui-tooltip__val {
|
|
font-size: 24rpx;
|
|
line-height: 24rpx;
|
|
color: #fff;
|
|
margin-left: 6rpx;
|
|
}
|
|
|
|
.tui-tooltip__val-ml {
|
|
margin-left: 20rpx;
|
|
}
|
|
</style>
|