vue.js中使用d3.js画家谱关系图
2018-03-23 00:00
309 查看
项目中需要做个家谱图,网上查了好多资料没找到合适的,就自己写个简单的,方便以后查看,附上效果图
首先展示父亲、配偶、子女,三代人信息,然后选择其他人可以展开他的三代关系。如下图
下面是代码,这个关系图还只是个初稿,里有些逻辑不全,其中母亲这个通过父亲展开就不合适。以后有机会再完善吧。
首先展示父亲、配偶、子女,三代人信息,然后选择其他人可以展开他的三代关系。如下图
下面是代码,这个关系图还只是个初稿,里有些逻辑不全,其中母亲这个通过父亲展开就不合适。以后有机会再完善吧。
<template lang='html'> <div class='demo'> <div class='left'> <div class='left-top'> <el-table height='250' highlight-current-row @current-change="handleCurrentChange" :data='nodes'> <el-table-column prop='id' label='id' width='50'> </el-table-column> <el-table-column prop='name' label='姓名'> </el-table-column> </el-table> </div> <div class='left-bottom'> <el-table height='250' :data='links'> <el-table-column prop='srcId' label='源id' width='50'> </el-table-column> <el-table-column prop='toId' label='目标id'> </el-table-column> <el-table-column label='关系'> <template slot-scope='scope'>{{ scope.row.type | toCn }} </template> </el-table-column> </el-table> </div> </div> <div class='testd3' ref="testd3"> </div> </div> </template> <script> import * as d3 from 'd3'; let width_ = 60; export default { data () { return { srcNode: null, svg: null, nodes: [ {id: '0', name: '张三'}, {id: '1', name: '张父'}, {id: '2', name: '张母'}, {id: '3', name: '张三妻'}, {id: '4', name: '张大'}, {id: '5', name: '张小'}, {id: '6', name: '张小妻'}, {id: '7', name: '张小小'}, {id: '8', name: '张三妻父'}, {id: '9', name: '张三妻母'}, {id: '10', name: '张三妻弟'} ], links: [ {srcId: '0', toId: '1', type: 0}, // 0 父子 // {srcId: '0', toId: '2', type: 1}, // 1 母子 {srcId: '1', toId: '2', type: 2}, // 2 配偶 {srcId: '0', toId: '3', type: 2}, // 2 配偶 {srcId: '0', toId: '4', type: 3}, // 子女 {srcId: '0', toId: '5', type: 3}, // 子女 {srcId: '5', toId: '6', type: 2}, // 配偶 {srcId: '5', toId: '7', type: 3}, // 子女 {srcId: '3', toId: '8', type: 0}, // 父 {srcId: '8', toId: '9', type: 2}, // 配偶 {srcId: '8', toId: '10', type: 3} // 子女 ], drag: false }; }, filters: { toCn (src) { let res = ['父子', '母子', '配偶', '子女']; return res[src]; } }, methods: { handleCurrentChange (row) { this.srcNode = row; // 查找中心点连线关系 srcId = 0 let srcId = row.id; let srcNode = this.nodes.filter(n => n.id === srcId)[0]; // 获取与此节点关系点 let links = this.links.filter(l => l.srcId === srcId); let otherlinks = this.links.filter(l => l.toId === srcId).map(l => { let link = {}; if (l.type === 3) { // 子女 -> 父子 link.type = 0; } else if (l.type === 0) { // 父女 -> 子女 link.type = 3; } else { link.type = l.type; } link.srcId = l.toId; link.toId = l.srcId; return link; }); links.push(...otherlinks); // 计算节点坐标 3ff0 let nodes_ = this.convert(srcNode, links); let that = this; // 设置画布 let width = 1000; let height = 720; // console.log(d3.select('.testd3')[0].innerHTML); d3.select('.testd3').selectAll('*').remove(); // 画中心点 let svg = d3.select('.testd3').append('svg') .attr('width', width) .attr('height', height) .append('g') .attr('transform', 'translate(40,0)'); var drag = d3.behavior.drag() .on('drag', function(d) { d3.select(this) .attr('transform', 'translate(' + (d3.event.x - 30) + ',' + (d3.event.y - 30) + ')') .append('rect') .attr('x', d.x = d3.event.x - 30) .attr('y', d.y = d3.event.y - 30); // 线条 svg.selectAll(`.link-${d.id}`).attr('d', function(dd) { return that.getPath(d, dd); }); }); that.drag = drag; // 画线 this.drawLinks(svg, links); // 画点 console.log('nodes_', nodes_); this.drawNodes(svg, nodes_); }, // 转换 convert (srcNode, links) { let nodes = []; let map = new Map(); // 子女个数 let childsize = links.filter(l => l.type === 3).length; // 子女开始位置 let start = -(childsize - 1) * width_; if (srcNode.x === undefined) { // 默认坐标 srcNode.x = 150; srcNode.y = 150; } map.set(srcNode.id, srcNode); let {x, y} = srcNode; links.forEach(l => { if (l.type === 0) { // 父子 map.set(l.toId, {x: x, y: y - width_ * 2, type: l.type}); } if (l.type === 1) { // 母子 map.set(l.toId, {x: x + width_ * 2, y: y - width_ * 2, type: l.type}); } if (l.type === 2) { // 配偶 map.set(l.toId, {x: x + width_ * 2, y: y, type: l.type}); } if (l.type === 3) { // 子女 map.set(l.toId, {x: x + start, y: y + width_ * 2, type: l.type}); start = start + width_ * 2; } }); this.nodes.forEach(n => { let m = map.get(n.id); if (m) { n['x'] = n.x || m.x; n['y'] = n.y || m.y; n['type'] = m.type; nodes.push(n); } }); return nodes; }, getPath (move, stas) { // 获取对方坐标 let srcId = stas.srcId; let flag = false; if (move.id === stas.srcId) { srcId = stas.toId; flag = true; } let path; // 源 let {x, y} = this.nodes.filter(n => n.id === srcId)[0]; if (stas.type === 0) { // 父子 if (flag) { path = `M${x + width_ / 2} ${y} L${x + width_ / 2} ${y + width_ * 1.5} L${move.x + width_ / 2} ${move.y - width_ / 2} L${move.x + width_ / 2} ${move.y}`; } else { path = `M${x + width_ / 2} ${y} L${x + width_ / 2} ${y - width_ / 2} L${move.x + width_ / 2} ${move.y + width_ * 1.5} L${move.x + width_ / 2} ${move.y}`; } } if (stas.type === 2 || stas.type === 1) { // 配偶 let w_ = move.x > x ? width_ : -width_; let padding = move.x > x ? -width_ / 2 : width_ * 1.5; if (flag) { path = `M${x + width_ / 2 + w_ / 2} ${y + width_ / 2} L${move.x + padding} ${y + width_ / 2} L${move.x + width_ / 2 - w_ / 2} ${move.y + width_ / 2}`; } else { path = `M${x + width_ / 2 + w_ / 2} ${y + width_ / 2} L${move.x + padding} ${y + width_ / 2} L${move.x + width_ / 2} ${move.y + width_ / 2}`; } } if (stas.type === 3) { // 子女 if (flag) { path = `M${x + width_ / 2} ${y + width_} L${x + width_ / 2} ${y - width_ / 2} L${move.x + width_ / 2} ${move.y + width_ * 1.5} L${move.x + width_ / 2} ${move.y + width_}`; } else { path = `M${x + width_ / 2} ${y + width_} L${x + width_ / 2} ${y + width_ * 1.5} L${move.x + width_ / 2} ${move.y - width_ / 2} L${move.x + width_ / 2} ${move.y + width_}`; } } return path; }, drawNode (svg, node) { let node_ = svg.selectAll(`.node-${node.id}`) .data([node]) .enter() .append('g') .attr('class', `node-${node.id}`) .attr('transform', function(d) { return 'translate(' + (d.x) + ',' + (d.y) + ')'; }) .on('dblclick', (d) => { let links = this.links.filter(l => l.srcId === d.id); if (links.length === 0) { this.$message('没有关联数据'); return; } let nodes2 = this.convert(d, links); this.drawLinks(svg, links); this.drawNodes(svg, nodes2); }) .on('mouseover', function(d) { d3.select(this).select('rect') .attr('stroke', '#FFCC33') .attr('stroke-width', 3); // 设置边框 }) .on('mouseout', function(d) { d3.select(this).select('rect') .attr('stroke-width', 0); // 取消边框 }) .call(this.drag); node_.append('rect') .attr('width', 60) .attr('height', 60) .attr('x', 0) .attr('y', 0) .attr('style', (d) => { return (d.type === 1 || d.type === 2) ? 'fill:#FFAD5B;' : 'fill:#35AD5B;'; }); node_.append('text') .attr('dx', function(d) { return 30; }) .attr('dy', 30) .style('text-anchor', function(d) { return 'middle'; }) .style('fill', '#fff') .text(function(d) { return d.name; }); }, drawNodes (svg, nodes) { nodes.forEach(n => { this.drawNode(svg, n); }); }, // 添加链接 drawLinks (svg, linksData) { let that = this; linksData.forEach(l => { let classFlag = l.srcId > l.toId ? `${l.srcId}-${l.toId}` : `${l.toId}-${l.srcId}`; svg.selectAll(`.link-${classFlag}`) .data([l]) .enter() .append('path') .attr('class', d => { return `link-${d.srcId} link-${d.toId} link-${classFlag}`; }) .attr('d', function(d) { let node = that.nodes.filter(n => n.id === d.toId)[0]; return that.getPath(node, d); }) .attr('style', function() { return 'stroke:#F7881F'; }); }); } }, mounted() { } }; </script> <style lang='css'> [class^=link] { fill: none; stroke: #ccc; stroke-width: 1.5px; } .demo { width: 100%; height: 100%; } .left { float: left; width: 30%; } .testd3 { float: left; width: 70%; } </style>
相关文章推荐
- Vue 使用Spread.js没有层级关系(隐藏与显示)
- 使用node+vue.js实现SPA应用
- Vue.js——使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用
- 浅析angular,react,vue.js jQuery使用区别
- 基于webpack和vue.js搭建的H5端框架(其实主要用于Hybrid开发H5端框架,但是依然能够作为纯web端使用)
- 使用Jquery与vuejs操作dom比较
- js css html和DOM的关系和使用场景
- Vue.js——使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用【6】
- 使用Vue.js 2.0搭建单页应用:从构建到部署
- 使用 Vuex + Vue.js 构建单页应用
- 使用Angularjs和Vue.js对比
- vue.js插件使用(01) vue-resource
- 如何使用Vuex+Vue.js构建单页应用
- 4、vue.js的使用
- 使用vue.js开发时一些注意事项
- Vue.js-----轻量高效的MVVM框架(十一、使用slot分发内容)
- Vue.js-----轻量高效的MVVM框架(使用slot分发内容)
- phoenix使用vue--单独js(不使用app.js)
- 使用 Vue.js 制作一个简单的调查问卷平台
- 使用node+vue.js实现SPA应用