Web 可视化基础

吴心役
2023-12-03

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>versionbaseProfile 表示 SVG 的版本。xmlnsSVG 绑定的命名空间。

SVG 的渲染规则是后来居上,即越后面的元素越可见。

嵌入

SVG 可以嵌入其他内容,如 图片、XML 等。

  1. 嵌入图片:使用 <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 设置属性方法:

  1. 使用 style 属性直接插入到元素中,如:

    <path d="M100 100 L 400 400" style="stroke: black" /> 
    
  2. 使用 <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>
    
  3. 使用外部样式表,如:

    <?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>
    

基本形状

  1. 矩形

    <rect
      x = 矩形左上角的x位置
      y = 矩形左上角的y位置
      rx = 圆角的x方位的路径
      ry = 圆角的y方位的路径
      width = 矩形的宽度
      height = 矩形的高度
    ></rect>
    
  2. 圆型

    <circle
      r = 圆的半径
      cx = 圆心的x位置
      cy = 圆心的y位置
    ></circle>
    
  3. 椭圆:

    <ellipse
      rx = 椭圆的x半径
      ry = 椭圆的y半径
      cx = 椭圆中心的x位置
      cy = 椭圆中心的y位置  
    ></ellipse>
    
  4. 线条

    <line
      x1 = 起点的x位置
      x2 = 起点的y位置
      x2 = 起点的x位置
      y2 = 起点的y位置
    ></line>
    
  5. 折线

    <polyline
      points = 点集数列。每个点包含两个数字,分别表示点的x、y坐标,数字之间用空格分隔,点与点之间用逗号分隔,如:10 0,11 10,12 13
    ></polyline>
    
  6. 多边形

    <polygon
      points = 点集数列。点集数列。每个点包含两个数字,分别表示点的x、y坐标,数字之间用空格分隔,点与点之间用逗号分隔,如:10 0,11 10,12 13
    >
    </polygon>
    

    注:多边形在绘制完成时,会将起点和终点连接起来组成闭合图形。

  7. 路径

    <path
      d = [命令+参数]的序列。[命令]用英文字母表示,每一个[命令]都有大写和小写两种方式,大写表示采用绝对定位;小写表示采用相对定位(相对于上一个点的定位),例如[d = M 10 10 m 20 20]中[m]表是的点是从[M点]向[x轴]移动[20],向[y]轴移动[20],这里[m 20 20]转换为绝对定位是[M 30 30]。
    >
    </path>
    

    直线命令

    1. Mm :表示将画笔移动到指定点,M 有两个参数,分别表示点的 xy 坐标,如:M x ym x y 。注意,它只表示将画笔移动到某点,并不会画图。

    2. Ll :表示从之前点到当前点画一条线段,当前位置是 L 所在的点,之前点是 L 之前的点。L 有两个参数,分别表示点的 xy 坐标,例如:L x yl 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

    3. Hh :绘制水平线,H 绘制水平线时,会使用之前点的 y 坐标,所以只有一个参数,表示点的 x 坐标,例如:H xh x

      示例:绘制一条从点 (50,50)(100,50) 的线段

      <path d="M10 10 M 50 50 H 100" stroke="black"/>
      

      没有用到点 (10,10)

    4. Vv :绘制垂直线,V 绘制垂直线时,会使用之前点的 x 坐标,所以只有一个参数,表示点的 y 坐标,例如:V xv x

      示例:绘制一条从点 (50,50)(50,100) 的线段

      <path d="M10 10 M 50 50 V 100" stroke="black"/>
      
    5. Zz :闭合命令,从当前点绘制一条直线到路径的起点,Z 没有参数,当前点值 Z 之前的点。。

      示例:绘制一个从经过点 (10, 10)(20, 10)(15,15) 的三角形

      <path d="M10 10 L 20 10 L 15 15 Z" stroke="black"/>
      

    曲线命令

    1. Qq :二次贝塞尔曲线,从之前点根据控制点绘制一条到当前点的二次贝塞尔曲线,有两组参数,分别表示控制点和当前点,例如:Q x1 y1, x yq 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"/>
      
    2. Cc :三次贝塞尔曲线,从之前点根据两个控制点绘制一条到当前点的三次贝塞尔曲线,有三组参数,分别表示两个控制点和当前点,例如:C x1 y1, x2 y2, x yc x1 y1, x2 y2, x y ,其中 x1 y1x2 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"/>
      
    3. Ss :三次贝塞尔曲线,从之前点根据两个控制点绘制一条到当前点的三次贝塞尔曲线,有两个参数,分别表示两个控制点中的一个和当前点(另有一个控制点会根据之前点计算),例如:S x1 y1, x ys x1 y1, x y ,其中 x1 y1 表示控制点,x y 表示当前点。

      注,如果 S 跟在 CS 命令后面,它的第一个控制点会被设定成前一个命令曲线的第二个控制点的中心对称点。如果 S 前面没有 CS 命令,那么当前点将作为第一个控制点。

      示例:略

    4. Aa :弧形。根据椭圆的长半轴和短半轴,和在椭圆上的两点可以绘制出两种椭圆,每种椭圆根据两点又能画出两种弧形,所以根据椭圆的半轴和椭圆上的两点绘制的弧形是不唯一的,需要其他参数确定唯一的弧形。

      Aa 的参数如下:

      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 表示 xy 轴半径

      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:变形属性,为元素设置变形,它可以取如下值:

  1. translate(x,y) :平移,将元素平移到 (x,y) 位置。
  2. rotate(num) :旋转。
  3. skewX(num)skewY(num) :斜切。
  4. scale(num, num):缩放。
  5. 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 字体。

文本

  1. 元素:<text>

    属性:

    xy :在视口中的位置。

  2. 元素:<tspan>

    描述:作为 <text> 元素的子元素使用,用来表示一个文本段落。

    属性:

    xy :设置 <tspan> 的绝对坐标,它会脱离默认的文本位置,可以是一个数列,如果是一个数列,则数列将一个一个应用到 <tspan> 元素内的每一个字符上。

    dxdy :设置 <tspan> 的相对坐标,可以是一个数列,如果是一个数列,则数列将一个一个应用到 <tspan> 元素内的每一个字符上。

    rotate :将所有字符旋转一个角度,可以是一个数列,如果是一个数列,则数列将一个一个应用到 <tspan> 元素内的每一个字符上。

  3. 元素:<tref>

    描述:作为 <text> 元素的子元素使用,用来引用一个已经定义的文本。

    属性:

    xlink:href :引用的文本。

  4. 元素:<textPath>

    描述:作为 <text> 元素的子元素使用,引用一个路径,把字符对齐到路径,字符会绕着路径走。

    属性:

    xlink:href :引用的路径。

  5. 示例:

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

线条属性

线条属性是描述线条样式的属性

  1. stroke :设置对象线条的颜色。

  2. stroke-opacity :设置线条的透明度。

  3. stroke-width :设置线条的宽度。

  4. stroke-linecap :控制边框终点的形状。

    参数:

    butt :直边结束线段。

    square :直边结束线段,但是会稍微超出实际路径的范围,超出的大小由stroke-width控制。

    round :圆角结束线段,圆角的半径由stroke-width控制。

  5. stroke-linejoin :两条线段之间用什么方式连接。

    miter :默认值,在连接处形成尖角。

    round :在连接处形成圆角。

    bevel :在连接处形成尖角,但是尖角的尖端会被切掉。

  6. stroke-dasharray :将线条变为虚线。

填充属性

填充属性是描述元素内样式的属性。

  1. fill :设置对象内部的颜色。
  2. fill-opacity :设置填充色的透明度。

图案

图案用来定义元素的填充,需要放在 <defs> 元素内,图案可以包含任何其他元素来共同组成图案,然后作为元素的填充使用。

WebGL

CPU 与 GPU

都是计算机的处理单元,区别是 CPU 处理单个任务的功能强大,而 GPU 没有 CPU 的功能强大,但由于它是由大量的小型处理单元构成,可以同时处理大量的简单任务,所以 GPU 更适合用来处理图形问题(因为图形任务大多是重复的大批量的任务),总之,CPU 基于低延时的设计,GPU是基于大的吞吐量设计。

WebGL (web 图形库)是一个 JavaScript API ,将 JavaScriptOpenGL ES 结合在一起,达到可以使用 GPU 为 <canvas> 绘图提供硬件加速的目的(该 API 可以在 <cancas> 元素中使用)。

WebGL 渲染流程

  1. 数据准备

    提供顶点坐标、索引(三角形绘制顺序)、uv(决定贴图坐标)、法线(决定光照效果)、各种矩阵(投影矩阵等)。

    其中顶点数据存储在缓存区(因为数量较多),以修饰符 attribute 传递给顶点着色器。

    矩阵以修饰符 uniform 传递给顶点着色器。

  2. 生成顶点着色器

    编译由 JavaScript 定义的顶点着色器程序(字符串),并传递给 GPU

    获取顶点坐标(获取图形的顶点的三维坐标)

  3. 图元装配

    GPU根据顶点数量,逐个执行顶点着色器程序,生成顶点的最终坐标,完成坐标转换。

    这里的坐标转换是指将三维的坐标转换为二维的绘图形式,将这些二维的坐标连起来,可以使图形看起来像是立体的一样。

  4. 生成片元着色器

    处理模型的颜色、质地、光照效果、阴影等

  5. 光栅化

    通过片元着色器确定每个像素点,以及根据深度缓存区判断哪些像素点被挡住了,不需要渲染,最终将像素点信息存在颜色缓存区,完成整个渲染。

注意

  • 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 使用的缩放空间坐标系,每个轴的坐标范围从 -11,且不考虑纵横比、实际尺寸和其他因素。

顶点着色器 需要对顶点坐标进行转化,通过将其保存在由 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个无符号整数的向量
  • 声明一个向量类型的变量v2vec2 v2
  • 向量在着色器代码中很有用,可用方便的存储和操作颜色、位置、纹理、坐标等不仅包含一个组成部分的量,也能但能访问向量中的某个分量,基本语法是 向量名.分量名

矩阵

3D场景中的移位、旋转、缩放等变换都是由矩阵的运算来实现的,故着色语言提供了对矩阵类型的支持。

矩阵类型说明
mat22×2 的浮点数矩阵
mat33×3 的浮点数矩阵
mat44×4 的浮点数矩阵
mat3×23×2 的浮点数矩阵
mat3×33×3 的浮点数矩阵
mat3×43×4 的浮点数矩阵
mat2×22×2 的浮点数矩阵
mat2×32×3 的浮点数矩阵
mat2×42×4 的浮点数矩阵
mat4×24×2 的浮点数矩阵
mat4×34×3 的浮点数矩阵
mat4×44×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 的信息和其他一些方法属性;在选择元素时,D3DOM 元素翻译为 DOM对象 ;渲染时,D3DOM对象 翻译成供浏览器渲染的 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 进行识别和关联,当然也可以自定义关联关系。

EnterUpdateExit

dataDOM对象 关联的时候,存在三种关系:

  • 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 ,由于原有的 spankey = Gkey = H 的不在 ABCD 中(即这两个数据与 DOM对象 没有关联),所以会得到两个需要 enterDOM 元素,使用 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 ,由于原有的 spankey 值有 key = Akey = C(即数据与 DOM对象 有关联),这时会得到两个需要 UpdateDOM 元素。将这两个元素改为红色,最终得到四个 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 ,由于原有的 spankey 值有 key = Akey = C(即数据与 DOM对象 有关联),这时会得到两个需要 ExitDOM 元素,使用 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 的类型使用相应的算法。如:当 bnullundefinedboolean 时,直接使用常熟 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(过渡):使用 D3Transitions 生成动画, Transitions 类似于 Selections 用于在 DOM 更改时生成动画,它支持大多数的选择方法,但在开始之前必须插入元素和绑定数据。 Transitions 可以自动使用 D3Interpolators(插值器) 计算中间帧,也可以自定义中间帧。

    示例:将 div 的颜色由 red 过渡到 blue ,持续时间为 1s 。注意,假如 div 元素没有设置 background 属性的话,在过渡之前要先设置 background 属性,不然 transition 会找不到起始帧,导致没有过渡效果。

    d3.select('div')
      .style('background', 'red')
      .transition()
      .duration(1000)
      .style('background', 'blue')
    
  • Generators(生成器):自定义中间帧,每次生成中间帧时刷新整个视图的方法。

    示例:将 divred 变为 blue,中间帧为 blackyellow

    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 = 26x(10) = 18 + 10 * 2 =38D3 代码如下:

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(张三) = 18x(李四) = 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))