D3基础02 - 对SVG的核心操作

1,096 阅读7分钟

写在前面:

  1. 本文是单纯的个人学习总结和课堂笔记整理,大部分知识点来自“开课吧”线上直播课程,以及官方资料。
  2. 由于目前仍处于前端学习、入门阶段,因此对于知识的掌握和理解难免会有偏差,因此该文章仅仅是个人记录,请查阅者不要以此作为资料依据。

工欲善其事,必先利其器。在了解D3对SVG的核心操作之前,我们首先需要知道什么是SVG,以及SVG绘图的基本用法。

SVG可缩放矢量图形是一种基于可扩展标记语言,用于描述二维矢量图形的图形格式。SVG由W3C制定,是一个开放标准。

通过SVG绘制图形

SVG根视图

SVG根视图可以认为就是一块画布,所有绘图工作都是在SVG那实现的。SVG初始化时需要配置一些参数:

  • version :SVG的版本
  • xmlns : 命名空间
  • widthheight :SVG画布的吃顿
  • viewBox :视图框,总会在画布中完全显示,其中的图形会基于视图框的宽高按比例显示
<svg version="1.2" 
     xmlns="http://www.w3.org/2000/svg"
     width="400"
     height="400"
     viewBox="0 0 400 400"
     ></svg>

声明好SVG跟容器之后,我们就可以在svg标签内部进行绘图了,常见的图形如下:

  • 矩形
  • 圆形
  • 路径 ==> 万能绘图方式
    • 直线
    • 圆弧
    • 二次贝塞尔曲线
    • 二次贝塞尔曲线的延续
    • 三次贝塞尔曲线

下面我们来一个一个的看如何绘制上述图形。

矩形

<!--
rect 矩形
    x|y 矩形左上角点位置
    width|height 尺寸
-->
<rect x="100" y="100" width="400" height="400" fill="#00acec" />

圆形

<!--
    circle 圆形
        cx|cy 圆心位置
        r 半径
    -->
<circle cx="300" cy="300" r="100" fill="#00acec" />

路径 - 直线

关于路径(也就是线条),有几个常用的属性,用来控制路径的样式:

  • fill 用来控制路径内部的填充色(闭合曲线才生效)
  • stroks 用来控制路径线条的颜色
  • stroke-width 用来控制线条的宽度
  • stroke-dasharray 用来控制线条的连续性,常用来绘制虚线
<!--path 路径
    d 路径形状,大写字母为绝对点位,小写字母为相对点位
    起点: M|m
    下一个点: L|l
-->

<path
  d="
    M
    300 300
    L
    500 300
    "
  fill="none"
  stroke="#000"
  stroke-width="10"
  stroke-dasharray="30 10"
/>

路径 - 圆弧

<!--圆弧A|a,如 M 300 300 A 150 200 0 0 0 500 300
        rx ry 半径
        x-axis-rotation x轴方向 [0,360]
        large-arc-flag 是否显示最大弧 0|1
        sweep-flag 弧在哪一侧 0|1
        x y 结束点
    -->
<path
  d="
    M
    300 300
    A
    150 200
    20
    1
    0
    500 300
    "
  fill="none"
  stroke="#000"
  stroke-width="5"
/>

路径 - 二次贝塞尔曲线

<!--二次贝塞尔曲线 Q|q,如
    M 100 400 
		Q 200 100 300 400 
    其中200 100 分别是 cpx1 cpy1 控制点,用来控制曲线的弯曲程度
    其中300 400 分别是 x y 结束点
-->

<path
  d="
    M
    100 400
    Q
    200 100
    300 400
    "
  fill="none"
  stroke="#000"
  stroke-width="10"
/>

二次贝塞尔曲线的延续

上述我们通过Q|q生成了一段二次贝塞尔曲线,如果我们想要接着画,就需要通过T|t声明接下来曲线的所有控制点。

<!--二次贝塞尔曲线的延续 T|t,如T 500 400
    x y 结束点
    x y 结束点
    ……
-->

<path
  d="
    M
    100 400
    Q
    200 100
    300 400
    T
    500 400
    700 400
    900 400
    "
  fill="none"
  stroke="#000"
  stroke-width="10"
/>

三次贝塞尔曲线

三次贝塞尔曲线与上述二次曲线除了声明的参数变成了C|c以外,唯一的不同之处就在于控制点从一个变成了两个

<!--三次贝塞尔曲线 C|c,如 M 100 400 C 200 400 200 200 300 200
    上述的 C 200 400 是 cpx1 cpy1 控制点1
    紧接在 C 200 400 后面的 200 200 是cpx2 cpy2 控制点2
    最后的 300 200 是 x y 结束点
-->
<path
  d="
    M
    100 400
    C
    200 400
    200 200
    300 200
    "
  fill="none"
  stroke="#000"
  stroke-width="10"
/>

三次贝塞尔曲线的延续

同上,只需要在继续通过S|s声明所有控制点即可,这时通过S|s声明的控制点只需要声明控制点2即可:

<!--三次贝塞尔曲线的延续 S|s,如 S 400 400 500 400
	上述的S 400 400 是 cpx2 cpy2 控制点2
	500 400 是 x y 结束点
-->
<path
  d="
    M
    100 400
    C
    200 400
    200 200
    300 200
    S
    400 400
    500 400

    600 200
    700 200
		"
  fill="none"
  stroke="#000"
  stroke-width="10"
/>

图形容器

上述的路径属性都用到了一些相同的属性,如fill stroke等,每个path标签内都写这些重复的属性,显然是不够优雅的。并且,如果项目很大,这么多类似的path标签,写着写着我们就不知道哪个path标签对应什么功能了,不便于我们管理。

所以D3提供了图形容器g,用来统一管理处理同一对象或者同一任务的图形,并管理公共属性。

<g fill="none" stroke="#000">
  <path
    d="
    M
    100 400
    L
    200 400
    200 200
    300 200

    400 400
    500 400
    "
    stroke-width="2"
    stroke-dasharray="3"
  />
  <path
    d="
    M
    100 400
    C
    200 400
    200 200
    300 200
    S
    400 400
    500 400

    600 200
    700 200
    "
    stroke-width="10"
  />
</g>

设置SVG的样式

其实在上面绘制图形的时候已经通过fill stroke 等属性控制SVG样式了,现在我们以控制SVG图形的颜色为例,来看看详细一下如何设置SVG样式。

着色按照着色区域可以分为“填充区域”和“描边区域”

  • fill 属性,用来控制填充区域颜色,默认为black,负责填充图形内部的颜色
  • stroke属性,用来控制描边区域,默认为none,负责描边,填充图形轮廓的颜色
<circle cx="300" cy="300" r="200" fill="rgba(255,100,0,0.5)"/>
<circle cx="300"
        cy="300"
        r="200"
        fill="none"
        stroke="#00acec"/>

上面都是只填充了纯色,事实上可以使用多种不同着色方式:

  • 纯色
  • 渐变色
    • 线性渐变
    • 径向渐变
  • 纹理

渐变 - 线性渐变

通过linearGradient 标签声明线性渐变参数,相关API可详见MDN

在标签内通过gradientTransform 属性声明渐变转换的参数:

  1. rotate(a) 控制线性旋转的角度
  2. translate(x,y) 控制x、y方向上的移动
  3. scale(sx,sy) 控制x、y方向上的缩放

linearGradient 内通过 stop 子标签声明填充的节点颜色。

  1. offset 偏移量[0,1]
  2. stop-color 颜色

最后在图形标签内,通过 fill="url(#id)" 调用渐变填充颜色

<defs>
  <linearGradient
    id="gr"
    gradientTransform="rotate(30)"
  >
    <stop offset="0" stop-color="#fff" />
    <stop offset="0.5" stop-color="#00acec" />
    <stop offset="1" stop-color="#fff" />
  </linearGradient>
</defs>

<circle cx="300" cy="300" r="200" fill="url(#gr)"/>

渐变 - 径向填充

通过 radialGradient 标签声明径向填充,其余完全一致。

<defs>
  <linearGradient
    id="gr"
    gradientTransform="rotate(30)"
  >
    <stop offset="0" stop-color="#fff" />
    <stop offset="0.5" stop-color="#00acec" />
    <stop offset="1" stop-color="#fff" />
  </linearGradient>
</defs>
<circle cx="300" cy="300" r="200" fill="url(#gr)" />

纹理着色

可以通过绘制好的SVG图形或者插入的图片,来对图形内部进行纹理填充。

  • pattern 纹理着色

    • id
    • viewBox 视图框
    • width height 纹理元素的尺寸,建议使用百分百
  • polygon 多边形

    • points 点位,如 0,0 20,50 0,100 50,80 100,100 80,50 100,0 50,20
  • image 插入图片

    • href 图片地址
    • x|y 位置
    • widht|height 宽高
<polygon points="0,0 20,50 0,100 50,80 100,100 80,50 100,0 50,20" />
<defs>
  <pattern width="30%" height="30%" viewBox="0 0 100 100" id="pt">
    <!--<polygon points="0,0 20,50 0,100 50,80 100,100 80,50 100,0 50,20"/>-->
    <image href="./images/rose.jpg" width="100" height="100" />
  </pattern>
</defs>
<circle cx="300" cy="300" r="200" fill="url(#pt)" />

利用D3绘图

上面已经基本搞通了SVG是如何绘制图形以及如何设置样式的了,那么就通过D3操作DOM元素和SVG,来绘制一个图形吧。

step1 通过D3的 select 方法获取到main容器。

const main=d3.select('#main')

step2 在main容器中建立svg跟容器,并设置其相关属性

const svg=main.append('svg')
        .attr('version',1.2)
        .attr('xmlns','http://www.w3.org/2000/svg')
        .attr('width','100%')
        .attr('height','100%')
        .attr('viewBox','-400 -400 800 800')

step3 在svg容器中建立svg图形,并设置相关属性

svg.append('rect')
	.attr('x',-200)
	.attr('y',-100)
	.attr('width',400)
	.attr('height',200)
	.attr('fill','red')

然后以此类推在svg容器里面添加各种各样的图形,可是我们会发现,这样写要写很多的appen方法,很多很多的attr方法,很不优雅。回过头一看,发现:不管是在main里建立svg容器,还是在svg容器里面添加图形,整体的调用逻辑都是一样的。因此我们可以把这一系列的调用逻辑封装成一个方法。

step4 封装Render方法,该方法以真实DOM节点作为参数传入,然后返回一个真正的处理函数。该返回函数以需要生成的元素节点和节点属性项为参数传入,并返回真实的元素节点。

function Render(dom) { // 真实的DOM节点作为参数传入,并直接返回一个处理函数
   return function(shape,option){ // 想要生成的DOM节点、节点配置作为参数传入
       const obj=dom.append(shape)
       for(let [key,val] of Object.entries(option)){ 
         	// 遍历所配置项,并赋值给DOM节点对象
           obj.attr(key,val)
       }
       return obj 
   }
}

这样,我们就可以直接优雅的通过Render方法添加DOM节点了:

/*获取main 容器*/
const main = d3.select("#main");
function Render(dom) {
  return function (shape, option) {
    const obj = dom.append(shape);
    for (let [key, val] of Object.entries(option)) {
      obj.attr(key, val);
    }
    return obj;
  };
}

const svg = Render(main)("svg", {
  version: 1.2,
  xmlns: "http://www.w3.org/2000/svg",
  width: "100%",
  height: "100%",
  viewBox: "-400 -400 800 800",
});
// 获取到创建DOM节点的方法
const draw = Render(svg);
/*绘制图形……*/
draw("rect", {
  x: -200,
  y: 0,
  width: 400,
  height: 200,
  fill: "red",
});
draw("rect", {
  x: -200,
  y: 0,
  width: 400,
  height: 200,
  fill: "none",
  stroke: "#000",
  "stroke-width": 40,
});
draw("rect", {
  x: -200,
  y: 50,
  width: 400,
  height: 60,
  fill: "antiquewhite",
});
draw("path", {
  d: `
            M
            -100 150
            L
            100 150
        `,
  fill: "none",
  stroke: "#000",
  "stroke-width": 40,
});
draw("circle", {
  cx: -100,
  cy: 80,
  r: 20,
  fill: "red",
});
draw("path", {
  d: `
            M
            80 90
            A
            20 20
            0
            0
            1
            120 90
        `,
  fill: "red",
});
draw("path", {
  d: `
            M
            -200 -200
            C
            -100 -200
            -100 0
            0 0
            S
            100 -200
            200 -200
        `,
  fill: "none",
  stroke: "#000",
  "stroke-width": 40,
});

以上,便是通过D3操作DOM实现SVG绘图的实例。