從零設計可視化大屏搭建引擎
幾個月前我寫了一篇關于從零開發(fā)一款可視化大屏制作平臺 的文章, 簡單概述了一下可視化大屏搭建平臺的一些設計思路和效果演示, 這篇文章我會就 如何設計可視化大屏搭建引擎 這一主題, 詳細介紹一下實現(xiàn)原理。
按照我一向的寫作風格, 我會在下面列出文章的大綱,以便大家有選擇且高效率的閱讀和學習:
- 快速了解數(shù)據(jù)可視化
- 如何設計通用的大屏搭建引擎
- 大屏搭建引擎核心功能實現(xiàn)
- 拖拽器實現(xiàn)
- 物料中心設計
- 動態(tài)渲染器實現(xiàn)
- 配置面板設計
- 控制中心概述
- 功能輔助設計
- 可視化大屏后期規(guī)劃和未來展望
大家可以輕松根據(jù)右側的文章導航, 快速定位到自己想看的位置, 接下來我們開始進入正文。
快速了解數(shù)據(jù)可視化
說到數(shù)據(jù)可視化, 想必大家多多少少稍接觸過, 從技術層面談, 最直觀的就是前端可視化框架, 比如:
- echart
- antv
- Chart.js
- D3.js
- Vega
這些庫都能幫我們輕松制作可視化圖表。
從實用性的角度來談, 其最主要的意義就在于幫助用戶更好的分析和表達數(shù)據(jù)。所以說談到數(shù)據(jù)可視化, 更多的是和各種圖表打交道, 通過 數(shù)據(jù) -> 圖表組合 -> 可視化頁面 這一業(yè)務流程, 就構成了我們今天要研究的話題——設計可視化大屏搭建引擎。
如何設計通用的大屏搭建引擎
說到 “引擎” 這個詞也許有種莫名的高大上, 其實在互聯(lián)網技術中, 我們經常會聽到各種相關的名詞,比如 “瀏覽器渲染引擎” , “規(guī)則引擎” , “圖像識別引擎” 等, 我覺得 “引擎” 的本質就是提供一套可靠的機制, 為系統(tǒng)提供源源不斷的生產力。所以我們今天談的“可視化大屏搭建引擎”, 本質上也是提供一套搭建機制, 支撐我們設計各種復雜的可視化頁面。
為了方便大家理解可視化搭建, 我這里展示2張可視化大屏的頁面, 來和大家一起分析一下可視化大屏的組成要素:
當然實際應用中大屏展現(xiàn)的內容和形式遠比這復雜, 我們從上圖可以提煉出大屏頁面的2個直觀特征:
- 可視化組件集
- 空間坐標關系
因為我們可視化大屏載體是頁面, 是html, 所以還有另外一個特征: 事件/交互。綜上我們總結出了可視化大屏的必備要素:
我們只要充分的理解了可視化大屏的組成和特征, 我們才能更好的設計可視化大屏搭建引擎, 基于以上分析, 我設計了一張基礎引擎的架構圖:
接下來我就帶大家一起來拆解并實現(xiàn)上面的搭建引擎。
大屏搭建引擎核心功能實現(xiàn)
俗話說: “好的拆解是成功的一半”, 任何一個復雜任務或者系統(tǒng), 我們只要能將其拆解成很多細小的子模塊, 就能很好的解決并實現(xiàn)它. (學習也是一樣)
接下來我們就逐一解決上述基礎引擎的幾個核心子模塊:
- 拖拽器實現(xiàn)
- 物料中心設計
- 動態(tài)渲染器實現(xiàn)
- 配置面板設計
- 控制中心概述
- 功能輔助設計
拖拽器實現(xiàn)
拖拽器是可視化搭建引擎的核心模塊, 也是用來解決上述提到的大屏頁面特征中的“空間坐標關系”這一問題。我們先來看一下實現(xiàn)效果:
有關拖拽的技術實現(xiàn), 我們可以利用原生 js 實現(xiàn), 也可以使用第三方成熟的拖拽庫, 比如:
- DnD
- React-Dragable
- react-moveable
我之前也開源了一個輕量級自由拖拽庫 rc-drag , 效果如下:
有關它的技術實現(xiàn)可以參考我的另一篇文章: 輕松教你搞定組件的拖拽, 縮放, 多控制點伸縮和拖拽數(shù)據(jù)上報。大家也可以基于此做二次擴展和封裝。
我們拖拽器的基本原型代碼如下:
- export default function DragBox(props) {
- const [x, y, config] = props;
- const [target, setTarget] = React.useState();
- const [elementGuidelines, setElementGuidelines] = React.useState([]);
- const [frame, setFrame] = React.useState({
- translate: [x, y],
- });
- React.useEffect(() => {
- setTarget(document.querySelector(".target")!);
- }, []);
- return <div className="container">
- <div className="target">拖拽內部組件, 比如圖表/基礎組件等</div>
- <Moveable
- target={target}
- elementGuidelines={elementGuidelines}
- snappable={true}
- snapThreshold={5}
- isDisplaySnapDigit={true}
- snapGap={true}
- snapElement={true}
- snapVertical={true}
- snapHorizontal={true}
- snapCenter={false}
- snapDigit={0}
- draggable={true}
- throttleDrag={0}
- startDragRotate={0}
- throttleDragRotate={0}
- zoom={1}
- origin={true}
- padding={{"left":0,"top":0,"right":0,"bottom":0}}
- onDragStart={e => {
- e.set(frame.translate);
- // 自定義的拖拽開始邏輯
- }}
- onDrag={e => {
- frame.translate = e.beforeTranslate;
- e.target.style.transform = `translate(${e.beforeTranslate[0]}px, ${e.beforeTranslate[1]}px)`;
- // 自定義的拖拽結束邏輯
- }}
- />
- </div>;
- }
以上只是實現(xiàn)了基本的拖拽功能, 我們需要對拖拽位置信息做保存以便在預覽是實現(xiàn)“所搭即所得”的效果。位置信息會和其他屬性統(tǒng)一保存在組件的DSL數(shù)據(jù)中, 這塊在接下來內容中會詳細介紹。
對于拖拽器的進一步深入, 我們還可以設置參考線, 對齊線, 吸附等, 并且可以在拖拽的不同時期(比如onDragStart和onDragEnd)做不同的業(yè)務邏輯。這些 Moveable 都提供了對應的api支持, 大家可以參考使用。
物料中心設計
物料中心主要為大屏頁面提供“原材料”。為了設計健壯且通用的物料, 我們需要設計一套標準組件結構和屬性協(xié)議。并且為了方便物料管理和查詢, 我們還需要對物料進行分類, 我的分類如下:
- 可視化組件 (柱狀圖, 餅圖, 條形圖, 地圖可視化等)
- 修飾型組件 (圖片, 輪播圖, 修飾素材等)
- 文字類組件 (文本, 文本跑馬燈, 文字看板)
具體的物料庫演示如下:
這里我拿一個可視化組件的實現(xiàn)來舉例說明:
- import React, { memo, useEffect } from 'react'
- import { Chart } from '@antv/g2'
- import { colors } from '@/components/BasicShop/common'
- import { ChartConfigType } from './schema'
- interface ChartComponentProps extends ChartConfigType {
- id: string
- }
- const ChartComponent: React.FC<ChartComponentProps> = ({
- id, data, width, height,
- toggle, legendPosition, legendLayout, legendShape,
- labelColor, axisColor, multiColor, tipEvent, titleEvent,
- dataType, apiAddress, apiMethod, apiData, refreshTime,
- }) => {
- useEffect(() => {
- let timer:any = null;
- const chart = new Chart({
- container: `chart-${id}`,
- autoFit: true,
- width,
- height
- })
- // 數(shù)據(jù)過濾, 接入
- const dataX = data.map(item => ({ ...item, value: Number(item.value) }))
- chart.data(dataX)
- // 圖表屬性組裝
- chart.legend(
- toggle
- ? {
- position: legendPosition,
- layout: legendLayout,
- marker: {
- symbol: legendShape
- },
- }
- : false,
- )
- chart.tooltip({
- showTitle: false,
- showMarkers: false,
- })
- // 其他圖表信息源配置, 方法雷同, 此處省略
- // ...
- chart.render()
- }, [])
- return <div id={`chart-${id}`} />
- }
- export default memo(ChartComponent)
以上就是我們的基礎物料的實現(xiàn)模式, 可視化組件采用了g2, 當然大家也可以使用熟悉的echart, D3.js等. 不同物料既有通用的 props , 也有專有的 props, 取決于我們如何定義物料的Schema。
在設計 Schema 前我們需要明確組件的屬性劃分, 為了滿足組件配置的靈活性和通用性, 我做了如下劃分:
- 外觀屬性 (組件寬高, 顏色, 標簽, 展現(xiàn)模式等)
- 數(shù)據(jù)配置 (靜態(tài)數(shù)據(jù), 動態(tài)數(shù)據(jù))
- 事件/交互 (如單擊, 跳轉等)
有了以上劃分, 我們就可以輕松設計想要的通用Schema了。我們先來看看實現(xiàn)后的配置面板:
這些屬性項都是基于我們定義的schema配置項, 通過 解析引擎 動態(tài)渲染出來的, 有關 解析引擎 和配置面板, 我會在下面的章節(jié)和大家介紹。我們先看看組件的 schema 結構:
- const Chart: ChartSchema = {
- editAttrs: [
- {
- key: 'layerName',
- type: 'Text',
- cate: 'base',
- },
- {
- key: 'y',
- type: 'Number',
- cate: 'base',
- },
- ...DataConfig, // 數(shù)據(jù)配置項
- ...eventConfig, // 事件配置項
- ],
- config: {
- width: 200,
- height: 200,
- zIndex: 1,
- layerName: '柱狀圖',
- labelColor: 'rgba(188,200,212,1)',
- // ... 其他配置初始值
- multiColor: ['rgba(91, 143, 249, 1)', 'rgba(91, 143, 249, 1)', 'rgba(91, 143, 249,,1)', 'rgba(91, 143, 249, 1)'],
- data: [
- {
- name: 'A',
- value: 25,
- },
- {
- name: 'B',
- value: 66,
- }
- ],
- },
- }
其中 editAttrs 表示可編輯的屬性列表, config 為屬性的初始值, 當然大家也可以根據(jù)自己的喜好, 設計類似的通用schema。
我們通過以上設計的標準組件和標準schema, 就可以批量且高效的生產各種物料, 還可以輕松集成任何第三方可視化組件庫。
動態(tài)渲染器實現(xiàn)
我們都知道, 一個頁面中元素很多時會影響頁面整體的加載速度, 因為瀏覽器渲染頁面需要消耗CPU / GPU。對于可視化頁面來說, 每一個可視化組件都需要渲染大量的信息元, 這無疑會對頁面性能造成不小的影響, 所以我們需要設計一種機制, 讓組件異步加載到畫布上, 而不是一次性加載幾十個幾百個組件(這樣的話頁面會有大量的白屏時間, 用戶體驗極度下降)。
動態(tài)加載器就是提供了這樣一種機制, 保證組件的加載都是異步的, 一方面可以減少頁面體積, 另一方面用戶可以更早的看到頁面元素。目前我們熟的動態(tài)加載機制也有很多, Vue 和 React 生態(tài)都提供了開箱即用的解決方案(雖然我們可以用 webpack 自行設計這樣的動態(tài)模型, 此處為了提高行文效率, 我們直接基于現(xiàn)成方案封裝)。我們先看一下動態(tài)渲染組件的過程:
上面的演示可以細微的看出從左側組件菜單拖動某個組件圖標到畫布上后, 真正的組件才開始加載渲染。
這里我們以 umi3.0 提供的 dynamic 函數(shù)來最小化實現(xiàn)一個動態(tài)渲染器. 如果不熟悉 umi 生態(tài)的朋友, 也不用著急, 看完我的實現(xiàn)過程和原理之后, 就可以利用任何熟悉的動態(tài)加載機制實現(xiàn)它了。實現(xiàn)如下:
- import React, { useMemo, memo, FC } from 'react'
- import { dynamic } from 'umi'
- import LoadingComponent from '@/components/LoadingComponent'
- const DynamicFunc = (cpName: string, category: string) => {
- return dynamic({
- async loader() {
- // 動態(tài)加載組件
- const { default: Graph } = await import(`@/components/materies/${cpName}`)
- return (props: DynamicType) => {
- const { config, id } = props
- return <Graph {...config} id={id} />
- }
- },
- loading: () => <LoadingComponent />
- })
- }
- const DynamicRenderEngine: FC<DynamicType> = memo((props) => {
- const {
- type,
- config,
- // 其他配置...
- } = props
- const Dynamic = useMemo(() => {
- return DynamicFunc(config)
- }, [config])
- return <Dynamic {...props} />
- })
- export default DynamicRenderEngine
是不是很簡單? 當然我們也可以根據(jù)自身業(yè)務需要, 設計更復雜強大的動態(tài)渲染器。
配置面板設計
實現(xiàn)配置面板的前提是對組件 Schema 結構有一個系統(tǒng)的設計, 在介紹組件庫實現(xiàn)中我們介紹了通用組件 schema 的一個設計案例, 我們基于這樣的案例結構, 來實現(xiàn) 動態(tài)配置面板。
由上圖可以知道, 動態(tài)配置面板的一個核心要素就是 表單渲染器。表單渲染器的目的就是基于屬性配置列表 attrs 來動態(tài)渲染出對應的表單項。我之前寫了一篇文章詳細的介紹了表單設計器的技術實現(xiàn)的文章, 大家感興趣也可以參考一下: Dooring可視化之從零實現(xiàn)動態(tài)表單設計器。
我這里來簡單實現(xiàn)一個基礎的表單渲染器模型:
- const FormEditor = (props: FormEditorProps) => {
- const { attrs, defaultValue, onSave } = props;
- const onFinish = (values: Store) => {
- // 保存配置項數(shù)據(jù)
- onSave && onSave(values);
- };
- const handlechange = (value) => {
- // 更新邏輯
- }
- const [form] = Form.useForm();
- return (
- <Form
- form={form}
- {...formItemLayout}
- onFinish={onFinish}
- initialValues={defaultValue}
- onValuesChange={handlechange}
- >
- {
- attrs.map((item, i) => {
- return (
- <React.Fragment key={i}>
- {item.type === 'Number' && (
- <Form.Item label={item.name} name={item.key}>
- <InputNumber />
- </Form.Item>
- )}
- {item.type === 'Text' && (
- <Form.Item label={item.name} name={item.key}>
- <Input placeholder={item.placeholder} />
- </Form.Item>
- )}
- {item.type === 'TextArea' && (
- <Form.Item label={item.name} name={item.key}>
- <TextArea rows={4} />
- </Form.Item>
- )}
- // 其他配置類型
- </React.Fragment>
- );
- })}
- </Form>
- );
- };
如果大家想看更完整的配置面板實現(xiàn), 可以參考開源項目 H5-Dooring | H5可視化編輯器
我們可以看看最終的配置面板實現(xiàn)效果:
控制中心概述 & 功能輔助設計
控制中心的實現(xiàn)主要是業(yè)務層的, 沒有涉及太多復雜的技術, 所以這里我簡單介紹一下。因為可視化大屏頁面展示的信息有些可能是私密數(shù)據(jù), 只希望一部分人看到, 所以我們需要對頁面的訪問進行控制。其次由于企業(yè)內部業(yè)務戰(zhàn)略需求, 可能會對頁面進行各種驗證, 狀態(tài)校驗, 數(shù)據(jù)更新頻率等, 所以我們需要設計一套控制中心來管理。最基本的就是訪問控制, 如下:
功能輔助設計 主要是一些用戶操作上的優(yōu)化, 比如快捷鍵, 畫布縮放, 大屏快捷導航, 撤銷重做等操作, 這塊可以根據(jù)具體的產品需求來完善。大家后期設計搭建產品時也可以參考實現(xiàn)。
可視化大屏后期規(guī)劃和未來展望
為了實現(xiàn)更富有展現(xiàn)力, 滿足更多場景的可視化大屏引擎, 我們一方面需要提高引擎擴展性, 一方面需要完善物料生態(tài), 其次只要與時俱進, 提供更多智能化的場景功能, 比如搭建埋點, 數(shù)據(jù)預警等, 具體規(guī)劃如下:
- 豐富組件物料, 支持3D組件, 地理空間組件等
- 搭建埋點, 方便后期對組件進行分析
- 實現(xiàn)數(shù)據(jù)源, 事件機制閉環(huán)
- 支持用戶自定義組件
本文轉載自微信公眾號「趣談前端」,可以通過以下二維碼關注。轉載本文請聯(lián)系趣談前端公眾號。