Skip to content

Commit

Permalink
Feat/visual (#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
chaxus authored Jul 18, 2024
1 parent 6ca6b61 commit bc16869
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 37 deletions.
110 changes: 106 additions & 4 deletions packages/docs/cn/src/article/visual.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
在业务持续演进的过程中,遭遇了高度定制化图表的需求。为了寻找解决方案,首先深入探索了现有的开源图表库,遗憾的是,这些库虽能覆盖大部分功能需求(约 90%),但在满足特定定制化设计上却显得力不从心,那至关重要的 10% 缺口难以通过直接应用现有方案来填补
在业务持续演进的过程中,遭遇了高度定制化图表的需求。为了寻找解决方案,首先深入探索了现有的开源图表库,遗憾的是,这些库虽能覆盖大部分常见的功能,但在一些定制化设计上却显得力不从心,但 UI 层的定制化又是十分常见的

为了精准复现设计愿景,我转而采用 canvas 技术手动绘制图表。这一过程中,尽管已尽力通过函数式编程范式封装和模块化绘制逻辑以提升代码的可维护性,但 canvas 的命令式特性在面临需求变动时,仍易导致直接而频繁的代码调整,影响了整体的开发效率和灵活性。

Expand All @@ -15,7 +15,76 @@
5. **事件系统:**集成一个高效的事件处理系统,允许用户将事件监听器绑定到单个图形元素或整个组上。这样,无论是用户交互(如点击、拖动)还是系统事件(如加载完成、数据更新),都能得到及时响应和处理。
6. **应用层封装:**在应用层,我们需要封装绘制引擎提供的底层功能,使其更加贴近业务需求。这包括提供易于使用的 API 接口、优化性能、处理异常和错误等。同时,通过持续的应用层反馈,不断优化和调整绘制引擎,确保其能够更好地服务于业务的发展。

在上述绘制引擎架构中,各架构设计存在明确的依赖链:基础图形支撑图形组与层级管理;图形组与层级管理又是变换矩阵与事件系统的基础;最终,所有组件共同作用于应用的呈现,确保高效协同与用户体验。
因此,在绘制引擎架构中,我们会首先实现一个节点类 Vertex,这个类代表了最原始的‘节点’的概念,所有可以被展示到 canvas 画布上的、各种类型的节点都会继承于这个类,这是一个抽象类,我们并不会直接实例化这个类。

这个类上面挂载了‘节点’的各种通用属性,比如:父元素、透明度、旋转角度、缩放、平移、节点是否可见等。

```ts

```

为了进行图形组的管理,会继续实现一个容器类 Container,这个类代表了‘组’的概念,它提供了添加子元素,移除子元素等的方法;后续的要被渲染的一些类 (如 Graphics,Text,Sprite 等) 会继承于这个类;这个类本身不会被渲染 (因为它只是一个‘组’,它本身没有内容可以渲染)。

这个类继承于 Vertex 类,‘组’也算作‘节点’。

Graphics 这个类会用来构建一些几何图形元素;它会继承 Container 类。

在渲染引擎中,一切变换 (平移、旋转、缩放等) 都会转化成变换矩阵 (matrix),因为 canvas 只接受矩阵变换,虽然 canvas 为了开发的便捷,也提供了 ctx.rotate,ctx.scale 等操作,但是 canvas 中的这些操作会直接转换成变换矩阵,而不像 DOM 那样,有锚点的概念,所以 canvas 提供的 rotate,scale 等操作,和 DOM 提供的 rotate,scale 的表现是不一样的。

Matrix 类将会提供各种各样的与矩阵操作相关的函数 (矩阵相乘,矩阵求逆等),任何变换的叠加都将会转换成 matrix,方便我们调用 canvas 的指令。

Transform 类就类似 CSS 的 transform,它提供了一些更清晰、更符合人类直觉的变换,而不用直接使用矩阵变换,当然,这些变换最终会转换成矩阵变换。

线性变换从几何直观有三个要点:

- 变换前是直线的,变换后依然是直线
- 直线比例保持不变
- 变换前是原点的,变换后依然是原点

比如有一个二维基向量

<r-math latex="\left[\begin{matrix}x & 0 \\0 & y \\ \end{matrix}\right]"></r-math>

旋转矩阵:

<r-math latex="\left[ \begin{matrix} cos(r) & -sin(r) \\ sin(r) & cos(r) \\ \end{matrix} \right]"></r-math>

浏览器的 skew:

<r-math latex="\left[ \begin{matrix} 1 & tan(x) \\ tan(y) & 1 \\ \end{matrix} \right]"></r-math>

pixijs 的 skew:

<r-math latex="\left[ \begin{matrix} cos(y) & sin(x) \\ sin(y) & cos(x) \\ \end{matrix} \right]"></r-math>

平移操作:

这里就需要仿射变换

仿射变换从几何直观只有两个要点:

- 变换前是直线的,变换后依然是直线
- 直线比例保持不变

少了原点保持不变这一条。

因此,平移不再是线性变化了,而是仿射变化。

每一个矩阵变换都是线性变换,反正则不成立。

仿射变换不能光通过矩阵乘法来实现,还得有加法。

主要用途是,通过高纬度的线性变换,模拟低维度的仿射变换。从而把平移操作也数据化。

# 实现曲线

## 二阶贝塞尔曲线

<r-math latex="B(t) = (1-t)^2 \mathbf{P_0} + 2t(1-t) \mathbf{P_1} + t^2 \mathbf{P_2}, \quad t \in [0, 1]"></r-math>

起点(P0):这是曲线开始的位置。在绘制过程中,曲线会精确地通过这个点。
控制点(P1):这个点是用来控制曲线形状和弯曲程度的。它不一定在曲线上,但会对曲线的走向产生重要影响。通过调整控制点的位置,可以改变曲线的弯曲程度和方向。
终点(P2):这是曲线结束的位置。同样地,曲线也会精确地通过这个点。

## 二 基础图形库:

Expand Down Expand Up @@ -257,6 +326,39 @@ if (lineStyle.visible) {
}
```

## 三 层级和节点
## 三 层级管理

在 canvas 绘图环境中,先绘制的图形会被后绘制的图形所覆盖,因此,层级的管理就自然地通过绘制顺序来实现。在这种情况下,最先被绘制的图形将位于最底层,而随后绘制的图形则逐层叠加,直至最上层。

我们已经实现了各种基础图形的绘制,接下来继续分离绘制时的数据和绘制操作。

我们需要一个容器类,在容器类中统一绘制所有的图形。

用一个 children 属性来添加所有的图形,在 render 方法中统一绘制。

同时,我们会根据 zIndex 属性来排序子元素,zIndex 越大的元素会被排在 children 数组的越后面,但是注意,排序的时候,要保证相同 zIndex 的相对顺序不变。

## 四 图形组的管理

我们会实现一个节点类 Vertex 这个类代表了最原始的‘节点’的概念,所有可以被展示到 canvas 画布上的、各种类型的节点都会继承于这个类,这是一个抽象类,我们并不会直接实例化这个类。

这个类上面挂载了‘节点’的各种属性,比如:父元素、透明度、旋转角度、缩放、平移、节点是否可见等。

为了进行图形组的管理,会继续实现一个容器类 Container,这个类代表了‘组’的概念,它提供了添加子元素,移除子元素等的方法;后续的要被渲染的一些类 (如 Graphics,Text,Sprite 等) 会继承于这个类;这个类本身不会被渲染 (因为它只是一个‘组’,它本身没有内容可以渲染)。

这个类继承于 Vertex 类,‘组’也算作‘节点’。

Graphics 这个类会用来构建一些几何图形元素;它会继承 Container 类。

在渲染引擎中,一切变换 (平移、旋转、缩放等) 都会转化成变换矩阵 (matrix),因为 canvas 只接受矩阵变换,虽然 canvas 为了开发的便捷,也提供了 ctx.rotate,ctx.scale 等操作,但是 canvas 中的这些操作会直接转换成变换矩阵,而不像 DOM 那样,有锚点的概念,所以 canvas 提供的 rotate,scale 等操作,和 DOM 提供的 rotate,scale 的表现是不一样的。

Matrix 类将会提供各种各样的与矩阵操作相关的函数 (矩阵相乘,矩阵求逆等),任何变换的叠加都将会转换成 matrix,方便我们调用 canvas 的指令。

Transform 类就类似 CSS 的 transform,它提供了一些更清晰、更符合人类直觉的变换,而不用直接使用矩阵变换,当然,这些变换最终会转换成矩阵变换。

# 参考资料:

层级的管理非常简单,在 canvas 绘图环境中,先绘制的图形会被后绘制的图形所覆盖,因此,层级的管理就自然地通过绘制顺序来实现。在这种情况下,最先被绘制的图形将位于最底层,而随后绘制的图形则逐层叠加,直至最上层。
1. [如何通俗地讲解「仿射变换」?](https://www.zhihu.com/question/20666664/answer/157400568)
2. [仿射变换及其变换矩阵的理解](https://www.cnblogs.com/shine-lee/p/10950963.html)
3. [深入理解贝塞尔曲线](https://juejin.cn/post/6844903666361565191)
4. [如何理解并应用贝塞尔曲线](https://juejin.cn/post/6844903796582121485)
2 changes: 1 addition & 1 deletion packages/docs/variable/SERVICE_WORK_VERSION.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const SERVICE_WORK_VERSION = "1721111713"
export const SERVICE_WORK_VERSION = '1721111713';
Loading

0 comments on commit bc16869

Please sign in to comment.