Canvas绘制带标签导引线的环形图
2019-07-25 本文已影响0人
Odeng
运行效果
line-pie.png实例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>CirclePie</title>
<style>
html {
background-color: black;
}
.pie {
width: 300px;
height: 300px;
border: 1px solid red;
margin: 100px auto;
}
</style>
</head>
<body>
<div class="pie"></div>
</body>
<script>
function CirclePie(options) {
this.x = options.x || 0;
this.y = options.y || 0;
this.r = options.r || 50;
this.width = 100;
this.height = 100;
this.ctx = null;
//初始鼠标位置
this.pos = {
x: 0,
y: 0
};
}
CirclePie.prototype.init = function (dom) {
if (!dom) {
console.log('canvas挂载节点不存在');
return;
}
var canvas = document.createElement("canvas");
this.width = dom.offsetWidth;
this.height = dom.offsetHeight;
canvas.width = this.width;
canvas.height = this.height;
this.ctx = canvas.getContext("2d");
//添加canvas节点
dom.appendChild(canvas);
//定点圆心
this.ctx.translate(this.width / 2, this.height / 2);
var _this = this;
//绑定事件
canvas.addEventListener('mousemove', function (event) {
_this.pos = _this.getEventPosition(event);
_this.draw(_this.options);
});
};
CirclePie.prototype.getEventPosition = function (event) {
var x, y;
if (event.layerX || event.layerX == 0) {
x = event.layerX;
y = event.layerY;
} else if (event.offsetX || event.offsetX == 0) { // Opera
x = event.offsetX;
y = event.offsetY;
}
return {
x: x,
y: y
};
}
CirclePie.prototype.ring = function (x, y, r, start, end, color) {
this.ctx.save();
this.ctx.beginPath();
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 10;
this.ctx.arc(x, y, r, start, end);
// this.ctx.closePath();
if (this.ctx.isPointInPath(this.pos.x, this.pos.y)) {
this.ctx.stroke();
this.ctx.beginPath();
if (this.pos.x > this.width / 2) {
x = x + 10 * Math.sin(end - start);
} else {
x = x - 10 * Math.sin(end - start);
}
if (this.pos.y > this.height / 2) {
y = y + 10 * Math.cos(end - start);
} else {
y = y - 10 * Math.cos(end - start);
}
this.ctx.arc(x, y, r, start, end);
this.ctx.stroke();
} else {
this.ctx.stroke();
}
this.ctx.closePath();
this.ctx.restore();
};
CirclePie.prototype.drawGuidLine = function (
x0,
y0,
radius,
startAngle,
angle,
color,
text
) {
this.ctx.beginPath();
this.ctx.save();
let out = 10;
let cAngle = (startAngle + angle) / 2;
let x = x0 + (radius + 2) * Math.cos(cAngle);
let y = y0 + (radius + 2) * Math.sin(cAngle);
let x2 = x0 + (radius + 15) * Math.cos(cAngle);
let y2 = y0 + (radius + 15) * Math.sin(cAngle);
this.ctx.moveTo(x, y);
this.ctx.strokeStyle = color;
this.ctx.lineTo(x2, y2);
this.ctx.textBaseline = "middle";
this.ctx.textAlign = "center";
//默认字体大小
this.ctx.font = "14px Microsoft Yahei";
this.ctx.fillStyle = color;
if (x2 > x) {
x2 = x2 + 20;
this.ctx.lineTo(x2, y2);
this.ctx.fillText(text, x2 + 20, y2);
} else {
x2 = x2 - 20;
this.ctx.lineTo(x2, y2);
this.ctx.fillText(text, x2 - 20, y2);
}
if (this.ctx.isPointInPath(this.pos.x, this.pos.y)) {
alert('line in path');
}
this.ctx.stroke();
// this.ctx.beginPath();
this.ctx.restore();
};
//绘制文本
CirclePie.prototype.drawText = function (x, y, color, fontSize, text) {
this.ctx.save();
this.ctx.fillStyle = color;
this.ctx.textBaseline = "middle";
this.ctx.textAlign = "center";
this.ctx.font = `${fontSize}px Microsoft Yahei`;
if (this.ctx.isPointInPath(this.pos.x, this.pos.y)) {
alert('text in path');
}
this.ctx.fillText(text, x, y);
this.ctx.restore();
};
CirclePie.prototype.clearLable = function () {
this.ctx.clearRect(-50, -25, 100, 100);
};
CirclePie.prototype.clear = function () {
this.ctx.clearRect(
-this.width / 2,
-this.height / 2,
this.width,
this.height
);
};
CirclePie.prototype.draw = function (options, offsetX, offsetY, index) {
//清空上一次绘制
this.clear();
var data = options.data;
var sum = 0;
var map = {};
//求总数
data.forEach((element, index) => {
sum += element.value;
map[element.name] = element.value;
});
let i = 0;
let startAngle = (Math.PI / 180) * 0;
let endAngle = 0;
let offset = 0;
// this.ctx.beginPath();
for (let key in map) {
offset = (((map[key] / sum) * Math.PI) / 180) * 360;
endAngle = startAngle + offset;
//画扇区
this.ring(0, 0, this.r, startAngle, endAngle, options.colors[i]);
//话导引线
this.drawGuidLine(0, 0, this.r, startAngle, endAngle, options.colors[i], key);
startAngle = endAngle;
i++;
}
this.drawText(0, -15, "rgba(255, 255, 255, 1)", 12, options.name);
this.drawText(0, 15, "rgba(255, 255, 255, 1)", 30, sum);
};
CirclePie.prototype.render = function (options) {
this.options = options;
//中间名称
this.name = options.name;
//颜色值
this.colors = options.colors;
//初始化数据
if (!this.data) {
this.data = JSON.parse(JSON.stringify(options.data));
this.data.forEach((item, index) => {
item.value = 0;
});
}
let _this = this;
this.timer = window.requestAnimationFrame(function () {
let data = [];
let j = 0;
for (let i = 0; i < _this.data.length; i++) {
if (_this.data[i].value > options.data[i].value) {
_this.data[i].value -= 1;
} else if (_this.data[i].value == options.data[i].value) {
j++;
if (j >= _this.data.length) {
window.cancelAnimationFrame(_this.timer);
return;
}
} else {
_this.data[i].value += 1;
}
}
_this.draw({
name: options.name,
colors: options.colors,
data: _this.data
});
_this.render(options);
});
};
window.onload = function () {
var colors = ["rgba(239, 58, 113, 1)", "rgba(95, 222, 145, 1)"];
var circlePie = new CirclePie({
x: 0,
y: 0,
r: 50
});
circlePie.init(document.getElementsByClassName('pie')[0]);
circlePie.render({
name: "名称",
colors: [
"red",
"green",
"skyblue"
],
data: [
{
name: "扇区1",
value: 20//Math.floor(Math.random() * 100)
},
{
name: "扇区2",
value: 30//Math.floor(Math.random() * 100)
},
{
name: "扇区3",
value: 50//Math.floor(Math.random() * 100)
}
]
});
}
</script>
</html>