【D3.js in Action 3 精译】1.2.2 可缩放矢量图形(三)

当前内容所在位置

  • 第一部分 D3.js 基础知识
    • 第一章 D3.js 简介
      • 1.1 何为 D3.js?
      • 1.2 D3 生态系统------入门须知
        • 1.2.1 HTML 与 DOM
        • 1.2.2 SVG - 可缩放矢量图形 ✔️
          • 第一部分
          • 第二部分
          • 【第三部分】✔️
        • 1.2.3 Canvas 与 WebGL(精译中 ⏳)
        • 1.2.4 CSS(待翻译)
        • 1.2.5 JavaScript(待翻译)
        • 1.2.6 Node 与 JavaScript 框架(待翻译)
        • 1.2.7 Observable 记事本(待翻译)
          译注

上接 1.2.2 小节(二)

...... 关于 SVG 描边(strokes)的位置

当对齐可视化项目中的图形时,需要特别注意:SVG 图形绘制出的描边是在内外边界上平均展布的。如下图所示,已知一个 width 属性为 40px 的矩形,令 stroke-width 的值为 1,则在视觉效果上会在矩形的左右两边各增加宽度为 0.5px 的描边(而不是下意识地以为的那样在各边均增加 1px),最终实际的总宽度为 41px;若令 stroke-width 的值为 2,则左右两边各增加 1px,以此类推。

描边宽度 stroke-width 对 SVG 图形实际宽度的影响

(......关于 SVG 描边(strokes)的位置)

5 圆与椭圆

在数据可视化中常常会用到圆形。它们天然吸引眼球,使可视化效果看起来更友好、更有趣。SVG 的圆是通过 <circle /> 元素绘制的,其必选属性包括圆心位置(cx, cy)与半径(r),如图 1.17 所示。圆的半径是从圆心到圆周上任意一点绘制的线的长度。将以下圆形添加到示例中,令圆心位于 (530, 80) 处,且半径为 50px

html 复制代码
<circle cx="530" cy="80" r="50" />


图 1.17 在 SVG 容器的坐标系中定位圆和椭圆并调整其大小

还可以为圆设置填充(fill)与描边(stroke)属性(attribute)。要生成图 1.17 中的圆,令填充色为透明(transparent)、描边宽度为 3px、描边颜色为 #81c21c(译注:这里应该参考的是最终效果图 1.8,图 1.17 只是示意图)。

同理,<ellipse /> 元素必须指定圆心坐标(cx, cy),但不像圆有固定半径,椭圆的半径会变化,使其呈扁平状------这是通过声明椭圆的水平半径(rx)和垂直半径(ry)来实现的。将如下代码片段添加到示例中,将在圆的下方绘制一个椭圆,其水平半径为 50px、垂直半径为 30px

html 复制代码
<ellipse cx="530" cy="205" rx="50" ry="30" />
6 路径

SVG 的路径(path)元素是目前为止所有 SVG 元素中最灵活的一种。在 D3 中,路径被广泛用于绘制几乎所有的复杂形状和曲线。这些形状和曲线是无法用目前讨论过的图形基元(线、矩形、圆和椭圆)来表示的。

路径元素通过声明 d 属性(这里的"d"代表"draw",即"绘制")来指示浏览器进行绘制。d 属性包含一系列命令:从开始绘制路径的位置,到使用的一系列曲线类型,一直到确定该路径是否为一个封闭图形为止。例如,在图形画廊示例中添加以下路径元素:

html 复制代码
<path d="M680 150 C 710 80, 725 80, 755 150 S 810 220, 840 150" fill="none" stroke="#773b9a" stroke-width="3" />

如图 1.18 所示, d 属性以 M680 150 开头,表示"移动(M)到坐标(680,150)处"。接着从起点坐标(680 150)到 d 属性最后一个坐标(840 150)所指定的终点,绘制一条三阶贝塞尔曲线(cubic Bézier curve)。三阶贝塞尔曲线还需要设置控制点来定义曲线的陡峭程度和弯曲方向。这些控制点从字母 C(这里的"C"代表"cubic curve",即"三次方曲线")之后开始,直到字母 S(这里的"S"代表"stop",即"停止")之后结束。


图 1.18 一条简单的 SVG 路径及其控制点

注意

如需深入了解 SVG 路径,请参阅 MDN 的教程:https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

手动设置 d 属性仅适用于简单的图形,随着图形复杂度增加,纯手动编写将会变得非常繁琐。所幸 D3 提供了强大的图形生成工具来专门处理 d 属性的计算。本书第 4 章将深入讨论。

路径还有一个需要牢记的重要知识点:无论路径元素是否闭合,只要其 fill 属性值不为 nonetransparent,浏览器就会默认填充为黑色,如本例所示。

7 文本

内联 SVG 图形的最大优势之一在于支持屏幕阅读器访问其包含的文本内容。这对于可访问性而言是一大优势。由于数据可视化通常包含多个标签,因此有必要了解如何使用 <text> 元素来操作 SVG 文本。通过往示例中添加标签可以了解 SVG 文本的基本工作原理。

目前为止我们讨论的 SVG 图形都使用了自闭合标签(如 <line /><rect /><path /> ......)。在使用 SVG 的 <text> 元素时,需要同时使用开标签和闭标签,并将待显示文本放置在这两个标签之间。例如,添加一个内容为"line"的文本元素:

html 复制代码
<text>line</text>

保存并重新加载页面。您可能以为文本会出现在 SVG 容器的左上角,但却看不到......这是为什么呢?默认情况下,SVG 文本的位置是参照其基线计算得来的,通过 dominant-baseline 属性控制。如果文本基线的坐标是 (0, 0),如图 1.19 所示,实际文本最终将位于 SVG 容器之外。由于任何位于 SVG 容器之外的元素都是不可见的,所以看不到文本。


图 1.19 在 SVG 容器外定位文本

在使用 SVG 文本元素时,另一个需要考虑的问题是文本如何流动(flow)。常规 HTML 元素在页面上的位置是按照控制内容流动的特定规则来确定的。如果在页面中插入大量 <div> 元素,它们会自然地堆叠在一起,其内容也会回流(reflow),不会超出容器的范围;而 SVG 文本根本不会流动,开发人员必须单独设置每个文本元素的 xy 属性。例如通过它们将文本放在坐标点 (60, 260) 处,标签"line"才会出现在图形画廊示例中 SVG 直线段的下方:

html 复制代码
<text x="60" y="260">line</text>

作为练习,再创建一个新的文本元素,并将标签"rect"置于矩形和正方形的下方。

至此,我们通过 xy 属性声明了文本元素 左下角 的位置。怎样设置文本中点位置呢?通过属性 text-anchor 来实现:令其值为 middle 即可,如图 1.20 所示。再比如,利用该属性还可以将圆形的文本标签居中。

html 复制代码
<text x="530" y="155" text-anchor="middle">circle</text>

图 1.20 text-anchor 属性对 SVG 文本对齐方式的影响。默认值为 start;根据其中间位置对齐,值为 middle;设置末尾对齐,值为 end

最后,分别为椭圆和路径元素各添加一个文本标签。SVG 文本默认为黑色,可以通过 fill 属性(attribute)进行更改。

8 分组元素

本小节要讨论的最后一个 SVG 元素为分组元素(group element)。分组元素(即 <g> 元素)与之前讨论过的其他 SVG 元素有所不同。它既没有视觉上的图形表示,也不存在具有边界的空间;相反,它是 SVG 各元素的逻辑分组,广泛应用于由多个图形和标签组成的可视化效果中。

如果希望正方形标签和"rect"文本标签一同显示,并在 SVG 容器中整体平移,可以如以下示例将它们放到一个 <g> 元素中。注意,<rect> 元素的左上角已变为了坐标原点 (0, 0)<text> 位于 (0, 85) 处,保持在 <rect> 的下方:

html 复制代码
<g>
  <rect x="0" y="0" width="60" height="60" />
  <text x="0" y="85">rect</text>
</g>

此时,包含正方形及其文本标签的组出现在了 SVG 容器的左上角。我们可以在 SVG 容器中任意移动这个组及其包含的所有元素,始终保持正方形与文本标签之间的对齐方式不变。

使用 transform 属性可以在 SVG 容器中平移该分组。transform 属性比之前学过的其他属性略显复杂,但与 CSS 中 transform 属性的用法相同。它接受一个或一系列变换(transformation,如平移、旋转、缩放等)作为属性值。平移一个分组使用 translate(x, y) 变换。如果要将 <rect><text> 元素移回原位,需要令分组元素右移 260px 并下移 175px,即令 <g> 元素 transform 属性的值为 transform="translate(260,175)"

html 复制代码
<g transform="translate(260,175)">
  <rect x="0" y="0" width="60" height="60" />
  <text x="0" y="85">rect</text>
</g>

<g> 元素的另一个特点,是其子元素可以继承它的样式属性(styling attributes)。下面举例说明。由于前面"rect"文本标签已经和正方形归为同一组,故先将其余文本元签统一放到另一个 <g> 元素内:

html 复制代码
<g>
  <text x="60" y="260">line</text>
  <text x="530" y="155" style="text-anchor:middle">circle</text>
  <text x="530" y="260" style="text-anchor:middle">ellipse</text>
  <text x="730" y="260">path</text>
</g>

如果令 <g>fill 属性值为 #636466,则里面的每个 <text> 元素都将继承相同的颜色;同理,如果添加的是 font-familyfont-size 属性,同一组内的文本元素也将继承这些样式属性(properties)。

html 复制代码
<g fill="#636466" style="font-size:16px; font-family:monospace">
  <text x="60" y="260">line</text>
  <text x="530" y="155" style="text-anchor:middle">circle</text>
  <text x="530" y="260" style="text-anchor:middle">ellipse</text>
  <text x="730" y="260">path</text>
</g>

最后一次再重新加载页面,注意观察分组内的标签继承的颜色和字体效果;再看看分组外的标签是否保持原来的外观不变。像这样在分组元素上应用共享样式的做法非常方便,可以帮助您在工作中遵循 DRY(即 Don't Repeat Yourself,不要重复自己)原则。需要更新属性时,在分组容器上操作也会很简单。

恭喜您完成了本书的第一个练习!您可以在代码清单 1.2 及源码文件的 end 文件夹找到 SVG 图形画廊的完整代码。在构建首个 D3 项目时,该练习可以作为参考。

代码清单 1.2 SVG 图形画廊示例的完整 HTML 代码

html 复制代码
<!DOCTYPE html>
<html>
<head> <!-- [略] --> </head>
<body>
  <div style="width:100%; max-width:1200px; margin:0 auto;">
    <svg viewBox="0 0 900 300" style="border:1px solid black;">
 
      <line x1="50" y1="45" x2="140" y2="225" stroke="black" />
 
      <rect x="260" y="25" width="120" height="60" fill="#6ba5d7" />
      <rect x="260" y="100" width="120" height="60" rx="20" ry="20" fill="#6ba5d7" /> 
      <g transform="translate(260, 175)">
        <rect x="0" y="0" width="60" height="60" fill="transparent"  stroke="#6ba5d7" />
        <text x="0" y="85">rect</text>
      </g>
 
      <circle cx="530" cy="80" r="50" fill="none" stroke="#81c21c" stroke- width="3" />
      <ellipse cx="530" cy="205" rx="50" ry="30" fill="#81c21c" />
 
      <path d="M680 150 C 710 80, 725 80, 755 150 S 810 220, 840 150" fill="none" stroke="#773b9a" stroke-width="3" />
 
      <g fill="#636466" style="font-size:16px; font-family:monospace">
        <text x="60" y="260">line</text>
        <text x="530" y="155" style="text-anchor:middle">circle</text>
        <text x="530" y="260" style="text-anchor:middle">ellipse</text>
        <text x="730" y="260">path</text>
      </g>
 
    </svg>
  </div>
</body>
</html>

强化练习:创建 SVG 图形

现在该您小试牛刀了------创建如下图所示的 SVG 图形。您可以在源码文件夹 02_SVG_exercise/start 中进行操作。具体要求如下:

  • 创建一个响应式 SVG 容器,宽高均为 400px(屏幕上留足空间)。
  • 绘制一个宽高均为 200px 的正方形。将其置于 SVG 容器的中心位置,并指定透明色填充及 5px 的黑色描边。
  • 在 SVG 容器的中心添加一个半径为 100px 的圆。将其 fill 属性设置为 CSS 颜色名称"plum"。
  • 绘制两条描边为 5px 的黑色对角线:一条从正方形的左上角画到右下角;另一条从正方形的右上角画到左下角。
  • 在正方形上方添加文本"SVG is awesome!"字样,并将其置于 SVG 容器的中心。其他文本样式:字号 18px、无衬线字体。


强烈建议通过练习该 SVG 图形来强化本小节介绍的知识点

该练手项目的完整参考代码,请参阅 附录 DD.1.1 小节或随书源码文件中的 02_SVG_exercise/end 文件夹中。建议尽量独立完成。

(SVG 基础知识译完了,内容有点多,再自己做个小结吧)

本节 SVG 基础知识要点梳理
  • 圆(circle)与椭圆(ellipse):都具有圆心坐标(cx / cy)和半径,只是椭圆的半径有两个(rxry),圆只有一个(r);圆其实是椭圆的 特殊情况
  • 路径(path)是 SVG 图形中最复杂的几何基元,其属性 d 包含一系列命令,可借助 D3 的生成工具代替纯手工赋值;
  • 文本(text)------
    • 在 SVG 中不随常规 HTML 内容流动;
    • 其位置是基于文本基线计算得来的,需要手动设置(xy);
    • 文本对齐通过 text-anchor 属性调整,可选值:start | middle | end
  • 分组(g)元素------
    • 没有视觉图形表示,也不占据空间;
    • 放入同一分组的元素可整体移动;
    • 设置在 g 上的样式可被子元素继承:如 fillfont-sizefont-family 等;
相关推荐
西猫雷婶3 分钟前
python学opencv|读取图像(二十一)使用cv2.circle()绘制圆形进阶
开发语言·python·opencv
kiiila4 分钟前
【Qt】对象树(生命周期管理)和字符集(cout打印乱码问题)
开发语言·qt
滚雪球~20 分钟前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语22 分钟前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport23 分钟前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg25 分钟前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
小_太_阳29 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
胡西风_foxww32 分钟前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
直裾32 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
m0_7482548833 分钟前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui