您的位置:首页 > 产品设计 > UI/UE

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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息