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

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

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

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

image.png

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

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 内置有缩放系数计算函数:
image.png
  • 将(P0,P1)还原到(k,x,y)=(1,0,0)时的坐标:
image.png
  • 由于(P0,P1)为缩放原点,它的偏移量是不变,由此求得 x' 与 y'
image.png

对于拖拽问题,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)

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


image.png
image.png
image.png
image.png

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

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

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


    image.png

总结

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

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

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

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

image.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 1 MAP对象 该Map毒性代表您的网页上的地图。他公开了方法和属性,是您能够以编程方式更改地图,并在用户与之交互...
    Cyril丶阅读 807评论 0 0
  • 最近在重构之前上架的一款画板应用,期间用到了一些UIView的transform相关的特性。借此机会也系统整理了一...
    Kirn阅读 2,648评论 0 9
  • d3 (核心部分)选择集d3.select - 从当前文档中选择一系列元素。d3.selectAll - 从当前文...
    谢大见阅读 3,400评论 1 4
  • 虽然本身是一枚前端数据可视化工程师,但是说实话,由于本身从事这一行的时间并不长,因此对于一些基础的知识,了解的其实...
    纯爱枫若情阅读 1,587评论 0 0
  • Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Laye...
    小猫仔阅读 3,664评论 1 4