diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts new file mode 100644 index 0000000000..476716103d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts @@ -0,0 +1,166 @@ +import DataVisualizationPlugin from '..'; +import { Chart } from '../chart/chart'; +import { ChartGroup } from '../chart/group'; +import { FieldOption } from '../hooks'; + +describe('api', () => { + describe('group', () => { + const plugin = new DataVisualizationPlugin({}, null); + afterEach(() => { + plugin.charts = new ChartGroup(); + }); + + test('setGroup', () => { + const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + plugin.charts.setGroup('group', charts1); + expect(plugin.charts.charts.get('group')).toEqual(charts1); + + const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + plugin.charts.setGroup('group', charts2); + expect(plugin.charts.charts.get('group')).toEqual(charts2); + }); + + test('addGroup', () => { + const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + plugin.charts.setGroup('group1', charts1); + const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + plugin.charts.addGroup('group2', charts2); + expect(plugin.charts.charts.get('group1')).toEqual(charts1); + expect(plugin.charts.charts.get('group2')).toEqual(charts2); + }); + + test('add', () => { + const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + plugin.charts.setGroup('group', charts1); + + const chart = new Chart({ name: 'test2', title: 'Test2', component: null }); + plugin.charts.add('group', chart); + expect(plugin.charts.charts.get('group').length).toEqual(2); + expect(plugin.charts.charts.get('group')[1].name).toEqual('test2'); + }); + + test('getChartTypes', () => { + const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + plugin.charts.setGroup('group1', charts1); + const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + plugin.charts.setGroup('group2', charts2); + expect(plugin.charts.getChartTypes()).toEqual([ + { + label: 'group1', + children: [ + { + key: 'group1.test1', + label: 'Test1', + value: 'group1.test1', + }, + ], + }, + { + label: 'group2', + children: [ + { + key: 'group2.test2', + label: 'Test2', + value: 'group2.test2', + }, + ], + }, + ]); + }); + + test('getCharts', () => { + const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + plugin.charts.setGroup('group1', charts1); + const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + plugin.charts.setGroup('group2', charts2); + expect(plugin.charts.getCharts()).toEqual({ + 'group1.test1': charts1[0], + 'group2.test2': charts2[0], + }); + }); + + test('getChart', () => { + const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + plugin.charts.setGroup('group1', charts1); + const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + plugin.charts.setGroup('group2', charts2); + expect(plugin.charts.getChart('group1.test1')).toEqual(charts1[0]); + expect(plugin.charts.getChart('group2.test2')).toEqual(charts2[0]); + }); + }); + + describe('auto infer', () => { + const chart = new Chart({ name: 'test', title: 'Test', component: null }); + const fields = [ + { + name: 'price', + value: 'price', + type: 'number', + label: 'Price', + }, + { + name: 'count', + value: 'count', + type: 'number', + label: 'Count', + }, + { + name: 'title', + value: 'title', + type: 'string', + label: 'Title', + }, + { + name: 'name', + value: 'name', + type: 'string', + label: 'Name', + }, + { + name: 'createdAt', + value: 'createdAt', + type: 'date', + label: 'Created At', + }, + ] as FieldOption[]; + + test('1 measure, 1 dimension', () => { + const { xField, yField } = chart.infer(fields, { + measures: [{ field: ['price'] }], + dimensions: [{ field: ['title'] }], + }); + expect(yField.value).toEqual('price'); + expect(xField.value).toEqual('title'); + }); + + test('1 measure, 2 dimensions with date', () => { + const { xField, yField, seriesField } = chart.infer(fields, { + measures: [{ field: ['price'] }], + dimensions: [{ field: ['title'] }, { field: ['createdAt'] }], + }); + expect(yField.value).toEqual('price'); + expect(xField.value).toEqual('createdAt'); + expect(seriesField.value).toEqual('title'); + }); + + test('1 measure, 2 dimensions without date', () => { + const { xField, yField, seriesField } = chart.infer(fields, { + measures: [{ field: ['price'] }], + dimensions: [{ field: ['title'] }, { field: ['name'] }], + }); + expect(yField.value).toEqual('price'); + expect(xField.value).toEqual('title'); + expect(seriesField.value).toEqual('name'); + }); + + test('2 measures, 1 dimension', () => { + const { xField, yField, yFields } = chart.infer(fields, { + measures: [{ field: ['price'] }, { field: ['count'] }], + dimensions: [{ field: ['title'] }], + }); + expect(yField.value).toEqual('price'); + expect(xField.value).toEqual('title'); + expect(yFields.length).toEqual(2); + }); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-library.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-library.test.ts deleted file mode 100644 index 0ef816948f..0000000000 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-library.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Chart } from '../chart/chart'; -import { FieldOption } from '../hooks'; -const chart = new Chart('test', 'Test', null); - -describe('library', () => { - describe('auto infer', () => { - const fields = [ - { - name: 'price', - value: 'price', - type: 'number', - label: 'Price', - }, - { - name: 'count', - value: 'count', - type: 'number', - label: 'Count', - }, - { - name: 'title', - value: 'title', - type: 'string', - label: 'Title', - }, - { - name: 'name', - value: 'name', - type: 'string', - label: 'Name', - }, - { - name: 'createdAt', - value: 'createdAt', - type: 'date', - label: 'Created At', - }, - ] as FieldOption[]; - - test('1 measure, 1 dimension', () => { - const { xField, yField } = chart.infer(fields, { - measures: [{ field: ['price'] }], - dimensions: [{ field: ['title'] }], - }); - expect(yField.value).toEqual('price'); - expect(xField.value).toEqual('title'); - }); - - test('1 measure, 2 dimensions with date', () => { - const { xField, yField, seriesField } = chart.infer(fields, { - measures: [{ field: ['price'] }], - dimensions: [{ field: ['title'] }, { field: ['createdAt'] }], - }); - expect(yField.value).toEqual('price'); - expect(xField.value).toEqual('createdAt'); - expect(seriesField.value).toEqual('title'); - }); - - test('1 measure, 2 dimensions without date', () => { - const { xField, yField, seriesField } = chart.infer(fields, { - measures: [{ field: ['price'] }], - dimensions: [{ field: ['title'] }, { field: ['name'] }], - }); - expect(yField.value).toEqual('price'); - expect(xField.value).toEqual('title'); - expect(seriesField.value).toEqual('name'); - }); - - test('2 measures, 1 dimension', () => { - const { xField, yField, yFields } = chart.infer(fields, { - measures: [{ field: ['price'] }, { field: ['count'] }], - dimensions: [{ field: ['title'] }], - }); - expect(yField.value).toEqual('price'); - expect(xField.value).toEqual('title'); - expect(yFields.length).toEqual(2); - }); - }); -}); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/transformers.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/transformers.ts index f3f81e8319..2668f23c72 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/transformers.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/transformers.ts @@ -1,8 +1,10 @@ import dayjs from 'dayjs'; +export type Transformer = (val: any, locale?: string) => string | number; + const transformers: { [key: string]: { - [key: string]: (val: any, locale?: string) => string | number; + [key: string]: Transformer; }; } = { datetime: { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts index b790d29e52..3d7298970c 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts @@ -1,9 +1,7 @@ import { AntdChart } from './antd'; import { Statistic as AntdStatistic } from 'antd'; import { lang } from '../../locale'; -import { FieldOption } from '../../hooks'; -import { QueryProps } from '../../renderer'; -import { RenderProps } from '../chart'; +import { ChartType, RenderProps } from '../chart'; export class Statistic extends AntdChart { constructor() { @@ -30,16 +28,7 @@ export class Statistic extends AntdChart { }); } - init( - fields: FieldOption[], - { - measures, - dimensions, - }: { - measures?: QueryProps['measures']; - dimensions?: QueryProps['dimensions']; - }, - ) { + init: ChartType['init'] = (fields, { measures, dimensions }) => { const { yField } = this.infer(fields, { measures, dimensions }); return { general: { @@ -47,7 +36,7 @@ export class Statistic extends AntdChart { title: yField?.label, }, }; - } + }; getProps({ data, fieldProps, general, advanced }: RenderProps) { const record = data[0] || {}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts index 5aa5ef7462..39ae62c896 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts @@ -4,14 +4,16 @@ import { QueryProps } from '../renderer'; import { parseField } from '../utils'; import { ISchema } from '@formily/react'; import configs, { AnySchemaProperties, Config } from './configs'; +import { Transformer } from '../block/transformers'; export type RenderProps = { - data: any[]; + data: Record[]; general: any; advanced: any; fieldProps: { - [field: string]: FieldOption & { - transformer: (val: any) => string; + [field: string]: { + label: string; + transformer: Transformer; }; }; }; @@ -161,7 +163,7 @@ export class Chart implements ChartType { * Accept the information that the chart component needs to render, * process it and return the props of the chart component. */ - getProps(props: RenderProps) { + getProps(props: RenderProps): any { return props; } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts index 83387f886c..b943a91e54 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts @@ -46,4 +46,7 @@ const booleanField = ({ name, title, defaultValue = false }: FieldConfigProps) = export default { field: selectField, booleanField, + xField: (props: FieldConfigProps) => selectField({ name: 'xField', title: 'xField', required: true, ...props }), + yField: (props: FieldConfigProps) => selectField({ name: 'yField', title: 'yField', required: true, ...props }), + seriesField: (props: FieldConfigProps) => selectField({ name: 'seriesField', title: 'seriesField', ...props }), }; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts index 2479e3e542..211bb19765 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts @@ -1,23 +1,13 @@ -import { FieldOption } from '../../hooks'; -import { QueryProps } from '../../renderer'; import { Bar as G2PlotBar } from '@ant-design/plots'; import { G2PlotChart } from './g2plot'; +import { ChartType } from '../chart'; export class Bar extends G2PlotChart { constructor() { super({ name: 'bar', title: 'Bar Chart', component: G2PlotBar, config: ['isGroup', 'isStack', 'isPercent'] }); } - init( - fields: FieldOption[], - { - measures, - dimensions, - }: { - measures?: QueryProps['measures']; - dimensions?: QueryProps['dimensions']; - }, - ) { + init: ChartType['init'] = (fields, { measures, dimensions }) => { const { xField, yField, seriesField } = this.infer(fields, { measures, dimensions }); return { general: { @@ -26,5 +16,5 @@ export class Bar extends G2PlotChart { seriesField: seriesField?.value, }, }; - } + }; } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts index ea6b7c61fe..45ec934422 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts @@ -1,10 +1,7 @@ import config, { FieldConfigProps } from '../configs'; -const { field, booleanField } = config; +const { booleanField } = config; export default { - xField: (props: FieldConfigProps) => field({ name: 'xField', title: 'xField', required: true, ...props }), - yField: (props: FieldConfigProps) => field({ name: 'yField', title: 'yField', required: true, ...props }), - seriesField: (props: FieldConfigProps) => field({ name: 'seriesField', title: 'seriesField', ...props }), isStack: (props: FieldConfigProps) => booleanField({ name: 'isStack', title: 'isStack', ...props }), smooth: (props: FieldConfigProps) => booleanField({ name: 'smooth', title: 'smooth', ...props }), isPercent: (props: FieldConfigProps) => booleanField({ name: 'isPercent', title: 'isPercent', ...props }), diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts index ce44980222..437c2d18c8 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts @@ -1,10 +1,7 @@ -import { ISchema } from '@formily/react'; import { G2PlotChart } from './g2plot'; -import { RenderProps } from '../chart'; +import { ChartType, RenderProps } from '../chart'; import React from 'react'; import { DualAxes as G2DualAxes } from '@ant-design/plots'; -import { FieldOption } from '../../hooks'; -import { QueryProps } from '../../renderer'; export class DualAxes extends G2PlotChart { constructor() { @@ -57,16 +54,7 @@ export class DualAxes extends G2PlotChart { ]; } - init( - fields: FieldOption[], - { - measures, - dimensions, - }: { - measures?: QueryProps['measures']; - dimensions?: QueryProps['dimensions']; - }, - ) { + init: ChartType['init'] = (fields, { measures, dimensions }) => { const { xField, yFields } = this.infer(fields, { measures, dimensions }); return { general: { @@ -74,7 +62,7 @@ export class DualAxes extends G2PlotChart { yField: yFields?.map((f) => f.value).slice(0, 2) || [], }, }; - } + }; render({ data, general, advanced, fieldProps }: RenderProps) { const props = this.getProps({ data, general, advanced, fieldProps }); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts index f34eb400ad..ae6a5f9c1b 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts @@ -1,4 +1,4 @@ -import { Chart, ChartProps, RenderProps } from '../chart'; +import { Chart, ChartProps, ChartType, RenderProps } from '../chart'; import { FieldOption } from '../../hooks'; import { QueryProps } from '../../renderer'; import configs from './configs'; @@ -14,19 +14,7 @@ export class G2PlotChart extends Chart { this.addConfigs(configs); } - init( - fields: FieldOption[], - { - measures, - dimensions, - }: { - measures?: QueryProps['measures']; - dimensions?: QueryProps['dimensions']; - }, - ): { - general?: any; - advanced?: any; - } { + init: ChartType['init'] = (fields, { measures, dimensions }) => { const { xField, yField, seriesField } = this.infer(fields, { measures, dimensions }); return { general: { @@ -35,7 +23,7 @@ export class G2PlotChart extends Chart { seriesField: seriesField?.value, }, }; - } + }; getProps({ data, general, advanced, fieldProps }: RenderProps) { const meta = {}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts index c03040c49b..dc75e17b37 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts @@ -1,8 +1,6 @@ -import { ISchema } from '@formily/react'; import { G2PlotChart } from './g2plot'; import { Pie as G2Pie } from '@ant-design/plots'; -import { FieldOption } from '../../hooks'; -import { QueryProps } from '../../renderer'; +import { ChartType } from '../chart'; export class Pie extends G2PlotChart { constructor() { @@ -23,16 +21,7 @@ export class Pie extends G2PlotChart { ]; } - init( - fields: FieldOption[], - { - measures, - dimensions, - }: { - measures?: QueryProps['measures']; - dimensions?: QueryProps['dimensions']; - }, - ) { + init: ChartType['init'] = (fields, { measures, dimensions }) => { const { xField, yField } = this.infer(fields, { measures, dimensions }); return { general: { @@ -40,5 +29,5 @@ export class Pie extends G2PlotChart { angleField: yField?.value, }, }; - } + }; } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts index 91624d8dc9..d947c4b80e 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts @@ -44,6 +44,12 @@ export class ChartGroup { children, }); }); + // Put group named "Built-in" at the first + const index = result.findIndex((item) => item.label === lang('Built-in')); + if (index > -1) { + const [item] = result.splice(index, 1); + result.unshift(item); + } return result; } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx index 47a4637301..bef3d7e7cf 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx @@ -31,6 +31,7 @@ import { createRendererSchema, getField, getSelectedFields } from '../utils'; import { getConfigSchema, querySchema, transformSchema } from './schemas/configure'; import { useChartTypes, useCharts, useDefaultChartType } from '../chart/group'; import { FilterDynamicComponent } from './FilterDynamicComponent'; +import { css } from '@emotion/css'; const { Paragraph, Text } = Typography; export type ChartConfigCurrent = { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx index 60a2f87281..2412223557 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx @@ -5,7 +5,6 @@ import g2plot from './chart/g2plot'; import antd from './chart/antd'; import { ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block'; import { ChartRenderer, ChartRendererProvider } from './renderer'; - class DataVisualizationPlugin extends Plugin { public charts: ChartGroup = new ChartGroup(); @@ -25,4 +24,7 @@ class DataVisualizationPlugin extends Plugin { export default DataVisualizationPlugin; export { Chart } from './chart/chart'; -export type { ChartType } from './chart/chart'; +export type { ChartType, RenderProps, ChartProps } from './chart/chart'; +export type { FieldOption } from './hooks'; +export type { QueryProps } from './renderer'; +export { ChartConfigContext } from './configure/ChartConfigure'; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx index 8bdd18b1e5..907e945f4f 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx @@ -41,7 +41,7 @@ export const ChartRenderer: React.FC & { if (!props[name]) { const field = getField(fields, name.split('.')); const transformer = transformers[name]; - props[name] = { ...field, transformer }; + props[name] = { label: field?.label || name, transformer }; } return props; }, {}), diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts index 0dd7038ae4..595314b3e7 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts @@ -303,9 +303,7 @@ export const cacheMiddleware = async (ctx: Context, next: Next) => { } await next(); if (useCache) { - console.log(uid, ctx.body); await cache.set(uid, ctx.body, cacheConfig?.ttl || 30); - console.log(cache.get(uid)); } }; @@ -329,10 +327,9 @@ export const query = async (ctx: Context, next: Next) => { parseBuilder, queryData, postProcess, - ])(ctx, async () => {}); + ])(ctx, next); } catch (err) { ctx.app.logger.error('charts query: ', err); ctx.throw(500, err); } - await next(); }; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/.npmignore b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/.npmignore new file mode 100644 index 0000000000..65f5e8779f --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/README.md b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/README.md new file mode 100644 index 0000000000..a387f6f98c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/README.md @@ -0,0 +1 @@ +# @nocobase/plugin-sample-add-custom-chart diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.d.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.d.ts new file mode 100644 index 0000000000..6c459cbac4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.js b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.js new file mode 100644 index 0000000000..b6e3be70e6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/package.json b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/package.json new file mode 100644 index 0000000000..d70b0a50b4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/package.json @@ -0,0 +1,15 @@ +{ + "name": "@nocobase/plugin-sample-add-custom-charts", + "version": "0.14.0-alpha.7", + "main": "dist/server/index.js", + "devDependencies": { + "echarts": "^5.4.3", + "echarts-for-react": "^3.0.2" + }, + "peerDependencies": { + "@nocobase/client": "0.x", + "@nocobase/server": "0.x", + "@nocobase/test": "0.x", + "@nocobase/plugin-data-visualization": "0.x" + } +} diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.d.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.d.ts new file mode 100644 index 0000000000..c41081ddc6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.js b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.js new file mode 100644 index 0000000000..972842039a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/bar.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/bar.ts new file mode 100644 index 0000000000..a9fcfdeca9 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/bar.ts @@ -0,0 +1,40 @@ +import { RenderProps } from '@nocobase/plugin-data-visualization/client'; +import { ECharts } from './echarts'; + +export class Bar extends ECharts { + constructor() { + super({ + name: 'bar', + title: 'Bar Chart', + series: { type: 'bar' }, + }); + this.config = [ + { + property: 'yField', + title: 'xField', + }, + { + property: 'xField', + title: 'yField', + }, + 'seriesField', + ]; + } + + getProps({ data, general, advanced, fieldProps }: RenderProps) { + const props = super.getProps({ data, general, advanced, fieldProps }); + const xLabel = fieldProps[general.xField]?.label; + const yLabel = fieldProps[general.yField]?.label; + props.xAxis = { + ...props.xAxis, + type: 'value', + name: yLabel, + }; + props.yAxis = { + ...props.yAxis, + type: 'category', + name: xLabel, + }; + return props; + } +} diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/echarts.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/echarts.ts new file mode 100644 index 0000000000..5108724cdb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/echarts.ts @@ -0,0 +1,100 @@ +import { Chart, ChartProps, ChartType, RenderProps } from '@nocobase/plugin-data-visualization/client'; +import { ReactECharts } from './react-echarts'; +import { EChartsReactProps } from 'echarts-for-react'; +import deepmerge from 'deepmerge'; +import './transform'; + +export class ECharts extends Chart { + series: any; + + constructor({ + name, + title, + series, + config, + }: { + name: string; + title: string; + series: any; + config?: ChartProps['config']; + }) { + super({ + name, + title, + component: ReactECharts, + config: ['xField', 'yField', 'seriesField', ...(config || [])], + }); + this.series = series; + } + + init: ChartType['init'] = (fields, { measures, dimensions }) => { + const { xField, yField, seriesField } = this.infer(fields, { measures, dimensions }); + return { + general: { + xField: xField?.value, + yField: yField?.value, + seriesField: seriesField?.value, + }, + }; + }; + + getProps({ data, general, advanced, fieldProps }: RenderProps): EChartsReactProps['option'] { + const { xField, yField, seriesField, ...others } = general; + const xLabel = fieldProps[xField]?.label; + const yLabel = fieldProps[yField]?.label; + let seriesName = [yLabel]; + if (seriesField) { + seriesName = Array.from(new Set(data.map((row: any) => row[seriesField]))).map((value) => value || 'null'); + } + return deepmerge( + { + legend: { + data: seriesName, + }, + tooltip: { + data: seriesName, + }, + dataset: [ + { + dimensions: [xField, ...(seriesField ? seriesName : [yField])], + source: data, + }, + { + transform: [ + { + type: 'data-visualization:transform', + config: { fieldProps }, + }, + { + type: 'data-visualization:toSeries', + config: { xField, yField, seriesField }, + }, + ], + }, + ], + series: seriesName.map((name) => ({ + name, + datasetIndex: 1, + ...this.series, + ...others, + })), + xAxis: { + name: xLabel, + type: 'category', + }, + yAxis: { + name: yLabel, + }, + animation: false, + }, + advanced, + ); + } + + getReference() { + return { + title: 'ECharts', + link: 'https://echarts.apache.org/en/option.html', + }; + } +} diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/index.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/index.ts new file mode 100644 index 0000000000..6060b34483 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/index.ts @@ -0,0 +1,23 @@ +import { Bar } from './bar'; +import { ECharts } from './echarts'; +import { Pie } from './pie'; + +export default [ + new ECharts({ + name: 'line', + title: 'Line Chart', + series: { type: 'line' }, + }), + new ECharts({ + name: 'column', + title: 'Column Chart', + series: { type: 'bar' }, + }), + new ECharts({ + name: 'area', + title: 'Area Chart', + series: { type: 'line', areaStyle: {} }, + }), + new Bar(), + new Pie(), +]; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/pie.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/pie.ts new file mode 100644 index 0000000000..fbd0cba819 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/pie.ts @@ -0,0 +1,64 @@ +import { ChartType, RenderProps } from '@nocobase/plugin-data-visualization/client'; +import { ECharts } from './echarts'; +import deepmerge from 'deepmerge'; + +export class Pie extends ECharts { + constructor() { + super({ + name: 'pie', + title: 'Pie Chart', + series: { type: 'pie' }, + }); + this.config = [ + { + property: 'field', + name: 'angleField', + title: 'angleField', + required: true, + }, + { + property: 'field', + name: 'colorField', + title: 'colorField', + required: true, + }, + ]; + } + + init: ChartType['init'] = (fields, { measures, dimensions }) => { + const { xField, yField } = this.infer(fields, { measures, dimensions }); + return { + general: { + colorField: xField?.value, + angleField: yField?.value, + }, + }; + }; + + getProps({ data, general, advanced, fieldProps }: RenderProps) { + return deepmerge( + { + legend: {}, + tooltip: {}, + dataset: [ + { + dimensions: [general.colorField, general.angleField], + source: data, + }, + { + transform: { + type: 'data-visualization:transform', + config: { fieldProps }, + }, + }, + ], + series: { + name: fieldProps[general.angleField]?.label, + datasetIndex: 1, + ...this.series, + }, + }, + advanced, + ); + } +} diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/react-echarts.tsx b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/react-echarts.tsx new file mode 100644 index 0000000000..9abc142d82 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/react-echarts.tsx @@ -0,0 +1,10 @@ +import React, { useEffect } from 'react'; +import ReactEChartsComponent, { EChartsInstance, EChartsReactProps } from 'echarts-for-react'; + +export const ReactECharts = (props: EChartsReactProps['option']) => { + const echartRef = React.useRef(); + useEffect(() => { + echartRef.current?.resize(); + }); + return (echartRef.current = e)} />; +}; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/transform.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/transform.ts new file mode 100644 index 0000000000..bfd077b3ef --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/echarts/transform.ts @@ -0,0 +1,48 @@ +import { RenderProps } from '@nocobase/plugin-data-visualization/client'; +import * as echarts from 'echarts'; + +echarts.registerTransform({ + type: 'data-visualization:transform', + transform: function (params: any) { + const fieldProps = params.config.fieldProps as RenderProps['fieldProps']; + const data = params.upstream.cloneRawData(); + return { + data: data.map((row: any) => { + Object.entries(fieldProps).forEach(([key, props]) => { + if (props.transformer) { + row[key] = props.transformer(row[key]); + } + }); + return row; + }), + }; + } as any, +}); + +echarts.registerTransform({ + type: 'data-visualization:toSeries', + transform: function (params: any) { + const data = params.upstream.cloneRawData(); + const { xField, yField, seriesField } = params.config || {}; + if (!seriesField) { + return { data }; + } + const dataMap = data.reduce((map: any, row: any) => { + if (!map[row[xField]]) { + map[row[xField]] = { [row[seriesField]]: row[yField] }; + return map; + } + map[row[xField]][row[seriesField]] = row[yField]; + return map; + }, {}); + const result = Object.entries(dataMap).map(([key, value]: any) => { + return { + [xField]: key, + ...value, + }; + }); + return { + data: result, + }; + }, +}); diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/index.tsx b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/index.tsx new file mode 100644 index 0000000000..8e8501c6c8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/client/index.tsx @@ -0,0 +1,19 @@ +import { Plugin } from '@nocobase/client'; +import DataVisualizationPlugin from '@nocobase/plugin-data-visualization/client'; +import echarts from './echarts'; + +export class PluginSampleAddCustomChartClient extends Plugin { + async afterAdd() { + // await this.app.pm.add() + } + + async beforeLoad() { + const plugin = this.app.pm.get(DataVisualizationPlugin); + plugin.charts.addGroup('ECharts', echarts); + } + + // You can get and modify the app instance here + async load() {} +} + +export default PluginSampleAddCustomChartClient; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/index.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/index.ts new file mode 100644 index 0000000000..7e74612df8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/index.ts @@ -0,0 +1,2 @@ +export * from './server'; +export { default } from './server'; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/collections/.gitkeep b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/collections/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/index.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/index.ts new file mode 100644 index 0000000000..b68aea57f9 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/plugin.ts new file mode 100644 index 0000000000..d2b550a8d8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-sample-add-custom-charts/src/server/plugin.ts @@ -0,0 +1,19 @@ +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class PluginSampleAddCustomChartServer extends Plugin { + afterAdd() {} + + beforeLoad() {} + + async load() {} + + async install(options?: InstallOptions) {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default PluginSampleAddCustomChartServer; diff --git a/yarn.lock b/yarn.lock index 5cc4b8a5dd..ea9f86eb83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10734,6 +10734,22 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" +echarts-for-react@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1" + integrity sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA== + dependencies: + fast-deep-equal "^3.1.3" + size-sensor "^1.0.1" + +echarts@^5.4.3: + version "5.4.3" + resolved "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz#f5522ef24419164903eedcfd2b506c6fc91fb20c" + integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA== + dependencies: + tslib "2.3.0" + zrender "5.4.4" + editions@^2.2.0: version "2.3.1" resolved "https://registry.npmmirror.com/editions/-/editions-2.3.1.tgz#3bc9962f1978e801312fbd0aebfed63b49bfe698" @@ -22512,6 +22528,11 @@ tslib@1.9.3: version "1.9.3" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -24017,6 +24038,13 @@ zip-stream@^4.1.0: compress-commons "^4.1.0" readable-stream "^3.6.0" +zrender@5.4.4: + version "5.4.4" + resolved "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz#8854f1d95ecc82cf8912f5a11f86657cb8c9e261" + integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw== + dependencies: + tslib "2.3.0" + zustand@^4.3.1: version "4.3.8" resolved "https://registry.npmmirror.com/zustand/-/zustand-4.3.8.tgz#37113df8e9e1421b0be1b2dca02b49b76210e7c4"