1. data 資料
//data:雷達圖所依據的資料
var data = {
scoreData : [
{ text : '攻擊', value : '45' },
{ text : '力量', value : '23' },
{ text : '體質', value : '67' },
{ text : '精神', value : '89' },
{ text : '敏捷', value : '17' },
{ text : '防禦', value : '49' },
{ text : '速度', value : '88' },
{ text : '屬性', value : '17' },
{ text : '愛咋咋', value : '49' }
],
option : {
polygonBgColor : ['#f97683', '#bf93d8', '#44909e'],//多邊形背景色
} //自定義樣式
}複製程式碼
2. 雷達圖初始化
let init = {
id : document.querySelector('#redarCanvas'), //html檔案中有盛放canvas的標籤
canvasW : '560', //自定義canvas 寬度
canvasH : '560', //自定義canvas 高度
animationLoop : '', //data中scoreData的最大值會有動畫效果
animation : '',
start : 'true', //雷達圖是否要展示
defaultStyle : {
polygonBgColor : ['orange', 'red', 'skyblue'],//多邊形背景色
Bgshadow : { //雷達圖陰影
color : '',
shadowBlur : 0
},
translate : {
x : 1,
y : 8/9
}, //影象偏移量(相對於中心點的倍數)
rayLine : {// 輻射線樣式
color : '#B7AB72'
},
pointLine : {// 連線分數點線條樣式
color : 'rgba(255,255,255,.1)'
},
points : {// 分數點樣式
size : 1/25
},
tipText : {
color : 'rgb(75,66,66)', //Array || String
fontSize : '64'
},
alpha : 0.3,
Reminder : { //右下角標註樣式
bgColor : [
'#f97683',
'#bf93d8',
'#44909e'
],
text : [
'高',
'中',
'低'
],
color : 'rgba(119,119,119,1)',
width : .04,
fontSize : 1,
coordinate : {
x : 1/7,
y : 1.05
}
}
}
}複製程式碼
3. 定義一個函式objAssign,將自定義樣式替換成預設樣式
function objAssign(source, target) {
try {
var json = JSON.parse(JSON.stringify(source))
var assign = function(source, target) {
for (var key in target) {
if (key in source) {
if (Object.prototype.toString.call(target[key])==='[object Object]') {
assign(source[key], target[key])
} else {
if (target[key]!==source[key]) {
source[key] = target[key]
}
}
}
}
}
assign(json, target)
console.log(json)
return json
} catch (e) {
console.error(e)
return {}
}
}
var pointScore = [] //將data.scoreData 中的value值儲存在一個陣列裡面
var pointText = [] // text值也儲存在一個陣列中
data.scoreData.sort(function(x, y) {
return x.value-y.value
})
data.scoreData.forEach(function(e) {
pointText.push(e.text)
pointScore.push(e.value)
})
if (data.option) {
var polygonStyle = objAssign(init.defaultStyle, data.option)
} else {
var polygonStyle = init.defaultStyle
}
if (pointScore.length==3) {
polygonStyle.translate.y = 1/2
//polygonStyle.Reminder.coordinate.y = 1.6
}複製程式碼
4. 繪製
let myCanvas = init.id
let ctx = myCanvas.getContext('2d')
/*設定畫布寬高*/
myCanvas.width = init.canvasW
myCanvas.height = init.canvasH
let width = myCanvas.width,
height = myCanvas.height
//根據裝置的DPR設定畫布的寬高
if (window.devicePixelRatio) {
myCanvas.style.width = width+'px'
myCanvas.style.height = height+'px'
myCanvas.height = height*window.devicePixelRatio
myCanvas.width = width*window.devicePixelRatio
}
let polygonArr = []//存放多邊形例項
let Radius = myCanvas.height/2.6
let numofSide = pointScore.length //n邊形(n>=3)
var maxScore = Math.max.apply(Math, pointScore)
var end = false
//閃爍
var twinkleSpeed = 1
var shadowBlur = 0
/*多邊形建構函式*/
function Polygon(option, ctx) {
this.option = option
this.pointY = option.pointY || 0
this.pointX = option.pointX || 0
this.translateX = this.pointX
this.translateY = this.pointY
this.lengthofSide = option.lengthofSide
this.numofSide = option.numofSide || 3
this.animationFrame = 0
this.scaleRate = 0 // 縮放比例
this.radian = 360/this.numofSide/2*Math.PI/180
this.shadow = option.shadow || undefined
//多邊形外接圓的半徑 cos(360/numofside/2) = L/2/r;
if (this.lengthofSide) {
this.r = this.lengthofSide/2/Math.sin(this.radian)
} else {
this.r = option.r
}
this.isFill = option.isFill || false
this.strokeStyle = option.strokeStyle || '#fff'
this.fillStyle = option.fillStyle || '#000'
this.ctx = ctx
this.isScale = option.isScale
this.line = 0
this.drawlineSpeed = 5
this.progress = 0
polygonArr.push(this)
}
Polygon.prototype.draw = function() {
this.pointY = this.isScale ? 0 : this.option.pointY
this.pointX = this.isScale ? 0 : this.option.pointX
this.ctx.beginPath()
this.ctx.strokeStyle = this.strokeStyle
this.ctx.setLineDash([])
var startX = this.pointX+this.r*Math.sin(2*Math.PI*0/this.numofSide)
var startY = this.pointY+this.r*Math.cos(2*Math.PI*0/this.numofSide)
this.ctx.moveTo(startX, startY)
for (var i = 1; i <= this.numofSide; i++) {
var X = this.pointX+this.r*Math.sin(2*Math.PI*i/this.numofSide)
var Y = this.pointY+this.r*Math.cos(2*Math.PI*i/this.numofSide)
this.ctx.lineTo(X, Y)
}
if (this.shadow) {
this.ctx.shadowColor = this.shadow.color
this.ctx.shadowBlur = 40
this.ctx.shadowOffsetY = 30
} else {
this.ctx.shadowBlur = 0
this.ctx.shadowColor = 'none'
this.ctx.shadowOffsetY = 0
this.ctx.shadowOffsetX = 0
}
if (this.isFill) {
this.ctx.fillStyle = this.fillStyle
this.ctx.fill()
}
this.ctx.closePath()
this.ctx.stroke()
}
複製程式碼
5.
Polygon.prototype.update = function() {
var t = this.animationFrame*16/1000
this.scaleRate = -1/2*Math.pow(Math.E, (-6*t/1.5))*(-2*Math.pow(Math.E, (6*t/1.5))+Math.sin(12*t/1.5)+2*Math.cos(12*t/1.5))
this.animationFrame += 1
}
Polygon.prototype.scale = function() {
this.ctx.save()
this.ctx.translate(this.translateX, this.translateY)
this.ctx.scale(this.scaleRate, this.scaleRate)
this.draw()
this.ctx.restore()
}
//右下角標註
Polygon.prototype.drawReminder = function(text, color, x, y, height, width, bgColor, alpha) {
var fontSize = width*polygonStyle.Reminder.fontSize
this.ctx.beginPath()
this.ctx.fillStyle = bgColor
this.ctx.fillRect(x, y, width, height)
this.ctx.textAlign = 'start'
this.ctx.font = fontSize+'px 微軟雅黑'
this.ctx.fillStyle = 'rgba(75,66,66,'+alpha+')'
this.ctx.textBaseline = 'middle'
this.ctx.fillText(text, x+width*1.4, y+height/2)
this.ctx.closePath()
}
//繪製輻射線
Polygon.prototype.drawLine = function(callback) {
this.ctx.beginPath()
for (var i = 1; i <= this.numofSide; i++) {
this.ctx.lineWidth = myCanvas.height*.002
this.ctx.strokeStyle = polygonStyle.rayLine.color
this.ctx.setLineDash([5, 10])
this.ctx.moveTo(this.pointX, this.pointY)
var X = this.pointX+this.r*Math.sin(2*Math.PI*i/this.numofSide)
var Y = this.pointY+this.r*Math.cos(2*Math.PI*i/this.numofSide)
this.ctx.lineTo(X, Y)
}
this.ctx.stroke()
this.ctx.closePath()
}
Polygon.prototype.drawPoint = function(callback) {
if (this.progress >= maxScore) {
this.progress===maxScore
callback && callback()
} else {
this.progress += .4
}
/*連線分數點*/
this.ctx.beginPath()
this.ctx.shadowBlur = 0
this.ctx.shadowColor = ''
this.ctx.strokeStyle = polygonStyle.pointLine.color
this.ctx.setLineDash([])
this.ctx.lineWidth = '2'
this.ctx.moveTo(
this.pointX+0.92*(this.progress*pointScore[0]/maxScore/100*this.r-this.r*polygonStyle.points.size)*Math.sin(2*Math.PI*0/this.numofSide),
this.pointY+0.92*(this.progress*pointScore[0]/maxScore/100*this.r-this.r*polygonStyle.points.size)*Math.cos(2*Math.PI*0/this.numofSide))
for (var j = 1; j < this.numofSide; j++) {
let len = this.progress*pointScore[j]/maxScore/100*this.r
this.ctx.lineTo(this.pointX+0.9*(len-this.r*polygonStyle.points.size)*Math.sin(2*Math.PI*j/this.numofSide), this.pointY+
0.9*(len-this.r*polygonStyle.points.size)*Math.cos(2*Math.PI*j/this.numofSide))
}
this.ctx.fillStyle = 'rgba(255, 0, 0, 0.05)'
this.ctx.fill()
this.ctx.closePath()
this.ctx.stroke()
/*繪製分數點*/
for (var i = 0; i < this.numofSide; i++) {
if (pointScore[i]==maxScore && this.progress >= maxScore) {
this.ctx.shadowBlur = shadowBlur
this.ctx.shadowColor = 'white'
if (shadowBlur >= 40) {
twinkleSpeed = -.2
}
if (shadowBlur <= 1) {
twinkleSpeed = .2
}
shadowBlur += twinkleSpeed
}
this.ctx.beginPath()
this.ctx.strokeStyle = 'white'
this.ctx.setLineDash([])
this.ctx.lineWidth = myCanvas.height*.004
var r = this.r*polygonStyle.points.size
if (maxScore===0) {
this.ctx.arc(this.pointX, this.pointY, r, 0, 2*Math.PI, false)
} else {
this.ctx.arc(this.pointX+this.progress*pointScore[i]*0.94/maxScore/100*this.r*Math.sin(2*Math.PI*i/
this.numofSide), this.pointY+this.progress*pointScore[i]*0.94/maxScore/100*this.r*
Math.cos(2*Math.PI*i/this.numofSide), r, 0, 2*Math.PI, false)
}
this.ctx.fillStyle = '#FCF3DF'
this.ctx.fill()
this.ctx.stroke()
}
}
Polygon.prototype.render = function(callback) {
if (this.animationFrame >= 1000/16*1.5 || this.isScale===false) {
this.isScale = false
this.draw()
callback && callback()
} else {
this.update()
this.scale()
}
}
Polygon.prototype.drawTip = function(text, color, x, y, fontSize, alpha) {
this.ctx.beginPath()
this.ctx.font = fontSize+'px 微軟雅黑'
this.ctx.fillStyle = 'rgba(75,66,66,'+alpha+')'
this.ctx.textAlign = 'center'
this.ctx.fillText(text, x, y)
this.ctx.closePath()
}
var layer1 = new Polygon({
pointX : myCanvas.width/2*polygonStyle.translate.x,
pointY : myCanvas.height/2*polygonStyle.translate.y,
numofSide : numofSide,
// lengthofSide: 100,
r : myCanvas.width/3.5,
strokeStyle : polygonStyle.polygonBgColor[0],
fillStyle : polygonStyle.polygonBgColor[0],
isFill : true,
isScale : true,
shadow : polygonStyle.Bgshadow
}, ctx)
var layer2 = new Polygon({
pointX : myCanvas.width/2*polygonStyle.translate.x,
pointY : myCanvas.height/2*polygonStyle.translate.y,
numofSide : numofSide,
r : myCanvas.width/3.5*2/3,
fillStyle : polygonStyle.polygonBgColor[1],
strokeStyle : polygonStyle.polygonBgColor[1],
isFill : true,
isScale : true
}, ctx)
var layer3 = new Polygon({
pointX : myCanvas.width/2*polygonStyle.translate.x,
pointY : myCanvas.height/2*polygonStyle.translate.y,
numofSide : numofSide,
r : myCanvas.width/3.5*1/3,
fillStyle : polygonStyle.polygonBgColor[2],
strokeStyle : polygonStyle.polygonBgColor[2],
isFill : true,
isScale : true
}, ctx)
var totalFrame = 0
var change = 8
var alpha = 0
var change2 = 8
var alpha2 = 0
init.animationLoop = function() {
totalFrame++
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
for (var i = 0, len = polygonArr.length; i < len; i++) {
if (totalFrame > 1000/16*1.5*1/5*i) {
polygonArr[i].render()
if (i===len-1) {
polygonArr[i].render(function() {
polygonArr[0].drawLine()
for (var i = 2; i >= 0; i--) {
let bgColor = polygonStyle.Reminder.bgColor[i], text = polygonStyle.Reminder.text[i],
specX = 0,
color = polygonStyle.Reminder.color,
y = (polygonStyle.Reminder.coordinate.y*polygonStyle.translate.y)*myCanvas.height+myCanvas.height/8*change/8,
width = polygonStyle.Reminder.width*myCanvas.height
if (i===0) {
specX = width*polygonStyle.Reminder.fontSize
}
let x = polygonStyle.Reminder.coordinate.x*myCanvas.width+(2-i)*myCanvas.width*1/4+specX
polygonArr[0].drawReminder(text, color, x, y, width, width, bgColor, alpha)
if (change > 0) {
change -= 8/60
alpha += 1/30
}
}
for (var i = 0, len = pointScore.length; i < len; i++) {
var restR = Radius*1/30
var x = myCanvas.width/2*polygonStyle.translate.x+(Radius)*Math.sin(2*Math.PI*i/pointScore.length) -10
var y = myCanvas.height/2*polygonStyle.translate.y+(Radius)*Math.cos(2*Math.PI*i/pointScore.length)+myCanvas.height/8*change2/8 + 25
var fontSize = init.defaultStyle.tipText.fontSize
if (Array.isArray(polygonStyle.tipText.color)) {
var color = polygonStyle.tipText.color[i]
} else {
var color = polygonStyle.tipText.color
}
polygonArr[0].drawTip(pointText[i], color, x, y, fontSize, alpha2)
if (change2 > 0) {
change2 -= 8/60
alpha2 += 1/30
} else {
polygonArr[0].drawPoint()
}
}
})
}
}
}
if (true) {
init.animation = requestAnimationFrame(init.animationLoop)
}
}
requestAnimationFrame(init.animationLoop)複製程式碼
github:https://github.com/dashaosunxigui/canvas-redar