D3.js画图:3D动态饼图(齿轮图)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了D3.js画图:3D动态饼图(齿轮图)相关的知识,希望对你有一定的参考价值。
参考技术A 通常画可视化图的工具很多,除了d3.js,还有echarts.js等。通过比较,看起来ECharts.js更容易上手,但是因为我需要更灵活更符合个性定制化的工具,所以选了d3.js。
经过一段时间的磨炼,从折线图、闭合路径图、蜂窝图、直角坐标、极坐标都玩了个遍。
那这次就来个3D的吧,其实d3.js做3D的图不是很容易的,有更好的选择,但我认准了d3.js,一条道走到黑吧(想起高中数学老师说的话,当你解题解到一半时发现有更好的办法,不,赶紧忘掉,接着当前的方法,只要方法没错,总能解出来,也许会傻一点,但是一定会有正确的结果;如果中途放弃,也许另一个方法更快更聪明,但也许更慢或者错误,不算到最后,谁都不知道谁最准确。我选择相信他的话,于是。。。我成了程序员O(∩_∩)O哈哈~)。
有人鄙视拿来主义,要我说,你能拿来那是你的本事,如果还能在此基础上做出更好的东西,何乐而不为呢?
每个人时间有限,每个项目也有deadline,不可能从每一个螺丝钉怎么拧开始学起,不然怎么会有那么多五花八门的框架,会有封装好的组件和接口,正因为有人已经做了前期工作,所以时间才能省下来做更有意义的事情,这就是站在巨人肩上的道理所在吧。
但是我们得明白拿来的东西的原理,以及出了问题该怎么解决的能力。然后才能做出更厉害的东西。
首选当然是官网的例子咯,目测搜了一圈,终于找到一个3D Donut。就是你了,我的巨人。
把该地址的donut3d.js拷贝下来作为画3D饼图的基础js,待会会在此基础上修改,以满足我的要求(长的像齿轮的要求)。
那我们就一睹她的芳容吧。
如果这张图符合你的要求,那就打住,不用往下看了,直接看官网例子即可。
注意d3版本的问题,如果你用d3.v3.js,恭喜你,啥也不用改,直接拿来用;如果你用d3.v5.js,那稍微改下方法,比如d3.v5.js没有d3.layout,所以d3.layout.pie改成d3.pie。我就是那个不幸的人,用的d3.v5.js。没关系,改起来很快,运行下,看哪里有错,就改哪里,O(∩_∩)O哈哈~so easy!
还是先上个我已经改好之后的3D饼图(齿轮图)吧,方便说明。
其实显示的时候是个动态的,一节一节显示出齿轮的。
背景是黑乎乎的,据说现在流行黑乎乎的背景,显得有科技感,技术也要赶时髦啊,我这么fashion的人,做出来的东西也要fashion啊O(∩_∩)O~
从以上分析可以看出,难啃的骨头在第4点。这个图断断续续花了3天时间才搞定,为啥是断断续续呢,因为还有其他工作要做嘛,你懂得。
那就按顺序一条一条实现,总有一天我们的愿望都能实现!
首先新建svg及设置宽高。
我是切分成了32个小齿轮(包含透明的),如果你想分的更细,可以分成40或50个,只要你觉得好看就行。
既然要分成32个小立体快,那数据也要切分成32个。
通过以上处理,把数据整合成可以生成齿轮的完整数据dataset。
如果不增加左侧面和右侧面,那调用donut3d.js的draw方法后,会生成什么样的图形呢?
请各位仔细看。
是不是有种被掏空的感觉?如果你觉得这样挺好看,那也行,打住吧,后面就不用再看了;如果你想补齐其他面,请耐心往下看。
经过观察和比较,增加左侧面和右侧面就能填满空虚的心啦啦啦~
这次要在donut3d.js这个巨人身上添砖加瓦咯。
然后再用新增加的两个方法画出左右侧面。
终于填满需要的每一面,看上去像个立体齿轮图了。
这个图是很久之前做的,当时花了很长时间调试,每一个面有4条边,定位2个点,再加上高度和内半径,就可以计算出4个点,然后就可以画出4条边,最后填充颜色,一个面就完成了。
最近整理文档时觉得有必要写出来,方便以后查阅和探讨,也告诉自己积累是一个长期过程,不急不躁,慢慢来,一步一步完成既定目标,总有一天你会走遍技术的每个角落。
现在我整理成vue组件,传一个百分比的参数,就可以显示3D齿轮图了,我的3D齿轮图也成巨人啦。
canvas+js实现动态饼图效果
canvas+js实现动态饼图效果
参考了一些网上的例子,利用canvas+js实现动态饼图效果demo,包括鼠标移入图块颜色变化以及带有tooltip提示。实现逻辑可分为三个步骤:
1、首先利用canvas画布原理画饼图。
2、添加鼠标移入监听事件,获取鼠标当前坐标。
3、判断当前鼠标坐标是否在所画饼图扇形区域,改变饼图颜色和显示tooltip提示。
1、画饼图
利用canvas实现一个饼图效果,我们首先需要知道canvas画布画图原理以及相关需要的API。
W3school这样介绍canvas,还不太了解的朋友可以先去熟悉熟悉。
什么是 Canvas?
HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像。
画布是一个矩形区域,您可以控制其每一像素。
canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
创建 Canvas 元素
向 HTML5 页面添加 canvas 元素。
规定元素的 id、宽度和高度:
<canvas id="myCanvas" width="200" height="100"></canvas>
通过上面的介绍,即画图的html部分我们已经了解,只需要定义一个带有宽高及id的canvas即可,接下来便是通过js来绘制图形,首先我们需要利用id来寻找canvas元素。
let el = document.getElementById("myCanvas");
然后创建画布的绘制对象:
let cxt = el.getContext("2d");
接下来,即可利用canvas的相关API来绘制我们需要的图像,canvas的API可参考《HTML Canvas 参考手册》。本次画饼图设计需要的API整理如下:
ctx.beginPath(); // 起始一条路径,或重置当前路径
ctx.fillStyle; // 设置或返回用于填充绘画的颜色、渐变或模式 ctx.moveTo(x, y); // 把路径移动到画布中的指定点,不创建线条
ctx.arc(x, y, radius, sAngle, eAngle, false); // 创建弧/曲线(用于创建圆形或部分圆)
ctx.fill(); // 填充当前绘图(路径)
以上只列出了几个化饼图的关键API,其他包括饼图具体实现、小图标以及文字的相关API详细参见下面的代码。
2、添加鼠标监听
利用js的addEventListener,添加所绘画布鼠标监听,获取当前鼠标移入的坐标。
// 鼠标位置监听
getPosition(element) {
let mouseTimer = null;
element.addEventListener('mousemove', (e) => {
e = e || window.event;
if ( e.ofSfsetX || e.offsetX==0 ) {
this.mousePosition.x = e.offsetX;
this.mousePosition.y = e.offsetY;
} else if ( e.layerX || e.layerX==0 ){
this.mousePosition.x = e.layerX;
this.mousePosition.y = e.layerY;
}
// 监听tooltip显示
let el = document.getElementById('self-tooltip');
el.style.visibility = 'hidden';
clearTimeout(mouseTimer);
mouseTimer = setTimeout(() => {
this.drawPie(element, this.pieData);
}, 50)
});
},
3、鼠标移入,改变饼图样式
在绘制饼图方法中调用getPosition(element)方法,利用ctx.isPointInPath()API判断当前鼠标坐标是否在所绘制扇形中,以改变相关样式和显示tooltip提示。
// 监听鼠标是否移动到绘制扇形处
if (ctx.isPointInPath(this.mousePosition.x, this.mousePosition.y)) {
// 鼠标移上改变颜色
ctx.fillStyle = '#F56C6C';
// tooltip显示
let el = document.getElementById('self-tooltip');
el.innerHTML = data.labels[index] + ":" + (percent * 100).toFixed(2) + "%";
el.style.left = `${this.mousePosition.x + 15}px`;
el.style.top = `${this.mousePosition.y + 15}px`;
el.style.visibility = 'visible';
}
4、详细代码如下
<template>
<div class="box-style">
<canvas :id="canvasId" :width="width" :height="height" style="border: 1px solid #909399"></canvas>
<span id="self-tooltip" class="tooltip-text"></span>
</div>
</template>
<script>
export default {
props: {
pieData: {
type: Object,
default: function() {
return {
colors: ['#AFB4DB', '#91CC75', '#FFC333', '#FFC0CB', '#73C0DE'], // 颜色
labels: ['周一', '周二', '周三', '周四', '周五'], //标签
values: [10, 20, 30 , 40, 50], //值
radius: 100 //圆半径
};
}
},
width: {
type: Number,
default: function() {
return 400;
}
},
height: {
type: Number,
default: function() {
return 400;
}
},
canvasId: {
type: String,
default: function() {
return 'pie';
}
}
},
data() {
return {
// 鼠标移动是坐标
mousePosition: {}
}
},
mounted() {
let pieElement = document.getElementById(this.canvasId);
this.drawPie(pieElement, this.pieData);
this.getPosition(pieElement);
},
methods: {
// 画饼状图
drawPie(element, data) {
// 在画布上初始化绘图环境
let ctx = element.getContext('2d');
let drawData = data.values; // 画图数据
let sum = this.getSum(drawData); //获取绘制数据的总和
let sAngle = 0; // 扇形开始的角度
let eAngle; // 结束的角度
let x = element.width / 2;
let y = element.height / 2; //圆心坐标
let radius = data.radius; // 圆半径
let xMarker = 20; // 标记坐标
let yMarker = 20; // 标记坐标
drawData.forEach((value, index) => {
// 绘制饼图
let percent = value / sum; // 计算每个数据的占比,根据占比求扇形的弧度,即每个扇形结束的角度
eAngle = sAngle + Math.PI * 2 * (percent);
ctx.beginPath(); //新路径
ctx.fillStyle = data.colors[index];
ctx.moveTo(x, y);
ctx.arc(x, y, radius, sAngle, eAngle, false);
// 监听鼠标是否移动到绘制扇形处
if (ctx.isPointInPath(this.mousePosition.x, this.mousePosition.y)) {
// 鼠标移上改变颜色
ctx.fillStyle = '#F56C6C';
// tooltip显示
let el = document.getElementById('self-tooltip');
el.innerHTML = data.labels[index] + ":" + (percent * 100).toFixed(2) + "%";
el.style.left = `${this.mousePosition.x + 15}px`;
el.style.top = `${this.mousePosition.y + 15}px`;
el.style.visibility = 'visible';
}
// 绘制左侧标记
ctx.moveTo(xMarker, yMarker);
// 绘制矩形
ctx.fillRect(xMarker, yMarker, 30, 10);
// 绘制文字
ctx.font = 'blod 14px';
ctx.txtalgin = 'center';
ctx.textBaseline = 'top';
ctx.fillText(data.labels[index] + ":" + (percent * 100).toFixed(2) + "%", xMarker + 35, yMarker);
// 填充以上绘制
ctx.fill();
// 绘制下一个扇形时初始值改变
sAngle = eAngle;
yMarker += 20;
});
},
// 求和
getSum(data) {
let sum = 0;
data.map(value => {
sum += value;
});
return sum;
},
// 鼠标位置监听
getPosition(element) {
let mouseTimer = null;
element.addEventListener('mousemove', (e) => {
e = e || window.event;
if ( e.ofSfsetX || e.offsetX==0 ) {
this.mousePosition.x = e.offsetX;
this.mousePosition.y = e.offsetY;
} else if ( e.layerX || e.layerX==0 ){
this.mousePosition.x = e.layerX;
this.mousePosition.y = e.layerY;
}
// 监听tooltip显示
let el = document.getElementById('self-tooltip');
el.style.visibility = 'hidden';
clearTimeout(mouseTimer);
mouseTimer = setTimeout(() => {
this.drawPie(element, this.pieData);
}, 50)
});
},
}
}
</script>
<style lang="less" scoped>
.box-style {
position: relative;
display: inline-block;
width: 100%;
height: 100%;
// 画布
canvas {
cursor: pointer;
}
}
// 提示工具
.tooltip-text {
position: absolute;
width: 150px;
z-index: 10;
background-color: #909399;
color: #fff;
border-radius: 5px;
padding: 5px;
left: 0;
top: 0;
visibility: hidden;
}
</style>>
5、效果
以上是关于D3.js画图:3D动态饼图(齿轮图)的主要内容,如果未能解决你的问题,请参考以下文章