在很多定制化的可视化场景中, 拖拽, 缩放, 全屏操作必不可少, 尤其对于业务复杂的可视化需求, 当画布内容足够多, 可视区域显示不下时, 就需要借助缩略图来一览全局.
本文将介绍一款轻量级的画布引擎 ReScreen,ReScreen 是集缩略图与画布操作为一体的轻量级绘图引擎, 为 React 专门定制, 统一封装了画布的操作和缩略图功能, 支持对画布的全屏, 复位, 显示所有, 重置, 平移缩放等常见功能.
基于 ReScreen, 我们搭建了一个通用编排场景 https://nefe.github.io/regraph-demo 的 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.
我们再简单介绍一下其他功能:
复位: 将 screenTransform 和 minimapTransform 恢复到默认初始值, 即设置为单位矩阵 zoomIdentity.
重置: 重置不仅具有复位的功能, 还需要通知画布内部一切数据需要恢复到初始状态.
显示全部: 这个也比较简单, 跟
screenToMapTransform
的过程类似.
以画布中心缩放: 假设 P0 为画布中心坐标, 当前变换为 transform, 反求出在变换前 P0 的坐标 P1; 那么在新的 transform1 下, P1 移到了 P2, 所以此时想要让画布的中心保持不变, 就需要让画布平移 P0 - P2 的距离. 如图所示:
总结
拖拽在可视化场景中是很常见的交互形式, 想做好拖拽的交互其实不容易, 社区里也有很多强大的可视化绘图引擎, 如 AntV 的底层 G 引擎, 提供了统一的渲染机制.
本文介绍的 ReScreen 是一种基于 React 的轻量级绘图引擎方案, 目的是借助 React 生态的能力, 更大程度地帮助用户实现业务需求, 用户仅仅只关注业务的开发, 降低了学习成本, 达到开箱即用的效果.
ReScreen 是 ReGraph https://github.com/nefe/regraph 体系中的重要一环, ReGraph 是结合基础操作层, 渲染交互层, 布局算法层三层结构, 针对数据领域图表 (以数, 图为基础数据结构, 带有数据业务属性与特征的可视化图表) 提供的解决方案. 基于 ReGraph 体系, 我们已经支持了多个领域图表场景的开发, 比如复杂的 DAG 场景, ERGraph, 服务编排场景等. 目前 ReGraph 正在逐渐完善与开放, 如果您有兴趣, 也欢迎提供宝贵的意见.
最后打一下广告: 阿里巴巴数据技术及产品部 - 体验技术团队招大量高级前端开发工程师 / 前端技术专家, 技术氛围好, 大神多, 妹纸也多. 欢迎投递简历: perkin.pj@alibaba-inc.com
来源: https://segmentfault.com/a/1190000021957690