d3-v5绘制一个关系图谱(一)前置知识

2,437 阅读6分钟

svg介绍

svg是用xml描述的矢量图,放大不会失真,可以通过直接编写svg标签属性描述图形,也可以通过js操作svg的dom结构,设置修改属性来定义图形,当然svg作为dom标签,也可以通过css进行属性设置,而它具有一些不同于我们平时使用的css属性。

下面介绍关系图谱中用到的基础图形

矩形 rect

  • x: 矩形左上角x轴坐标
  • y: 矩形左上角y轴坐标
  • width: 矩形宽度
  • height: 矩形高度
  • rx : 圆角,x轴的半径,的最大值是矩形宽度的一半,
  • ry: 圆角,y轴的半径,的最大值是矩形高度的一半。
<svg width="300" height="300" style="border: 1px solid red;">
  <rect width="200" height="100"></rect>
</svg>
想要一个规则的圆角,只设置rx或者ry其中一个就可以了,设置x、y可以让矩形以svg左上角为起始点进行移位。

image.png

圆形 circle

  • cx: 圆心在x轴的坐标
  • cy: 圆心在y轴的坐标
  • r: 半径

路径 path

path可以说是svg中最核心灵活的标签了,可以灵活绘制各种各样的图形,而它最核心的属性d就是用来设置绘制路径的。

  • M: 移动画笔到指定坐标位置,moveto 的意思。每个路径都必须以 M 开始。M 传入 xy 坐标,用逗号或者空格隔开。
  • L: 轮廓坐标,lineto 的意思。L 是跟在 M 后面的。它也是可以传入一个或多个坐标。大写的 L 是一个绝对位置
  • l: 这是小写 L,和 L 的作用差不多,但 l 是一个相对位置
  • H: 和上一个点的Y坐标相等,是 horizontal lineto 的意思。它是一个绝对位置
  • h: 和 H 差不多,但 h 使用的是相对定位
  • V: 和上一个点的X坐标相等,是vertical lineto 的意思。它是一个绝对位置
  • v: 这是一个小写的 v ,和大写 V 的差不多,但小写 v 是一个相对定位。
  • Z: 关闭当前路径,closepath 的意思。它会绘制一条直线回到当前子路径的起点。

椭圆弧路径 path

椭圆弧曲线顾名思义,画出来的弧线其实就是一个椭圆的一部分,我们需要通过几个参数,根据椭圆上两个坐标点、长半轴、短半轴,就可以确定出两个椭圆,四条弧形、而通过下面的参数可以筛选出我们想要的那一条弧线

image.png 弧线参数 A rx ry x-axis-rotation large-arc-flag sweep-flag x y

  • rx x半径
  • ry y半径
  • x-axis-rotation x轴旋转角度
  • laf即large-arc-flag 值为1表示大角弧度,大于180度;0表示小角弧度,小于180度。
  • sf即sweep-flag 值为1表示从起点到终点绕中心顺时针方向;0表示逆时针方向。
  • x 弧线终点x坐标
  • y 弧线终点y坐标

image.png 画一个圆弧,可以自己调参数试试效果

 <svg width="1500" height="1500">
     <path d="M 100 100 A 100 200 0 1 0 200 300" fill="none" stroke="red"></path>
 </svg>

d3.js

d3.js是一个面向过程的JavaScript库,d3提供了跟多操作dom的api,这点功能类似于jQuery,比如d3.select(css选择器),功能类似document.querySelector(),可以通过css选择器选中第一个选中的元素,如果想要选中所有的选中元素,可以用document.selectAll(css选择器),类似于 document.querySelectorAll。选中之后可以通过链式调用设置属性,比如要给一个类名为test的标签设置属性。通过下面的属性设置,可以给这个标签添加widthheight属性。

    const testDom = d3.select('.test');
    testDom
        .attr('width', 100)
        .attr('height', 100);

d3是如何工作的

数据图元绑定

官方api: github.com/d3/d3-selec…

d3-selection是d3非常重要和实用的机制,可以根据数据绑定图元,比如我们有这样一段数据,意思是选中类名为container下的所有p标签。返回值update是一个Pt对象(selection),每个Pt对象的原型上会有很多用于操作节点的方法,比如appendremoveattrstyletext,调用这些方法会返回的也是Pt对象,所以可以进行链式调用

    <div class="container">
        <p>1</p>
        <p>2</p>
    </div>
        const data = ['h', 'e', 'l', 'l', 'o', 'w'];
        const p = d3.select('.container').selectAll('p')

        const updateSelection = p.data(data);
        console.log(updateSelection);

image.png 在这个updateSelection对象中,会有三个状态的节点(选中、新增、多余),_group 这里放的是可以操作的节点,在接下来链式调用d3的方法操作节点属性的时候,d3会对_groups中的每个元素进行更新。

enter

enter enter中是数据数量大于选中标签数量的时候,因为container下只有两个p标签,而data数据的数量有六个,所以enter中前两个元素是空节点,后面有四个rt节点(一个描述对象,用于根据数据新增节点)。

当调用updateSelection.enter()方法的时候,d3会根据updateSelection对象中的_enter返回一个新的Pt对象,并在新对象中把_enter作为_groups

        const data = ['h', 'e', 'l', 'l', 'o', 'w'];
        const p = d3.select('.container').selectAll('p')
        const updateSelection = p.data(data);
        const enterSelection = updateSelection.enter()
        console.log(enterSelection)

image.png

这时候这个新的enter对象拥有了_group属性,我们就可以操作它了,比如,我们想将enter中每个rt节点变成div节点,只需要对enter调用append方法。

   const divSelecton = enterSelection.append('div')
   console.log(divSelecton)

image.png 并且此时的div已经插入到了container容器中

image.png

当然如果想设置div的属性,也可以链式设置

enterSelection
    .append('div')
    .style('width', 100)
    .style('height', 100)
    .style('background-color', 'red');

image.png 可以看到通过设置后,属性已经添加上去了。

exit

exit是选中标签数量大于数据数量的时候。 而调用exit()方法也是同理,会返回一个新的Pt对象,在新的对象中会将_exit(多余节点)变成_group,然后就可以对多余节点进行下一步操作了,比如调用remove()方法将多余的节点从dom结构中删除。

merge

每个Pt对象中还会有一个merge方法,这个方法可以将两个Pt对象中的_group进行合并,然后统一操作,举个🌰。

   <div class="container">
        <p>1</p>
        <p>2</p>
    </div>
     const data = ['h', 'e', 'l', 'l', 'o', 'w'];
        const p = d3.select('.container').selectAll('p')
        // updateSelection中具有_groups属性存放了选中的container下的两个p标签
        const updateSelection = p.data(data);
        // 调用enter方法取出_enter中的属性作为新Pt对象的_groups
        // 再链式操作新Pt对象,将rt节点变成div插入到container中
        const enterSelection = updateSelection.enter().append('div');
        // 调用merge方法,合并两个Pt对象的_groups并返回新的Pt对象
        const mergeSelection = updateSelection.merge(enterSelection)
        console.log(mergeSelection);

image.png

通过上面的图可以看出,新的Pt对象中的_groups合并了两个Pt对象的_groups,接下来就可以统一处理了,比如将每个p和div加上宽高属性。

 mergeSelection.style('color', 'red')
            .style('width', "200px")
            .style('height', "200px")

image.png

函数属性

当我们用data进行数据-图元绑定以后,在接下来的属性设置中,属性值可以是一个函数,这个通过这个函数的参数可以获取到当前图元对应的数据以及获取到当前dom节点,这也是我们在d3中最常用的属性设置方式。

        const data = ['h', 'e', 'l', 'l', 'o', 'w'];
        const p = d3.select('.container').selectAll('p')

        const updateSelection = p.data(data);

        const enterSelection = updateSelection.enter().append('div');

        const mergeSelection = updateSelection.merge(enterSelection)

        mergeSelection
            .style('color', 'red')
            .text(d => {
                // 这里获取到的就是data中的每一项数据
                console.log(d);
                // 函数返回值作为属性值
                return d;
            })

image.png 通过text方法中函数返回值,可以看到p标签和div标签的值已经变成了data中的每一项数据。当然如果设置的属性具有key,比如width就可以这样设置style('width',d=>{ return ... })