D3库实践笔记之SVG预定义元素与使用 |可视化系列34

在第一篇笔记里简单介绍了SVG,SVG(Scalable Vector Graphics,可伸缩矢量图形)是HTML里的一种标签,也可以独立保存为.svg的文件,SVG 基于 XML 格式定义图像,通常使用SVG作为d3绘图的容器。

个人认为熟悉SVG中各种图形元素的属性对于学习D3是磨刀不误砍柴工。而且svg的应用场景广泛,不止于D3,也方便自己理解其他库或者软件生成的svg图形文档。

SVG元素的基础写法:

1
2
<svg width="500" height="400" version="1.1">
</svg>

单纯以上语句只是声明了一个500x400的空白画布,还需要添加形状,

SVG预定义形状有6种基本形状,分别为rect(矩形)、circle(圆形)、ellipse(椭圆)、line(线段)、polyline(折线)、polygon(多边形)以及功能强大的path(路径),在SVG里也可以添加text(文本)元素。一个包含矩形的SVG标签如下:

1
2
3
<svg width="500" height="400" version="1.1">
<rect width="300" height="100" style="fill:#1EAFAE;stroke-width:1;stroke:rgb(0,0,0)"/>
</svg>

可以将其保存为后缀为.svg的独立文件,也可以在html文档里使用。

SVG 坐标系统原点位于画布的左上角,也就是说增大 x 的值,图形是向右移动;增大 y 值,图形会向下移动。

SVG 坐标系统

以下对SVG预定义形状进行枚举。

矩形

矩形的标签为<rect>

<rect x="50" y="10" width="60" height="40"/>声明一个矩形。

svg-rect

在绘制柱状图、条形图、直方图、堆叠柱状图等统计图时,广泛地用到了<rect>标签。

<rect>的属性有:

  • x:矩形左上角的x坐标,注意是左上角不是中心点的坐标;
  • y:矩形左上角的y坐标;
  • width:矩形宽度;
  • height:矩形高;
  • rx:缺省是正常矩形,否则是圆角矩形,圆角处椭圆在x方向的半径;
  • ry:圆角矩形的圆角在y方向的半径;

sytle用于指定矩形的样式,如填充色,透明度,边框;写作<rect style="fill:#1EAFAE; opacity:0.6">,这些在svg的其他图形中也适用,就不再重复。

  • fill:表示要填充的颜色,css颜色,常用的是3种,可以是blue这种名称,也可以是16进制颜色值#1EAFAE或者是rgb(30, 175, 174)
  • stroke:表示边框的颜色;
  • stroke-width:边框宽度;
  • opacity:透明度。数值在[0,1]之间,值越小越透明;

一个SVG矩形的例子:

1
2
3
4
<svg width="500" height="400" version="1.1">
<rect x="20" y="10" rx="30" ry="12" width="200" height="120"
style=" fill:#BA5C25; stroke:#1EAFAE" />
</svg>

渐变表示一种颜色平滑过渡到另一种颜色。SVG中有<linearGradient>线性渐变和<radialGradient>放射性渐变。渐变的标签写在<defs></defs>标签内,defs用于定义可重复利用的图形元素,在图形中通过id引用defs标签里定义的效果,达到重用的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<svg width="500" height="400" version="1.1">
<defs>
<linearGradient id="grad2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#FFFAF0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#BA5C25;stop-opacity:1" />
</linearGradient>
</defs>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#FFFAF0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1EAFAE;stop-opacity:1" />
</linearGradient>
</defs>
<rect x="20" y="10" width="400" height="100" fill="url(#grad1)" />
<text x="100" y="145" >水平渐变与垂直方向渐变</text>
<rect x="20" y="180" width="400" height="100" fill="url(#grad2)" />
</svg>

svg-filter-linear-gradien

linearGradient标签里,x1、y1、x2、y2定义渐变的方向,x1="0%" y1="0%" x2="100%" y2="0%"是水平渐变,改为x2="0%" y2="100%"则为垂直渐变,offset定义渐变开始的位置,stop-color定义边界位置的颜色。

滤镜<filter>能使图形更具有艺术效果,对源图形使用滤镜能修改其显示结果。滤镜也是写在<defs></defs>标签内。例如以下代码定义了一个高斯模糊的滤镜,并应用到矩形rect上。

1
2
3
4
5
6
7
8
9
10
<svg width="500" height="400" version="1.1">
<defs>
<filter id="gaussianBlur">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" />
</filter>
</defs>
<rect x="300" y="100" width="150" height="100"
filter="url(#gaussianBlur)"
style=" fill:#BA5C25; stroke:#1EAFAE; stroke-width:4"/>
</svg>

svg-filter-gaussian

feGaussianBlur标签定义了一个高斯模糊的滤镜,in是使用滤镜的对象,此处是源图形用SourceGraphic表示,
stdDeviation是高斯模糊唯一的参数,数值越大,模糊程度越高。在矩形中通过 filter = "url(#gaussianBlur)" 代码使用我们创建的滤镜。

滤镜的种类很多,例如feMorphology、feGaussianBlur、feFlood等等,还有定义光源的滤镜feDistantLight、fePointLight、feSpotLight,都是以fe开头的。feOffset用于生成图形的阴影效果,结合高斯模糊可以构建图形的投射阴影,增强表现力。

圆形

<circle>标签绘制圆形,圆形的参数是3个:

svg-circle

  • cx:圆心的x坐标,可以记为circle-x;
  • cy:圆心的y坐标;
  • r:圆的半径;

椭圆

ellipse表示椭圆,椭圆的参数与圆形类似,但半径分为了水平半径和垂直半径;

svg-circle-ellipse

  • cx:椭圆圆心的x坐标;
  • cy:圆心的y坐标;
  • rx:椭圆的水平半径;
  • ry:椭圆的垂直半径;

圆和椭圆的示例:

1
2
3
4
5
<svg width="500" height="400" version="1.1">
<circle cx="120" y="80" r="70"/>
<ellipse cx="330" y="80" rx="50" ry="76"
style=" fill:#BA5C25; stroke:#1EAFAE" />
</svg>

<svg>标签里有多个形状时,会按照顺序渲染,也就是写在后面的就覆盖到之前的图形上。利用覆盖和叠加可以形成很多有趣实用的效果,例如点的涟漪效果等。

线段

<line>标签绘制一条线,用D3绘制柱状图时,坐标轴就需要用到line标签,各种统计图表普遍用到了line在坐标轴等元素中;

线段<line>的参数有4个:

svg-line

  • x1:起点的x坐标;
  • y1:起点的y坐标;
  • x2:终点的x坐标;
  • y2:终点的y坐标;

一个线段的例子:

1
2
3
<svg width="500" height="400" version="1.1">
<line x1="30" y1="16" x2="200" y2="120" style="stroke:#1EAFAE; stroke-width:3" />
</svg>

折线和多边形

折线<polyline>和多边形<polygon>的参数是一样的,因此放一起对比着看,它们都是只有一个points参数,表示一系列的点坐标,效果的不同之处在于多边形会将终点和起点连接起来。

1
2
3
4
5
6
<svg width="500" height="400" version="1.1">
<polygon points="130,150 190,85 30,20 50,75 50,150 "
style=" fill:#BA5C25; stroke:#1EAFAE; stroke-width:3; opacity:0.6"/>
<polyline points="130,150 190,85 30,20 50,75 50,150 "
style=" fill:#BA5C25; stroke:#1EAFAE; stroke-width:3; opacity:0.6" transform="translate(180,0)"/>
</svg>

svg-poly

折线通常用来绘制折线图,多边形可以用来绘制面积图、区间注释等等。上面代码中transform="translate(180,0)"将polyline折线的坐标原点移动到svg元素的[180,0]处,效果就是polyline整体向右边平移了180像素。

路径

<path>标签的功能最丰富也最强大,表示一段路径,前面列举的图形都可以用路径画出来。与折线类似,path也是通过给定的一系列点坐标来绘制。在D3中绘制地图时,会经常用到此标签,读取geojson数据,然后绘制一段段路径。如果使用过Python里的turtle库,就会对SVG绘制路径的过程感到轻车熟路,当然没用过也不难去理解绘制的过程。<path>的写法是:给出一个坐标点,在坐标点前面添加一个英文字母,用于标识如何运动到此坐标点。英文字母按照功能可分为五类。大写表示绝对坐标,小写表示相对坐标。

  • 移动类: M即moveto,表示将画笔移动到指定坐标,不绘制移动的路径;
  • 直线类:L,画直线到指定坐标(lineto);H,画水平直线到指定坐标(horizontal lineto);V,画垂直直线到指定坐标(vertical lineto);
  • 曲线类:C,curveto,画三次贝塞尔曲线经两个指定控制点到达终点坐标;S,smooth curveto,与前一条三次贝塞尔曲线相连,第一个控制点为前一条曲线第二个控制点的对称点,只需输入第二个控制点和终点,即可绘制一个三次贝塞尔曲线;Q,quadratic Bézier curveto,画二次贝塞尔曲线经一个指定控制点到达终点坐标;T,Shorthand/smooth quadratic Bézier curveto,与前一条二次贝塞尔曲线相连,控制点为前一条二次贝塞尔曲线控制点的对称点,只需输入终点,即可绘制一个二次贝塞尔曲线;
  • 弧线类:A,elliptical arc,画椭圆曲线到指定坐标;
  • 闭合类:Z,closepath,绘制一条直线连接终点和起点,用来封闭图形;

一个路径的例子如下:

1
2
3
4
<svg width="500" height="400" version="1.1">
<path d="M 100 350 q 150 -300 300 0"
style="fill:#fff; stroke:#1EAFAE; stroke-width:3"/>
</svg>

svg-path

文本

在SVG中可以使用<text>标签绘制文字,其属性有:

  • x:文字位置的x坐标。
  • y:文字位置的y坐标。
  • dx:相对于当前位置在x方向上平移的距离(值为正则往右,负则往左);
  • dy:相对于当前位置在y方向上平移的距离(值为正则往下,负则往上)。
  • textLength:文字的显示长度(不足则拉长,足则压缩)
  • rotate:旋转角度(顺时针为正,逆时针为负)。

因为文本内容是写在标签外的,因此用的是成对显示的标签,不是写<text x="20" y="30"/>而是写``内容`。

用rotate参数的旋转是每个字符都旋转,因此要整体旋转还是用transform里的rotete。两者对比效果如下:

1
2
3
4
5
<svg width="500" height="400" version="1.1">
<text x="10" y="20" rotate="40" style="fill:#1EAFAE" >蛰虫始航</text>
<text x="10" y="55" rotate="40" style="fill:#1EAFAE" >lynsdata</text>
<text x="30" y="80" transform="rotate(40 20,40)" style="fill:#1EAFAE">蛰虫始航</text>
</svg>

svg-text-rotate

文本内容可以用到html其他标签,用<tspan>对文本里的某一部分文字单独定义样式,示例如下:

1
2
3
<text x="30" y="50" dx="-5" dy="5" textLength="100" >
lynsdata <tspan fill="#1EAFAE">D3-svg</tspan>
</text>

标记

标记<marker>是SVG中一个重要的概念,增强了svg基本形状的表达力,标记贴附于path、line、polyline、
polygon元素上。和滤镜filter一样,标记marker也是定义在<defs></defs>中,defs用于定义可重复利用的图形元素。一个典型的应用是基于路径排列文本。

1
2
3
4
5
6
7
8
<svg width="500" height="400" version="1.1">
<defs>
<path id="txtpath" d="M70,30 a1,1 0 0,0 120,0" />
</defs>
<text x="10" y="100" style="fill:#1EAFAE">
<textPath xlink:href="#txtpath">蛰虫始航 D3-svg-text</textPath>
</text>
</svg>

还有就是给线段添加箭头:

1
2
3
4
5
6
7
8
9
10
11
12
<svg width="500" height="400" version="1.1">
<defs>
<marker id="arrow" markerUnits="strokeWidth" markerWidth="12" markerHeight="12"
viewBox="0 0 12 12" refX="6" refY="6" orient="auto">
<path d="M2,2 L10,6 L2,10 L6,6 L2,2" style="fill: #000;" />
</marker>
</defs>
<line x1="60" y1="160" x2="260" y2="80" marker-end="url(#arrow)" style="stroke:#1EAFAE; stroke-width:2"/>
<path d="M200,210 T160,165 T240,120 T370,100 T420,150"
marker-mid="url(#arrow)" marker-end="url(#arrow)"
style="stroke:#1EAFAE; stroke-width:2; fill:#fff"/>
</svg>

效果如下图,在<defs>里定义了id为arrow的标记,在path里通过marker-end="url(#arrow)" 调用。标记的位置属性有marker-start、marker-mid和marker-end。

svg-marker

总结

SVG是HTML里的一种标签,D3的可视化功能大部分通过操纵SVG里的图形元素达成,虽然D3可以直接操作div或其他原生HTML元素来绘图,但操纵SVG更简便,也更可靠,SVG生成的图形效果更一致,对各种浏览器都兼容,而操纵div则不然,可能在某些版本的浏览器里就解析出期待之外的效果。而且SVG很强大,绘图速度更快,因此不难选择。SVG的标签和规范有很多内容,还能深入学习的有各种滤镜、放射性渐变、强大的路径<path>标签等,文中的内容对于D3可视化基本够用,后续再深入。

参考资料