10

如何实现一款轻量级的可视化画布引擎

 3 years ago
source link: https://zhuanlan.zhihu.com/p/111582835
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如何实现一款轻量级的可视化画布引擎

阿里巴巴集团 前端工程师

在很多定制化的可视化场景中,拖拽、缩放、全屏操作必不可少,尤其对于业务复杂的可视化需求,当画布内容足够多,可视区域显示不下时,就需要借助缩略图来一览全局。

本文将介绍一款轻量级的画布引擎ReScreen,ReScreen是集缩略图与画布操作为一体的轻量级绘图引擎,为React专门定制,统一封装了画布的操作和缩略图功能,支持对画布的全屏、复位、显示所有、重置、平移缩放等常见功能。

基于ReScreen,我们搭建了一个通用编排场景的

https://nefe.github.io/regraph-demo/​nefe.github.io

可以在使用了之后,再阅读下文,体感会更强。

editor demo

ReScreen 介绍

ReScreen具备如下的特性:

  • 支持缩放、拖拽等基本功能
  • 支持缩略图,提供缩略图的点击聚焦功能、缩略图的位置、大小、间距等样式
  • 支持缩略图的自定义传入,默认使用画布的缩小版
  • 支持是否启动鼠标滚动缩放
  • 支持缩放的范围设置
  • 支持锁定某一方向的拖拽
  • 支持控制按钮的自定义
  • 支持拖拽动画
  • 画布内容为SVG,或者原生DOM,目前暂未支持Canvas情况

ReScreen基于React与TypeScript开发,其属性参数为:

class Props {
  /** 画布内容的类型,默认为SVG */
  type?: 'SVG' | 'DOM' | 'CANVAS';
  /** 组件整体的尺寸,支持传入百分数 */
  height?: number | string;
  width?: number | string;
  /** 是否启动鼠标滚动缩放画布,默认为true */
  zoomEnabled?: boolean;
  /** 是否启动聚焦功能,0表示不启动,1表示单击触发,2表示双击触发 */
  focusEnabled?: number;
  /** 缩放范围 */
  minZoom?: number;
  maxZoom?: number; 
  /** 拖拽方向的锁定,默认为ALL */
  dragDirection?: 'ALL' | 'HOR' | 'VER';
  /** 是否需要缩略图,默认为true */
  needMinimap?: boolean;
  /** 支持自定义传入缩略图组件 */
  Minimap?: React.ReactElement<any>;
  /** 缩略图位置,默认为RT,右上角;-IN表示在画布的内部 */
  mapPosition?: 'RT' | 'RB' | 'LT' | 'LB' | 'RT-IN' | 'RB-IN' | 'LT-IN' | 'LB-IN';
  /** 缩略图和原图之间的大小,默认为20 */
  mapPadding?: number;
  /** 缩略图大小,默认为100px */
  mapWidth?: number;
  mapHeight?: number;
  /** 缩略图矩形的样式,svg语法 */
  mapRectStyle?: object;
  /** 按钮组件,如果不需要就不传 */
  Buttons?: React.ReactElement<any>;
  /** 由于画布元素的变化而引起的视图变化 */
  needRefresh?: boolean;
  /** 通知外层重置needRefresh为false */
  resetNeedRefresh?: () => void;
  /** 画布发生变化时的回调,对外暴露当前的缩放信息 */
  onScreenChange?: (transform: ZoomTransform) => void;
  /** 对外暴露画布操作函数 */
  getScreenHandler?: any;
}

实现原理详解

画布的缩放能力主要借助了d3-zoom,但整体上还是涉及一些数学计算。这里涉及包括画布上可以平移缩放操作,也包括在缩略图上进行反向的操作。缩略图采用的是DOM复制,每次画布操作发生变化时,就重新复制一份。当然也支持自定义缩略图组件。

基本原理

d3-zoom 里将问题进行了如下的抽象,假设当前画布缩放系数为 k,x 轴平移偏移量在当前缩放系数下为 x ,y 轴平移偏移量在当前缩放系数下为 y,(k,x,y)在点(P0,P1)处进行缩放,需要求得缩放后的(k',x',y'),在 SVG 中的表现就是

<svg width="w" height="h">
  <g transform="translate(x,y) scale(k)">
  </g>
</svg>
  • 通过鼠标滚动操作/放大按钮等等,求得缩放系数 k',在 d3.zoom 内置有缩放系数计算函数:
  • 将(P0,P1)还原到(k,x,y)=(1,0,0)时的坐标:
  • 由于(P0,P1)为缩放原点,它的偏移量是不变,由此求得 x' 与 y'

对于拖拽问题,k 值是不变的,拖拽仅改变 x 与 y 值,这块就比较简单了,通过鼠标拖拽的起点与重点,能得到相应变化量,将变化量给予 x 与 y 即可。

实现步骤

整体主要分成如下几个步骤:

1. 根据传入属性,计算(或者直接获取)画布可视窗口的大小screenWidth/screenHeight和缩略图的大小 mapWidth/mapHeight

2. 计算画布内容全部映射到缩略图中所需要的变化值screenToMapTransform

3. 监听画布的zoom事件,用screenTransform记录画布当前的变换;

4. 监听缩略图可视矩形的zoom事件,用minimapTransform记录矩形当前的变换;

5. 画布的最终缩放效果transform = screenTransform * invert(minimapTransform),而缩略图可视矩形的变换为invert(transform)

具体涉及到的计算原理如下图:

这样就具备了最基本的缩略图缩放功能了。这里涉及到一个问题,当画布内容变化时,缩略图也需要适时地自适应,即重新获取一次screenToMapTransform

我们再简单介绍一下其他功能:

  • 复位:将screenTransformminimapTransform恢复到默认初始值,即设置为单位矩阵zoomIdentity
  • 重置:重置不仅具有复位的功能,还需要通知画布内部一切数据需要恢复到初始状态。
  • 显示全部:这个也比较简单,跟screenToMapTransform的过程类似。
  • 以画布中心缩放:假设P0为画布中心坐标,当前变换为transform,反求出在变换前P0的坐标P1;那么在新的transform1下,P1移到了P2,所以此时想要让画布的中心保持不变,就需要让画布平移P0 - P2的距离。如图所示:

拖拽在可视化场景中是很常见的交互形式,想做好拖拽的交互其实不容易,社区里也有很多强大的可视化绘图引擎,如AntV的底层G引擎,提供了统一的渲染机制。

本文介绍的ReScreen是一种基于React的轻量级绘图引擎方案,目的是借助React生态的能力,更大程度地帮助用户实现业务需求,用户仅仅只关注业务的开发,降低了学习成本,达到开箱即用的效果。

ReScreen 是 ReGraph 体系中的重要一环,ReGraph 是结合基础操作层、渲染交互层、布局算法层三层结构,针对数据领域图表(以数、图为基础数据结构,带有数据业务属性与特征的可视化图表)提供的解决方案。基于ReGraph体系,我们已经支持了多个领域图表场景的开发,比如复杂的DAG场景、ERGraph、服务编排场景等。目前ReGraph正在逐渐完善与开放,如果您有兴趣,也欢迎提供宝贵的意见。

最后打一下广告:阿里巴巴数据技术及产品部-体验技术团队招大量高级前端开发工程师/前端技术专家,技术氛围好,大神多,妹纸也多。欢迎投递简历:[email protected]




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK