相关文章: 从零开发一款可视化大屏制做平台
演示地址: 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可视化编辑器
咱们能够看看最终的配置面板实现效果:
控制中心的实现主要是业务层的, 没有涉及太多复杂的技术, 因此这里我简单介绍一下。 由于可视化大屏页面展现的信息有些多是私密数据, 只但愿一部分人看到, 因此咱们须要对页面的访问进行控制。 其次因为企业内部业务战略需求, 可能会对页面进行各类验证, 状态校验, 数据更新频率等, 因此咱们须要设计一套控制中心来管理。 最基本的就是访问控制, 以下:
功能辅助设计 主要是一些用户操做上的优化, 好比快捷键, 画布缩放, 大屏快捷导航, 撤销重作等操做, 这块能够根据具体的产品需求来完善。 你们后期设计搭建产品时也能够参考实现。
为了实现更富有展示力, 知足更多场景的可视化大屏引擎, 咱们一方面须要提升引擎扩展性, 一方面须要完善物料生态, 其次只要与时俱进, 提供更多智能化的场景功能, 好比搭建埋点, 数据预警等, 具体规划以下:
若是你们对可视化搭建或者低代码/零代码感兴趣, 也能够参考我往期的文章或者在评论区交流你的想法和心得。