图标库

图标库
匡思进渲染引擎
原因
- 管理图元:使用渲染引擎能够更轻松的绘制并管理图形元素。
- 提供完善的动画与事件机制:原生语法绘制动画相对比较麻烦。
- 性能优化:渲染引擎基于底层渲染器的特性进行了大量优化工作,如脏矩阵渲染、分层渲染等,能够取得更好的渲染性能。使得开发者能够专注于视图的构建。
- 多个渲染器之间任意切换:如果有同时在这两种渲染器中进行绘制的需求,需要针对不同的渲染器进行单独开发,提高工作量的同时也难以保证其一致性。使用渲染引擎绘制时只需要指定所需的渲染器即可完成切换。
功能设计
绘制基本图形 : 支持 rect、circle、line、path、text、ring 这几种基本图形的绘制。
进行坐标系变换: 支持 translate,scale,rotate 这三种变换,同时可以使用类似 Canvas2D 的 save 和 restore 去管理坐标系变换的状态。
测试相关
本项目采用vitest单元测试, 具体操作可参考官网快速起步 | 指南 | Vitest
同时我们在写代码的过程中不会完全遵循 airbnb-base 的规范,所以需要修改 .eslintrc.js 如下,关闭一些规则的校验。
1 | |
比例尺
可视化中的比例尺, 是用来度量数据属性的, 将数据抽象的属性映射为一个视觉属性
这决定了我们如何理解图形的颜色, 大小, 形状和位置. 将这些数据以什么样的形式表现出来
比例尺本质上是一个函数,会将一个值(变量)从一个特定的范围(定义域)映射到另一个特定的范围(值域)。定义域(Domain)是由数据的属性决定,值域(Range)是由图形的视觉属性决定。根据定义域和值域的不同,我们需要选择不同的比例尺。
Identity
“恒等映射”, 将输入原封不动的返回
连续比例尺
连续比例尺主要用于数值属性的映射,它的特点是定义域和值域都是连续的,它们之间的关系可以表示为:y = a * f(x) + b。
对于所有的连续比例尺来说,除了拥有映射功能之外,还有一个很重要的功能:生成坐标轴需要的坐标刻度。在理解可视化图表的过程中,坐标轴往往是一个很好的辅助,因为它能给我们提供更多辅助信息。
Linear
(1) 线性映射
Linear 比例尺是支持线性映射的比例尺,它的表达式 y = a * f(x) + b 中的 f(x) 应该为 f(x) = x。
Linear 比例尺常常用于视觉元素的布局,比如在做散点图的时候,可以用 Linear 比例尺来完成对点的布局,比如将数据的某个数值属性映射为点的 x 坐标,将数据的另一个个数值属性映射为点的 y 坐标
输入在定义域里到两端的比例,应该和输出在值域到两端的比例相同
它期望的使用方式如下:
1 | |
(2)ticks 和 nice
生成坐标刻度的方法
通过数学方法 选取合适的刻度间隔, 同时减少误差
给定一个范围(min 和 max)和一个刻度数量(count),我们希望计算出 最终的刻度间隔(step1),这个间隔满足以下条件:
step1形式是10^n * b,其中b是1, 2, 5中的一个,n是整数。这种形式保证了间隔是“对称的”,符合数轴的常见标度。step1和初始估算的step0的误差尽量小。step0是直接根据(max - min) / count得到的目标间隔,但这个值不一定是符合10^n * b形式的,我们需要调整它。
代码逐行分析
1 | |
给定一个范围(min 和 max)和一个刻度数量(count),我们希望计算出 最终的刻度间隔(step1),这个间隔满足以下条件:
step1 形式是 10^n * b,其中 b 是 1, 2, 5 中的一个,n 是整数。这种形式保证了间隔是“对称的”,符合数轴的常见标度。
step1 和初始估算的 step0 的误差尽量小。step0 是直接根据 (max - min) / count 得到的目标间隔,但这个值不一定是符合 10^n * b 形式的,我们需要调整它。
代码逐行分析
1 | |
这三行定义了误差的阈值,分别对应于 误差范围。它们代表了在 step0 和 step1 之间容忍的误差程度。如果误差大于这些值,我们需要调整 step1。
1 | |
step0 是根据给定的 最小值(min)和最大值(max) 计算出来的初步间隔。具体计算方法是将范围 (max - min) 除以刻度数量 count,从而得到大致的间隔。
1 | |
这行计算了 step0 的 最接近的 10 的幂次,即找到 step0 所对应的 10^n 的值。这个值是 step0 的初步刻度间隔,但还不能保证符合 10^n * b 的形式。
1 | |
error 表示 step0 与 step1 的误差,即 step0 和 step1 的比例关系。我们希望 step1 与 step0 的误差尽量小。
// 根据误差调整 step1 的值
1 | |
1 | |
1 | |
1 | |
代码总结
step0 是初步估算的刻度间隔,通过 (max - min) / count 得到。
step1 是通过 10 的幂次逼近的刻度间隔,然后通过调整乘以 1, 2, 5 或 10 来减小误差,使得 step1 更接近 step0,且符合 10^n * b 的形式。
Time
Linear 比例尺要求定义域都是数字,但是有的时候我们希望定义域是浏览器的时间对象(Date),这个时候就需要用到 Time 比例尺了。
序数类比例尺
如果说连续比例尺复杂数值属性的映射, 那么序数类比例尺就负责序数属性的映射
Oridinal
Oridinal要用于将序数属性映射为同为序数属性的视觉属性,比如颜色,形状等。
实现思路: 首先从定义域中找到输入对应的索引, 然后返回值域中对应索引的元素
Band
Band 比例尺主要用于将离散的序数属性映射为连续的数值属性,往往用于条形图中确定某个条的位置。
比如下面我们将水果的名字映射为下面每个条的位置,其中每一个条使一个 Band,它的宽度为 BandWidth,条之间的间距为 Padding,步长是 BandWidth 和 Padding 之和。
1 | |
Point
Point 比例尺是一种特殊的 Band 比例尺,它的 Padding 始终为 1,也就是说它的 BandWidth 始终为 0
分布比例尺
将我们提供的数据分组, 每一组使用一个颜色来编码
Threshold
它的定义域是连续的,并且会被指定的分割值分成不同的组
Quantize
相对于 Threshold 比例尺需要我们指定分割值,Quantize 比例尺会根据数据的范围帮我们选择分割值,从而把定义域分成间隔相同的组。
这出现了一个问题: 当数据的分布有倾斜的时候,会出现几乎所有数据都在一个组,只有一些极端值在自己组的情况
Quantile
和 Quantize 比例尺不同是,Quantile 比例尺采用了另外得到分割值的策略: 根据数据出现的频率分组。
Quantile 是根据数据在整个数据集的排名来分组的,所以会缺少数据绝对大小相关的分布信息。
坐标系
坐标系是将视觉元素的位置属性映射为在画布上的坐标
每一个坐标系都包含两个部分: 画布的位置和大小 和 一系列坐标系变换函数
将一个统计意义上的点, 转换成画布上的点
统计意义上的点是指:点的两个维度(x和y)都被归一化了,都在 [0, 1] 的范围之内。这样在将点在真正绘制到画布上的之前,我们不用考虑它们的绝对大小,只用关心它们相对大小等统计学特征。这些特征在变换过程中都不会丢失。
基本变换
基本变换本质上是一个函数,输入是变换前点的坐标,输入是变换后点的坐标。同时该变换函数有 type 方法返回自己的变换类型(后面会使用到)
平移, 缩放, 反射, 转置
这里就不细讲啦, 直接上代码
1 | |
极坐标
相同的点在极坐标系下就会被表示为 (raduis, theta),radius 是到极点的距离,theta 是点和极点的连线和极轴的角度。两者可以相互转换。具体参考下面这张图。
1 | |
坐标系变换
坐标系变换会根据画布的位置和大小, 以及基本变换本身需要的参数去生成一个由基本变换构成的数组. 所以所有的坐标系变换都应该接受两个参数: transformOptions 和 canvasOptions, 然后返回以一个数组.
笛卡尔坐标系变换
Cooridante 里的笛卡尔坐标系变换是将统计学上的点线性转换成画布上的点。
![[6a43aa4b1c30fdf16bb9927d1d11c18e_MD5.webp]]
使用方法
1 | |
但是这里存在一个问题 transformOptions 在定义坐标系的时候需要用户显示指定的,canvasOptions 是在执行坐标系函数的时候被传入的,两者被传入的时间不同。
这个时候就需要延迟函数的执行,只有当 transformOptions 和 canvasOptions 都被传入的时候才执行该函数。这久可以用到我们前面提到的函数柯里化了。柯里化后的 cartesian 就可以如下使用。
1 | |
1 | |
当然这里使用的 curry 会和之前提到的有一点不一样:当不传入参数的时候,需要等价于传入了 undefined 参数。也就是在使用柯里化后的 cartesian 函数的时候 cartesian() 等价于caresian(undefined)。
1 | |
极坐标系变换
这里的极坐标系变换和前面的极坐标变换的区别在于:
- 极点不同:极点从画布左上角变成了画布中心。
- 大小不同:坐标系构成的圆形会内切画布。
- 范围不同:可以指定坐标系开始的角度:
startAngle和结束的角度endAngle。也可以指定内半径innerRadius和外半径outerRadius(范围都是:[0, 1])。
![[218d475eceeab5f02881d7136a491e02_MD5.webp]]
转置坐标变换
![[23776155cdabd681c0e4f872b6b25761_MD5.webp]]
几何图形
数值通道(Magnitude Channel)会我们提供和有多少相关的信息,主要用来编码数值属性,比如下图中的位置(Position)、大小(Size)和倾斜角度(Tilt)都是数值属性;
特征通道(Identity Channel)给我们提供是什么、在哪里相关的信息,主要用来编码分类属性,比如形状(Shape)和颜色(Color)。
几何图形渲染的数据不是一个数组,而是一个对象。这个对象的每一个 key 都是该几何图形的一个通道,对应的 value 是一个数组,数组的每一个元素是数据和该通道绑定的属性的值
创建通道
每一个通道都是一个对象,它拥有的属性如下。
| 属性名 | 描述 | 可选 | 默认值 |
|---|---|---|---|
| name | 属性的名字 | 否 | - |
| optional | values 里面是否需要该属性对应的值 | 否 | true |
| scale | 需要使用的比例尺 | 是 |
通道一方面可以对我们渲染的数据进行校验,另一方面可以在后面的开发中使用。
几何图形
在代码中的几何图形和比例尺、坐标一样,都是一个函数,它会将处理好的数据转化成屏幕上的像素点,因为我们的渲染器是基于 SVG 的,所以其实是转换成对应的 SVG 元素。
对于每一个几何图形,我们需要定义它的通道和渲染函数,并且在渲染之前检查一下是否提供了需要的数据和正确的比例尺。







