优秀的编程知识分享平台

网站首页 > 技术文章 正文

耗时6个月做的可视化大屏编辑器, 开源!

nanyue 2025-02-09 13:35:58 技术文章 4 ℃

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 (
    
{title}
); }; export default memo(XChart);

以上只是一个简单的例子, 更具业务需求的复杂度我们往往会做更多的控制, 比如动画(animation), 事件(event), 数据获取(data inject)等.

当然实际应用中大屏展现的内容和形式远比这复杂, 我们从上图可以提炼出大屏页面的2个直观特征:

  • 可视化组件集
  • 空间坐标关系

因为我们可视化大屏载体是页面, 是html 所以还有另外一个特征: 事件/交互综上我们总结出了可视化大屏的必备要素:

我们只要充分的理解了可视化大屏的组成和特征, 我们才能更好的设计可视化大屏搭建引擎, 基于以上分析, 我设计了一张基础引擎的架构图:

接下来我就带大家一起来拆解并实现上面的搭建引擎。

大屏搭建引擎核心功能实现

俗话说: “好的拆解是成功的一半”, 任何一个复杂任务或者系统, 我们只要能将其拆解成很多细小的子模块, 就能很好的解决并实现它. (学习也是一样)

接下来我们就逐一解决上述基础引擎的几个核心子模块:

  • 拖拽器实现
  • 物料中心设计
  • 动态渲染器实现
  • 配置面板设计
  • 控制中心概述
  • 功能辅助设计

1.拖拽器实现

拖拽器是可视化搭建引擎的核心模块, 也是用来解决上述提到的大屏页面特征中的“空间坐标关系”这一问题。我们先来看一下实现效果:

组件拖拽可以采用市面已有的 Dragable 等插件, 也可以采用 H5-Dooring 的智能网格拖拽. 这里笔者选择自由拖拽来实现. 已有的有:

  • rc-drag
  • sortablejs
  • react-dnd
  • react-dragable
  • vue-dragable

等等. 具体拖拽呈现流程如下:


具体拖拽流程就是:

  1. 使用H5 dragable API拖拽左侧组件(component data)进入目标容器(targetBox)
  2. 监听拖拽结束事件拿到拖拽事件传递的data来渲染真实的可视化组件
  3. 可视化组件挂载, schema注入编辑面板, 编辑面板渲染组件属性编辑器
  4. 拖拽, 属性修改, 更新
  5. 预览, 发布

组件的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 = ({
  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 
} export default memo(ChartComponent)

以上就是我们的基础物料的实现模式, 可视化组件采用了g2, 当然大家也可以使用熟悉的echartD3.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。对于可视化页面来说, 每一个可视化组件都需要渲染大量的信息元, 这无疑会对页面性能造成不小的影响, 所以我们需要设计一种机制, 让组件异步加载到画布上, 而不是一次性加载几十个几百个组件(这样的话页面会有大量的白屏时间, 用户体验极度下降)。

动态加载器就是提供了这样一种机制, 保证组件的加载都是异步的, 一方面可以减少页面体积, 另一方面用户可以更早的看到页面元素。目前我们熟的动态加载机制也有很多, VueReact 生态都提供了开箱即用的解决方案(虽然我们可以用 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 
      }
    },
    loading: () => 
  })
}

const DynamicRenderEngine: FC = memo((props) => {
  const { 
  type, 
  config, 
  // 其他配置... 
  } = props
  const Dynamic = useMemo(() => {
    return DynamicFunc(config)
  }, [config])

  return 
})

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 (
    
{ attrs.map((item, i) => { return ( {item.type === 'Number' && ( )} {item.type === 'Text' && ( )} {item.type === 'TextArea' && (