文档章节

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

lindeyi
 lindeyi
发布于 2018/03/23 17:31
字数 1228
阅读 4937
收藏 3

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

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

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

<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>

 

© 著作权归作者所有

lindeyi
粉丝 5
博文 16
码字总数 14150
作品 0
海淀
私信 提问
加载中

评论(4)

皇子弗
java+vuejs+d3的家谱这里有个案例:https://blog.csdn.net/xunileida/article/details/80250608
我乃小神神
我乃小神神

引用来自“我乃小神神”的评论

您好,博主,我想问问,这个引入d3,不得行咋解决


175:14-25 "export 'behavior' (imported as 'd3') was not found in 'd3'

这个是报错内容

引用来自“lindeyi”的评论

需要引入d3.js包,npm install --save d3
你好,对于上次的那个问题。并不是没有引入,是引入d3的那个版本取消了对你的behavior的支持,那个问题我已经解决了,就是我想问问,你这个有完整一点的吗,虽然我不想做伸手党,但是我canvas差点画出来了,但是剩下的太难了,就看见你这很符合,能给我看一下嘛,学习一下,谢谢,我邮箱424363283@qq.com。谢谢
lindeyi
lindeyi 博主

引用来自“我乃小神神”的评论

您好,博主,我想问问,这个引入d3,不得行咋解决


175:14-25 "export 'behavior' (imported as 'd3') was not found in 'd3'

这个是报错内容
需要引入d3.js包,npm install --save d3
我乃小神神
我乃小神神
您好,博主,我想问问,这个引入d3,不得行咋解决


175:14-25 "export 'behavior' (imported as 'd3') was not found in 'd3'

这个是报错内容
在Vue项目里面使用d3.js

之前写一个 Demo里面 有些东西要使用d3实现一些效果 但是在很多论坛找资源都找不到可以在Vue里面使用D3.js的方法,npm 上面的D3相对来说 可以说是很不人性化了 完全没有说 在webpack上怎么使用...

peakedness丶
2018/10/23
0
0
使用D3.js+Vue实现一个简单的柱形图

最近想在Vue的项目里尝试使用d3.js,封装一些常用的图表。这里记录一下自己搭建项目的过程,以及实现一个简单的柱形图。不了解D3的请移步D3 Data-Driven Documents,它是基于数据驱动文档工作...

levin在掘金
2018/08/04
0
0
深入理解Vue的watch实现原理及其实现方式

理解Vue中Watch的实现原理和方式之前,你需要深入的理解MVVM的实现原理,如果你还不是很理解,推荐你阅读我之前的几篇文章: 彻底搞懂Vue针对数组和双向绑定(MVVM)的处理方式 vue.js源码解读...

wangweianger
2018/05/14
0
0
vue 脚手架 配置 及文件介绍

一 : vue 是 单文件组件 导 : 之前注册组件有什么缺点 ? 1- 缺乏语法高亮 2-格式不好整体 3-没有专门的写css代码等等 参考 : vue => 工具 => 单文件组件 什么是单文件组件 ? 后缀为 .vue 的文...

端端的土化代码
03/31
0
0
Vue组件一-父组件传值给子组件

Vue组件一-父组件传值给子组件 开始 Vue组件是学习Vue框架最比较难的部分,而这部分难点我认为可以分为三个部分学习,即 组件的传值 - 父组件向子组件中传值 事件回馈 - 子组件向父组件发送消...

mdiep
2018/05/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

解决问题&发现问题

作为一个程序员非常重要的的能力就是解决问题的能力,当然除了解决问题之外,还有一个经常被疏忽的能力-发现问题的能力。 解决问题 一套有效的解决问题的能力非常重要,下面是一个解决问题的...

Lubby
21分钟前
5
0
Leetcode PHP题解--D104 167. Two Sum II - Input array is sorted

D104 167. Two Sum II - Input array is sorted 题目链接 167. Two Sum II - Input array is sorted 题目分析 给定一个已经排序好的整数数组,从中寻找两个数字,使其相加之后等于给定的一个...

skys215
28分钟前
3
0
IntelliJ IDEA Spring Boot 2.x 多模块项目创建

在学习Spring Boot 2的时候顺便来学习创建下Maven下的多模块项目创建。方便学习使用整套开发流程。 第一步,检查IDEA,新版本的IDEA可能没有Spring Assistant可通过插件安装(Preferences->P...

被猪拱了的JAVA
28分钟前
5
0
Java运行状态分析2:获取线程堆栈信息

Java运行状态分析2:获取线程堆栈信息 基本概念 出现内存泄漏或者运行缓慢场景,有时候无法直接从业务日志看出问题时候,需要分析jvm内存和线程堆栈 线程堆栈信息主要记录jvm线程在某时刻线程...

indi_yugj
28分钟前
8
0
解决java编译错误:编码GBK的不可映射字符

https://www.cnblogs.com/charleswong/p/8481593.html 新建java文件,存储时Encoding选择了UTF-8, 由于语句中包含中文,javac编译时报错,提示"编码GBK的不可映射字符": 解决办法: 方法一...

时刻在奔跑
35分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部