如何實現一款輕量級的可視化畫布引擎

在不少定製化的可視化場景中,拖拽、縮放、全屏操做必不可少,尤爲對於業務複雜的可視化需求,當畫布內容足夠多,可視區域顯示不下時,就須要藉助縮略圖來一覽全局。前端

本文將介紹一款輕量級的畫布引擎ReScreen,ReScreen是集縮略圖與畫布操做爲一體的輕量級繪圖引擎,爲React專門定製,統一封裝了畫布的操做和縮略圖功能,支持對畫布的全屏、復位、顯示全部、重置、平移縮放等常見功能。git

基於ReScreen,咱們搭建了一個通用編排場景的demo。
image.png
能夠在使用了以後,再閱讀下文,體感會更強。github

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複製,每次畫布操做發生變化時,就從新複製一份。固然也支持自定義縮略圖組件。svg

基本原理函數

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)

具體涉及到的計算原理以下圖:

image.png

image.png

image.png

image.png

這樣就具有了最基本的縮略圖縮放功能了。這裏涉及到一個問題,當畫布內容變化時,縮略圖也須要適時地自適應,即從新獲取一次screenToMapTransform

咱們再簡單介紹一下其餘功能:

  • 復位:將screenTransformminimapTransform恢復到默認初始值,即設置爲單位矩陣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

相關文章
相關標籤/搜索