mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:45:18 +00:00
chore(bi): complete chart api (#2771)
* chore(bi): complete chart api * feat(bi): sample plugin for adding custom charts * chore: improve * chore: remove console.log
This commit is contained in:
parent
d8759dc199
commit
8bd6ef897c
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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: {
|
||||
|
@ -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] || {};
|
||||
|
@ -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<string, any>[];
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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 }),
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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 }),
|
||||
|
@ -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 });
|
||||
|
@ -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 = {};
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}, {}),
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
@ -0,0 +1 @@
|
||||
# @nocobase/plugin-sample-add-custom-chart
|
2
packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-sample-add-custom-charts/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
@ -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"
|
||||
}
|
||||
}
|
2
packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.d.ts
vendored
Normal file
2
packages/plugins/@nocobase/plugin-sample-add-custom-charts/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
@ -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;
|
||||
}
|
||||
}
|
@ -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',
|
||||
};
|
||||
}
|
||||
}
|
@ -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(),
|
||||
];
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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<EChartsInstance>();
|
||||
useEffect(() => {
|
||||
echartRef.current?.resize();
|
||||
});
|
||||
return <ReactEChartsComponent option={props} ref={(e) => (echartRef.current = e)} />;
|
||||
};
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
@ -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;
|
@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export { default } from './server';
|
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
@ -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;
|
28
yarn.lock
28
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"
|
||||
|
Loading…
Reference in New Issue
Block a user