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:
YANG QIA 2023-07-31 23:51:23 +08:00 committed by GitHub
parent 07f1f16ea0
commit f82b6a9d38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 684 additions and 548 deletions

View File

@ -5,7 +5,7 @@ import { Button, Card } from 'antd';
import cls from 'classnames';
import React, { useContext } from 'react';
import { useChartsTranslation } from './locale';
import { ChartLibraryContext, useToggleChartLibrary } from './renderer';
import { ChartLibraryContext, useToggleChartLibrary } from './chart/library';
export const Settings = () => {
const { t } = useChartsTranslation();

View File

@ -1,5 +1,6 @@
import { Chart } from '../chart/chart';
import { FieldOption } from '../hooks';
import { infer } from '../renderer';
const chart = new Chart('test', 'Test', null);
describe('library', () => {
describe('auto infer', () => {
@ -37,7 +38,7 @@ describe('library', () => {
] as FieldOption[];
test('1 measure, 1 dimension', () => {
const { xField, yField } = infer(fields, {
const { xField, yField } = chart.infer(fields, {
measures: [{ field: ['price'] }],
dimensions: [{ field: ['title'] }],
});
@ -46,7 +47,7 @@ describe('library', () => {
});
test('1 measure, 2 dimensions with date', () => {
const { xField, yField, seriesField } = infer(fields, {
const { xField, yField, seriesField } = chart.infer(fields, {
measures: [{ field: ['price'] }],
dimensions: [{ field: ['title'] }, { field: ['createdAt'] }],
});
@ -56,7 +57,7 @@ describe('library', () => {
});
test('1 measure, 2 dimensions without date', () => {
const { xField, yField, seriesField } = infer(fields, {
const { xField, yField, seriesField } = chart.infer(fields, {
measures: [{ field: ['price'] }],
dimensions: [{ field: ['title'] }, { field: ['name'] }],
});
@ -66,7 +67,7 @@ describe('library', () => {
});
test('2 measures, 1 dimension', () => {
const { xField, yField, yFields } = infer(fields, {
const { xField, yField, yFields } = chart.infer(fields, {
measures: [{ field: ['price'] }, { field: ['count'] }],
dimensions: [{ field: ['title'] }],
});

View File

@ -26,9 +26,10 @@ import {
useTransformers,
} from '../hooks';
import { useChartsTranslation } from '../locale';
import { ChartRenderer, ChartRendererContext, useChartTypes, useCharts, useDefaultChartType } from '../renderer';
import { ChartRenderer, ChartRendererContext } from '../renderer';
import { createRendererSchema, getField, getSelectedFields } from '../utils';
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
import { useChartTypes, useCharts, useDefaultChartType } from '../chart/library';
const { Paragraph, Text } = Typography;
export type ChartConfigCurrent = {
@ -98,7 +99,7 @@ export const ChartConfigure: React.FC<{
}
const query = form.values.query;
const selectedFields = getSelectedFields(fields, query);
const { general, advanced } = init(selectedFields, query);
const { general, advanced } = chart.init(selectedFields, query);
if (general || overwrite) {
form.values.config.general = general;
}
@ -364,7 +365,7 @@ ChartConfigure.Config = function Config() {
const charts = useCharts();
const getChartFields = useChartFields(fields);
const getReference = (chartType: string) => {
const reference = charts[chartType]?.reference;
const reference = charts[chartType]?.getReference?.();
if (!reference) return '';
const { title, link } = reference;
return (

View File

@ -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}`,
};
}
}

View File

@ -0,0 +1,4 @@
import { Statistic } from './statistic';
import { Table } from './table';
export default [new Statistic(), new Table()];

View File

@ -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,
};
}
}

View File

@ -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,
};
}
}

View 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 }),
});
}
}

View File

@ -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,
},
};
}
}

View File

@ -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],
});
}
}

View File

@ -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}`,
};
}
}

View File

@ -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),
];

View File

@ -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,
},
};
}
}

View File

@ -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>;
};

View File

@ -2,10 +2,12 @@ import { Plugin, SchemaComponentOptions, SchemaInitializerContext, SchemaInitial
import React, { useContext } from 'react';
import { ChartInitializers, ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block';
import { useChartsTranslation } from './locale';
import { ChartRenderer, ChartRendererProvider, InternalLibrary } from './renderer';
import { ChartLibraryProvider } from './renderer/ChartLibrary';
import { ChartRenderer, ChartRendererProvider } from './renderer';
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 initializers = useContext<any>(SchemaInitializerContext);
const children = initializers.BlockInitializers.items[0].children;
@ -29,7 +31,7 @@ const Chart: React.FC = (props) => {
}}
>
<SchemaInitializerProvider initializers={{ ...initializers, ChartInitializers }}>
<ChartLibraryProvider name="Built-in" charts={InternalLibrary}>
<ChartLibraryProvider name="Built-in" charts={[...g2plot, ...antd]}>
{props.children}
</ChartLibraryProvider>
</SchemaInitializerProvider>
@ -39,9 +41,11 @@ const Chart: React.FC = (props) => {
class DataVisualizationPlugin extends Plugin {
async load() {
this.app.addProvider(Chart);
this.app.addProvider(DataVisualization);
}
}
export default DataVisualizationPlugin;
export { ChartLibraryProvider };
export { Chart } from './chart/chart';
export type { ChartType } from './chart/chart';

View File

@ -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,
},
};
};

View File

@ -14,7 +14,7 @@ import { ChartConfigContext } from '../block';
import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks';
import { useChartsTranslation } from '../locale';
import { createRendererSchema, getField } from '../utils';
import { useCharts } from './ChartLibrary';
import { useCharts } from '../chart/library';
import { ChartRendererContext } from './ChartRendererProvider';
const { Paragraph, Text } = Typography;
@ -32,10 +32,9 @@ export const ChartRenderer: React.FC & {
const charts = useCharts();
const chart = charts[config?.chartType];
const Component = chart?.component;
const locale = api.auth.getLocale();
const transformers = useFieldTransformer(transform, locale);
const info = {
const Component = chart?.render({
data,
general,
advanced,
@ -47,18 +46,17 @@ export const ChartRenderer: React.FC & {
}
return props;
}, {}),
locale,
};
const componentProps = chart?.useProps?.(info) || info;
});
const C = () =>
Component ? (
chart ? (
<ErrorBoundary
onError={(error) => {
console.error(error);
}}
FallbackComponent={ErrorFallback}
>
<Component {...componentProps} />
<Component />
</ErrorBoundary>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />

View File

@ -1,4 +1,2 @@
export * from './ChartLibrary';
export * from './ChartRenderer';
export * from './ChartRendererProvider';
export * from './library';

View File

@ -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/',
},
},
};

View File

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

View File

@ -1,4 +0,0 @@
import { AntdLibrary } from './AntdLibrary';
import { G2PlotLibrary } from './G2PlotLibrary';
export const InternalLibrary = { ...G2PlotLibrary, ...AntdLibrary };

View File

@ -251,8 +251,18 @@ export const cacheWrap = async (
};
export const query = async (ctx: Context, next: Next) => {
const { uid, collection, measures, dimensions, orders, filter, limit, sql, cache: cacheConfig, refresh } = ctx.action
.params.values as QueryParams;
const {
uid,
collection,
measures,
dimensions,
orders,
filter,
limit,
sql,
cache: cacheConfig,
refresh,
} = ctx.action.params.values as QueryParams;
const roleName = ctx.state.currentRole || 'anonymous';
const can = ctx.app.acl.can({ role: roleName, resource: collection, action: 'list' });
if (!can && roleName !== 'root') {

View File

@ -20581,14 +20581,9 @@ prettier@2.2.1:
version "2.2.1"
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:
version "3.0.0"
resolved "https://registry.npmmirror.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==
pretty-error@^4.0.0:
version "4.0.0"