mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-15 22:19:28 +00:00
feat(web): add download image function&fix null data point (#1653)
This commit is contained in:
@@ -19,7 +19,7 @@ const en = {
|
|||||||
Description: 'Description',
|
Description: 'Description',
|
||||||
Storage: 'Storage',
|
Storage: 'Storage',
|
||||||
Please_input_the_description: 'Please input the description',
|
Please_input_the_description: 'Please input the description',
|
||||||
Please_select_the_storage:'Please select the storage',
|
Please_select_the_storage: 'Please select the storage',
|
||||||
Next: 'Next',
|
Next: 'Next',
|
||||||
the_name_can_only_contain: 'the name can only contain numbers, letters, Chinese characters, "-" and "_"',
|
the_name_can_only_contain: 'the name can only contain numbers, letters, Chinese characters, "-" and "_"',
|
||||||
Text: 'Text',
|
Text: 'Text',
|
||||||
@@ -223,6 +223,7 @@ const en = {
|
|||||||
Chinese: 'Chinese',
|
Chinese: 'Chinese',
|
||||||
English: 'English',
|
English: 'English',
|
||||||
refreshSuccess: 'Refresh Success',
|
refreshSuccess: 'Refresh Success',
|
||||||
|
Download: 'Download'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type I18nKeys = keyof typeof en;
|
export type I18nKeys = keyof typeof en;
|
||||||
@@ -249,7 +250,7 @@ const zh: Resources['translation'] = {
|
|||||||
Description: '描述',
|
Description: '描述',
|
||||||
Storage: '存储类型',
|
Storage: '存储类型',
|
||||||
Please_input_the_description: '请输入描述',
|
Please_input_the_description: '请输入描述',
|
||||||
Please_select_the_storage:'请选择存储类型',
|
Please_select_the_storage: '请选择存储类型',
|
||||||
Next: '下一步',
|
Next: '下一步',
|
||||||
the_name_can_only_contain: '名称只能包含数字、字母、中文字符、-或_',
|
the_name_can_only_contain: '名称只能包含数字、字母、中文字符、-或_',
|
||||||
Text: '文本',
|
Text: '文本',
|
||||||
@@ -452,6 +453,7 @@ const zh: Resources['translation'] = {
|
|||||||
Chinese: '中文',
|
Chinese: '中文',
|
||||||
English: '英文',
|
English: '英文',
|
||||||
refreshSuccess: '刷新成功',
|
refreshSuccess: '刷新成功',
|
||||||
|
Download: '下载',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
|
@@ -66,3 +66,12 @@ export const sortData = ({ data, chartType, xField }: {
|
|||||||
}
|
}
|
||||||
return sortedData
|
return sortedData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 数据空值处理:后端返回的空数据为 '-', 在展示为图表时会有问题,修改为 null */
|
||||||
|
export const processNilData = (data: Datum[], emptyValue = '-') => data.map((datum) => {
|
||||||
|
const processedDatum: Record<string, string | number | null> = {};
|
||||||
|
Object.keys(datum).forEach((key) => {
|
||||||
|
processedDatum[key] = datum[key] === emptyValue ? null : datum[key];
|
||||||
|
});
|
||||||
|
return processedDatum;
|
||||||
|
});
|
||||||
|
@@ -1,26 +1,31 @@
|
|||||||
import { Empty, Row, Col, Select, Tooltip } from 'antd';
|
import { Empty, Row, Col, Select, Tooltip, Button, Space } from 'antd';
|
||||||
import { Advice, Advisor } from '@antv/ava';
|
import { Advice, Advisor, Datum } from '@antv/ava';
|
||||||
import { Chart } from '@berryv/g2-react';
|
import { Chart, ChartRef } from '@berryv/g2-react';
|
||||||
import i18n, { I18nKeys } from '@/app/i18n';
|
import i18n, { I18nKeys } from '@/app/i18n';
|
||||||
import { customizeAdvisor, getVisAdvices } from './advisor/pipeline';
|
import { customizeAdvisor, getVisAdvices } from './advisor/pipeline';
|
||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { defaultAdvicesFilter } from './advisor/utils';
|
import { defaultAdvicesFilter } from './advisor/utils';
|
||||||
import { AutoChartProps, ChartType, CustomAdvisorConfig, CustomChart, Specification } from './types';
|
import { AutoChartProps, ChartType, CustomAdvisorConfig, CustomChart, Specification } from './types';
|
||||||
import { customCharts } from './charts';
|
import { customCharts } from './charts';
|
||||||
import { ChatContext } from '@/app/chat-context';
|
import { ChatContext } from '@/app/chat-context';
|
||||||
import { compact, concat, uniq } from 'lodash';
|
import { compact, concat, uniq } from 'lodash';
|
||||||
import { sortData } from './charts/util';
|
import { processNilData, sortData } from './charts/util';
|
||||||
|
import { downloadImage } from '../helpers/downloadChartImage';;
|
||||||
|
import { DownloadOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
export const AutoChart = (props: AutoChartProps) => {
|
export const AutoChart = (props: AutoChartProps) => {
|
||||||
const { data, chartType, scopeOfCharts, ruleConfig } = props;
|
const { chartType, scopeOfCharts, ruleConfig, data: originalData } = props;
|
||||||
|
|
||||||
|
// 处理空值数据 (为'-'的数据)
|
||||||
|
const data = processNilData(originalData) as Datum[];
|
||||||
const { mode } = useContext(ChatContext);
|
const { mode } = useContext(ChatContext);
|
||||||
|
|
||||||
const [advisor, setAdvisor] = useState<Advisor>();
|
const [advisor, setAdvisor] = useState<Advisor>();
|
||||||
const [advices, setAdvices] = useState<Advice[]>([]);
|
const [advices, setAdvices] = useState<Advice[]>([]);
|
||||||
const [renderChartType, setRenderChartType] = useState<ChartType>();
|
const [renderChartType, setRenderChartType] = useState<ChartType>();
|
||||||
|
const chartRef = useRef<ChartRef>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const input_charts: CustomChart[] = customCharts;
|
const input_charts: CustomChart[] = customCharts;
|
||||||
@@ -106,6 +111,7 @@ export const AutoChart = (props: AutoChartProps) => {
|
|||||||
autoFit: true,
|
autoFit: true,
|
||||||
height: 300,
|
height: 300,
|
||||||
}}
|
}}
|
||||||
|
ref={chartRef}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -115,28 +121,38 @@ export const AutoChart = (props: AutoChartProps) => {
|
|||||||
if (renderChartType) {
|
if (renderChartType) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row justify="start" className="mb-2">
|
<Row justify='space-between' className="mb-2">
|
||||||
<Col>{i18n.t('Advices')}</Col>
|
<Col>
|
||||||
<Col style={{ marginLeft: 24 }}>
|
<Space>
|
||||||
<Select
|
<span>{i18n.t('Advices')}</span>
|
||||||
className="w-52"
|
<Select
|
||||||
value={renderChartType}
|
className="w-52"
|
||||||
placeholder={'Chart Switcher'}
|
value={renderChartType}
|
||||||
onChange={(value) => setRenderChartType(value)}
|
placeholder={'Chart Switcher'}
|
||||||
size={'small'}
|
onChange={(value) => setRenderChartType(value)}
|
||||||
>
|
size={'small'}
|
||||||
{advices?.map((item) => {
|
>
|
||||||
const name = i18n.t(item.type as I18nKeys);
|
{advices?.map((item) => {
|
||||||
|
const name = i18n.t(item.type as I18nKeys);
|
||||||
return (
|
return (
|
||||||
<Option key={item.type} value={item.type}>
|
<Option key={item.type} value={item.type}>
|
||||||
<Tooltip title={name} placement={'right'}>
|
<Tooltip title={name} placement={'right'}>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Option>
|
</Option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Tooltip title={i18n.t('Download')}>
|
||||||
|
<Button
|
||||||
|
onClick={() => downloadImage(chartRef.current, i18n.t(renderChartType as I18nKeys))}
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<div className="auto-chart-content">{visComponent}</div>
|
<div className="auto-chart-content">{visComponent}</div>
|
||||||
|
40
web/components/chart/helpers/downloadChartImage.ts
Normal file
40
web/components/chart/helpers/downloadChartImage.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { ChartRef as G2Chart } from "@berryv/g2-react";
|
||||||
|
|
||||||
|
const getChartCanvas = (chart: G2Chart) => {
|
||||||
|
if (!chart) return;
|
||||||
|
const chartContainer = chart.getContainer();
|
||||||
|
const canvasNode = chartContainer.getElementsByTagName('canvas')[0];
|
||||||
|
return canvasNode
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得 g2 Chart 实例的 dataURL */
|
||||||
|
function toDataURL(chart: G2Chart) {
|
||||||
|
const canvasDom = getChartCanvas(chart);
|
||||||
|
if (canvasDom) {
|
||||||
|
const dataURL = canvasDom.toDataURL('image/png');
|
||||||
|
return dataURL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图表图片导出
|
||||||
|
* @param chart chart 实例
|
||||||
|
* @param name 图片名称
|
||||||
|
*/
|
||||||
|
export function downloadImage(chart: G2Chart, name: string = 'Chart') {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const filename = `${name}.png`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const dataURL = toDataURL(chart);
|
||||||
|
if (dataURL) {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
link.download = filename;
|
||||||
|
link.href = dataURL;
|
||||||
|
});
|
||||||
|
const e = document.createEvent('MouseEvents');
|
||||||
|
e.initEvent('click', false, false);
|
||||||
|
link.dispatchEvent(e);
|
||||||
|
}
|
||||||
|
}, 16);
|
||||||
|
}
|
Reference in New Issue
Block a user