You need to enable JavaScript to run this app.
导航
自定义图表插件结构与开发
最近更新时间:2024.04.19 10:46:25首次发布时间:2024.03.26 15:53:05

1.概述

图表插件的开发是自定义可视化中一个关键的组成部分,它支持用户将个性化的数据展示变为现实。本文将深入探讨图表插件的内部结构和开发过程,引导您通过具体的示例理解项目架构并完成自定义图表插件的创建。(目前,自定义可视化功能还在内测阶段,若您对该功能有具体需求,欢迎联系火山引擎商务团队,以获取更多购买和合作的详细信息)。
注意:在您阅读并完成本文的配置项前,需要先完成环境配置、初始化、调试等准备工作,详细内容可以阅读《自定义可视化概述》一文。

2.图表插件结构

《自定义可视化概述》一文中,您完成了以下配置内容:

  • 通过 Html 和 Javascript 实现了图表渲染。
  • 通过结构描述文件 package.jsoncontributes 属性,并指定 Id、Html、Icon 等信息。
  • 在插件的入口文件中,调用 Context API 在本产品中注册了该自定义渲染的相关逻辑。

本章节将通过示例插件的项目结构为您解释以上配置内容的详细信息:

├── 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 配置文件

2.1 插件入口文件 src/main.ts

在该文件中,您需要导出 activatedeactivate 两个生命周期方法,本产品应用会在插件激活和退出时调用这些方法。

  • activate 方法中,您需要调用本产品应用提供的插件上下文 Context 对象中的注册方法完成图表信息注册。
  • deactivate 方法用于在卸载插件时进行清理工作,默认为空。
export function activate(context) {
    context.vizQueryChartRenderer.register({    
        id: 'extension-demo'
    })
}
export function deactivate() {} 

出于安全性考虑,插件入口文件运行在独立的 Javascript 沙箱中,因此您并不能在其中调用浏览器 API。

2.2 渲染实现

对于自定义图表这类需要渲染的扩展功能,需要额外提供 Html 文件用于加载图表,如 src/index.html
渲染实现相关的代码运行在 Iframe 沙箱中,您可以调用浏览器 API 实现需要的功能。

2.3 插件描述文件 package.json

该文件是基于 package.json 的超集,通过 maincontributes 两个字段分别描述了插件入口文件位置以及插件包含了哪些扩展功能。
比如示例插件包的自定义图表,需要在 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/** 下的文件路径。

2.4 插件 CLI 配置文件 extension.config.js

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',
  }
}

3.图表插件开发

在项目初始化后,您可以在项目文件夹中看到如下内容:

  • assets - 存放资源文件
  • src/main.ts - 插件入口文件,需要在其中的 activate 函数注册图表插件
  • src/index.html - 图表 html 文件
  • src/chart.ts - 图表实现代码
  • package.json - 插件信息,需要在 contributes 中的 vizQuery.chart.renderer 扩展点配置图表信息
  • extension.config.js - 插件编译配置

接下来,您可以进一步了解该项目的文件结构。

3.1 src/main.ts

首先,对于图表插件,在插件入口文件的 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需要 DimensionMeasure 两个字段,通过 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 暂无自定义配置。

您还可以阅读《自定义图表数据结构》了解相关字段的细节说明。

3.2 src/index.html

图表 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>

3.3 src/App.tsx

通过图表实现代码:

  • window.onmessage 接收到 e.datatypepropertiesChange 的事件时,从 data.vizData 中获取图表数据并调用 setVizData 方法。
  • 通过transformData方法将vizData转化为echarts需要的数据结构。
  • 使用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 来实现自定义图表。

3.4 package.json

对于自定义图表,需要在 package.jsoncontributes 中描述图表扩展点。
可视化查询自定义图表扩展点为 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": ""
      }
    ]
  }
}

3.5 extension.config.js

示例代码中的 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,
}