* feat(charts-v2): init * chore(charts-v2): init chart renderer * feat(chart-v2): add chart grid and initializer * feat(chart-v2): improve ui * feat(chart-v2): ui * feat(charts-v2): query sort ui * feat(charts-v2): field select component * feat(charts-v2): improve ui && add query action * feat(charts-v2): imporve ui, work in progress * fix(charts-v2): chart renderer request api twice * feat(charts-v2): add dimension formatter * feat(charts-v2): filter, sort, limit * feat(charts-v2): sql mode ui * feat(charts-v2): support duplicate & sql mode * fix(charts-v2): wrong defaultValue of json config * feat(charts-v2): transformer ui * feat(charts-v2): transformer * chore(charts-v2): rename transfromer to transform * feat(charts-v2): support cache * feat(charts-v2): add acl provider * chore(charts-v2): hide sql mode * refactor(charts-v2): add renderer provider * feat: collection permission check * feat(charts-v2): add antd statistic * test(charts-v2): backend * chore: improve code * test(charts-v2): add test * chore: add Chinese translation * fix(charts-v2): locale switch bug * chore: add dependency * feat(charts-v2): init chart config from query * feat: change layout * test: fix frontend test * feat: improve auto infer * fix: ui issues * chore: translation * fix: sql error * fix: some issues * feat: support table * fix: bug * chore: improve code and fix query * feat: add config reference * chore: add translation * fix: process data due to pg issue * test: fix parseBuilder * chore: upgrade formily to 2.2.25 * fix: some issues and import style * fix: bug when query with sort * feat: parse enum data * fix: yarn.lock * fix: type error * fix: infer bug and frontend test * test: fix frontend * fix: test * feat: improve preview * chore: downgrade formily * feat: support associations, draft, in testing * fix: typo * test: frontend & backend * fix: infer bug * feat: measure selection of statistics * fix: bug of group by alias * fix: some issues * fix: order issues * fix: yarn.lock * chore: fix filter include & 'data-visualization' * style: improve style * docs: add readme * chore: add translation --------- Co-authored-by: chenos <chenlinxh@gmail.com>
179 lines
4.6 KiB
TypeScript
179 lines
4.6 KiB
TypeScript
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: chart.name,
|
|
value: type,
|
|
}));
|
|
return [
|
|
...charts,
|
|
{
|
|
label: lang(name),
|
|
children,
|
|
},
|
|
];
|
|
}, []);
|
|
};
|
|
|
|
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,
|
|
},
|
|
};
|
|
};
|