可视化大屏编辑器, 开源!
hi, 大家好, 我是徐小夕.
5年前就开始着手设计和研发可视化大屏编辑器, 当时低代码在国内还没有现在那么火, 有人欢喜有人哀, 那个时候我就比较坚定的认为无码化搭建未来一定是个趋势, 能极大的帮助企业提高研发效率和降低研发成本, 所以 all in 做了2年, 上线了一个相对闭环的MVP可视化大屏搭建平台——V6.Dooring.
通过在技术社区不断的分享可视化搭建的技术实践和设计思路, 也参与了很多线上线下的技术分享, 慢慢市场终于“热了”起来.(机缘巧合)
从V6.Dooring的技术架构的设计, 到团队组建, 再到帮助企业做解决方案, 当时几乎所有的周末都花在这上面了, 想想收获还是挺大的, 接触到了形形色色的企业需求, 也不断完整着可视化大屏编辑器的功能, 最后推出了一个还算通用的解决方案:
当然上面介绍的还都不是这篇文章的重点.
重点是, 时隔4年, 我们打算把通用的可视化大屏解决方案, 开源!
一方面是供大家学习参考, 更好的解决企业自身的业务需求, 另一方面可以提供一个技术交流的平台, 大家可以对可视化搭建领域的技术实践, 提出自己的想法和观点, 共同打造智能化, 体验更好的搭建产品.
先上github地址: https://github.com/MrXujiang/v6.dooring.public
V6.Dooring开源大屏编辑器演示
其实最近几年我在公众号分享了很多零代码和可视化搭建的技术实现和产品设计:
这里为了让大家更近一步了解V6-Dooring可视化大屏编辑器, 我还是会从技术设计到产品解决方案设计的角度, 和大家详细分享一下, 让大家在学习我们可视化大屏开源方案的过程中, 对可视化搭建技术产品, 有更深入的理解.
如果大家觉得有帮助, 不要忘记点赞 + 收藏哦, 后面我会持续分享最干的互联网干货.
你将收获
可视化大屏产品设计思路
主流可视化图表库技术选型
大屏编辑器设计思路
大屏可视化编辑器Schema设计
用户数据自治探索
方案实现
1.可视化大屏产品设计思路
目前很多企业或多或少的面临“信息孤岛”问题,各个系统平台之间的数据无法实现互通共享,难以实现一体化的数据分析和实时呈现。
相比于传统手工定制的图表与数据仪表盘,可视化大屏制作平台的出现,可以打破抵消的定制开发, 数据分散的问题,通过数据采集、清洗、分析到直观实时的数据可视化展现,能够多方位、多角度、全景展现各项指标,实时监控,动态一目了然。
针对以上需求, 我们设计了一套可视化大屏解决方案, 具体包含如下几点:
上图是笔者4个月前设计的基本草图, 后期会持续更新. 通过以上的设计分解, 我们基本可以搭建一个可自己定制的数据大屏.
2.主流可视化图表库技术选型
目前我调研的已知主流可视化库有:
echart 一个基于 JavaScript 的老牌开源可视化图表库
D3.js 一个数据驱动的可视化库, 可以不需要其他任何框架独立运行在现代浏览器中,它结合强大的可视化组件来驱动 DOM 操作
antv 包含一套完整的可视化组件体系
Chart.js 基于 HTML5 的 简单易用的 JavaScript 图表库
metrics-graphics 建立在D3之上的可视化库, 针对可视化和布置时间序列数据进行了优化
C3.js 通过包装构造整个图表所需的代码,使生成基于D3的图表变得容易
我们使用以上任何一个库都可以实现我们的可视化大屏搭建的需求, 各位可以根据喜好来选择.
3.大屏编辑器设计思路
在上面的分析中我们知道一个大屏编辑器需要有个编辑器核心, 主要包含以下部分:
组件库
拖拽(自由拖拽, 参考线, 自动提示)
画布渲染器
属性编辑器
如下图所示:
组件库我们可以用任何组件封装方式(react/vue等), 这里沿用H5-Dooring的可视化组件设计方式, 对组件模型进行优化和设计.
类似的代码如下:
import { Chart } from '@antv/f2';
import React, { memo, useEffect, useRef } from 'react';
import styles from './index.less';
import { IChartConfig } from './schema';
const XChart = (props:IChartConfig) => {
const { data, color, size, paddingTop, title } = props;
const chartRef = useRef(null);
useEffect(() => {
const chart = new Chart({
el: chartRef.current || undefined,
pixelRatio: window.devicePixelRatio, // 指定分辨率
});
// step 2: 处理数据
const dataX = data.map(item => ({ ...item, value: Number(item.value) }));
// Step 2: 载入数据源
chart.source(dataX);
// Step 3:创建图形语法,绘制柱状图,由 genre 和 sold 两个属性决定图形位置,genre 映射至 x 轴,sold 映射至 y 轴
chart
.interval()
.position('name*value')
.color('name');
// Step 4: 渲染图表
chart.render();
}, [data]);
return (
<div className={styles.chartWrap}>
<div className={styles.chartTitle} style={{ color, fontSize: size, paddingTop }}>
{title}
</div>
<canvas ref={chartRef}></canvas>
</div>
);
};
export default memo(XChart);
以上只是一个简单的例子, 更具业务需求的复杂度我们往往会做更多的控制, 比如动画(animation), 事件(event), 数据获取(data inject)等.
当然实际应用中大屏展现的内容和形式远比这复杂, 我们从上图可以提炼出大屏页面的2个直观特征:
可视化组件集
空间坐标关系
因为我们可视化大屏载体是页面, 是html
, 所以还有另外一个特征: 事件/交互。综上我们总结出了可视化大屏的必备要素:
我们只要充分的理解了可视化大屏的组成和特征, 我们才能更好的设计可视化大屏搭建引擎, 基于以上分析, 我设计了一张基础引擎的架构图:
接下来我就带大家一起来拆解并实现上面的搭建引擎。
大屏搭建引擎核心功能实现
俗话说: “好的拆解是成功的一半”, 任何一个复杂任务或者系统, 我们只要能将其拆解成很多细小的子模块, 就能很好的解决并实现它. (学习也是一样)
接下来我们就逐一解决上述基础引擎的几个核心子模块:
拖拽器实现
物料中心设计
动态渲染器实现
配置面板设计
控制中心概述
功能辅助设计
1.拖拽器实现
拖拽器是可视化搭建引擎的核心模块, 也是用来解决上述提到的大屏页面特征中的“空间坐标关系”这一问题。我们先来看一下实现效果:
组件拖拽可以采用市面已有的 Dragable 等插件, 也可以采用 H5-Dooring 的智能网格拖拽. 这里笔者选择自由拖拽来实现. 已有的有:
rc-drag
sortablejs
react-dnd
react-dragable
vue-dragable
等等. 具体拖拽呈现流程如下:
具体拖拽流程就是:
使用H5 dragable API拖拽左侧组件(component data)进入目标容器(targetBox)
监听拖拽结束事件拿到拖拽事件传递的
data
来渲染真实的可视化组件可视化组件挂载,
schema
注入编辑面板, 编辑面板渲染组件属性编辑器拖拽, 属性修改, 更新
预览, 发布
组件的schema
参考H5-Dooring DSL设计.
2.物料中心设计
物料中心主要为大屏页面提供“原材料”。为了设计健壮且通用的物料, 我们需要设计一套标准组件结构和属性协议。并且为了方便物料管理和查询, 我们还需要对物料进行分类, 我的分类如下:
可视化组件 (柱状图, 饼图, 条形图, 地图可视化等)
修饰型组件 (图片, 轮播图, 修饰素材等)
文字类组件 (文本, 文本跑马灯, 文字看板)
具体的物料库演示如下:
这里我拿一个可视化组件的实现来举例说明:
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
, 就可以批量且高效的生产各种物料, 还可以轻松集成任何第三方可视化组件库。
3.动态渲染器实现
我们都知道, 一个页面中元素很多时会影响页面整体的加载速度, 因为浏览器渲染页面需要消耗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
是不是很简单? 当然我们也可以根据自身业务需要, 设计更复杂强大的动态渲染器。
4.配置面板设计
实现配置面板的前提是对组件 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可视化编辑器
我们可以看看最终的配置面板实现效果:
5.控制中心概述 & 功能辅助设计
控制中心的实现主要是业务层的, 没有涉及太多复杂的技术, 所以这里我简单介绍一下。因为可视化大屏页面展示的信息有些可能是私密数据, 只希望一部分人看到, 所以我们需要对页面的访问进行控制。其次由于企业内部业务战略需求, 可能会对页面进行各种验证, 状态校验, 数据更新频率等, 所以我们需要设计一套控制中心来管理。最基本的就是访问控制, 如下:
功能辅助设计 主要是一些用户操作上的优化, 比如快捷键, 画布缩放, 大屏快捷导航, 撤销重做等操作, 这块可以根据具体的产品需求来完善。大家后期设计搭建产品时也可以参考实现。
可视化大屏数据自治探索
目前我们实现的搭建平台可以静态的设计数据源, 也可以注入第三方接口, 如下:
我们可以调用内部接口来实时获取数据, 这块在可视化监控平台用的场景比较多, 方式如下:
参数(params
)编辑区可以自定义接口参数. 代码编辑器笔者这里推荐两款, 大家可以选用:
react-monaco-editor
react-codemirror2
使用以上之一可以实现mini
版vscode
, 大家也可以尝试一下.
辅助功能
可视化大屏一键截图 一键截图功能还是沿用H5-Dooring 的快捷截图方案, 主要用于对大屏的分享, 海报制作等需求, 我们可以使用以下任何一个组件实现:
dom-to-image
html2canvas
撤销重做
撤销重做功能我们可以使用已有的库比如react-undo
, 也可以自己实现, 实现原理:
有点链表的意思, 我们将每一个状态存储到数组中, 通过指针来实现撤销重做的功能, 如果要想更健壮一点, 我们可以设计一套“状态淘汰机制”, 设置可保留的最大状态数, 之前的自动淘汰(删除, 更高大上一点的叫出栈). 这样可以避免复杂操作中的大量状态存储, 节约浏览器内存.
标尺参考线 标尺和参考线这里我们自己实现, 通过动态dom渲染来实现参考线在缩放后的动态收缩, 实现方案核心如下:
arr.forEach(el => {
let dom = [...Array.from(el.querySelectorAll('.calibrationNumber'))][0] as HTMLElement;
if (dom) {
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(multiple + 0.1).toFixed(
1,
)})`;
}
});
详细源码可参考: H5-Dooring | 参考线设计源码
如果大家有好的建议也欢迎随时交流反馈, 开源不易, 别忘了star哦~
github地址: https://github.com/MrXujiang/v6.dooring.public
往期精彩:
分享10款开源工作流+思维导图项目
安利一款支持多人协同的多模态文档编辑器
又做了一款新产品!多模态文档Saas平台
点个在看你最好看