feat(bi): make more config visualizable (#2386)

* feat(bi): make more config visualizable

* refactor: improve config schema of chart

* chore: set default isStack of area chart as true
This commit is contained in:
YANG QIA 2023-08-04 15:31:56 +08:00 committed by GitHub
parent dcee24a1b0
commit 1ea1e903ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 296 deletions

View File

@ -7,28 +7,28 @@ import { QueryProps } from '../../renderer';
import { RenderProps } from '../chart';
export class Statistic extends AntdChart {
schema: ISchema = {
type: 'object',
properties: {
field: {
title: lang('Field'),
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required: true,
},
title: {
title: lang('Title'),
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
};
constructor() {
super('statistic', 'Statistic', AntdStatistic);
super({
name: 'statistic',
title: 'Statistic',
component: AntdStatistic,
config: [
{
property: 'field',
name: 'field',
title: 'Field',
required: true,
},
{
title: {
title: lang('Title'),
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
],
});
}
init(

View File

@ -4,7 +4,7 @@ import { Table as AntdTable } from 'antd';
export class Table extends AntdChart {
constructor() {
super('table', 'Table', AntdTable);
super({ name: 'table', title: 'Table', component: AntdTable });
}
getProps({ data, fieldProps, general, advanced }: RenderProps) {

View File

@ -3,6 +3,7 @@ import { FieldOption } from '../hooks';
import { QueryProps } from '../renderer';
import { parseField } from '../utils';
import { ISchema } from '@formily/react';
import configs, { AnySchemaProperties, ConfigProps } from './configs';
export type RenderProps = {
data: any[];
@ -58,16 +59,78 @@ export interface ChartType {
render: (props: RenderProps) => React.FC<any>;
}
type Config = (
| (ConfigProps & {
property?: string;
})
| string
)[];
export type ChartProps = {
name: string;
title: string;
component: React.FC<any>;
config?: Config;
};
export class Chart implements ChartType {
name: string;
title: string;
component: React.FC<any>;
schema = {};
config: Config;
configs = new Map<string, Function>();
constructor(name: string, title: string, component: React.FC<any>) {
constructor({ name, title, component, config }: ChartProps) {
this.name = name;
this.title = title;
this.component = component;
this.config = config;
this.addConfigs(configs);
}
/*
* Generate config schema according to this.config
* How to set up this.config:
* 1. string - the config function name in config.ts
* 2. object - { property: string, ...props }
* - property is the config function name in config.ts, and the other props are the arguments of the function
* 3. object - use the object directly as the properties of the schema
* 4. function - use the custom function to return the properties of the schema
*/
get schema() {
if (!this.config) {
return {};
}
const properties = this.config.reduce((properties, conf) => {
let schema: AnySchemaProperties = {};
if (typeof conf === 'string') {
const func = this.configs.get(conf);
schema = func?.() || {};
} else if (typeof conf === 'function') {
schema = conf();
} else {
if (conf.property) {
const func = this.configs.get(conf.property);
schema = func?.(conf) || {};
} else {
schema = conf as AnySchemaProperties;
}
}
return {
...properties,
...schema,
};
}, {} as AnySchemaProperties);
return {
type: 'object',
properties,
};
}
addConfigs(configs: { [key: string]: Function }) {
Object.entries(configs).forEach(([key, func]) => {
this.configs.set(key, func);
});
}
infer(

View File

@ -0,0 +1,43 @@
import { SchemaProperties } from '@formily/react';
import { lang } from '../locale';
export type FieldConfigProps = Partial<{
name: string;
title: string;
required: boolean;
defaultValue: any;
}>;
export type AnySchemaProperties = SchemaProperties<any, any, any, any, any, any, any, any>;
export type ConfigProps = FieldConfigProps | AnySchemaProperties | (() => AnySchemaProperties);
const selectField = ({ name, title, required, defaultValue }: FieldConfigProps) => {
return {
[name]: {
title: lang(title),
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required,
default: defaultValue,
},
};
};
const booleanField = ({ name, title, defaultValue = false }: FieldConfigProps) => {
return {
[name]: {
'x-content': lang(title),
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
default: defaultValue,
},
};
};
export default {
field: selectField,
booleanField,
};

View File

@ -5,7 +5,7 @@ import { G2PlotChart } from './g2plot';
export class Bar extends G2PlotChart {
constructor() {
super('bar', 'Bar Chart', G2PlotBar);
super({ name: 'bar', title: 'Bar Chart', component: G2PlotBar, config: ['isGroup', 'isStack', 'isPercent'] });
}
init(

View File

@ -0,0 +1,12 @@
import config, { FieldConfigProps } from '../configs';
const { field, 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 }),
isGroup: (props: FieldConfigProps) => booleanField({ name: 'isGroup', title: 'isGroup', ...props }),
};

View File

@ -7,63 +7,54 @@ import { FieldOption } from '../../hooks';
import { QueryProps } from '../../renderer';
export class DualAxes extends G2PlotChart {
schema: ISchema = {
type: 'object',
properties: {
xField: {
title: '{{t("xField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required: true,
},
yField: {
title: '{{t("yField")}}',
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems',
items: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
'x-component-props': {
style: {
minWidth: '200px',
},
},
required: true,
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
properties: {
add: {
type: 'void',
title: '{{t("Add")}}',
'x-component': 'ArrayItems.Addition',
},
},
},
},
};
constructor() {
super('dualAxes', 'Dual Axes Chart', G2DualAxes);
super({ name: 'dualAxes', title: 'Dual Axes Chart', component: G2DualAxes });
this.config = [
'xField',
{
yField: {
title: '{{t("yField")}}',
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems',
items: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
'x-component-props': {
style: {
minWidth: '200px',
},
},
required: true,
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
properties: {
add: {
type: 'void',
title: '{{t("Add")}}',
'x-component': 'ArrayItems.Addition',
},
},
},
},
];
}
init(

View File

@ -1,37 +1,18 @@
import { Chart, RenderProps } from '../chart';
import { Chart, ChartProps, RenderProps } from '../chart';
import { FieldOption } from '../../hooks';
import { QueryProps } from '../../renderer';
import { ISchema } from '@formily/react';
import configs from './configs';
export class G2PlotChart extends Chart {
schema: ISchema = {
type: 'object',
properties: {
xField: {
title: '{{t("xField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required: true,
},
yField: {
title: '{{t("yField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required: true,
},
seriesField: {
title: '{{t("seriesField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
},
},
};
constructor({ name, title, component, config }: ChartProps) {
super({
name,
title,
component,
config: ['xField', 'yField', 'seriesField', ...(config || [])],
});
this.addConfigs(configs);
}
init(
fields: FieldOption[],
@ -73,7 +54,7 @@ export class G2PlotChart extends Chart {
obj[key] = value;
if (key.includes('Field')) {
if (Array.isArray(value)) {
obj[key] = value.map((v) => (v.includes('.') ? replace(v) : v));
obj[key] = value.map((v) => (v?.includes('.') ? replace(v) : v));
} else if (typeof value === 'string' && value.includes('.')) {
obj[key] = replace(value);
}
@ -101,7 +82,7 @@ export class G2PlotChart extends Chart {
getReference() {
return {
title: this.title,
link: `https://g2plot.antv.antgroup.com/api/plots/${this.name}`,
link: `https://charts.ant.design/api/plots/${this.name}`,
};
}
}

View File

@ -1,16 +1,37 @@
import { Area, Column, Line, Scatter } from '@ant-design/plots';
import { Chart } from '../chart';
import { Bar } from './bar';
import { Pie } from './pie';
import { DualAxes } from './dualAxes';
import { G2PlotChart } from './g2plot';
export default [
new G2PlotChart('line', 'Line Chart', Line),
new G2PlotChart('area', 'Area Chart', Area),
new G2PlotChart('column', 'Column Chart', Column),
new G2PlotChart({
name: 'line',
title: 'Line Chart',
component: Line,
config: ['smooth', 'isStack'],
}),
new G2PlotChart({
name: 'area',
title: 'Area Chart',
component: Area,
config: [
'smooth',
{
property: 'isStack',
defaultValue: true,
},
'isPercent',
],
}),
new G2PlotChart({
name: 'column',
title: 'Column Chart',
component: Column,
config: ['isGroup', 'isStack', 'isPercent'],
}),
new Bar(),
new Pie(),
new DualAxes(),
new G2PlotChart('scatter', 'Scatter Chart', Scatter),
new G2PlotChart({ name: 'scatter', title: 'Scatter Chart', component: Scatter }),
];

View File

@ -5,30 +5,22 @@ import { FieldOption } from '../../hooks';
import { QueryProps } from '../../renderer';
export class Pie extends G2PlotChart {
schema: ISchema = {
type: 'object',
properties: {
angleField: {
title: '{{t("angleField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required: true,
},
colorField: {
title: '{{t("colorField")}}',
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': '{{ useChartFields }}',
required: true,
},
},
};
constructor() {
super('pie', 'Pie Chart', G2Pie);
super({ name: 'pie', title: 'Pie Chart', component: G2Pie });
this.config = [
{
property: 'field',
name: 'angleField',
title: 'angleField',
required: true,
},
{
property: 'field',
name: 'colorField',
title: 'colorField',
required: true,
},
];
}
init(

View File

@ -69,4 +69,8 @@ export default {
Max: '最大值',
'Please select a chart type.': '请选择图表类型',
Collection: '数据表',
isStack: '堆叠',
isPercent: '显示为百分比',
isGroup: '分组',
smooth: '平滑曲线',
};

155
yarn.lock

File diff suppressed because it is too large Load Diff