tachybase_todo/packages/plugins/data-visualization/src/client/renderer/ChartLibrary.tsx
YANG QIA 3aa65cb30c
feat: data visualization (#2160)
* 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>
2023-06-30 20:49:44 +08:00

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