[?5分钟学会,或者1秒CV代码] d3.js 组合 vue, 新手也实现 ?? 拓扑(svg 版)
2020/5/19 11:25:30
本文主要是介绍[?5分钟学会,或者1秒CV代码] d3.js 组合 vue, 新手也实现 ?? 拓扑(svg 版),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目标效果
🚀 在线实例
源码地址
如果急用, 可先cv走代码修改, 哪里没明白再回来看哪里.
github.com/any86/any-t…
介绍下 D3.js
d3 是一个大而全的图形库, 集成了svg 元素操作和常见图表(图形)的数据结构.
本文基于v5 版本的 d3 编写, d3 的功能都是拆分成独立包的, 我们这里只需要引入d3-hierarch和d3-shape生成拓扑的数据结构.
// hierarchy 用来生成d3的树形对象, 同时挂载一些方法, 比如获取子元素descendants // tree 用来给节点分配x/y坐标 // linkHorizontal用来生成水平连接线 import {hierarchy, tree} from 'd3-hierarchy'; import {linkHorizontal} from 'd3-shape'; 复制代码
代码架构
- 使用 d3 生成拓扑数据结构(其实就是树形, 每个节点会生成 x/y 坐标).
- 使用 vue 进行 svg 的 dom 结构渲染(也可用 canvas 进行渲染).
- 使用 any-touch 添加"拖拽"和"点击关闭/展开子节点"功能.
- 简单封装一个动画函数, 实现关闭/展开动画, 真的很简单, 仅仅为了锦上添花, 如果不需要动画此处内容可以跳过.
代码解读👨🏫
下面代码看起来长, 但是主要都是注释.
d3 生成拓扑数据结构
// 测试数据 // 普通树形 const dataset = { name: '第1级', children: [ { name: '第2级', children: [ { name: '第3级A', children: [ { name: '第4级A', }, { name: '第4级B', }, ], }, ], }, ], }; 复制代码
下面代码看起来很长, 但实际只有4个方法总20几行代码,剩余都是注释.
{ /** * 把普通树形变成d3需要的树形 */ genTreeData(data) { const width = 1000; const height = 1000; // hierarchy把普通的树形数据变成d3的tree结构, // 这样tree就有了d3的方法, 可以通过方法获取子节点(tree.descendants)/父节点/节点数等信息 const root = hierarchy(data); // 遍历子节点,descendants是后代的意思, // 但是其实也会包含当前节点本身. // 给节点增加hidden字段用来控制当前节点显示/隐藏. root.descendants().forEach((node) => { node.hidden = false; }); // d3.tree运行后会返回一个函数, // 通过函数可以设置图形的一些尺寸(nodeSize)/位置间距(separation)信息 // 这样在返回的函数中传入刚才输入的d3.tree结构数据, 比如上面的root, // 那么拓扑所需的数据就都全了. return ( tree() .separation(function(a, b) { // 同级元素调整间隙比例 // 一般就用2:1就好 return (a.parent == b.parent ? 2 : 1) / a.depth; }) // 节点尺寸 .nodeSize([110, width / (root.height + 1)])(root) ); }, /** * 生成节点数组 => [Node, Node] * 用来给模板渲染元素 */ updateNodes() { this.nodes = this.tree.descendants(); }, /** * 生成线 */ updateLinks() { // tree.links会根据节点数据生成连线数据 this.linkPaths = this.tree.links().map((link) => { // d.linkHorizontal和上面的d3.tree一样, // 可以当做构造函数, // 其返回一个函数 // 可以用函数上的x/y方法指定 // 由于默认生成tree数据是上下结构的拓扑数据, // 所以为了生成左至右的线需要把X/Y数据颠倒 // 最终生成线数据结构类似这样:{source:{},target:{}} if (!link.target.hidden) { return linkHorizontal() .x((d) => d.y) .y((d) => d.x)(link); } }); }, /** * 生成所需数据 */ renderTree() { this.tree = this.genTreeData(dataset); this.updateLinks(); this.updateNodes(); }, } 复制代码
使用 vue 生成 svg 的 dom 结构
dom 结构看起来太长了, 但实际只做了 2 件事:
- 循环生成节点, 用
<foreignObject>
元素实现 svg 内部可以嵌套普通 html 元素. - 循环生成节点间的连线.
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" style="width:100%"> <g transform="translate(100, 100)"> <template v-for="(linkPath, index) in linkPaths"> <path v-if="linkPath" :key="index" :d="linkPath" class="line" /> </template> </g> <g transform="translate(100, 100)"> <foreignObject v-for="(node,index) in nodes" v-show="!node.hidden" :class="{[`at-${action}`]:activeNode===node}" :key="'foreignObject'+index" :width="itemWidth" :height="itemHeight" :x="node.y - itemWidth/2" :y="node.x - itemHeight/2" @panstart="onPanstart(index,$event)" @panmove="onPanmove(index,$event)" @panend="onPanend" @pancancel="onPanend" @tap="onTap(index)" > <body xmlns="http://www.w3.org/1999/xhtml"> <div class="text"> <p>节点层级: {{node.depth}}</p> <p>节点顺序: {{index}}</p> </div> </body> </foreignObject> </g> </svg> 复制代码
使用any-touch增加拖拽和点击功能
{ /** * 拖拽开始, 记录当前节点 */ onPanstart(index, e) { const [item] = this.nodes.splice(index, 1); this.nodes.push(item); this.activeNode = item; }, /** * 拖拽中 * 变化节点坐标 * 重新生成连线数据 */ onPanmove(index, e) { this.action = e.type; const { deltaX, deltaY } = e; const { length } = this.nodes; this.activeNode.x += deltaY; this.activeNode.y += deltaX; this.updateLinks(); }, /** * 取消当前节点激活 */ onPanend() { this.activeNode = null; }, /** * 收起/展开子节点 */ onTap(index) { this.activeNode = this.nodes[index]; // 当前节点记录是否收起/展开 if (void 0 === this.activeNode.collapse) { this.$set(this.activeNode, 'collapse', true); } else { this.activeNode.collapse = !this.activeNode.collapse; } const { x, y, collapse } = this.activeNode; // descendants返回的子节点包含自己, 所以排除自己 const [a, ...childNodes] = this.activeNode.descendants(); // 根据节点折叠状态来展开/折叠子节点显示 childNodes.forEach((node) => { if (collapse) { const x1 = node.x; const y1 = node.y; // 存储展开时候的位置, // 下次复原位置用 node._x = x1; node._y = y1; animate(1, 0, 200, (value, isDone) => { node.x = x - (x - x1) * value; node.y = y - (y - y1) * value; if (isDone) { node.hidden = true; } this.updateLinks(); }); } else { node.hidden = false; // 此处让value从0 - 1在200ms内不停变化 // 从而让节点位置变化实现展开收缩动画 animate(0, 1, 200, (value) => { node.x = x + (node._x - x) * value; node.y = y + (node._y - y) * value; this.updateLinks(); }); } }); } } 复制代码
动画(animate函数)
源码: github.com/any86/any-t…
animate函数实现其实很简单, 主要说下easeInOut
函数, 他其实就是一个"时间为x轴, 值为y轴的曲线", 是我百度搜的, 其实还有很多类似的曲线函数, 大家可自行搜索. 大家可以自己尝试写一个, 比如借助Math.sin
.
动画在本例就是锦上添花, 逻辑也很简单不展开讲解, 如果有兴趣可留言讨论.
/** * t 时间 * b 起始值 * c 目标值 * d 所需时间 * */ function easeInOut(t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t + b; return -c / 2 * ((--t) * (t - 2) - 1) + b; } /** * 用requestAnimationFrame不断执行easeInOut * */ export function animate(from = 0, to = 0, duration = 1000, callback = () => void 0) { const startTime = window.performance.now(); function run() { const timeDiff = window.performance.now() - startTime; const value = easeInOut(timeDiff, from, to - from, duration); if (timeDiff <= duration) { callback(value); requestAnimationFrame(run); } else { // 修正超出边界 callback(to, true); } } run(); }; 复制代码
全部代码: github.com/any86/any-t…
未来
计划封装成vue组件并开源, 一切看大家反馈, 如果支持这个计划请下方留言☎️.
🔥typescript系列课程
基础教程从这里开始
第一课, 体验typescript
第二课, 基础类型和入门高级类型
第三课, 泛型
第四课, 解读高级类型
第五课, 命名空间(namespace)是什么
特别篇, 在vue3🔥源码中学会typescript🦕 - "is"
第六课, 什么是声明文件(declare)? 🦕 - 全局声明篇
新手前端学🔥typescript - 实战篇, 实现浏览器全屏(59行)
🔥往期热门文章
🔥常用正则大全2020
🚆新手前端不要慌! 给你✊10根救命稻草🍃
真.1px边框, 🚀 支持任意数量边和圆角, 1 个万金油的方法
🚀揭秘vue/react组件库中🤚5个"作者不造的轮子"
vue / react的UI库都在用的几个DOM API🚀
微博
刚玩微博, 咱们可以互相关注, 嘿嘿
微信群
这篇关于[?5分钟学会,或者1秒CV代码] d3.js 组合 vue, 新手也实现 ?? 拓扑(svg 版)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南