当前位置: 首页 > news >正文

【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 图形实际宽度的影响描边宽度 stroke-width 对 SVG 图形实际宽度的影响

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

5 圆与椭圆

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

<circle cx="530" cy="80" r="50" />

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

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

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

<ellipse cx="530" cy="205" rx="50" ry="30" />
6 路径

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

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

<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 路径及其控制点
图 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”的文本元素:

<text>line</text>

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

图 1.19 在 SVG 容器外定位文本
图 1.19 在 SVG 容器外定位文本

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

<text x="60" y="260">line</text>

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

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

<text x="530" y="155" text-anchor="middle">circle</text>

图 1.20图 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> 的下方:

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

<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> 元素内:

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

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

<!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 强化练习项目效果图
强烈建议通过练习该 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 等;

相关文章:

  • GCP FrontendConfig 详解:优化您的云负载均衡
  • 自然语言处理-BERT处理框架-transformer
  • centos 7系统升级内核(ELRepo仓库)、小版本升级、自编译内核
  • Element-plus点击当前行之后获取数据显示跟随行数据
  • Java将list数组中重复的对象进行去重
  • java反射和注解
  • 基于Spring Boot与Vue的智能房产匹配平台+文档
  • Log4j日志框架讲解(全面,详细)
  • 上帝之眼(BEVSee):多相机间无需标定,将各自目标统一到同一坐标系下(代码开源,提供数据集)
  • C++编程(五)单例模式 友元
  • 如何寻找一个领域的顶级会议,并且判断这个会议的影响力?
  • SpringBoot学习04-[定制SpringMVC]
  • 在Qt中,直接include <moc_xxxxx.cpp> 为什么不会出现符号冲突的错误?
  • UTONMOS:探索未来区块链与元宇宙的游戏奇妙融合
  • linux的常用系统维护命令
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • Angular Elements 及其运作原理
  • CentOS7 安装JDK
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • gops —— Go 程序诊断分析工具
  • java概述
  • Kibana配置logstash,报表一体化
  • mongodb--安装和初步使用教程
  • Python中eval与exec的使用及区别
  • rc-form之最单纯情况
  • select2 取值 遍历 设置默认值
  • 记一次和乔布斯合作最难忘的经历
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 马上搞懂 GeoJSON
  • 数据可视化之 Sankey 桑基图的实现
  • 协程
  • 因为阿里,他们成了“杭漂”
  • 数据可视化之下发图实践
  • 数据库巡检项
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • #QT(一种朴素的计算器实现方法)
  • %@ page import=%的用法
  • (4.10~4.16)
  • (7)svelte 教程: Props(属性)
  • (9)STL算法之逆转旋转
  • (Note)C++中的继承方式
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (多级缓存)缓存同步
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (篇九)MySQL常用内置函数
  • (小白学Java)Java简介和基本配置
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)Linq学习笔记
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .net 使用ajax控件后如何调用前端脚本
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .Net6 Api Swagger配置