fix(bi): g2plot render wrong when fields contain .
(#2363)
* fix: g2plot render wrong when fields contains `.` * fix: build * fix: test * fix: dual axes bug * chore: add comment * fix: code style
This commit is contained in:
parent
07f1f16ea0
commit
f82b6a9d38
@ -5,7 +5,7 @@ import { Button, Card } from 'antd';
|
|||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useChartsTranslation } from './locale';
|
import { useChartsTranslation } from './locale';
|
||||||
import { ChartLibraryContext, useToggleChartLibrary } from './renderer';
|
import { ChartLibraryContext, useToggleChartLibrary } from './chart/library';
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
import { Chart } from '../chart/chart';
|
||||||
import { FieldOption } from '../hooks';
|
import { FieldOption } from '../hooks';
|
||||||
import { infer } from '../renderer';
|
const chart = new Chart('test', 'Test', null);
|
||||||
|
|
||||||
describe('library', () => {
|
describe('library', () => {
|
||||||
describe('auto infer', () => {
|
describe('auto infer', () => {
|
||||||
@ -37,7 +38,7 @@ describe('library', () => {
|
|||||||
] as FieldOption[];
|
] as FieldOption[];
|
||||||
|
|
||||||
test('1 measure, 1 dimension', () => {
|
test('1 measure, 1 dimension', () => {
|
||||||
const { xField, yField } = infer(fields, {
|
const { xField, yField } = chart.infer(fields, {
|
||||||
measures: [{ field: ['price'] }],
|
measures: [{ field: ['price'] }],
|
||||||
dimensions: [{ field: ['title'] }],
|
dimensions: [{ field: ['title'] }],
|
||||||
});
|
});
|
||||||
@ -46,7 +47,7 @@ describe('library', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('1 measure, 2 dimensions with date', () => {
|
test('1 measure, 2 dimensions with date', () => {
|
||||||
const { xField, yField, seriesField } = infer(fields, {
|
const { xField, yField, seriesField } = chart.infer(fields, {
|
||||||
measures: [{ field: ['price'] }],
|
measures: [{ field: ['price'] }],
|
||||||
dimensions: [{ field: ['title'] }, { field: ['createdAt'] }],
|
dimensions: [{ field: ['title'] }, { field: ['createdAt'] }],
|
||||||
});
|
});
|
||||||
@ -56,7 +57,7 @@ describe('library', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('1 measure, 2 dimensions without date', () => {
|
test('1 measure, 2 dimensions without date', () => {
|
||||||
const { xField, yField, seriesField } = infer(fields, {
|
const { xField, yField, seriesField } = chart.infer(fields, {
|
||||||
measures: [{ field: ['price'] }],
|
measures: [{ field: ['price'] }],
|
||||||
dimensions: [{ field: ['title'] }, { field: ['name'] }],
|
dimensions: [{ field: ['title'] }, { field: ['name'] }],
|
||||||
});
|
});
|
||||||
@ -66,7 +67,7 @@ describe('library', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('2 measures, 1 dimension', () => {
|
test('2 measures, 1 dimension', () => {
|
||||||
const { xField, yField, yFields } = infer(fields, {
|
const { xField, yField, yFields } = chart.infer(fields, {
|
||||||
measures: [{ field: ['price'] }, { field: ['count'] }],
|
measures: [{ field: ['price'] }, { field: ['count'] }],
|
||||||
dimensions: [{ field: ['title'] }],
|
dimensions: [{ field: ['title'] }],
|
||||||
});
|
});
|
||||||
|
@ -26,9 +26,10 @@ import {
|
|||||||
useTransformers,
|
useTransformers,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
import { useChartsTranslation } from '../locale';
|
import { useChartsTranslation } from '../locale';
|
||||||
import { ChartRenderer, ChartRendererContext, useChartTypes, useCharts, useDefaultChartType } from '../renderer';
|
import { ChartRenderer, ChartRendererContext } from '../renderer';
|
||||||
import { createRendererSchema, getField, getSelectedFields } from '../utils';
|
import { createRendererSchema, getField, getSelectedFields } from '../utils';
|
||||||
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
|
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
|
||||||
|
import { useChartTypes, useCharts, useDefaultChartType } from '../chart/library';
|
||||||
const { Paragraph, Text } = Typography;
|
const { Paragraph, Text } = Typography;
|
||||||
|
|
||||||
export type ChartConfigCurrent = {
|
export type ChartConfigCurrent = {
|
||||||
@ -98,7 +99,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
}
|
}
|
||||||
const query = form.values.query;
|
const query = form.values.query;
|
||||||
const selectedFields = getSelectedFields(fields, query);
|
const selectedFields = getSelectedFields(fields, query);
|
||||||
const { general, advanced } = init(selectedFields, query);
|
const { general, advanced } = chart.init(selectedFields, query);
|
||||||
if (general || overwrite) {
|
if (general || overwrite) {
|
||||||
form.values.config.general = general;
|
form.values.config.general = general;
|
||||||
}
|
}
|
||||||
@ -364,7 +365,7 @@ ChartConfigure.Config = function Config() {
|
|||||||
const charts = useCharts();
|
const charts = useCharts();
|
||||||
const getChartFields = useChartFields(fields);
|
const getChartFields = useChartFields(fields);
|
||||||
const getReference = (chartType: string) => {
|
const getReference = (chartType: string) => {
|
||||||
const reference = charts[chartType]?.reference;
|
const reference = charts[chartType]?.getReference?.();
|
||||||
if (!reference) return '';
|
if (!reference) return '';
|
||||||
const { title, link } = reference;
|
const { title, link } = reference;
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { Chart } from '../chart';
|
||||||
|
|
||||||
|
export class AntdChart extends Chart {
|
||||||
|
getReference() {
|
||||||
|
return {
|
||||||
|
title: this.title,
|
||||||
|
link: `https://ant.design/components/${this.name}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
import { Statistic } from './statistic';
|
||||||
|
import { Table } from './table';
|
||||||
|
|
||||||
|
export default [new Statistic(), new Table()];
|
@ -0,0 +1,64 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
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';
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { yField } = this.infer(fields, { measures, dimensions });
|
||||||
|
return {
|
||||||
|
general: {
|
||||||
|
field: yField?.value,
|
||||||
|
title: yField?.label,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getProps({ data, fieldProps, general, advanced }: RenderProps) {
|
||||||
|
const record = data[0] || {};
|
||||||
|
const field = general?.field;
|
||||||
|
const props = fieldProps[field];
|
||||||
|
return {
|
||||||
|
value: record[field],
|
||||||
|
formatter: props?.transformer,
|
||||||
|
...general,
|
||||||
|
...advanced,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import { RenderProps } from '../chart';
|
||||||
|
import { AntdChart } from './antd';
|
||||||
|
import { Table as AntdTable } from 'antd';
|
||||||
|
|
||||||
|
export class Table extends AntdChart {
|
||||||
|
constructor() {
|
||||||
|
super('table', 'Table', AntdTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProps({ data, fieldProps, general, advanced }: RenderProps) {
|
||||||
|
const columns = data.length
|
||||||
|
? Object.keys(data[0]).map((item) => ({
|
||||||
|
title: fieldProps[item]?.label || item,
|
||||||
|
dataIndex: item,
|
||||||
|
key: item,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
const dataSource = data.map((item: any) => {
|
||||||
|
Object.keys(item).map((key: string) => {
|
||||||
|
const props = fieldProps[key];
|
||||||
|
if (props?.transformer) {
|
||||||
|
item[key] = props.transformer(item[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
const pageSize = advanced?.pagination?.pageSize || 10;
|
||||||
|
return {
|
||||||
|
bordered: true,
|
||||||
|
size: 'middle',
|
||||||
|
pagination:
|
||||||
|
dataSource.length < pageSize
|
||||||
|
? false
|
||||||
|
: {
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
dataSource,
|
||||||
|
columns,
|
||||||
|
...general,
|
||||||
|
...advanced,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
134
packages/plugins/data-visualization/src/client/chart/chart.ts
Normal file
134
packages/plugins/data-visualization/src/client/chart/chart.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FieldOption } from '../hooks';
|
||||||
|
import { QueryProps } from '../renderer';
|
||||||
|
import { parseField } from '../utils';
|
||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
|
||||||
|
export type RenderProps = {
|
||||||
|
data: any[];
|
||||||
|
general: any;
|
||||||
|
advanced: any;
|
||||||
|
fieldProps: {
|
||||||
|
[field: string]: FieldOption & {
|
||||||
|
transformer: (val: any) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ChartType {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
component: React.FC<any>;
|
||||||
|
schema: ISchema;
|
||||||
|
infer: (
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
xField: FieldOption;
|
||||||
|
yField: FieldOption;
|
||||||
|
seriesField: FieldOption;
|
||||||
|
yFields: FieldOption[];
|
||||||
|
};
|
||||||
|
init?: (
|
||||||
|
fields: FieldOption[],
|
||||||
|
query: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
general?: any;
|
||||||
|
advanced?: any;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* getProps
|
||||||
|
* Accept the information that the chart component needs to render,
|
||||||
|
* process it and return the props of the chart component.
|
||||||
|
*/
|
||||||
|
getProps: (props: RenderProps) => any;
|
||||||
|
getReference?: () => {
|
||||||
|
title: string;
|
||||||
|
link: string;
|
||||||
|
};
|
||||||
|
render: (props: RenderProps) => React.FC<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Chart implements ChartType {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
component: React.FC<any>;
|
||||||
|
schema = {};
|
||||||
|
|
||||||
|
constructor(name: string, title: string, component: React.FC<any>) {
|
||||||
|
this.name = name;
|
||||||
|
this.title = title;
|
||||||
|
this.component = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
infer(
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
let xField: FieldOption;
|
||||||
|
let yField: FieldOption;
|
||||||
|
let seriesField: FieldOption;
|
||||||
|
let yFields: FieldOption[];
|
||||||
|
const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => {
|
||||||
|
if (selected.alias) {
|
||||||
|
return fields.find((f) => f.value === selected.alias);
|
||||||
|
}
|
||||||
|
const { alias } = parseField(selected.field);
|
||||||
|
return fields.find((f) => f.value === alias);
|
||||||
|
};
|
||||||
|
if (measures?.length) {
|
||||||
|
yField = getField(fields, measures[0]);
|
||||||
|
yFields = measures.map((m) => getField(fields, m));
|
||||||
|
}
|
||||||
|
if (dimensions) {
|
||||||
|
if (dimensions.length === 1) {
|
||||||
|
xField = getField(fields, dimensions[0]);
|
||||||
|
} else if (dimensions.length > 1) {
|
||||||
|
// If there is a time field, it is used as the x-axis field by default.
|
||||||
|
let xIndex: number;
|
||||||
|
dimensions.forEach((d, i) => {
|
||||||
|
const field = getField(fields, d);
|
||||||
|
if (['date', 'time', 'datetime'].includes(field?.type)) {
|
||||||
|
xField = field;
|
||||||
|
xIndex = i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (xIndex) {
|
||||||
|
// If there is a time field, the other field is used as the series field by default.
|
||||||
|
const index = xIndex === 0 ? 1 : 0;
|
||||||
|
seriesField = getField(fields, dimensions[index]);
|
||||||
|
} else {
|
||||||
|
xField = getField(fields, dimensions[0]);
|
||||||
|
seriesField = getField(fields, dimensions[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { xField, yField, seriesField, yFields };
|
||||||
|
}
|
||||||
|
|
||||||
|
getProps(props: RenderProps) {
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ data, general, advanced, fieldProps }: RenderProps) {
|
||||||
|
return () =>
|
||||||
|
React.createElement(this.component, {
|
||||||
|
...this.getProps({ data, general, advanced, fieldProps }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
import { FieldOption } from '../../hooks';
|
||||||
|
import { QueryProps } from '../../renderer';
|
||||||
|
import { Bar as G2PlotBar } from '@ant-design/plots';
|
||||||
|
import { G2PlotChart } from './g2plot';
|
||||||
|
|
||||||
|
export class Bar extends G2PlotChart {
|
||||||
|
constructor() {
|
||||||
|
super('bar', 'Bar Chart', G2PlotBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { xField, yField, seriesField } = this.infer(fields, { measures, dimensions });
|
||||||
|
return {
|
||||||
|
general: {
|
||||||
|
xField: yField?.value,
|
||||||
|
yField: xField?.value,
|
||||||
|
seriesField: seriesField?.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { G2PlotChart } from './g2plot';
|
||||||
|
import { 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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { xField, yFields } = this.infer(fields, { measures, dimensions });
|
||||||
|
return {
|
||||||
|
general: {
|
||||||
|
xField: xField?.value,
|
||||||
|
yField: yFields?.map((f) => f.value).slice(0, 2) || [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ data, general, advanced, fieldProps }: RenderProps) {
|
||||||
|
const props = this.getProps({ data, general, advanced, fieldProps });
|
||||||
|
const { data: _data } = props;
|
||||||
|
return () =>
|
||||||
|
React.createElement(this.component, {
|
||||||
|
...props,
|
||||||
|
data: [_data, _data],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
import { Chart, RenderProps } from '../chart';
|
||||||
|
import { FieldOption } from '../../hooks';
|
||||||
|
import { QueryProps } from '../../renderer';
|
||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
|
||||||
|
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 }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
init(
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
): {
|
||||||
|
general?: any;
|
||||||
|
advanced?: any;
|
||||||
|
} {
|
||||||
|
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) {
|
||||||
|
const meta = {};
|
||||||
|
// Some charts render wrong when the field name contains a dot in G2Plot
|
||||||
|
const replace = (key: string) => key.replace(/\./g, '_');
|
||||||
|
Object.entries(fieldProps).forEach(([key, props]) => {
|
||||||
|
if (key.includes('.')) {
|
||||||
|
key = replace(key);
|
||||||
|
}
|
||||||
|
meta[key] = {
|
||||||
|
formatter: props.transformer,
|
||||||
|
alias: props.label,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
general = Object.entries(general).reduce((obj, [key, value]) => {
|
||||||
|
obj[key] = value;
|
||||||
|
if (key.includes('Field')) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
obj[key] = value.map((v) => (v.includes('.') ? replace(v) : v));
|
||||||
|
} else if (typeof value === 'string' && value.includes('.')) {
|
||||||
|
obj[key] = replace(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
return {
|
||||||
|
data: data.map((item) => {
|
||||||
|
const obj = {};
|
||||||
|
Object.entries(item).forEach(([key, value]) => {
|
||||||
|
if (key.includes('.')) {
|
||||||
|
key = replace(key);
|
||||||
|
}
|
||||||
|
obj[key] = value;
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}),
|
||||||
|
meta,
|
||||||
|
animation: false,
|
||||||
|
...general,
|
||||||
|
...advanced,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getReference() {
|
||||||
|
return {
|
||||||
|
title: this.title,
|
||||||
|
link: `https://g2plot.antv.antgroup.com/api/plots/${this.name}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
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 Bar(),
|
||||||
|
new Pie(),
|
||||||
|
new DualAxes(),
|
||||||
|
new G2PlotChart('scatter', 'Scatter Chart', Scatter),
|
||||||
|
];
|
@ -0,0 +1,52 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
fields: FieldOption[],
|
||||||
|
{
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
measures?: QueryProps['measures'];
|
||||||
|
dimensions?: QueryProps['dimensions'];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { xField, yField } = this.infer(fields, { measures, dimensions });
|
||||||
|
return {
|
||||||
|
general: {
|
||||||
|
colorField: xField?.value,
|
||||||
|
angleField: yField?.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import { lang } from '../locale';
|
||||||
|
import { ChartType } from './chart';
|
||||||
|
|
||||||
|
export type ChartLibraries = {
|
||||||
|
[library: string]: {
|
||||||
|
enabled: boolean;
|
||||||
|
charts: ChartType[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChartLibraryContext = createContext<ChartLibraries>({});
|
||||||
|
|
||||||
|
export const useCharts = (): {
|
||||||
|
[name: string]: ChartType;
|
||||||
|
} => {
|
||||||
|
const library = useContext(ChartLibraryContext);
|
||||||
|
return Object.values(library)
|
||||||
|
.filter((l) => l.enabled)
|
||||||
|
.reduce((allCharts, l) => {
|
||||||
|
const charts = Object.values(l.charts);
|
||||||
|
return {
|
||||||
|
...allCharts,
|
||||||
|
...charts.reduce((all, chart) => {
|
||||||
|
return {
|
||||||
|
...all,
|
||||||
|
[chart.name]: chart,
|
||||||
|
};
|
||||||
|
}, {}),
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useChartTypes = (): {
|
||||||
|
label: string;
|
||||||
|
children: {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
}[] => {
|
||||||
|
const library = useContext(ChartLibraryContext);
|
||||||
|
return Object.entries(library)
|
||||||
|
.filter(([_, l]) => l.enabled)
|
||||||
|
.reduce((charts, [name, l]) => {
|
||||||
|
const children = Object.values(l.charts).map((chart) => ({
|
||||||
|
key: chart.name,
|
||||||
|
label: lang(chart.title),
|
||||||
|
value: chart.name,
|
||||||
|
}));
|
||||||
|
return [
|
||||||
|
...charts,
|
||||||
|
{
|
||||||
|
label: lang(name),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDefaultChartType = () => {
|
||||||
|
const chartTypes = useChartTypes();
|
||||||
|
return chartTypes[0]?.children?.[0]?.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useToggleChartLibrary = () => {
|
||||||
|
const ctx = useContext(ChartLibraryContext);
|
||||||
|
return {
|
||||||
|
toggle: (library: string) => {
|
||||||
|
ctx[library].enabled = !ctx[library].enabled;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChartLibraryProvider: React.FC<{
|
||||||
|
name: string;
|
||||||
|
charts: ChartType[];
|
||||||
|
}> = (props) => {
|
||||||
|
const { children, charts, name } = props;
|
||||||
|
const ctx = useContext(ChartLibraryContext);
|
||||||
|
const library = {
|
||||||
|
...ctx,
|
||||||
|
[name]: {
|
||||||
|
charts,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return <ChartLibraryContext.Provider value={library}>{children}</ChartLibraryContext.Provider>;
|
||||||
|
};
|
@ -2,10 +2,12 @@ import { Plugin, SchemaComponentOptions, SchemaInitializerContext, SchemaInitial
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { ChartInitializers, ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block';
|
import { ChartInitializers, ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block';
|
||||||
import { useChartsTranslation } from './locale';
|
import { useChartsTranslation } from './locale';
|
||||||
import { ChartRenderer, ChartRendererProvider, InternalLibrary } from './renderer';
|
import { ChartRenderer, ChartRendererProvider } from './renderer';
|
||||||
import { ChartLibraryProvider } from './renderer/ChartLibrary';
|
import { ChartLibraryProvider } from './chart/library';
|
||||||
|
import g2plot from './chart/g2plot';
|
||||||
|
import antd from './chart/antd';
|
||||||
|
|
||||||
const Chart: React.FC = (props) => {
|
const DataVisualization: React.FC = (props) => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const initializers = useContext<any>(SchemaInitializerContext);
|
const initializers = useContext<any>(SchemaInitializerContext);
|
||||||
const children = initializers.BlockInitializers.items[0].children;
|
const children = initializers.BlockInitializers.items[0].children;
|
||||||
@ -29,7 +31,7 @@ const Chart: React.FC = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SchemaInitializerProvider initializers={{ ...initializers, ChartInitializers }}>
|
<SchemaInitializerProvider initializers={{ ...initializers, ChartInitializers }}>
|
||||||
<ChartLibraryProvider name="Built-in" charts={InternalLibrary}>
|
<ChartLibraryProvider name="Built-in" charts={[...g2plot, ...antd]}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ChartLibraryProvider>
|
</ChartLibraryProvider>
|
||||||
</SchemaInitializerProvider>
|
</SchemaInitializerProvider>
|
||||||
@ -39,9 +41,11 @@ const Chart: React.FC = (props) => {
|
|||||||
|
|
||||||
class DataVisualizationPlugin extends Plugin {
|
class DataVisualizationPlugin extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
this.app.addProvider(Chart);
|
this.app.addProvider(DataVisualization);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DataVisualizationPlugin;
|
export default DataVisualizationPlugin;
|
||||||
export { ChartLibraryProvider };
|
export { ChartLibraryProvider };
|
||||||
|
export { Chart } from './chart/chart';
|
||||||
|
export type { ChartType } from './chart/chart';
|
||||||
|
@ -1,183 +0,0 @@
|
|||||||
import { ISchema } from '@formily/react';
|
|
||||||
import React, { createContext, useContext } from 'react';
|
|
||||||
import { FieldOption } from '../hooks';
|
|
||||||
import { lang } from '../locale';
|
|
||||||
import { parseField } from '../utils';
|
|
||||||
import { QueryProps } from './ChartRendererProvider';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @params {usePropsFunc} useProps - Accept the information that the chart component needs to render,
|
|
||||||
* process it and return the props of the chart component.
|
|
||||||
*/
|
|
||||||
export type usePropsFunc = (props: {
|
|
||||||
data: any[];
|
|
||||||
fieldProps: {
|
|
||||||
[field: string]: FieldOption & {
|
|
||||||
transformer: (val: any) => string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
general: any;
|
|
||||||
advanced: any;
|
|
||||||
}) => any;
|
|
||||||
|
|
||||||
export type ChartProps = {
|
|
||||||
name: string;
|
|
||||||
component: React.FC<any>;
|
|
||||||
schema?: ISchema;
|
|
||||||
useProps?: usePropsFunc;
|
|
||||||
// The init function is used to initialize the configuration of the chart component from the query configuration.
|
|
||||||
init?: (
|
|
||||||
fields: FieldOption[],
|
|
||||||
query: {
|
|
||||||
measures?: QueryProps['measures'];
|
|
||||||
dimensions?: QueryProps['dimensions'];
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
general?: any;
|
|
||||||
advanced?: any;
|
|
||||||
};
|
|
||||||
reference?: {
|
|
||||||
title: string;
|
|
||||||
link: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Charts = {
|
|
||||||
[type: string]: ChartProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChartLibraries = {
|
|
||||||
[library: string]: {
|
|
||||||
enabled: boolean;
|
|
||||||
charts: Charts;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChartLibraryContext = createContext<ChartLibraries>({});
|
|
||||||
|
|
||||||
export const useCharts = (): Charts => {
|
|
||||||
const library = useContext(ChartLibraryContext);
|
|
||||||
return Object.values(library)
|
|
||||||
.filter((l) => l.enabled)
|
|
||||||
.reduce((charts, l) => ({ ...charts, ...l.charts }), {});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useChartTypes = (): {
|
|
||||||
label: string;
|
|
||||||
children: (ChartProps & {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
})[];
|
|
||||||
}[] => {
|
|
||||||
const library = useContext(ChartLibraryContext);
|
|
||||||
return Object.entries(library)
|
|
||||||
.filter(([_, l]) => l.enabled)
|
|
||||||
.reduce((charts, [name, l]) => {
|
|
||||||
const children = Object.entries(l.charts).map(([type, chart]) => ({
|
|
||||||
...chart,
|
|
||||||
key: type,
|
|
||||||
label: lang(chart.name),
|
|
||||||
value: type,
|
|
||||||
}));
|
|
||||||
return [
|
|
||||||
...charts,
|
|
||||||
{
|
|
||||||
label: lang(name),
|
|
||||||
children,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDefaultChartType = () => {
|
|
||||||
const chartTypes = useChartTypes();
|
|
||||||
return chartTypes[0]?.children?.[0]?.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useToggleChartLibrary = () => {
|
|
||||||
const ctx = useContext(ChartLibraryContext);
|
|
||||||
return {
|
|
||||||
toggle: (library: string) => {
|
|
||||||
ctx[library].enabled = !ctx[library].enabled;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChartLibraryProvider: React.FC<{
|
|
||||||
name: string;
|
|
||||||
charts: Charts;
|
|
||||||
}> = (props) => {
|
|
||||||
const { children, charts, name } = props;
|
|
||||||
const ctx = useContext(ChartLibraryContext);
|
|
||||||
const library = {
|
|
||||||
...ctx,
|
|
||||||
[name]: {
|
|
||||||
charts,
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return <ChartLibraryContext.Provider value={library}>{children}</ChartLibraryContext.Provider>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const infer = (
|
|
||||||
fields: FieldOption[],
|
|
||||||
{
|
|
||||||
measures,
|
|
||||||
dimensions,
|
|
||||||
}: {
|
|
||||||
measures?: QueryProps['measures'];
|
|
||||||
dimensions?: QueryProps['dimensions'];
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
let xField: FieldOption;
|
|
||||||
let yField: FieldOption;
|
|
||||||
let seriesField: FieldOption;
|
|
||||||
let yFields: FieldOption[];
|
|
||||||
const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => {
|
|
||||||
if (selected.alias) {
|
|
||||||
return fields.find((f) => f.value === selected.alias);
|
|
||||||
}
|
|
||||||
const { alias } = parseField(selected.field);
|
|
||||||
return fields.find((f) => f.value === alias);
|
|
||||||
};
|
|
||||||
if (measures?.length) {
|
|
||||||
yField = getField(fields, measures[0]);
|
|
||||||
yFields = measures.map((m) => getField(fields, m));
|
|
||||||
}
|
|
||||||
if (dimensions) {
|
|
||||||
if (dimensions.length === 1) {
|
|
||||||
xField = getField(fields, dimensions[0]);
|
|
||||||
} else if (dimensions.length > 1) {
|
|
||||||
// If there is a time field, it is used as the x-axis field by default.
|
|
||||||
let xIndex: number;
|
|
||||||
dimensions.forEach((d, i) => {
|
|
||||||
const field = getField(fields, d);
|
|
||||||
if (['date', 'time', 'datetime'].includes(field?.type)) {
|
|
||||||
xField = field;
|
|
||||||
xIndex = i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (xIndex) {
|
|
||||||
// If there is a time field, the other field is used as the series field by default.
|
|
||||||
const index = xIndex === 0 ? 1 : 0;
|
|
||||||
seriesField = getField(fields, dimensions[index]);
|
|
||||||
} else {
|
|
||||||
xField = getField(fields, dimensions[0]);
|
|
||||||
seriesField = getField(fields, dimensions[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { xField, yField, seriesField, yFields };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const commonInit: ChartProps['init'] = (fields, { measures, dimensions }) => {
|
|
||||||
const { xField, yField, seriesField } = infer(fields, { measures, dimensions });
|
|
||||||
return {
|
|
||||||
general: {
|
|
||||||
xField: xField?.value,
|
|
||||||
yField: yField?.value,
|
|
||||||
seriesField: seriesField?.value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -14,7 +14,7 @@ import { ChartConfigContext } from '../block';
|
|||||||
import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks';
|
import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks';
|
||||||
import { useChartsTranslation } from '../locale';
|
import { useChartsTranslation } from '../locale';
|
||||||
import { createRendererSchema, getField } from '../utils';
|
import { createRendererSchema, getField } from '../utils';
|
||||||
import { useCharts } from './ChartLibrary';
|
import { useCharts } from '../chart/library';
|
||||||
import { ChartRendererContext } from './ChartRendererProvider';
|
import { ChartRendererContext } from './ChartRendererProvider';
|
||||||
const { Paragraph, Text } = Typography;
|
const { Paragraph, Text } = Typography;
|
||||||
|
|
||||||
@ -32,10 +32,9 @@ export const ChartRenderer: React.FC & {
|
|||||||
|
|
||||||
const charts = useCharts();
|
const charts = useCharts();
|
||||||
const chart = charts[config?.chartType];
|
const chart = charts[config?.chartType];
|
||||||
const Component = chart?.component;
|
|
||||||
const locale = api.auth.getLocale();
|
const locale = api.auth.getLocale();
|
||||||
const transformers = useFieldTransformer(transform, locale);
|
const transformers = useFieldTransformer(transform, locale);
|
||||||
const info = {
|
const Component = chart?.render({
|
||||||
data,
|
data,
|
||||||
general,
|
general,
|
||||||
advanced,
|
advanced,
|
||||||
@ -47,18 +46,17 @@ export const ChartRenderer: React.FC & {
|
|||||||
}
|
}
|
||||||
return props;
|
return props;
|
||||||
}, {}),
|
}, {}),
|
||||||
locale,
|
});
|
||||||
};
|
|
||||||
const componentProps = chart?.useProps?.(info) || info;
|
|
||||||
const C = () =>
|
const C = () =>
|
||||||
Component ? (
|
chart ? (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onError={(error) => {
|
onError={(error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}}
|
}}
|
||||||
FallbackComponent={ErrorFallback}
|
FallbackComponent={ErrorFallback}
|
||||||
>
|
>
|
||||||
<Component {...componentProps} />
|
<Component />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
) : (
|
) : (
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />
|
||||||
|
@ -1,4 +1,2 @@
|
|||||||
export * from './ChartLibrary';
|
|
||||||
export * from './ChartRenderer';
|
export * from './ChartRenderer';
|
||||||
export * from './ChartRendererProvider';
|
export * from './ChartRendererProvider';
|
||||||
export * from './library';
|
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
import { Statistic, Table } from 'antd';
|
|
||||||
import { lang } from '../../locale';
|
|
||||||
import { Charts, infer } from '../ChartLibrary';
|
|
||||||
|
|
||||||
export const AntdLibrary: Charts = {
|
|
||||||
statistic: {
|
|
||||||
name: lang('Statistic'),
|
|
||||||
component: Statistic,
|
|
||||||
schema: {
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
init: (fields, { measures, dimensions }) => {
|
|
||||||
const { yField } = infer(fields, { measures, dimensions });
|
|
||||||
return {
|
|
||||||
general: {
|
|
||||||
field: yField?.value,
|
|
||||||
title: yField?.label,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
useProps: ({ data, fieldProps, general, advanced }) => {
|
|
||||||
const record = data[0] || {};
|
|
||||||
const field = general?.field;
|
|
||||||
const props = fieldProps[field];
|
|
||||||
return {
|
|
||||||
value: record[field],
|
|
||||||
formatter: props?.transformer,
|
|
||||||
...general,
|
|
||||||
...advanced,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
reference: {
|
|
||||||
title: lang('Statistic'),
|
|
||||||
link: 'https://ant.design/components/statistic/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
table: {
|
|
||||||
name: lang('Table'),
|
|
||||||
component: Table,
|
|
||||||
useProps: ({ data, fieldProps, general, advanced }) => {
|
|
||||||
const columns = data.length
|
|
||||||
? Object.keys(data[0]).map((item) => ({
|
|
||||||
title: fieldProps[item]?.label || item,
|
|
||||||
dataIndex: item,
|
|
||||||
key: item,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
const dataSource = data.map((item: any) => {
|
|
||||||
Object.keys(item).map((key: string) => {
|
|
||||||
const props = fieldProps[key];
|
|
||||||
if (props?.transformer) {
|
|
||||||
item[key] = props.transformer(item[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
const pageSize = advanced?.pagination?.pageSize || 10;
|
|
||||||
return {
|
|
||||||
bordered: true,
|
|
||||||
size: 'middle',
|
|
||||||
pagination:
|
|
||||||
dataSource.length < pageSize
|
|
||||||
? false
|
|
||||||
: {
|
|
||||||
pageSize,
|
|
||||||
},
|
|
||||||
dataSource,
|
|
||||||
columns,
|
|
||||||
...general,
|
|
||||||
...advanced,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
reference: {
|
|
||||||
title: lang('Table'),
|
|
||||||
link: 'https://ant.design/components/table/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,236 +0,0 @@
|
|||||||
import { Area, Bar, Column, DualAxes, Line, Pie, Scatter } from '@ant-design/plots';
|
|
||||||
import { Charts, commonInit, infer, usePropsFunc } from '../ChartLibrary';
|
|
||||||
const init = commonInit;
|
|
||||||
|
|
||||||
const basicSchema = {
|
|
||||||
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 }}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const useProps: usePropsFunc = ({ data, fieldProps, general, advanced }) => {
|
|
||||||
const meta = {};
|
|
||||||
Object.entries(fieldProps).forEach(([key, props]) => {
|
|
||||||
meta[key] = {
|
|
||||||
formatter: props.transformer,
|
|
||||||
alias: props.label,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
meta,
|
|
||||||
animation: false,
|
|
||||||
...general,
|
|
||||||
...advanced,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const G2PlotLibrary: Charts = {
|
|
||||||
line: {
|
|
||||||
name: 'Line Chart',
|
|
||||||
component: Line,
|
|
||||||
schema: basicSchema,
|
|
||||||
init,
|
|
||||||
useProps,
|
|
||||||
reference: {
|
|
||||||
title: 'Line Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/line',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
area: {
|
|
||||||
name: 'Area Chart',
|
|
||||||
component: Area,
|
|
||||||
schema: basicSchema,
|
|
||||||
init,
|
|
||||||
useProps,
|
|
||||||
reference: {
|
|
||||||
title: 'Area Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/area',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
name: 'Column Chart',
|
|
||||||
component: Column,
|
|
||||||
schema: basicSchema,
|
|
||||||
init,
|
|
||||||
useProps,
|
|
||||||
reference: {
|
|
||||||
title: 'Column Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/column',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bar: {
|
|
||||||
name: 'Bar Chart',
|
|
||||||
component: Bar,
|
|
||||||
schema: basicSchema,
|
|
||||||
init: (fields, { measures, dimensions }) => {
|
|
||||||
const { xField, yField, seriesField } = infer(fields, { measures, dimensions });
|
|
||||||
return {
|
|
||||||
general: {
|
|
||||||
xField: yField?.value,
|
|
||||||
yField: xField?.value,
|
|
||||||
seriesField: seriesField?.value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
useProps,
|
|
||||||
reference: {
|
|
||||||
title: 'Bar Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pie: {
|
|
||||||
name: 'Pie Chart',
|
|
||||||
component: Pie,
|
|
||||||
schema: {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
init: (fields, { measures, dimensions }) => {
|
|
||||||
const { xField, yField } = infer(fields, { measures, dimensions });
|
|
||||||
return {
|
|
||||||
general: {
|
|
||||||
colorField: xField?.value,
|
|
||||||
angleField: yField?.value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
useProps,
|
|
||||||
reference: {
|
|
||||||
title: 'Pie Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/pie',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dualAxes: {
|
|
||||||
name: 'Dual Axes Chart',
|
|
||||||
component: DualAxes,
|
|
||||||
useProps: ({ data, fieldProps, general, advanced }) => {
|
|
||||||
return {
|
|
||||||
...useProps({ data, fieldProps, general, advanced }),
|
|
||||||
data: [data, data],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
init: (fields, { measures, dimensions }) => {
|
|
||||||
const { xField, yFields } = infer(fields, { measures, dimensions });
|
|
||||||
return {
|
|
||||||
general: {
|
|
||||||
xField: xField?.value,
|
|
||||||
yField: yFields?.map((f) => f.value).slice(0, 2) || [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
reference: {
|
|
||||||
title: 'Dual Axes Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/dual-axes',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// gauge: {
|
|
||||||
// name: 'Gauge Chart',
|
|
||||||
// component: Gauge,
|
|
||||||
// },
|
|
||||||
scatter: {
|
|
||||||
name: 'Scatter Chart',
|
|
||||||
component: Scatter,
|
|
||||||
schema: basicSchema,
|
|
||||||
init,
|
|
||||||
useProps,
|
|
||||||
reference: {
|
|
||||||
title: 'Scatter Chart',
|
|
||||||
link: 'https://g2plot.antv.antgroup.com/api/plots/scatter',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
import { AntdLibrary } from './AntdLibrary';
|
|
||||||
import { G2PlotLibrary } from './G2PlotLibrary';
|
|
||||||
|
|
||||||
export const InternalLibrary = { ...G2PlotLibrary, ...AntdLibrary };
|
|
@ -251,8 +251,18 @@ export const cacheWrap = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const query = async (ctx: Context, next: Next) => {
|
export const query = async (ctx: Context, next: Next) => {
|
||||||
const { uid, collection, measures, dimensions, orders, filter, limit, sql, cache: cacheConfig, refresh } = ctx.action
|
const {
|
||||||
.params.values as QueryParams;
|
uid,
|
||||||
|
collection,
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
orders,
|
||||||
|
filter,
|
||||||
|
limit,
|
||||||
|
sql,
|
||||||
|
cache: cacheConfig,
|
||||||
|
refresh,
|
||||||
|
} = ctx.action.params.values as QueryParams;
|
||||||
const roleName = ctx.state.currentRole || 'anonymous';
|
const roleName = ctx.state.currentRole || 'anonymous';
|
||||||
const can = ctx.app.acl.can({ role: roleName, resource: collection, action: 'list' });
|
const can = ctx.app.acl.can({ role: roleName, resource: collection, action: 'list' });
|
||||||
if (!can && roleName !== 'root') {
|
if (!can && roleName !== 'root') {
|
||||||
|
@ -20581,14 +20581,9 @@ prettier@2.2.1:
|
|||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.npmmirror.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
|
resolved "https://registry.npmmirror.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
|
||||||
|
|
||||||
prettier@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
|
||||||
|
|
||||||
prettier@^3.0.0:
|
prettier@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmmirror.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
resolved "https://registry.npmmirror.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
||||||
integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==
|
|
||||||
|
|
||||||
pretty-error@^4.0.0:
|
pretty-error@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user