vue.js中使用d3.js画家谱关系图

 项目中需要做个家谱图,网上查了好多资料没找到合适的,就自己写个简单的,方便以后查看,附上效果图

首先展示父亲、配偶、子女,三代人信息,然后选择其他人可以展开他的三代关系。如下图

下面是代码,这个关系图还只是个初稿,里有些逻辑不全,其中母亲这个通过父亲展开就不合适。以后有机会再完善吧。

<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);
      // 计算节点坐标
      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>

猜你喜欢

转载自my.oschina.net/Lady/blog/1649994
今日推荐