相關文章: 從零開發一款可視化大屏製做平臺
演示地址: V6可視化大屏編輯器
注: ⚠️本文爲掘金社區首發簽約文章,未獲受權禁止轉載html
幾個月前我寫了一篇關於從零開發一款可視化大屏製做平臺 的文章, 簡單概述了一下可視化大屏搭建平臺的一些設計思路和效果演示, 這篇文章我會就 如何設計可視化大屏搭建引擎 這一主題, 詳細介紹一下實現原理。前端
按照我一貫的寫做風格, 我會在下面列出文章的大綱,以便你們有選擇且高效率的閱讀和學習:react
你們能夠輕鬆根據右側的文章導航, 快速定位到本身想看的位置, 接下來咱們開始進入正文。webpack
說到數據可視化, 想必你們多多少少稍接觸過, 從技術層面談, 最直觀的就是前端可視化框架, 好比:git
這些庫都能幫咱們輕鬆製做可視化圖表。github
從實用性的角度來談, 其最主要的意義就在於幫助用戶更好的分析和表達數據。因此說談到數據可視化, 更多的是和各類圖表打交道, 經過 數據 -> 圖表組合 -> 可視化頁面 這一業務流程, 就構成了咱們今天要研究的話題——設計可視化大屏搭建引擎。web
說到 「引擎」 這個詞也許有種莫名的高大上, 其實在互聯網技術中, 咱們常常會聽到各類相關的名詞,好比 「瀏覽器渲染引擎」 , 「規則引擎」 , 「圖像識別引擎」 等, 我以爲 「引擎」 的本質就是提供一套可靠的機制, 爲系統提供源源不斷的生產力。 因此咱們今天談的「可視化大屏搭建引擎」, 本質上也是提供一套搭建機制, 支撐咱們設計各類複雜的可視化頁面。api
爲了方便你們理解可視化搭建, 我這裏展現2張可視化大屏的頁面, 來和你們一塊兒分析一下可視化大屏的組成要素:瀏覽器
固然實際應用中大屏展示的內容和形式遠比這複雜, 咱們從上圖能夠提煉出大屏頁面的2個直觀特徵:markdown
由於咱們可視化大屏載體是頁面, 是html
, 因此還有另一個特徵: 事件/交互。綜上咱們總結出了可視化大屏的必備要素:
咱們只要充分的理解了可視化大屏的組成和特徵, 咱們才能更好的設計可視化大屏搭建引擎, 基於以上分析, 我設計了一張基礎引擎的架構圖:
接下來我就帶你們一塊兒來拆解並實現上面的搭建引擎。
俗話說: 「好的拆解是成功的一半」, 任何一個複雜任務或者系統, 咱們只要能將其拆解成不少細小的子模塊, 就能很好的解決並實現它. (學習也是同樣)
接下來咱們就逐一解決上述基礎引擎的幾個核心子模塊:
拖拽器是可視化搭建引擎的核心模塊, 也是用來解決上述提到的大屏頁面特徵中的「空間座標關係」這一問題。 咱們先來看一下實現效果:
有關拖拽的技術實現, 咱們能夠利用原生 js
實現, 也可使用第三方成熟的拖拽庫, 好比:
我以前也開源了一個輕量級自由拖拽庫 rc-drag , 效果以下:
有關它的技術實現能夠參考個人另外一篇文章: 輕鬆教你搞定組件的拖拽, 縮放, 多控制點伸縮和拖拽數據上報。 你們也能夠基於此作二次擴展和封裝。
咱們拖拽器的基本原型代碼以下:
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>;
}
複製代碼
以上只是實現了基本的拖拽功能, 咱們須要對拖拽位置信息作保存以便在預覽是實現「所搭即所得」的效果。位置信息會和其餘屬性統一保存在組件的DSL數據中, 這塊在接下來內容中會詳細介紹。
對於拖拽器的進一步深刻, 咱們還能夠設置參考線, 對齊線, 吸附等, 而且能夠在拖拽的不一樣時期(好比onDragStart和onDragEnd)作不一樣的業務邏輯。這些 Moveable 都提供了對應的api支持, 你們能夠參考使用。
物料中心主要爲大屏頁面提供「原材料」。爲了設計健壯且通用的物料, 咱們須要設計一套標準組件結構和屬性協議。而且爲了方便物料管理和查詢, 咱們還須要對物料進行分類, 個人分類以下:
具體的物料庫演示以下:
這裏我拿一個可視化組件的實現來舉例說明:
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
})
// 數據過濾, 接入
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)
複製代碼
以上就是咱們的基礎物料的實現模式, 可視化組件採用了g2
, 固然你們也可使用熟悉的echart
, D3.js
等. 不一樣物料既有通用的 props
, 也有專有的 props
, 取決於咱們如何定義物料的Schema
。
在設計 Schema
前咱們須要明確組件的屬性劃分, 爲了知足組件配置的靈活性和通用性, 我作了以下劃分:
有了以上劃分, 咱們就能夠輕鬆設計想要的通用Schema
了。 咱們先來看看實現後的配置面板:
這些屬性項都是基於咱們定義的schema
配置項, 經過 解析引擎 動態渲染出來的, 有關 解析引擎 和配置面板, 我會在下面的章節和你們介紹。 咱們先看看組件的 schema
結構:
const Chart: ChartSchema = {
editAttrs: [
{
key: 'layerName',
type: 'Text',
cate: 'base',
},
{
key: 'y',
type: 'Number',
cate: 'base',
},
...DataConfig, // 數據配置項
...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 爲屬性的初始值, 固然你們也能夠根據本身的喜愛, 設計相似的通用schema
。
咱們經過以上設計的標準組件和標準schema
, 就能夠批量且高效的生產各類物料, 還能夠輕鬆集成任何第三方可視化組件庫。
咱們都知道, 一個頁面中元素不少時會影響頁面總體的加載速度, 由於瀏覽器渲染頁面須要消耗CPU / GPU。對於可視化頁面來講, 每個可視化組件都須要渲染大量的信息元, 這無疑會對頁面性能形成不小的影響, 因此咱們須要設計一種機制, 讓組件異步加載到畫布上, 而不是一次性加載幾十個幾百個組件(這樣的話頁面會有大量的白屏時間, 用戶體驗極度降低)。
動態加載器就是提供了這樣一種機制, 保證組件的加載都是異步的, 一方面能夠減小頁面體積, 另外一方面用戶能夠更早的看到頁面元素。目前咱們熟的動態加載機制也有不少, Vue
和 React
生態都提供了開箱即用的解決方案(雖然咱們能夠用 webpack
自行設計這樣的動態模型, 此處爲了提升行文效率, 咱們直接基於現成方案封裝)。咱們先看一下動態渲染組件的過程:
上面的演示能夠細微的看出從左側組件菜單拖動某個組件圖標到畫布上後, 真正的組件纔開始加載渲染。
這裏咱們以 umi3.0
提供的 dynamic
函數來最小化實現一個動態渲染器. 若是不熟悉 umi
生態的朋友, 也不用着急, 看完個人實現過程和原理以後, 就能夠利用任何熟悉的動態加載機制實現它了。 實現以下:
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() {
// 動態加載組件
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
複製代碼
是否是很簡單? 固然咱們也能夠根據自身業務須要, 設計更復雜強大的動態渲染器。
實現配置面板的前提是對組件 Schema
結構有一個系統的設計, 在介紹組件庫實現中咱們介紹了通用組件 schema
的一個設計案例, 咱們基於這樣的案例結構, 來實現 動態配置面板。
由上圖能夠知道, 動態配置面板的一個核心要素就是 表單渲染器。 表單渲染器的目的就是基於屬性配置列表 attrs
來動態渲染出對應的表單項。我以前寫了一篇文章詳細的介紹了表單設計器的技術實現的文章, 你們感興趣也能夠參考一下: Dooring可視化之從零實現動態表單設計器。
我這裏來簡單實現一個基礎的表單渲染器模型:
const FormEditor = (props: FormEditorProps) => {
const { attrs, defaultValue, onSave } = props;
const onFinish = (values: Store) => {
// 保存配置項數據
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>
);
};
複製代碼
若是你們想看更完整的配置面板實現, 能夠參考開源項目 H5-Dooring | H5可視化編輯器
咱們能夠看看最終的配置面板實現效果:
控制中心的實現主要是業務層的, 沒有涉及太多複雜的技術, 因此這裏我簡單介紹一下。 由於可視化大屏頁面展現的信息有些多是私密數據, 只但願一部分人看到, 因此咱們須要對頁面的訪問進行控制。 其次因爲企業內部業務戰略需求, 可能會對頁面進行各類驗證, 狀態校驗, 數據更新頻率等, 因此咱們須要設計一套控制中心來管理。 最基本的就是訪問控制, 以下:
功能輔助設計 主要是一些用戶操做上的優化, 好比快捷鍵, 畫布縮放, 大屏快捷導航, 撤銷重作等操做, 這塊能夠根據具體的產品需求來完善。 你們後期設計搭建產品時也能夠參考實現。
爲了實現更富有展示力, 知足更多場景的可視化大屏引擎, 咱們一方面須要提升引擎擴展性, 一方面須要完善物料生態, 其次只要與時俱進, 提供更多智能化的場景功能, 好比搭建埋點, 數據預警等, 具體規劃以下:
若是你們對可視化搭建或者低代碼/零代碼感興趣, 也能夠參考我往期的文章或者在評論區交流你的想法和心得。