Web 可视化基础
SVG
概述
SVG 与 HTML 一样是一种 XML 语言,用于绘制矢量图,与 HTML 一样,SVG 也提供了一些元素,同时支持使用 JavaScript 进行交互。
创建一个SVG
文件以 <svg></svg>
元素开始和结尾的 .svg
文件,其他 SVG
元素都位于 <svg></svg>
元素内,如下:
<svg version="1.1"
baseProfile="full"
width="300" height="200"
xmlns="http://www.w3.org/2000/svg">
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">hello world</text>
</svg>
<svg>
中 version
、baseProfile
表示 SVG
的版本。xmlns
是SVG
绑定的命名空间。
SVG
的渲染规则是后来居上,即越后面的元素越可见。
嵌入
SVG
可以嵌入其他内容,如 图片、XML 等。
-
嵌入图片:使用
<image>
元素嵌入图片。如:<image width="100" xlink:href="https://developer.mozilla.org/static/img/favicon144.png"/>
样式
可以在 SVG
元素中使用属性设置元素的样式,如:
<path d="M100 100 L 400 400" stroke=”black" />
SVG
元素的属性有些是可以使用 CSS
来设置的,SVG
规范将属性分为properties 和其他 attributes,其中 properties 可以使用 CSS
来设置。
使用 CSS
设置属性方法:
-
使用
style
属性直接插入到元素中,如:<path d="M100 100 L 400 400" style="stroke: black" />
-
使用
<defs>
和<style>
元素设置属性,如:<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1"> <defs> <style type="text/css"><![CDATA[ #app { stroke: black; } ]]></style> </defs> <path d="M100 100 L 400 400" id="app" /> </svg>
-
使用外部样式表,如:
<?xml version="1.0" standalone="no"?> <?xml-stylesheet type="text/css" href="style.css"?> <svg width="200" height="150" xmlns="http://www.w3.org/2000/svg" version="1.1"> <path d="M100 100 L 400 400" id="app" /> </svg>
基本形状
-
矩形:
<rect x = 矩形左上角的x位置 y = 矩形左上角的y位置 rx = 圆角的x方位的路径 ry = 圆角的y方位的路径 width = 矩形的宽度 height = 矩形的高度 ></rect>
-
圆型:
<circle r = 圆的半径 cx = 圆心的x位置 cy = 圆心的y位置 ></circle>
-
椭圆:
<ellipse rx = 椭圆的x半径 ry = 椭圆的y半径 cx = 椭圆中心的x位置 cy = 椭圆中心的y位置 ></ellipse>
-
线条:
<line x1 = 起点的x位置 x2 = 起点的y位置 x2 = 起点的x位置 y2 = 起点的y位置 ></line>
-
折线:
<polyline points = 点集数列。每个点包含两个数字,分别表示点的x、y坐标,数字之间用空格分隔,点与点之间用逗号分隔,如:10 0,11 10,12 13 ></polyline>
-
多边形:
<polygon points = 点集数列。点集数列。每个点包含两个数字,分别表示点的x、y坐标,数字之间用空格分隔,点与点之间用逗号分隔,如:10 0,11 10,12 13 > </polygon>
注:多边形在绘制完成时,会将起点和终点连接起来组成闭合图形。
-
路径:
<path d = [命令+参数]的序列。[命令]用英文字母表示,每一个[命令]都有大写和小写两种方式,大写表示采用绝对定位;小写表示采用相对定位(相对于上一个点的定位),例如[d = M 10 10 m 20 20]中[m]表是的点是从[M点]向[x轴]移动[20],向[y]轴移动[20],这里[m 20 20]转换为绝对定位是[M 30 30]。 > </path>
直线命令:
-
M
或m
:表示将画笔移动到指定点,M
有两个参数,分别表示点的x
、y
坐标,如:M x y
或m x y
。注意,它只表示将画笔移动到某点,并不会画图。 -
L
或l
:表示从之前点到当前点画一条线段,当前位置是L
所在的点,之前点是L
之前的点。L
有两个参数,分别表示点的x
、y
坐标,例如:L x y
或l x y
。示例:画两条线段
<path d="M10 10 M 30 30 L 40 40 M 50 50 L 60 60" stroke="black"/>
表示从点
(30,30)
到点(40, 40)
画一条线段,从点(50, 50)
到点(60, 60)
画一条线段,注意,因为L 40 40
最近的点是M 30 30
所以没用到M 10 10
。 -
H
或h
:绘制水平线,H
绘制水平线时,会使用之前点的y
坐标,所以只有一个参数,表示点的x
坐标,例如:H x
或h x
。示例:绘制一条从点
(50,50)
到(100,50)
的线段<path d="M10 10 M 50 50 H 100" stroke="black"/>
没有用到点
(10,10)
-
V
或v
:绘制垂直线,V
绘制垂直线时,会使用之前点的x
坐标,所以只有一个参数,表示点的y
坐标,例如:V x
或v x
。示例:绘制一条从点
(50,50)
到(50,100)
的线段<path d="M10 10 M 50 50 V 100" stroke="black"/>
-
Z
或z
:闭合命令,从当前点绘制一条直线到路径的起点,Z
没有参数,当前点值Z
之前的点。。示例:绘制一个从经过点
(10, 10)
、(20, 10)
、(15,15)
的三角形<path d="M10 10 L 20 10 L 15 15 Z" stroke="black"/>
曲线命令:
-
Q
或q
:二次贝塞尔曲线,从之前点根据控制点绘制一条到当前点的二次贝塞尔曲线,有两组参数,分别表示控制点和当前点,例如:Q x1 y1, x y
或q x1 y1, x y
,其中x1 y1
表示控制点,x y
表示当前点。示例:以点
(60,10)
作为控制点,绘制一条从点(10,10)
到(70,70)
的二次贝塞尔曲线<path d="M10 10 Q 60 10 70 70" stroke="black" fill="transparent"/>
-
C
或c
:三次贝塞尔曲线,从之前点根据两个控制点绘制一条到当前点的三次贝塞尔曲线,有三组参数,分别表示两个控制点和当前点,例如:C x1 y1, x2 y2, x y
或c x1 y1, x2 y2, x y
,其中x1 y1
、x2 y2
表示控制点,x y
表示当前点。示例:以点
(20,50)
、(20,-50)
作为控制点,绘制一条从点(10,10)
到(70,70)
的二次贝塞尔曲线<path d="M10 10 C 20 50 20 -50 70 70" stroke="black" fill="transparent"/>
-
S
或s
:三次贝塞尔曲线,从之前点根据两个控制点绘制一条到当前点的三次贝塞尔曲线,有两个参数,分别表示两个控制点中的一个和当前点(另有一个控制点会根据之前点计算),例如:S x1 y1, x y
或s x1 y1, x y
,其中x1 y1
表示控制点,x y
表示当前点。注,如果
S
跟在C
或S
命令后面,它的第一个控制点会被设定成前一个命令曲线的第二个控制点的中心对称点。如果S
前面没有C
或S
命令,那么当前点将作为第一个控制点。示例:略
-
A
或a
:弧形。根据椭圆的长半轴和短半轴,和在椭圆上的两点可以绘制出两种椭圆,每种椭圆根据两点又能画出两种弧形,所以根据椭圆的半轴和椭圆上的两点绘制的弧形是不唯一的,需要其他参数确定唯一的弧形。A
或a
的参数如下:A rx ry x-axis-rotation large-arc-flag sweep-flag x y a rx ry x-axis-rotation large-arc-flag sweep-flag x y
rx ry
表示x
、y
轴半径x-axis-rotation
表示x
轴的旋转角度。large-arc-flag
表示角度大小。sweep-flag
表示 弧线方向、x y
表示弧形的终点
-
渐变
SVG
中的渐变有线性渐变和径向渐变两种,为了复用渐变定义在 <defs>
标签内部,需要给渐变指定一个 id
方便元素引用。
线性渐变:沿直线改变称为线性渐变。
-
<linearGradient>
:声明线性渐变。 -
<stop>
:设置渐变单元。<stop offset = 偏移 stop-color = 当前颜色 stop-opacity = 当前透明度 > </stop>
-
一个线性渐变由
<linearGradient><linearGradient/>
元素,和它内部的若干<stop />
组成,如:绘制从 黑色 到 蓝色 再到 黄色 的渐变色,并作为矩形的填充色。<svg width="120" height="240" version="1.1" xmlns="http://www.w3.org/2000/svg"> <defs> <linearGradient id="ex"> <stop stop-color="black" offset="0%" /> <stop stop-color="blue" offset="50%" /> <stop stop-color="yellow" offset="100%" /> </linearGradient> </defs> <rect x="10" y="10" width="200" height="200" fill="url(#ex)" /> </svg>
注意,元素中使用
url()
来引用定义的渐变。
径向渐变:从一点开始发散称为径向渐变。
-
<radialGradient>
:声明径向渐变。<radialGradient cx、cy、r = 渐变结束时的圆环。cx、cy是圆环的中心点,r是圆环的半径。 fx、fy = 渐变的中心 ></radialGradient>
-
<stop>
:设置渐变单元,参数与线性渐变一致。
变形
<g>
:集合元素,将元素归为一个集合,为 <g>
设置的属性,将应用到集合内的所有元素上。
transform
:变形属性,为元素设置变形,它可以取如下值:
translate(x,y)
:平移,将元素平移到(x,y)
位置。rotate(num)
:旋转。skewX(num)
、skewY(num)
:斜切。scale(num, num)
:缩放。matrix()
:复杂变形,暂略。
裁剪
裁剪指移除元素的部分内容。<clipPath>
用来定义裁剪,应用裁剪的元素在渲染的时候,被裁剪遮住的部分才会渲染。
元素:<clipPath>
示例:定义一个矩形,并对矩形应用裁剪。
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<clipPath id="ex-clip">
<rect x="10" y="100" width="50" height="200" />
</clipPath>
</defs>
<rect x="10" y="10" width="50" height="200" clip-path="url(#ex-clip)" />
<rect x="70" y="10" width="50" height="200"/>
</svg>
遮罩
遮罩指给元素蒙上一层幕布。<mask>
用来定义遮罩。
元素:<mask>
示例:定义一个矩形,并为它加一层遮罩
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<mask id="ex-mask">
<rect x="10" y="100" width="50" height="200" fill="red" />
</mask >
</defs>
<rect x="10" y="10" width="50" height="200" fill="yellow" mask="url(#ex-mask)" />
<rect x="70" y="10" width="50" height="200"/>
</svg>
注意:没有被遮罩的部分好像变为了白色,不知道为什么?
文字
SVG
中有两种不同的文本模式,一种是写在图像中的文本,另一种是 SVG
字体。
文本:
-
元素:
<text>
属性:
x
、y
:在视口中的位置。 -
元素:
<tspan>
描述:作为
<text>
元素的子元素使用,用来表示一个文本段落。属性:
x
、y
:设置<tspan>
的绝对坐标,它会脱离默认的文本位置,可以是一个数列,如果是一个数列,则数列将一个一个应用到<tspan>
元素内的每一个字符上。dx
、dy
:设置<tspan>
的相对坐标,可以是一个数列,如果是一个数列,则数列将一个一个应用到<tspan>
元素内的每一个字符上。rotate
:将所有字符旋转一个角度,可以是一个数列,如果是一个数列,则数列将一个一个应用到<tspan>
元素内的每一个字符上。 -
元素:
<tref>
描述:作为
<text>
元素的子元素使用,用来引用一个已经定义的文本。属性:
xlink:href
:引用的文本。 -
元素:
<textPath>
描述:作为
<text>
元素的子元素使用,引用一个路径,把字符对齐到路径,字符会绕着路径走。属性:
xlink:href
:引用的路径。 -
示例:
<text id='ex_text'>引用的文本</text> <path id="ex_path" d="M 20,20 C 40,40 80,40 100,20"/> <text> 文本内容 <tspan>另一段内容</tspan> <tref xlink:href="#ex_text" /> <textPath xlink:href="#my_path">绕着路径走</textPath> </text>
线条属性
线条属性是描述线条样式的属性
-
stroke
:设置对象线条的颜色。 -
stroke-opacity
:设置线条的透明度。 -
stroke-width
:设置线条的宽度。 -
stroke-linecap
:控制边框终点的形状。参数:
butt
:直边结束线段。square
:直边结束线段,但是会稍微超出实际路径
的范围,超出的大小由stroke-width
控制。round
:圆角结束线段,圆角的半径由stroke-width
控制。 -
stroke-linejoin
:两条线段之间用什么方式连接。miter
:默认值,在连接处形成尖角。round
:在连接处形成圆角。bevel
:在连接处形成尖角,但是尖角的尖端会被切掉。 -
stroke-dasharray
:将线条变为虚线。
填充属性
填充属性是描述元素内样式的属性。
fill
:设置对象内部的颜色。fill-opacity
:设置填充色的透明度。
图案
图案用来定义元素的填充,需要放在 <defs>
元素内,图案可以包含任何其他元素来共同组成图案,然后作为元素的填充使用。
WebGL
CPU 与 GPU
都是计算机的处理单元,区别是 CPU 处理单个任务的功能强大,而 GPU 没有 CPU 的功能强大,但由于它是由大量的小型处理单元构成,可以同时处理大量的简单任务,所以 GPU 更适合用来处理图形问题(因为图形任务大多是重复的大批量的任务),总之,CPU 基于低延时的设计,GPU是基于大的吞吐量设计。
WebGL
(web 图形库)是一个 JavaScript API
,将 JavaScript
与 OpenGL ES
结合在一起,达到可以使用 GPU 为 <canvas>
绘图提供硬件加速的目的(该 API
可以在 <cancas>
元素中使用)。
WebGL 渲染流程
-
数据准备
提供顶点坐标、索引(三角形绘制顺序)、uv(决定贴图坐标)、法线(决定光照效果)、各种矩阵(投影矩阵等)。
其中顶点数据存储在缓存区(因为数量较多),以修饰符
attribute
传递给顶点着色器。矩阵以修饰符
uniform
传递给顶点着色器。 -
生成顶点着色器
编译由
JavaScript
定义的顶点着色器程序(字符串),并传递给 GPU获取顶点坐标(获取图形的顶点的三维坐标)
-
图元装配
GPU根据顶点数量,逐个执行顶点着色器程序,生成顶点的最终坐标,完成坐标转换。
这里的坐标转换是指将三维的坐标转换为二维的绘图形式,将这些二维的坐标连起来,可以使图形看起来像是立体的一样。
-
生成片元着色器
处理模型的颜色、质地、光照效果、阴影等
-
光栅化
通过片元着色器确定每个像素点,以及根据深度缓存区判断哪些像素点被挡住了,不需要渲染,最终将像素点信息存在颜色缓存区,完成整个渲染。
注意
- WebGL 只能绘制 三角形、线、点。
- 图形的顶点的三维坐标,一般使用三维软件做这个事情
WebGL 上下文
在 <canvas>
中使用 WebGL
,并创建 WebGL
上下文。
<cancas id='ex'></canvas>
function init() {
const canvas = document.getElementById('ex')
const gl = canvas.getContext('webgl') // 检测浏览器是否支持 WebGL,不支持返回 null, 支持返回 WebGL 上下文
gl.clearColor(0.0, 0.0, 0.0, 1.0) // 用黑色清除上下文内的所有元素
gl.clear(gl.COLOR_BUFFER_BIT) // 用上面指定的颜色清除缓冲区
}
着色器
着色器是使用 OpenGL ES 着色语言(GLSL)
编写的程序,它负责记录像素点的位置和颜色。
绘制 WebGL
有两种不同的着色器,顶点着色器
和 片元着色器
。通过用 GLSL
编写着色器,并将代码文本传递给 WebGL
,使其在 GPU 执行时编译,顶点着色漆
和 片元着色器
的集合称之为 着色器程序
。
顶点着色器
每渲染一个形状时,顶点着色器
会在形状中的每个顶点运行,它的作用是将输入顶点从原始坐标系转换到 WebGL
使用的缩放空间坐标系,每个轴的坐标范围从 -1
到 1
,且不考虑纵横比、实际尺寸和其他因素。
顶点着色器
需要对顶点坐标进行转化,通过将其保存在由 GLSL
提供的特殊变量中来返回转换后的顶点。
注:使用 顶点着色器
可以将一个图形按照一定的比例进行缩放。
示例:略
片元着色器
片元着色器
会在 顶点着色器
处理完图形后,对图形的每个像素点调用一次,它的作用是确定像素的颜色,通过指定应用到像素的纹理元素,获取纹理元素的颜色,然后将适当的光照应用于颜色。之后颜色存储在特殊变量gl_FragColor
中,返回到 WebGL
层。该颜色将最终绘制到屏幕上图形对应像素的对应位置。
注:使用 片元着色器
为图形设置颜色和纹理等外观。
GLSL着色器语言
GLSL 是一种高级图像编程语言,基于 C/C++ 语法即流程控制。
空类型
void
,用来声明不返回任何值的函数(在顶点着色器以及片元着色器中存在的 main 函数就是一个返回值为空类型的函数)。
void main(){}
向量
向量类型 | 说明 |
---|---|
vec2 | 包含2个浮点数的向量 |
vec3 | 包含3个浮点数的向量 |
vec4 | 包含4个浮点数的向量 |
ivec2 | 包含2个整数的向量 |
ivec3 | 包含3个整数的向量 |
ivec4 | 包含4个整数的向量 |
bvec2 | 包含2个布尔类型的向量 |
bvec3 | 包含3个布尔类型的向量 |
bvec4 | 包含4个布尔类型的向量 |
uvec2 | 包含2个无符号整数的向量 |
uvec3 | 包含3个无符号整数的向量 |
uvec4 | 包含4个无符号整数的向量 |
- 声明一个向量类型的变量
v2
:vec2 v2
。 - 向量在着色器代码中很有用,可用方便的存储和操作颜色、位置、纹理、坐标等不仅包含一个组成部分的量,也能但能访问向量中的某个分量,基本语法是
向量名.分量名
。
矩阵
3D场景中的移位、旋转、缩放等变换都是由矩阵的运算来实现的,故着色语言提供了对矩阵类型的支持。
矩阵类型 | 说明 |
---|---|
mat2 | 2×2 的浮点数矩阵 |
mat3 | 3×3 的浮点数矩阵 |
mat4 | 4×4 的浮点数矩阵 |
mat3×2 | 3×2 的浮点数矩阵 |
mat3×3 | 3×3 的浮点数矩阵 |
mat3×4 | 3×4 的浮点数矩阵 |
mat2×2 | 2×2 的浮点数矩阵 |
mat2×3 | 2×3 的浮点数矩阵 |
mat2×4 | 2×4 的浮点数矩阵 |
mat4×2 | 4×2 的浮点数矩阵 |
mat4×3 | 4×3 的浮点数矩阵 |
mat4×4 | 4×4 的浮点数矩阵 |
- 是否可以使用
matX×Y
来自定义维度的矩阵?
采样器
用来处理纹理采样相关操作的数据类型,一般一个采样器变量代表一副纹理贴图。
采样器类型 | 说明 |
---|---|
sampler2D | 用于访问浮点型的二维纹理 |
sampler3D | 用于访问浮点型的三维纹理 |
samplerCube | 用于访问浮点型的立方贴图纹理 |
samplerCubeShadow | 用于访问浮点型的立方阴影纹理 |
sampler2DShadow | 用于访问浮点型的二维阴影纹理 |
sampler2DArray | 用于访问浮点型的二维纹理数组 |
sampler2DArrayShadow | 用于访问浮点型的二维阴影纹理数组 |
issampler2D | 用于访问整型的二维纹理 |
issampler3D | 用于访问整型的三维纹理 |
issamplerCube | 用于访问整型的立方贴图纹理 |
issampler2DArray | 用于访问整型的二维纹理数组 |
usampler2D | 用于访问无符号整型的二维纹理 |
usampler3D | 用于访问无符号整型的三维纹理 |
usamplerCube | 用于访问无符号整型的立方贴图纹理 |
usampler2DArray | 用于访问无符号整型的二维纹理数组 |
three.js 示例
const scene = new THREE.Scene(); // 场景
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); // 相机
const renderer = new THREE.WebGLRenderer(); // 渲染器
renderer.setSize( window.innerWidth, window.innerHeight ); // 渲染器像素?
document.body.appendChild( renderer.domElement ); // 将渲染器加入 DOM, 是一个 canvas 元素
const geometry = new THREE.BoxGeometry(); // 立方体
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); // 材质
const cube = new THREE.Mesh( geometry, material ); // 网格, 包含立方体和作用在立方体上的材质
scene.add( cube ); // 将网格加入场景
camera.position.z = 5; // 摄像机与网格分开一些距离, 因为默认它们都在 (0, 0, 0) 坐标点
function animate() {
requestAnimationFrame( animate ); // 循环这个动画
cube.rotation.x += 0.01; // 设置旋转
cube.rotation.y += 0.01; // 设置旋转
renderer.render( scene, camera ); // 渲染场景 和 相机
};
animate();
D3.js
概述
D3
在操作元素时的方式是转换而不是表示,即 D3
在操作元素时的语法描述是指将当前的状态转换为期望的状态所需要的更改(插入、更新和删除),而不是直接表示元素的期望状态;例如,在 div
元素中插入 span
元素,在 D3
中的语法是这样的:
d3.select('div') // 选中要插入 span 的 div 元素
.selectAll('span') // 选中 div 元素中的所有 span 元素
.data([1,2,3,4,5]) // 为 span 绑定数据
.enter() // 获取所有没有绑定数据的空元素
.append('span') // 将 span 插入空元素的位置
虽然这样很繁琐,但这样允许更精细的操作。
DOM对象
:DOM对象
是 D3
定义的用以描述 DOM
元素的对象,包含 DOM
的信息和其他一些方法属性;在选择元素时,D3
将 DOM
元素翻译为 DOM对象
;渲染时,D3
将 DOM对象
翻译成供浏览器渲染的 DOM
。
选择器
D3
的选择器选中 DOM
元素时,会返回一个对象,该对象包含 _group(分组)
和 _parent(父级)
。
分组
是一个数组,由 DOM对象
组成的数组组成。
父级
也是一个数组,由所有分组中元素的父级的 DOM对象
组成。
分组的多少不是由真实元素的父元素数量来决定的,而是由 D3
的选择器来决定的,如:
<div>
<span>1</span>
</div>
<div>
<span>2</span>
</div>
d3.selectAll('span') // 这时会获得一个分组,分组中有两个span元素对象,父级为空。
d3.selectAll('div').selectAll('span') // 这时会获得两个分组,每个分组中包含一个span元素对象,父级包含两个div元素对象。
数据绑定
D3
将数据与 DOM
绑定是通过在 DOM对象
中创建 __data__
属性来实现的。
D3
中的数据是由数组构成的,如:const data = [1, 2, 3]
,使用 selection.data(data)
,可以使数据和元素进行绑定,但需要注意,数组中的项是和 分组
进行绑定,而不是和每个 元素
绑定。
如:const data = [0, 1, 2]
的绑定结果是这样的:
如:const data = [[3, 4, 5], [6, 7, 8, 9]]
的绑定结果:
数据和 DOM对象
之间默认以数据的 index
进行识别和关联,当然也可以自定义关联关系。
Enter
、Update
、Exit
将 data
与 DOM对象
关联的时候,存在三种关系:
-
Enter
:进入,即数据与DOM对象
没有关联。这时要创建新的DOM对象
与数据进行关联。示例:
首先在
div
中创建四个span
,并为span
绑定数据,key
值为数据内容,颜色为blue
。d3.select('div') .selectAll('span') .data('ABCD'.split(''), d => d) .enter() .append('span') .style('width', '20px') .style('height', '20px') .style('margin-right', '5px') .style('display', 'inline-block') .style('background', 'blue')
再次
div
中的span
,并绑定数据ACGH
,由于原有的span
的key = G
和key = H
的不在ABCD
中(即这两个数据与DOM对象
没有关联),所以会得到两个需要enter
的DOM
元素,使用enter().append()
将元素加入(颜色为red
),最终得到六个span
(原有的四个蓝色的和新加入的两个红色的)。d3.select('div') .selectAll('span') .data('ACEF'.split(''), d => d) .enter() .append('span') .style('width', '20px') .style('height', '20px') .style('margin-right', '5px') .style('display', 'inline-block') .style('background', 'red')
-
Update
:更新,即DOM对象
的数据在data
中存在,这时要根据数据更新DOM对象
。示例:
首先在
div
中创建四个span
,并为span
绑定数据,key
值为数据内容,颜色为blue
。d3.select('div') .selectAll('span') .data('ABCD'.split(''), d => d) .enter() .append('span') .style('width', '20px') .style('height', '20px') .style('margin-right', '5px') .style('display', 'inline-block') .style('background', 'blue')
再次
div
中的span
,并绑定数据AC
,由于原有的span
的key
值有key = A
和key = C
(即数据与DOM对象
有关联),这时会得到两个需要Update
的DOM
元素。将这两个元素改为红色,最终得到四个span
(原有的两个蓝色的和两个改为红色的)。d3.select('div') .selectAll('span') .data('AC'.split(''), d => d) .style('background', 'red')
-
Exit
:退出,即DOM对象
的数据在data
中不存在,这时要移除多余的DOM对象
。示例:
首先在
div
中创建四个span
,并为span
绑定数据,key
值为数据内容,颜色为blue
。d3.select('div') .selectAll('span') .data('ABCD'.split(''), d => d) .enter() .append('span') .style('width', '20px') .style('height', '20px') .style('margin-right', '5px') .style('display', 'inline-block') .style('background', 'blue')
再次
div
中的span
,并绑定数据AC
,由于原有的span
的key
值有key = A
和key = C
(即数据与DOM对象
有关联),这时会得到两个需要Exit
的DOM
元素,使用enter().append()
将元素移除,最后剩下两个span
。d3.select('div') .selectAll('span') .data('AC'.split(''), d => d) .exit() .remove()
-
元素更新示例
初始化
d3.select('div') .selectAll('span') .data('ABCD'.split(''), d => d) .enter() .append('span') .style('width', '20px') .style('height', '20px') .style('margin-right', '5px') .style('display', 'inline-block') .style('background', 'blue')
-
修改
const span = d3.select('div') .selectAll('span') .data('ACEF'.split(''), d => d) span.exit().remove() // 退出 span.style('background', 'yellow') // 更新 span.enter() // 进入 .append('span') .style('width', '40px') .style('height', '40px') .style('margin-right', '5px') .style('display', 'inline-block') .style('background', 'red')
动画
概述:连续动画通常由离散的关键帧和插值或补间生成的中间帧来定义。D3
中的动画也是这样生成,并提供了 Interpolators(插值器)
来生成中间帧,中间帧和关键帧的时间是 0-1
。
Interpolators(插值器)
d3.interpolate(a, b)
:接收两个参数,返回中间值。他会根据参数 b
的类型使用相应的算法。如:当 b
是 null
、undefined
、boolean
时,直接使用常熟 b
;当 b
是颜色时,使用 d3.interpolateRgb
; 具体可查看官方文档。
示例:创建一个从红色到蓝色的插值器。
const fx = d3.interpolate('red', 'blue') // 创建一个插值器
fx(0) // 'rgb(255, 0, 0)' // 起始值,即 red
fx(0.5) // 'rgb(128, 0, 128)' // 中间值
fx(1) // 'rgb(0, 0, 255)' // 结束值,即 blue
fx(1.1) // 'rgb(0, 0, 255)' // 结束值,即 blue
生成动画
-
Transitions(过渡)
:使用D3
的Transitions
生成动画,Transitions
类似于Selections
用于在DOM
更改时生成动画,它支持大多数的选择方法,但在开始之前必须插入元素和绑定数据。Transitions
可以自动使用D3
的Interpolators(插值器)
计算中间帧,也可以自定义中间帧。示例:将
div
的颜色由red
过渡到blue
,持续时间为1s
。注意,假如div
元素没有设置background
属性的话,在过渡之前要先设置background
属性,不然transition
会找不到起始帧,导致没有过渡效果。d3.select('div') .style('background', 'red') .transition() .duration(1000) .style('background', 'blue')
-
Generators(生成器)
:自定义中间帧,每次生成中间帧时刷新整个视图的方法。示例:将
div
由red
变为blue
,中间帧为black
、yellow
。const generators = () => { const color = ['red', 'black', 'yellow', 'blue'] let index = 0 let timer = null const div = d3.select(divRef.current) timer = d3.interval( () => { div.style('background', color[index]) index += 1 console.log(99999999999) if(index === 4 ){ timer.stop() } }, 1000) }
Scales(尺度)
D3
中的 scale
的作用是将抽象的维度数据映射为可视的变量,抽象的维度数据指我们手中的数据,可视的变量指图纸中的坐标点。
如有以下数据:
const data = [
{name: '张三', age: 18},
{name: '李四', age: 20},
{name: '王五', age: 22}
]
生成的条形图:
name
属性映射为条形图的 Y轴点
, age
属性映射为条形图的 X轴点
。
D3
中有多种 scale
使用哪一种取决维度数据要映射的可视变量。上面的 X轴
是 linear scale
,因为 age
是量化数据;Y轴
是 band scale
,因为 name
是指标数据。
维度变量与可视变量之间的映射关系
D3
中的 Scales
系列函数,会跟据抽象的维度数据和可视的变量数据,生成一个映射函数,映射函数接收抽象的维度数据返回可视的变量数据。
以 d3.scaleLinear
为例,将 [0, 9]
的范围映射到 [18, 36]
的范围中去,得到映射函数 x
,这样的话,每个单位是 2
,起点是 18
,则 x(4) = 18 + 4 * 2 = 26
,x(10) = 18 + 10 * 2 =38
,D3
代码如下:
const x = d3.scaleLinear()
.domain([0, 9])
.range([18, 36])
x(4) // 输出 26
x(10) // 输出 38
在 d3.scaleLinear()
中维度写在 domain
中,将可视变量写在 range
, 这样就可以建立起维度数据与可视变量之间的映射关系了。
以 d3.scaleBand
为例,将 [张三, 李四, 王五]
的映射到 [18, 36]
的范围中去,得到映射函数 x
,这样的话,将[18, 36]
的范围分为三份,[张三, 李四, 王五]
分别对应 [18, 27, 36]
,则 x(张三) = 18
,x(李四) = 27
,而 x(马六) = undefined
,应为 马六
在建立映射的时候不存在,所以没有相对应的可视变量,这点是 量化映射
和 指标映射
的区别,D3
代码如下:
const x = d3.scaleBand()
.domain(['张三', '李四', '王五'])
.range([18, 36])
x('张三') // 输出 18
x('李四') // 输出 27
x('马六') // 输出 undefined
思考:维度变量
既可以是 指标变量
也可以是 量化变量
,可视变量
则一定是 量化变量
。
Axis(坐标轴)
D3
提供了将 维度变量和可视变量的映射关系
以 Axis(坐标轴)
的形式表示的方法。
示例:
// 生成维度变量和可视变量的映射关系
const x = d3.scaleLinear()
.domain([0, 9])
.range([18, 360])
// 将映射关系以坐标轴表示
d3.select(svgRef.current)
.append('g')
.call(d3.axisBottom(x))