前言
在做Node Library依赖关系分析工具时,需要用可视化来实现依赖之间的关系,暂时决定使用d3.js的力有向图,后续可能会修改。故,学习了d3的一些基础的语法,将其和echarts可视化进行了对比。如果小伙伴们感兴趣,可以仔细看看。
✨ 为什么选择D3.js
➡ d3 优点
- 数据驱动视图 --将数据与DOM元素绑定,根据数据动态地生成图表和可视化效果
- 代码十分简洁
- 擅长矢量图形,缩放不损失图形精度,不擅长位图和瓦片,不擅长探索型可视化;
➡ 什么时候选择D3 ?什么时候选择echarts?
- 对于客户需求要求的图表拥有大量的用户交互场景,用 d3 比较方便,因为 d3 中 svg 画图支持事件处理器,他是基于 dom 进行操作的。
- 对于大量的数据展示并且对于用户交互场景没什么要求,就只是展示数据,那我建议使用 echarts,如果使用 d3 的话展示的每一个数据都是一个标签,那么当数据发生改变的时候这时候图表会重新渲染,会不停的操作 dom,操作 dom 是很耗费性能的。【简单版本:D3操作DOM,没什么交互的话,选择echarts】
- 从兼容性方面考虑:echarts 兼容到 IE6 及以上的所有主流浏览器,而 d3 兼容 IE9 及以上以及所有的主流浏览器,如果项目考虑兼容 IE6,建议使用 echarts。
✨ d3基础语法
1️⃣ 总结一下 D3 可视化的基本步骤如下:
- 创建新元素并绑定数据(html的元素可理解为划定区域和声明类型的闭合标签,如p表示其是一个段落,是段落就可以有段落文本、长宽、id等属性和标识);
- 设置相应元素的可视属性,将数据值映射为元素大小、颜色、位置等可视属性;
- 对元素进行排列和变换,还有响应交互;
2️⃣ Svg 基础知识了解
SVG(Scalable Vector Graphics,可伸缩矢量图形)基于XML标签来表示图形。基于HTML文档的可视化基本都使用canvas或svg元素作为数据到图形的映射容器。D3也可以直接操作div或其他原生HTML元素来绘图,但这就显得笨重不灵活,且容易出现浏览器间不一致的问题。而用 SVG就更可靠,图形效果更一致,且绘图速度更快。
SVG 元素可以理解为能在上面绘制各种形状的画布。
简单一句话概括: svg 是一个可伸缩矢量图形,缩放不损失图形精度
3️⃣ 具体应用
🟠 元素定位和数据绑定
简单介绍D3如何选择及增删文档对象的方法以及数据绑定的方法
-
元素定位
🔸 选择元素
select()方法 :用于选择单个元素的常用方法。示例: 假设 HTML 中有如下的 DOM 结构:
<div id="example"> <p>Hello, D3.js!</p> </div> // 你可以使用 D3.js 的 `select()` 方法来选择 `<p>` 元素,并对其进行操作: // 选择 <p> 元素 const pElement = d3.select("p"); // 对选中的 <p> 元素进行样式设置 pElement.style("color", "blue"); pElement.style("font-size", "20px");- d3.select("body") ; //选择HTML里的body元素;
- d3.select("#apple"); //选择id为apple的元素;例如会匹配上
<p id="apple">一段文本</p>; - d3.select(".apple"); //选择class为apple的元素;会匹配上
<p class="apple">一段文本</p>; - 如果想获得所有满足条件的元素,用selectAll()方法,写法和上面一致,把select变成selectAll
// react const LinePlot = (props) => { const svgRef = useRef(null); const gdefs = d3.create("svg:defs"); const svg = d3.select(svgRef.current) svg.append(() => gdefs.node()); gdefs.selectAll("marker") return ( <div > {/* 将 SVG 元素插入到 DOM 中 */} <svg ref={svgRef} > </svg> </div> ) }🔸添加元素
append()方法:添加元素// 创建一个 SVG 元素 const svg = d3.create("svg") // 在html文档里的<svg>标签下从无到有地增加了一个<rect></rect>元素; svg.append("rect") ``` -
设定属性
🔸 选定或添加我们需要操纵的元素后,便可以编辑元素的属性,例如图形位置、填充色、标识等属性。通过
.attr(name,value)给所选元素添加属性,name是属性名称,value是属性值。const svg = d3.select(svgRef.current) .attr("viewBox", [-width / 2, -height / 2, width, height]) .attr("width", width) .attr("height", height) .attr("style", "max-width: 100%; height: auto; font: 12px sans-serif;") -
数据绑定
🔸 通过
data(vals[,key])绑定数组vals中的每一项到选中的元素,key是一个用于指定绑定规则的函数。
gdefs.selectAll("marker")
. data (types) // 使用 data() 方法绑定数据
.join("marker") // 根据数据绑定状态对元素进行操作
.attr("id", d => `arrow-${d}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -0.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto") // 定义图形在路径上的朝向: auto-浏览器会自动根据路径的朝向来调整图形的朝向。
.append("path") // append 添加 用于绘制路径,例如曲线、线段等。主要用于箭头
.attr("fill", color)
.attr("d", "M0,-5L10,0L0,5");
-
读取本地数据
🔸
d3.csv("food.csv", function(data) {dataset=data;})可以读取本地的csv文件数据进行使用d3.csv("bar-data.csv", function(dataset) { d3.select("body").selectAll("svg") .data(dataset) .enter() .append("svg") .style("background-color","#1EAFAE") .attr("width",50) .attr("height",function(d){return d*10 +"px";}); });
🟡 SVG预定义元素
🔸 以下是 SVG 中的一些常见预定义元素:
| 预定义元素 | 定义 |
|---|---|
<rect> | 矩形元素,用于绘制矩形。可以设置矩形的位置、宽度、高度以及圆角属性。 |
<circle> | 圆形元素,用于绘制圆形。可以设置圆心的坐标和半径。 |
<ellipse> | 椭圆元素,用于绘制椭圆。可以设置椭圆的中心坐标和水平、垂直半径。 |
<line> | 直线元素,用于绘制直线。可以设置直线的起始点和终点坐标。 |
<polyline> | 折线元素,用于绘制由多个连接的线段组成的折线。 |
<polygon> | 多边形元素,用于绘制封闭的多边形。与折线类似,但是多边形的最后一个点会与第一个点相连,形成封闭的形状。 |
<path> | 路径元素,用于通过路径命令绘制任意复杂的图形。路径命令包括直线、曲线、弧线等。 |
<text> | 分组元素,用于将多个 SVG 元素组合在一起,并对这个组进行统一的变换、样式设置和事件绑定。 |
<marker> | 容器元素,它可以包含其他 SVG 元素,如 <path>、<circle>、<rect> 等,用于定义标记的图形样式。 |
🔸 一些通用属性:
| 通用属性 | |
|---|---|
fill | 表示要填充的颜色 |
stroke | 表示边框的颜色 |
stroke-width | 边框宽度 |
opacity | 透明度。数值在[0,1]之间,值越小越透明 |
💠 <rect> 元素
属性:
- x:矩形左上角的x坐标,注意是左上角不是中心点的坐标;
- y:矩形左上角的y坐标;
- width:矩形宽度;
- height:矩形高;
- rx:缺省是正常矩形,否则是圆角矩形,圆角处椭圆在x方向的半径;
- ry:圆角矩形的圆角在y方向的半径;
💠<circle>元素
属性:
- cx:圆心的x坐标,可以记为circle-x;
- cy:圆心的y坐标;
- r:圆的半径;
💠 <ellipse>元素
属性:
- cx:椭圆圆心的x坐标;
- cy:圆心的y坐标;
- rx:椭圆的水平半径;
- ry:椭圆的垂直半径;
💠<line>元素
属性:
- x1:起点的x坐标;
- y1:起点的y坐标;
- x2:终点的x坐标;
- y2:终点的y坐标;
💠<path>元素
<path>的写法是:给出一个坐标点,在坐标点前面添加一个英文字母,用于标识如何运动到此坐标点。英文字母按照功能可分为五类。大写表示绝对坐标,小写表示相对坐标。
分类:
- 移动类: M即moveto,表示将画笔移动到指定坐标,不绘制移动的路径;
- 直线类:L,画直线到指定坐标(lineto);H,画水平直线到指定坐标(horizontal lineto);V,画垂直直线到指定坐标(vertical lineto);
- 曲线类:C,curveto,画三次贝塞尔曲线经两个指定控制点到达终点坐标;S,smooth curveto,与前一条三次贝塞尔曲线相连,第一个控制点为前一条曲线第二个控制点的对称点,只需输入第二个控制点和终点,即可绘制一个三次贝塞尔曲线;Q,quadratic Bézier curveto,画二次贝塞尔曲线经一个指定控制点到达终点坐标;T,Shorthand/smooth quadratic Bézier curveto,与前一条二次贝塞尔曲线相连,控制点为前一条二次贝塞尔曲线控制点的对称点,只需输入终点,即可绘制一个二次贝塞尔曲线;
- 弧线类:A,elliptical arc,画椭圆曲线到指定坐标;
- 闭合类:Z,closepath,绘制一条直线连接终点和起点,用来封闭图形;
// 创建 <g> 元素并添加链接(<path>)
const link = gLink
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(graph)// 使用 data() 方法绑定数据
.join("path")// 使用 join() 方法来创建 <path> 元素
.attr("stroke", d => color(d.type))
.attr("marker-end", d => `url(${new URL(`#arrow-${d.type}`, location)})`)
// 节点拖动时,路径重新指向
function linkArc(d) {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${d.target.x},${d.target.y}
`;
}
simulation.on("tick", () => {
link.attr("d", linkArc);
node.attr("transform", d => `translate(${d.x},${d.y})`);
});
💠<text>元素
属性:
- x:文字位置的x坐标。
- y:文字位置的y坐标。
- dx:相对于当前位置在x方向上平移的距离(值为正则往右,负则往左);
- dy:相对于当前位置在y方向上平移的距离(值为正则往下,负则往上)。
- textLength:文字的显示长度(不足则拉长,足则压缩)
- rotate:旋转角度(顺时针为正,逆时针为负)
const text = node.append("text")
.attr("x", 8)
.attr("y", "1.5em")
.attr('text-anchor', 'middle')
.text(d => d.id)
.clone(true).lower()
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 3);
💠 <g> 元素
<g> 元素表示 SVG 中的分组元素(Group Element)。<g> 元素用于将多个 SVG 元素组合在一起,形成一个组,并且可以对这个组进行统一的变换、样式设置和事件绑定。
// 此处应该是代表 link 即点与点之间的连接
// 创建 <g> 元素并添加链接(<path>)
const link = gLink
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(graph)// 使用 data() 方法绑定数据
.join("path")// 使用 join() 方法来创建 <path> 元素
.attr("stroke", d => color(d.type))
.attr("marker-end", d => `url(${new URL(`#arrow-${d.type}`, location)})`)
💠<marker> 元素
SVG中的一个特殊元素,它用于定义可重复使用的箭头、标记或其他形状,标记贴附于path、line、polyline、 polygon元素上。(目前使用里面是箭头)
在SVG中,<marker> 元素通过定义一个可插入的图形,然后可以在路径元素的marker-start、marker-mid和marker-end属性中引用,用于显示在路径的起点、中点和终点位置。
属性:
id: 为<marker>元素定义一个唯一的ID,以便后续在路径元素中引用。viewBox: 定义<marker>元素的视口框,用于指定图形的可视区域。refX和refY: 指定图形在视口框中的参考点(坐标),用于定位图形在路径上的位置。markerWidth和markerHeight: 定义<marker>元素的宽度和高度。orient: 定义图形的方向,通常使用 "auto" 表示自动根据路径的朝向来调整图形的方向。
gdefs.selectAll("marker")
.data(types) // 使用 data() 方法绑定数据
.join("marker") // 根据数据绑定状态对元素进行操作
.attr("id", d => `arrow-${d}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -0.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto") // 定义图形在路径上的朝向: auto-浏览器会自动根据路径的朝向来调整图形的朝向。
.append("path") // append 添加 用于绘制路径,例如曲线、线段等。主要用于箭头
.attr("fill", color)
.attr("d", "M0,-5L10,0L0,5");
🟢 图表交互
◼ 事件监听器
D3的选择集有一个方法on(),用来设定事件的监听器。
const node = gNode
.attr("fill", "currentColor")
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(nodeArray)
.join("g")
.call(drag(simulation))
// 事件监听器 :mouseover 事件名称 ,handleMouseOver 监听器函数
. on ( 'mouseover' , handleMouseOver) // 鼠标移动到节点上
. on ( 'click' , handleMouseOut) // 点击节点
function handleMouseOut ( event,d ) {
// event 当前事件 d 获取的节点对象
var hoveredNode = d; // 获取当前鼠标悬停的节点数据
// 设置节点的透明度
node. style ( 'opacity' , 1 );
// 设置连接线的透明度
link. style ( 'opacity' , 1 );
}
◼ 键鼠事件
🔺 鼠标事件:
- click:单击事件,鼠标单击某个元素触发,相当于mousedown和mouseup组合在一起;
- dblclick:鼠标双击事件;
- mouseover:鼠标的光标放在某元素上(悬停在元素上);
- mouseout:光标从某元素上移出来时;
- mousedown:鼠标按钮被按下;
- mouseup:鼠标按钮被松开;
🔻 键盘事件:
- keydown:当用户按下任意键时触发,按住不放会重复触发此事件,这一事件不会区分字母的大小写,例如“A”和“a”被视为一致;
- keypress:当用户按下字符键(大小写字母、数字、加号、等号、回车等)时触发,按住不放会重复触发此事件,该事件就会区分字母的大小写;
- keyup:当用户松开按键时触发,该事件不区分字母的大小写;
keydown和keypress事件的区别在于keydown用于任意键的事件,而keypress用于字符键,如果只需要处理字母数字类的响应,或是要对大小写字母分别处理的时候,使用keypress;如果要处理上下左右(↑→)、Shift、Ctrl等特殊键的输入,使用keydown。
◼ 缩放
缩放事件:d3.zoom().on("zoom", zoomed)
drag和zoom一般通过call调用
const zoom = d3.zoom()
.scaleExtent([1, 10]) // 设置缩放范围,1 表示原始大小,10 表示最大放大为原始大小的10倍
.on("zoom", (d3) => zoomed(d3));
const svg = d3.select(svgRef.current)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; font: 12px sans-serif;")
. call (zoom)
// 定义缩放函数的回调
function zoomed(event) {
// 获取缩放和平移的变换
const transform = event.transform;
link.attr('transform', transform);
node_.attr('transform', transform)
}
◼ 悬停文本标签
◼ 过渡动画
过渡动画同样通过事件监听和缓动实现过渡效果和数据更新,实现友好的交互;
.transition() :transition()默认情况延迟(delay)为0ms,持续时长(duration)为250ms,可以自行设置这两个参数。
◼ 与HTML元素交互
D3作为一个JavaScript库,自然可以和原生的HTML元素进行交互,例如响应按钮的点击事件,在html中配置了按钮和点击监测,<button type="button" onclick="update()"> 更新 </button>,点击按钮触发事件,在函数update里面调用d3的绘制代码,实现交互。
状态条是很实用的元素,通过状态条调节d3图表的参数,例如下面通过状态条调节绘制矩形的填充颜色,给状态条添加了onchange的事件监听器,有变化时更新矩形的颜色。
//html中需要额外引入 <script src="https://unpkg.com/d3-simple-slider"></script>
var num2hex = rgb => {
return rgb.map(color => {
var s = color.toString(16);
if (s.length === 1) {
s= '0' + s;
}
return s;
}).join('');
};
var rgb = [0,175,174];
var colors = ['red', 'green', 'blue'];
var svg = d3.select('div#d3-slider').append('svg')
.attr('width', 600).attr('height', 400).append('g')
.attr('transform', 'translate(30,30)');
var box = svg
.append('rect')
.attr('width', 160)
.attr('height', 100)
.attr('transform', 'translate(400,30)')
.attr('fill', `#${num2hex(rgb)}`);
rgb.forEach((color, i) => {
var slider = d3
.sliderBottom()
.min(0)
.max(255)
.step(1)
.width(300)
.default(rgb[i])
.displayValue(false)
.fill(colors[i])
.on('onchange', num => {
rgb[i] = num;
box.attr('fill', `#${num2hex(rgb)}`);
d3.select('p#value-color').text(`#${num2hex(rgb)}`);
});
svg.append('g').attr('transform', `translate(30,${60 * i})`).call(slider);
});
d3.select('p#value-color').text(`#${num2hex(rgb)}`);
✨ 参考资料:
1、官方文档网站d3js.org.cn/;
2、D3库实践笔记之图表交互 |可视化系列 lynsdata.cn/2020/10/03/…