自定义插件化图表

Stephen Cui ... 2021-10-20 19:08:00 Datart
  • Datart
  • 插件化
  • 数据可视化
About 15 min

# 用户手册-自定义插件化图表

本章节主要描述了插件图表的核心概念、插件化图表加载的过程、详细的各项配置说明以及一个简单的例子作为补充说明。

# 1 图表插件化

# 1.1 核心概念

插件化图表:是一种“可插拔”使用的自定义图表,通过加载插件化图表使产品的使用更加灵活,增强软件的扩展性。同时,插件化提供了标准的输入接口,可以灵活适配由不同WEB绘图引擎渲染的图形,摆脱了传统的可视化BI产品在定制图表时只能通过二次开发方式的弊端。

自定义图表:是一种在官方提供的图表范围之外的图表制作方式。其原理是,在图表可“插拔”的基础上,Datart通过提供图表生命周期函数图表工具函数图表参数设置表单配置项图表元信息配置项 以及图表上下文信息等资源辅助进行图表的制作,方便快速开发基于Javascript渲染引擎的图表,满足不同种类的、更专业的图表需求,降低开发周期和准入门槛

WEB绘图引擎:这里专指基于浏览器运行环境的Javascript绘图类库以及图表组件库等,典型代表如D3JSEChartJS

图表参数设置表单: 通过配置相关数据及样式,方便图表绘图时对图表数据集进行处理以提高图表的扩展性。如数据配置项可对数据视图中的数据列进行操作,可聚合、格式化信息显示、别名以及着色等功能。样式配置项,记录用户的自定义图表样式配置,官方提供基础的表单组件,用户通过自定义JSON格式的表单配置项,来扩展图表参数设置表单以达到满足不同种类需求的目的,请参考本文<<参考配置项详细说明>>章节。

# 1.2 插件化图表时序图

插件化图表的加载是在项目启动的时候完成的,首先通过请求获取服务端custom-chart-plugins文件内的所有文件,然后加载符合要求的插件图表,然后即可在图表编辑器以及其他模块中使用。

插件图表加载时序图

Note: 请将制作好的自定义插件图表保存在服务端的custom-chart-plugins文件内,并刷新网页重新登陆系统即可,文件名不要和其他文件冲突,并且确保图表元信息配置项中的id的唯一性。

# 2 自定义图表

# 2.1 核心概念

在插件化图表的架构支持下,图表的制作者只需关注如何呈现某图表的核心业务逻辑,通过Datart可视化软件将行业数据根据图表进行丰富的展示。从本质上讲,Datart在图表展现层是作为一个绘图引擎和图表数据集的“适配器”,兼收并蓄是Datart插件化的核心理念,最终达到业务人员也可以制作出图表的目的。

一个最基本的自定义图表由以下几部分组成:

  • 图表生命周期函数
  • 图表工具函数
  • 图表参数设置表单配置项
  • 图表元信息配置项
  • 图表上下文信息
  • 图表数据集

图表生命周期函数: Datart图表回调函数,辅助绘制及更新图表。

  • OnMount:负责图表初始化,可初始化WEB绘图引擎相关资源,以及保存实例对象
  • OnUpdated:负责在图表数据集图表参数设置图表上下文信息等数据更新响应,可根据最新的数据更新图表的显示。
  • OnResize:负责在图表容器宽度或高度变化时响应,可根据最新的宽度和高度更新视图显示。
  • OnUnMount: 负责回收绘图引擎自身的非托管资源,如调用EChart实例的dispose (opens new window)方法可对EChart资源进行回收,避免内存溢出。

图表工具函数: 基于Datart的图表数据集以及图表参数配置项的工具函数集。

图表参数设置表单配置项: 本质上是一个表单生成器的语法定义部分,是图表参数设置表单的配置项,该配置项包含基于数据视图数据配置样式配置项以及其他设置的配置项。基于Datart预定义的基础表单组件图表工具函数中读取表单配置的工具方法,就可以组合出任何功能的图表配置表单。

基础表单组件:预定义的“单元”表单组件,包含布局组件、常规组件、基于数据配置的扩展组件等。通过,在图表参数设置表单配置项中指定属性comType来生成对应的组件,如指定comType为line,则会生成对应的线条设置组件。

表单生成器:通过定义表单描述文件来生成表单组件。Datart提供了一系列的表单语法基础表单组件来生成图表参数设置表单配置项,使用户可在图表编辑界面进行配置。

图表元信息配置项: 包含图表名称、图标信息、资源依赖等。(目前,暂时还没有将依赖项合并入图表元信息配置项中,将会在下一个版本中更新)

图表上下文信息: 包含当前运行期间的资源,包含当前工作环境(IFrame内部)Document、Window对象,图表宽度高度等信息。

图表数据集:这里专指Datart通过请求返回给前端的图表当前条件下的数据集合,这里强调的当前条件指的是满足图表参数设置表单中数据配置项的逻辑的数据集合。

更详细配置说明,请参考本文<<参考配置项详细说明>>章节。

# 2.2 自定义图表关系图

图表组件关系图

# 3 自定义图表示例

需求定义: 制作一个散点图,包含一个或多个的维度列数据,两个指标列数据作为散点图的X、Y轴。

完整代码及说明如下:

/**
 * Datart
 *
 * Copyright 2021
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function D3JSScatterChart({ dHelper }) {
  return {
    //【可选】扩展配置图表功能,可配合`数据视图`对数据处理
    config: {
      datas: [
        {
          label: 'metrics',
          key: 'metrics',
          required: true,
          type: 'group',
        },
        {
          label: 'deminsion',
          key: 'deminsion',
          required: true,
          type: 'aggregate',
        },
      ],
      styles: [
        {
          label: 'common.title',
          key: 'scatter',
          comType: 'group',
          rows: [
            {
              label: 'common.color',
              key: 'color',
              comType: 'fontColor',
            },
          ],
        },
      ],
      i18ns: [
        {
          lang: 'zh-CN',
          translation: {
            common: {
              title: '散点图配置',
              color: '气泡颜色',
            },
          },
        },
        {
          lang: 'en',
          translation: {
            common: {
              title: 'Scatter Setting',
              color: 'Bubble Color',
            },
          },
        },
      ],
    },

    isISOContainer: 'demo-d3js-scatter-chart',
    
    //【必须】加载D3JS绘图引擎,此处需给出CDN链接或者服务端相对资源地址即可
    dependency: ['https://d3js.org/d3.v5.min.js'],

    //【必须】设置图表的基本信息,icon可从Datart Icon图标中选取,暂时不支持自定义
    meta: {
      id: 'demo-d3js-scatter-chart',
      name: '[Plugin Demo] D3JS 散点图',
      icon: 'sandiantu',
      requirements: [
        {
          group: [0, 999],
          aggregate: 2,
        },
      ],
    },

    //【必须】Datart提供的生命周期函数,其他周期如onUpdated,onResize以及onUnMount
    onMount(options, context) {
      if (!context.document) {
        return;
      }

      const host = context.document.getElementById(options.containerId);
      var margin = { top: 10, right: 40, bottom: 30, left: 30 },
        width = context.width - margin.left - margin.right,
        height = context.height - margin.top - margin.bottom;

      // 初始化D3JS绘图区域
      this.chart = context.window.d3
        .select(host)
        .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
    },

    onUpdated(options, context) {
      if (!options.dataset || !options.dataset.columns || !options.config) {
        return;
      }

      // 获取当前绘图区域的宽高
      const clientWidth = context.window.innerWidth;
      const clientHeight = context.window.innerHeight;
      const margin = { top: 40, right: 40, bottom: 40, left: 40 };
      const width = clientWidth - margin.left - margin.right;
      const height = clientHeight - margin.top - margin.bottom;

      // 获取散点图数据及配置
      const { data, style } = this.getOptions(options.dataset, options.config);

      // 绘制基于百分比的横纵坐标轴散点图, 以下是D3JS绘图逻辑
      var x = context.window.d3
        .scaleLinear()
        .domain([0, 100]) // This is the min and the max of the data: 0 to 100 if percentages
        .range([0, width]); // This is the corresponding value I want in Pixel

      this.chart
        .append('g')
        .attr('transform', 'translate(0,' + height + ')')
        .call(context.window.d3.axisBottom(x));

      // X scale and Axis
      var y = context.window.d3
        .scaleLinear()
        .domain([0, 100]) // This is the min and the max of the data: 0 to 100 if percentages
        .range([height, 0]); // This is the corresponding value I want in Pixel

      this.chart.append('g').call(context.window.d3.axisLeft(y));

      // Add 3 dots for 0, 50 and 100%
      this.chart
        .selectAll('whatever')
        .data(data)
        .enter()
        .append('circle')
        .attr('cx', function (d) {
          return x(d.x);
        })
        .attr('cy', function (d) {
          return y(d.y);
        })
        .style('fill', style.color)
        .attr('r', 7);

      this.chart.selectAll('whatever').style('color', 'blue');
    },

    getOptions(dataset, config) {
      // 当前服务端返回的图表数据集
      const dataConfigs = config.datas || [];

      // 获取样式配置信息
      const styleConfigs = config.styles;
      const groupConfigs = dataConfigs
        .filter(c => c.type === 'group')
        .flatMap(config => config.rows || []);

      // 获取指标类型配置信息
      const aggregateConfigs = dataConfigs
        .filter(c => c.type === 'aggregate')
        .flatMap(config => config.rows || []);

      // 数据转换,根据Datart提供了Helper转换工具
      const objDataColumns = dHelper.transfromToObjectArray(
        dataset.rows,
        dataset.columns,
      );

      const data = objDataColumns.map(dc => {
        return {
          x: dc[dHelper.getValueByColumnKey(aggregateConfigs[0])],
          y: dc[dHelper.getValueByColumnKey(aggregateConfigs[1])],
        };
      });

      var xMinValue = Math.min(...data.map(o => o.x));
      var xMaxValue = Math.max(...data.map(o => o.y));

      var yMinValue = Math.min(...data.map(o => o.y));
      var yMaxValue = Math.max(...data.map(o => o.y));

      // 获取用户配置
      const color = dHelper.getStyleValueByGroup(
        styleConfigs,
        'scatter',
        'color',
      );

      return {
        style: {
          color,
        },
        data: data.map(d => {
          return {
            x: ((d.x || xMinValue - xMinValue) * 100) / (xMaxValue - xMinValue),
            y: ((d.y || yMinValue - yMinValue) * 100) / (yMaxValue - yMinValue),
          };
        }),
      };
    },
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

最终效果图如下:

D3JS散点图最终效果图

# 4 参考配置项详细说明

# 4.1 图表生命周期函数说明

图表生命周期状态图

# 4.1.1 onMount:

初始化图表事件,function(options, context) => void

# 4.1.2 onUpdated

更新图表事件,function(options, context) => void

# 4.1.3 onResize

图表容器大小变更事件,function(options, context) => void

# 4.1.4 onUnMount

销毁图表事件,function() => void

# 4.2 图表工具函数说明

DHelper (opens new window): Datart工具函数, 从自定义图表函数中获取

# 4.2.1 getStyleValue

从样式配置树对象中获取paths组合的路径值, function(styleConfigs, paths) => any

# 4.2.2 getColumnRenderName

ChartDataSectionField (opens new window)中获取当前数据列的显示名称,function(field) => string

# 4.2.3 getValueByColumnKey

获取当前数据列的唯一值,function(col) => string

# 4.2.4 valueFormatter

获取当前数据列的显示格式, function(config, value) => string

# 4.3 图表参数设置表单配置项说明

该配置项,需要在图表的config属性中设置。

  • config: ChartConfig (opens new window)
    • datas: ChartDataSectionConfig[]
    • styles: ChartStyleSectionConfig[]
    • settings: ChartStyleSectionConfig[]
    • i18ns: ChartI18NSectionConfig[]

# 4.3.1 ChartDataSectionConfig

图表配置之数据列配置,包含维度、指标、过滤、颜色、信息等,通过指定section type来显示不同作用的组件。

  • type: ChartDataSectionType (opens new window),
    • group: 维度类型组件,在当前组件中的数据列会进行数据分组操作。
    • aggregate: 指标类型组件,在当前组件中的数据列会进行聚合操作。
    • mixed: 混合类型组件,目前对于字符型(离散型)数据列会进行分组操作,对数字类型(连续型)数据列会进行聚合操作
    • filter:过滤器组件,对当前组件中数据列进行数据过滤操作。
    • info:信息类型组件,对当前类型的数据列进行聚合操作,数据不以图形的方式显示,只会在图表提示信息上显示
    • color: 颜色设置组件,对当前组件中的数据列进行第二次分组操作,并按分组后设置的颜色显示在图形上
  • maxFieldCount:number,可选项,当前组件中数据列的最大数量
  • allowSameField:boolean, 可选项,当前组件中是否允许相同的数据列
  • required:boolean, 可选项,用于标识当前组件中的数据列是否是图形绘制的必要数据列
  • rows: ChartDataSectionField (opens new window) [], 当前组件中的数据列集合
  • actions:ChartDataSectionFieldActionType (opens new window) [], 设置当前组件允许的数据列操作,如别名、着色、聚合、大小等操作。

# 4.3.2 ChartStyleSectionConfig

当前对象类型为ChartStyleSectionRow (opens new window),这是一个树形嵌套的结构,本身即是ChartStyleSectionRow类型,同时含有rows属性作为递归的子元素集合。

# 4.3.3 ChartI18NSectionConfig

图表I18N国际化,配置信息,Datart有全局国际化文件,也可以在图表中单独在此处设置国际化翻译。

  • lang: string, 国际化语言标识,如zh,us
  • translation:object,当前语言标识下的翻译属性。例如,在ChartStyleSectionConfig (opens new window)中的label属性中设置“common.label”时,则程序首先会在translation的common属性下label属性中读取,读取后会显示在组件外观中。
// Step 1. 首先定义一个国际化设置
i18ns: [
  {
    lang: 'zh',
    translation: {
      common: {
        title: '散点图配置',
        color: '气泡颜色',
      },
    },
  },
  {
    lang: 'en',
    translation: {
      common: {
        title: 'Scatter Setting',
        color: 'Bubble Color',
      },
    },
  },
],
...
// Step 2. 然后,假设我们设置了一个group类型的布局组件,其中包含了一个color类型的子组件
// Step 3. 当设置了label为"common.title"时,显示的名称会根据当前的语言环境设置读取响应的值,
// 例如中文时,color组件显示的名称为‘气泡颜色’,当英文环境时,显示的为‘Bubble Color’
{
  label: 'common.title', // 此处设置国际化文件对象查找路径
  key: 'scatter',
  comType: 'group',
  rows: [
    {
      label: 'common.color',
      key: 'color',
      comType: 'fontColor',
    },
  ],
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 4.4 基础表单组件说明

在程序中会映射成原子组件、布局组件、自定义组件等。如需要增加基础组件,请在ItemLayoout.tsx文件中注册。

# 4.4.1 ChartStyleSectionComponentType

基础表单组件的枚举类型

原子组件:

  • checkbox:单选框类型组件
  • input:输入框类型组件
  • switch:开关类型组件
  • select:单选下拉框组件
  • font:字体设置组件
  • inputNumber:数字类型输入框组件
  • slider:滑块组件

布局组件:

  • group:集合类型布局组件
  • tabs:tab类型布局组件

自定义组件:

  • cache:缓存设置相关组件
  • reference:图表参考线、参考区间组件
  • tableHeader:表格中的表格设置组件

# 4.4.2 ChartStyleSectionRowOption

基础表单组件的扩展设置属性,不同表单组件支持的属性不同。

  • min?: number | string,inputNumber和slider类型组件中支持的最小值设置
  • max?: number | string,inputNumber和slider类型组件中支持的最大值设置
  • step?: number | string,inputNumber和slider类型组件中支持的步进大小设置
  • type?: [ItemComponentType], 'modal', 布局类型容器是否作为弹窗模式
  • editable?: boolean,是否可编辑
  • modalSize?: string,弹窗大小设置
  • expand?: boolean,布局类型容器是否自动展开
  • items?: ChartStyleSelectorItem (opens new window) [], 当前列表值集合
  • hideLabel?: boolean,是否隐藏标签显示
  • style?: 组件css样式设置
  • getItems?: function(cols) => ChartStyleSelectorItem (opens new window) [], 获取当前组件列表值函数,如果设置了items则当前函数设置无效
  • needRefresh?: boolean,当组件值发生改变时,是否重新根据设置发起网络数据请求

# 4.5 图表元信息配置项说明

ChartMetadata (opens new window), 元信息配置,主要包含图表的id,名称,依赖信息以及图表渲染约束设置等内容。

  • id: 图表唯一值
  • name: 图表显示名称
  • icon: string, fonticon
  • requirements: ChartRequirement[], 图表绘制约束类型,分GROUPAGGREGATE类型

注意,将在接下来的版本中,将其移入到图表元信息配置项

  • dependency: string[], 图表依赖的第三方资源,可以是CDN地址,也可以是服务器相对资源地址。
  • isISOContainer: string, 对于某些含有相同依赖的图表而言,设置相同的isISOContainer的值,可共享依赖资源,避免多次的网络加载请求。

# 4.5.1 ChartRequirement

GROUPAGGREGATE可分别设置各自所需的数据列数目,作为图表绘制的约束条件。数字999代表不设置上限。

{
  group: [0, 999],
  aggregate: [0, 999],
},
1
2
3
4
Last update: November 20, 2021 03:49
Contributors: Stephen Cui