如何构建一个在线绘图工具:feakin是如何设计与构建的?(代码片段)

Phodal Phodal     2022-12-06     342

关键词:

高中,读过几本 3D 图形编程相关的书。怎么说呢,自那以后,图形学相关的东西,都不在我的兴趣范围里了。直到最近,我重新燃起了一点兴趣:

  • 架构治理工具 ArchGuard 依赖于「图即代码」,用于生成架构图,以更好的进行架构治理。

  • 年初,开源的知识管理工具 Quake 中,需要支持「概念构建系统」这样一个理念。

  • 需要管理多种不同的图形格式。

当然了,作为一个 Firefox 浏览器的忠实用户,Firefox 在 Feakin 里自然是支持最好的。开始之前,欢迎尝试在线 Demo:https://online.feakin.com/ , GitHub:https://github.com/feakin/feakin/,当然了 Bug 超级多。

引子:开源绘图工具实现的浅析

在设计 Feakin 的时候,参考了一些几个常用的图形工具。分析了它们的大致实现,以及部分的源码:

Graphviz

AT&A 实验室的作品,作为最古老的图形即代码的工具,它还提供了一个图形描述语言:Dot,可以直接将代码转换为图形。它的生态体系足够的完善,所以你在哪都能看到它的影子。

Mermaid

同样也是一个图形即代码的工具,使用的是纯 JavaScript 实现,从语法解析到图形渲染。Mermaid 使用 Jison 作为解析器,然后将其转换为不同的图模型,如流、时序等,再使用 graphlib、dargre 进行布局,最后使用 dagre-d3、d3 进行渲染。因此,在 Mermaid 里有三个核心要素:语法解析、图形布局、图形渲染。而,Mermaid 不存在一个图形模型,也变成了一个神奇的存在。

Cytoscape

第一次看到这个图形引擎的时候,是看到 ArchGuard 前人留下的一个功能:布局算法切换。所以,在源码实现上,Cytoscape 提供了这种算法上的扩展性,具体可以看官方网站。布局上的抽象,提供了更好的可扩展性 —— 我们就可以参考它的实现了。在它的图形模型里,Node(节点) 和 Edge(边) 从形式上都算是 Element,然后在渲染时根据图形类型展开。于是在渲染时,直接采用 HTML5 里的 Canvas 进行绘制即可。

Excalidraw

对我来说,其最有意思的是引入了射影几何,来进行节点变化时的,自动 Edge 跟踪;即当 A 从 B 的左边移动到右边时,对应的线自动连接到 B 右边的边上。当然了,其中的各种神奇算法,我也没看懂。对于其他人,可能就是使用 roughjs 来生成手绘风格的图。当然了, 就目前的代码实现来说,roughjs 在 renderElement 里过度的耦合,图形模型也耦合在其中。

MaxGraph

MaxGraph 是 Draw.io 底层的 mxGraph 的 TypeScript 实现,最开始研究时,是为了导入 Draw.io 生成的图。从模型上来说,MaxGraph 应该是几个工具里做得最好的,包含一系列的可参考的 Shape、Edge 等等。其次,也提供了 AbstractCanvas2D 这样的实现,虽然它没有实现真正的 HTML5 Canvas2D,但是抽象接口已经非常像了,诸如 .moveTo、 .lineTo 等。可能它就提供 SVG 和 XML,前者用于网页渲染,后者用于导出。

所以,从上述的几个工具里,我们就能得到一个绘图工具底层的基本要素:

  • 图形模型。即对图形建模,理清 Diagram/Graph、Node、Edge、Shape、Element 之间的关系,并包含基本的图形表示关系。

  • 图形绘制。即定义如何对图形进行绘制/渲染,如采用 SVG、Canvas 等不同的形式。

为了丰富这些功能:

  • 布局算法。提供自动化的布局方式,如 Cytoscape 这一类自动计算的方式。

  • 语法解析。诸如于为了支持图即代码(即 DSL)的形式来提供快捷的绘制方式。

  • 自动连线。即如 Excalidraw、Draw.io 中提供的功能,两者实现的方式完全不一样。

  • 图形风格。诸如于 Excalidraw 提供的手绘图形的功能。

  • 图形库。这也是 Drawio 最受欢迎的地方,也是 Excalidraw 一个很有意思的功能。

  • 等等

结合这些功能,我们就可以造出一些有意思的东西,比如 Feakin 中的二阶段渲染。

Step 1:实现第一个概念证明

为了 Feakin 能进行下去,我们所要做的就是快速实现一个 PoC(概念证明)。在这个 PoC 里,主要实现如下的功能:

  • DSL (领域特定语言)解析。

  • 图形模型生成。

  • 图形绘制。

如下图所示:


这样一来,我们就有一个「It works」了。

从图形引擎的误区中出来

在实现第一个 PoC 的时候,遇到的第一个困难是技术选型,到底是:SVG 还是 Canvas?SVG 可以方便于我们进行 TDD(测试驱动开发),只要所有的测试是通过的,理论上结果就是过的。但是,如我们所看到的那样,SVG 容易遇到性能瓶颈。Canvas 提供自由的绘制 API,测试时依赖于快照测试(snapshot),不易于编写测试。所以,结论就是:我们都要了吧。只需要像 MaxGraph 提供一个抽象图形接口,我们就能实现对于两种模式的支持。

随后,发现这样是不合理的,只在 PoC 阶段,并且没有经验的情况下,做一个 AbstractCanvas 还是存在很高的成本。于是乎,需要寻找一个合理的绘制引擎,诸如于 Raphaël、Fabric、Konva 等。最后,选择了 Konva,因为它支持了 React 框架。正所谓,工作用 Angular 心不累,业余用 React 放我自我

原型:语法解析-图形模型-图形绘制

在构建了基本的图形领域的相关知识之后,要构建出一个绘图工具并不困难。

  • 参考(复制) Mermaid 的语法解析。将通过 parser 解析类似于 Graphviz、Mermaid 设计的语法,将其转换为图形模型。

  • 引入 Dagre.js 作为图形布局引擎。通过 Dagre.js 来计算布局,返回我们所需要的图形模型。

  • 使用 React Konva 进行渲染。将图形模型匹配到 Konva 中的图形,如 RectangleShap 对应于 <Rect> 组件、Edge 对应于 <Line>、 <Arrow>等。

过程中,遇到的一个比较坑的点是:Lerna + Nx.js 管理 monorepo。React + Craco 的组合、风格各异的代码库,带来了持续失败的 CI,还好 GitHub Action 不会统计失败率。持续集成不来点失败,怎么能发挥它的用处呢。

Step 2:对模型进行反复重构(持续)

在 Poc 里,我们需要遇到不同的模型转换:

  • 解析器获得的模型。包含节点的信息,以及节点的关系(诸如于 A 到 B、A 依赖于 B 等)。

  • 布局引擎生成的模型。通常来说,只是补充一下模型里的层次关系(children/parent)、坐标信息(x、y)、几何信息(width、height)等。

  • 图形绘制引擎的模型。我们需要将上述的信息,再次转换到 Konva 的模型中。而其中会存在一些差距,比如 Konva 使用 Polygon(多边型)来表示Triangle(三角型)、Diamond(菱形)等。

所以,如何设计一个有用的模型,成为了个有意思的问题。

GIM:图中间模型

在那一篇《图的抽象:概念与模型的构建》中,我们介绍了从认知语义学的角度,如何仅凭基本的概念,设计出可用的模型?不过,这样的模型是未经验证的。那么,什么样的模型是经常验证的呢?自然是开源社区中,已经充分使用的代码模型。虽然说,各个模型受限于自己的场景,与其他软件的模型存在一定的差距。但是呢,在基本的核心概念图的表示上,它们是大差不差的。于是乎,我们有了一个 GIM(Graph Intermedia Model),图中间模型。

这个图模型的来源是源自其他图形工具成熟的模型,如下图所示:

所以,在持续的建模、提炼之后,我们可以轻松地进行我们的图模型转换。在有了 TDD 的加持之后,这个过程就更加地简单了。

在模型这一点上,Feakin 的设计初衷与 ArchGuard 底层的 Chapi (https://github.com/modernizing/chapi) 语言模型的想法是一致的。而这种所谓的通用模型会遇到的问题是,需要抛弃一些细节,诸如于只实现 80% 的核心功能。

图的模型

对于一个图(Graph)来说,它的模型也就变得相当的简单:

export interface Graph 
nodes: Node[];
edges: Edge[];
props?: GraphProperty;
subgraphs?: Graph[];

围绕于这四个核心元素再往下展开:

  • 节点(Node)。主要包含坐标信息,形态信息等,可以用于构建出不同的 shape。

  • 边(Edge)。主要包含点(Point),可以用于构建普通的直线、贝塞尔曲线(Bézier)曲线等,还有

  • 属性(Props)。这里只属于命名为 props 是为了对齐 🐶 ,对应于图形属性,诸如于 fillColor、color、strokeColor 等。

  • 子图(Graph[])。一个抽象的概念,在不同的图示中有不同的形式,如 Group、子集等。

而如上所述 Shape 和 Edge 就是两个大家庭,包含了一系列的子类,诸如于 Shape 包含了 PolygonShape(又包含了 TriangleShape、DiamondShape、RectangleShape 、HexagonShape)。

状态与属性(TBD)

对于属性来主,尚未进一步展开,但是初步分为:FillState、FontState、StrokeState、ImageState 等。

如果你也感兴趣的话,欢迎一起来设计。

Step 3:核心特性基础:二阶段绘图

在反复的设计了各种 Importer/Exporter 之后,并持续不断的进行模型重构之后,就构成了的核心特性的基础:二阶段绘图。简单来说,就是把绘图分为了两阶段:

  • 通过 DSL 生成图或者导入生成图。

  • 使用图形工具对生成的图进行编辑。

以在不同的工具之间转换,并实现图的互转。

二阶段绘图示例

在这里就可以尝试使用:https://online.feakin.com/ ,虽然还只是一个早期的版本,仍旧还有一系列的 bug,但是还可以尝试的。

如上图所示,我们可以

  1. 通过 File → Import 导入 Draw.io 或者 Excalidraw,又或者是通过 Graphviz 的 Dot 语法编写。

  2. 通过 Export 导出到 Draw.io 或者是 Excalidraw

图中,左边的编辑器是使用 Monaco Editor,配合了简单的 Dot 语法支持;右边则是一个早期版本的 Feakin Render,只能简单地渲染一下,看看效果。当前,仅支持简单的拖拉拽,还容易出错。

决策:过程一致或者结果一致?

在这个过程中,还有一系列有意思的东西,比如 Shape 在不同的图形工具是不一样的。先让我们看个代码示例:

digraph G 
a [shape="triangle"];
b [shape="diamond"];
a -> b;

也是截图中的代码简化,节点 a 的 shape 是一个 Triangle(三角形),然而:

  • 在 Excalidraw 中不存在三角形,需要自己用 Line 绘制一个。

  • 在 Draw.io 中默认的 Triangle 和正确的三角形不一样,正确的类似应该是 mxgraph.basic.acute_triangle 。

于是乎,为了结果上的一致,我们需要在对应的 ExcalidrawExporter、DrawioExporter 进行对应的 Shape 的处理和 Mapping。

Step 4:从 MVP 到真实世界

在这个 MVP(最小可行性产品)里,我们所构建的只是一个可以工作的原型,依旧有一系列的工作要完成。诸如于:

更丰富的图形

当前只支持基础的图形,在未来,支持其它工具的图形库 —— 有了 GIM,我们就不需要自己设计了。

图形的属性

从颜色到边框,一个功能也没有。难点主要在于,如何进行对应的属性抽象。在 MaxGraph 是一个胖模型,这种模型不利于维护,会带来额外的知识负载,它还是按字母顺序排序的,头疼。

生态兼容性

诸如于,虽然我们能成功导出 Excalidraw 的图形,也可以实现模型之间的绑定。但是,在一些属性上还是有区别的。

当然,这也是其中非常有意思的地方 —— 原来你只需要写一份未经验证的代码,现在你要看 N 份。

下一步:远程多人协作

既然,我们将代码作为第一等公民,那么实现代码的远程协作,也就是这个工具非常有意思的地方。一提到代码的多人协作,我就想起了我熟知的 Intellij IDEA。作为一个熟悉 Intellij IDEA Community 源码的人,我就联想到了 Fleet 架构里的 Rope Architecture Model 与 State Management 两篇相关文章。大体是关于如何使用 Rope 模型来管理 AST(抽象语法树),以及如何管理多人协作的状态问题。

除此,之前读过的 Xi Editor,也有关于 Rope 模型也有很好的介绍:https://xi-editor.io/docs.html。它提供了一个很好的 Rust 实现,这样一来,我们就可以使用 Rust 来开发 Feakin 的协作部分。

如果你也有兴趣,欢迎一起来用爱发电。如此一来,也不枉我花三个小时写的这篇文章。

在线 Demo:https://online.feakin.com/ 

GitHub:https://github.com/feakin/feakin/

如何构建一个在线绘图工具:feakin是如何设计与构建的?(代码片段)

高中,读过几本3D图形编程相关的书。怎么说呢,自那以后,图形学相关的东西,都不在我的兴趣范围里了。直到最近,我重新燃起了一点兴趣:架构治理工具ArchGuard依赖于「图即代码」,用于生成架... 查看详情

创建在线协作绘图:构建块是啥?

...的浏览器访问同一张表并一起绘制一些东西。我的问题是如何做到这一点。具体来说,我知道可能有无 查看详情

如何构建自己的知识体系

  构建可用的知识体系就要读书,书是有体系结构的。  何谓知识体系?  技术与技巧包括:计算机基础理论、  计算机模型:内存/IO/时钟/CPU...  专项技术领域:算法、数据挖掘、数据管理、智能推荐、搜索...... ... 查看详情

如何在线绘制思维导图流程图神奇,简单方法介绍

...、流程图都那么高大上,自己也想试试看?下面就教大家如何在线绘制思维导图、流程图。  工具/原料:  电脑  迅捷画图  思维导图绘制步骤:  1、既然是要画思维导图,那么在添加文件的时候,就需要选择新建... 查看详情

adobexd软件简单介绍

...工具,可以快速地将创意转变为原型并进行开发。创建、构建原型、共享,并向开发团队发送设计规范,从而快速地征集反馈并进行代替和轮换。3.以低保真或高保真形式进行设计使用低保真草图开发设计,并通过添加图像和副... 查看详情

如何构建一个流量无损的在线应用架构|专题中篇

简介:本篇是整个《如何流量无损的在线应用架构》系列的第二篇,这一系列共三篇,旨在使用最为朴素的语言将影响在线应用流量稳定性的技术问题做一个归类,这些问题的解决方案有的只是一些代码层面的细... 查看详情

如果 SNMP 计数器的值小于以前的读数,绘图工具应该如何表现?

】如果SNMP计数器的值小于以前的读数,绘图工具应该如何表现?【英文标题】:HowshouldgraphingtoolbehaveincaseSNMPcounterhassmallervaluethanpreviousreading?【发布时间】:2015-08-1212:10:43【问题描述】:我正在构建一个小型监控解决方案,并想... 查看详情

如何设计一个70w在线人数的弹幕系统

原文地址:如何设计一个70w在线人数的弹幕系统?如何设计一个70w在线人数的弹幕系统?问题分析1、带宽压力2、弱网导致的弹幕卡卡顿、丢失3、性能与可靠性启用Http压缩HTTP压缩是指在web服务器端和浏览器间传输压... 查看详情

如何选择javascript构建工具之babelbrowserifywebpackgrunt以及gulp(代码片段)

...的。想象一下,如果有一个很简单的判断标准,让你知道如何针对不同的项目,选取不同的前端构建工具,那是不是很美好呢?事实上,在使用自动构建系统,积累了5年的项目开发经验之后,我总结出来如下内容。相信我,它... 查看详情

绘图软件都有哪些?

...持XML、SVG及CSS等开放性的标准格式。2.LucidchartLucidchart是一个基于Html5的在线流程图绘制和协作应用平台,用户可以通过它快速的实现流程图标的绘制,还能实时的进行团队协作。3.3DMAX3DMAX是一款专业的室内设计效果图制作软件,... 查看详情

什么是最好的在线uml软件工具?

...在寻找UML绘图工具,我建议使用bullmind-在线uml软件,这是一个支持UML和许多其他图表类型的基于Web的绘图工具。支持UML图表,可以免费用于非商业目的。这是一个截图:官网地址:https://ww 查看详情

illustrator的功能和用途?

...联网页或在线内容的制作者,都会发现illustrator不仅仅是一个艺术产品工具。该软件为您的线稿提供无与伦比的精度和控制,适合生产任何小型设计到大型的复杂项目。用途:排版,印刷,设计。广告等等。 参考技术Bhttp://baike.b... 查看详情

图表即代码:以代码化的方式构建新一代图形库——feakin(代码片段)

...f0c;我们在ArchGuard中的架构工作台的进一步深入,需要构建一个架构设计线上化的功能。对 查看详情

图表即代码:以代码化的方式构建新一代图形库——feakin(代码片段)

...f0c;我们在ArchGuard中的架构工作台的进一步深入,需要构建一个架构设计线上化的功能。对 查看详情

想要成为一个游戏程序设计师要如何入门?

本人是一名初中生,对电脑有足够的兴趣,想要成为一名游戏程序设计师,如果有老师当然最好,想找个专业的程序设计师在网上教我入门,认为自己有资格的++++qq360408080当下互联网行业发展迅速,与之相关的产业大多发展都很... 查看详情

构建netcore应用框架之实战篇:什么是框架,如何设计一个框架

一、系列简述 本篇起,将通过一系列文章,去描述如何构建一个应用开发框架,并以作者开发的框架为例,逐个点展开分析,如何从零开始,构建自己的开发框架。本系列文章的目的,是带领有一编程经验的人,通过动手,... 查看详情

如何使用自动工具构建静态和共享库?

】如何使用自动工具构建静态和共享库?【英文标题】:Howtobuildstaticandsharedlibrarywithautotools?【发布时间】:2021-05-2022:21:25【问题描述】:我正在学习使用Autotools。我正在使用这些工具编写我的第一个程序,但我遇到了一个问题... 查看详情

cad绘图时如何查询id坐标呢?

CAD绘图时如何查询ID坐标呢?我们在日常的CAD绘图设计过程中,常常会用到CAD查询功能,其中查询坐标功能是一个特别常用的功能之一,我们要绘制出精准优质的CAD图纸,就必须要熟练地掌握好CAD绘图的各项功能,ID坐标查询功... 查看详情