图表插件的开发是自定义可视化中一个关键的组成部分,它支持用户将个性化的数据展示变为现实。本文将深入探讨图表插件的内部结构和开发过程,引导您通过具体的示例理解项目架构并完成自定义图表插件的创建。(目前,自定义可视化功能还在内测阶段,若您对该功能有具体需求,欢迎联系火山引擎商务团队,以获取更多购买和合作的详细信息)。
注意:在您阅读并完成本文的配置项前,需要先完成环境配置、初始化、调试等准备工作,详细内容可以阅读《自定义可视化概述》一文。
在《自定义可视化概述》一文中,您完成了以下配置内容:
package.json
的 contributes
属性,并指定 Id、Html、Icon 等信息。本章节将通过示例插件的项目结构为您解释以上配置内容的详细信息:
├── assets // 存放资源的目录,比如 icon ├── dist // dev/build 的产物 │ └── index.html // src/index.html 的编译产物 │ └── main.umd.js // src/main.ts 的编译产物 ├── src // 源码目录 │ └── index.html // (存在自定义图表时)自定义图表的 html 文件 │ └── main.ts // 插件入口文件,用于本产品中注册插件 ├── package.json // 插件描述文件 ├── extension.config.js // 插件 CLI 配置文件
在该文件中,您需要导出 activate
和 deactivate
两个生命周期方法,本产品应用会在插件激活和退出时调用这些方法。
activate
方法中,您需要调用本产品应用提供的插件上下文 Context 对象中的注册方法完成图表信息注册。deactivate
方法用于在卸载插件时进行清理工作,默认为空。export function activate(context) { context.vizQueryChartRenderer.register({ id: 'extension-demo' }) } export function deactivate() {}
出于安全性考虑,插件入口文件运行在独立的 Javascript 沙箱中,因此您并不能在其中调用浏览器 API。
对于自定义图表这类需要渲染的扩展功能,需要额外提供 Html 文件用于加载图表,如 src/index.html
。
渲染实现相关的代码运行在 Iframe 沙箱中,您可以调用浏览器 API 实现需要的功能。
该文件是基于 package.json 的超集,通过 main
和 contributes
两个字段分别描述了插件入口文件位置以及插件包含了哪些扩展功能。
比如示例插件包的自定义图表,需要在 vizQuery.chart.renderer
扩展点中填入相应信息。
{ "name": "@datawind/extension-template-react-echarts", "version": "2.0.3", "main": "dist/main.umd.js", "scripts": { "dev": "datawind-extension dev", "build": "datawind-extension build" }, "dependencies": { "react": "^16.0.0", "react-dom": "^16.0.0", "echarts": "~5.3.2" }, "devDependencies": { "@types/react": "~18.0.9", "@types/react-dom": "^16.0.0", "typescript": "^4.3.2" }, "contributes": { "vizQuery.chart.renderer": [ { "id": "extension-demo", "name": "extension-react", "content": "dist/index.html", "icon": "" } ] } }
需要注意的是,在该描述文件中配置的文件路径(如图片 Icon,插件入口文件)并非源代码路径,而是编译产物路径,如 assets/**
下和 dist/**
下的文件路径。
extension.config.js 允许用户对 @datawind/extension-cli 的相关行为进行配置。
entry
- 配置插件的入口文件,示例代码中的配置表示以 src/main.ts
为入口文件编译为 dist/main.umd.js
。htmlEntry
- 配置 Html 入口文件,当前仅在自定义图表场景中使用,示例代码中的配置表示以 src/index.html
为入口文件编译为 dist/index.html
。devPort
- 配置插件的调试端口,默认 5000。配置示例如下:
module.exports = { entry: { main: 'src/main.ts', }, htmlEntry: { index: 'src/index.html', } }
在项目初始化后,您可以在项目文件夹中看到如下内容:
assets
- 存放资源文件src/main.ts
- 插件入口文件,需要在其中的 activate
函数注册图表插件src/index.html
- 图表 html 文件src/chart.ts
- 图表实现代码package.json
- 插件信息,需要在 contributes
中的 vizQuery.chart.renderer
扩展点配置图表信息extension.config.js
- 插件编译配置接下来,您可以进一步了解该项目的文件结构。
首先,对于图表插件,在插件入口文件的 activate
方法中调用 context.vizQueryChartRenderer.register
注册自定义渲染图表。
import { FieldMap } from './types' export const activate = (context) => { context.vizQueryChartRenderer.register({ id: "extension-demo", fields: [ { label: "维度", location: FieldMap.Dimension, fieldType: 0, }, { label: "指标", location: FieldMap.Measure, fieldType: 1, } ], constraints: [ { [FieldMap.Dimension]: [1, 1], [FieldMap.Measure]: [1, 1], } ] }) } export const deactivate = () => { }
其次,在register
方法调用中传入的对象中存在几个字段:
id
- 自定义图表id,需要与package.json对应的描述一致fields
- 自定义字段, Pie Chart
需要 Dimension
和 Measure
两个字段,通过 fieldType
指定该字段是否需要进行聚合。constraints
- 自定义描述绘制图表的字段个数限制,Pie Chart
支持使用1个维度与1个指标。type Constraints = Record<string, number[]>[]
每个 constraint 之间是 or 关系,constraint 内部的多个条件是 and 关系。
最后,更多示例请您参考下述代码:
context.vizQueryChartRenderer.register({ // ... constraints: [ { // 该配置表示:维度字段区域至少放置1个字段 [FieldMap.Dimension]: [1, Infinity], // 该配置表示:指标字段区域不限制 [FieldMap.Measure]: [0, Infinity], }, { // 该配置表示:维度字段区域只能放置1个字段 [FieldMap.Dimension]: [1], // 该配置表示:指标字段区域放置2个至10个字段 [FieldMap.Measure]: [2, 10], }, ], });
settings
- 自定义配置,Pie Chart
暂无自定义配置。您还可以阅读《自定义图表数据结构》了解相关字段的细节说明。
图表 Html,其中引入的资源文件 ./chart.ts
可以实现图表插件。该文件中引入的所有 <script type="module" src='*' />
,如果配置的src
为本地相对路径,将经过依赖分析,并在编译阶段和 Html 资源一同打包。如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>react echarts</title> </head> <body> <div id="root"></div> <script type="module" src="/src/index.tsx"></script> </body> </html>
通过图表实现代码:
window.onmessage
接收到 e.data
中 type
为 propertiesChange
的事件时,从 data.vizData
中获取图表数据并调用 setVizData
方法。transformData
方法将vizData
转化为echarts需要的数据结构。import React, { useEffect, useState, useRef } from 'react' import * as echarts from 'echarts' import { FieldMap } from './types' interface RenderData { name: string value: number } const useChart = (chartRef: React.RefObject<HTMLElement>, options: any) => { let chartInstance: echarts.ECharts const renderChart = () => { if (chartRef?.current) { chartInstance = echarts.init(chartRef?.current) chartInstance.setOption(options) } } useEffect(() => { renderChart() }, [options]) useEffect(() => { return () => { chartInstance && chartInstance.dispose() } }, []) return } const App: React.FC = () => { const chartRef = useRef<HTMLElement>(null) const [vizData, setVizData] = useState<any>(null) const [renderData, setRenderData] = useState<RenderData[]>() useEffect(() => { window.onmessage = ( e: MessageEvent<{ type: string data: { vizData: any; language: 'zh_CN' | 'en_US'; } }> ) => { const { type, data } = e.data if (type === 'propertiesChange') { setVizData(data.vizData) } } }, []) useEffect(() => { if (vizData) { const data = transformData(vizData) setRenderData(data) } }, [vizData]) // 将vizData转换为echarts数据结构 const transformData = (vizData) => { const { locationMap, datasets } = vizData const nameField = locationMap[FieldMap.Dimension]?.[0] const valueField = locationMap[FieldMap.Measure]?.[0] const data = datasets.map((item) => ({ name: item[nameField], value: parseFloat(item[valueField]), })) return data } const options = { title: { text: 'react echarts demo', left: 'center' }, tooltip: { trigger: 'item' }, legend: { left: "center", top: "bottom", orient: "horizontal", }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: renderData, } ] } useChart(chartRef, options) return ( <> <div style={{ width: "1280px", height: "480px" }} ref={chartRef} /> </> ) } export default App
VizData
是本产品用于描述图表的数据结构,相关介绍可以参考《自定义图表数据结构》一文。
由于图表实现代码运行在 Iframe 沙箱中,执行环境对其没有任何的 API 限制。所以您可以自由使用浏览器 API 来实现自定义图表。
对于自定义图表,需要在 package.json
的 contributes
中描述图表扩展点。
可视化查询自定义图表扩展点为 vizQuery.chart.renderer
:
id
- 与插件入口文件注册的图表 id
一致name
- 图表名称,在可视化查询的图表信息中出现icon
- 图表图标文件地址,作为可视化查询中的图表图标content
- 图表HTML文件地址{ "name": "@datawind/extension-template-react-echarts", "version": "2.0.3", "main": "dist/main.umd.js", "scripts": { "dev": "datawind-extension dev", "build": "datawind-extension build" }, "dependencies": { "react": "^16.0.0", "react-dom": "^16.0.0", "echarts": "~5.3.2" }, "devDependencies": { "@types/react": "~18.0.9", "@types/react-dom": "^16.0.0", "typescript": "^4.3.2" }, "contributes": { "vizQuery.chart.renderer": [ { "id": "extension-demo", "name": "extension-react", "content": "dist/index.html", "icon": "" } ] } }
示例代码中的 entry
表示需要将 src/main.ts
作为插件入口文件编译到 dist/main.umd.js
。
除了默认的插件入口编译配置,自定义图表还需要提供图表HTML编译配置。示例代码中的 htmlEntry
表示需要将 src/index.html
作为入口文件编译到 dist/index.html
。
module.exports = { entry: { main: 'src/main.ts', }, htmlEntry: { index: 'src/index.html', }, react: true, }