Skip to content

Commit c57ee02

Browse files
authored
feat(web): add download image function&fix null data point (#1653)
1 parent 889c345 commit c57ee02

File tree

4 files changed

+97
-30
lines changed

4 files changed

+97
-30
lines changed

web/app/i18n.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const en = {
1919
Description: 'Description',
2020
Storage: 'Storage',
2121
Please_input_the_description: 'Please input the description',
22-
Please_select_the_storage:'Please select the storage',
22+
Please_select_the_storage: 'Please select the storage',
2323
Next: 'Next',
2424
the_name_can_only_contain: 'the name can only contain numbers, letters, Chinese characters, "-" and "_"',
2525
Text: 'Text',
@@ -223,6 +223,7 @@ const en = {
223223
Chinese: 'Chinese',
224224
English: 'English',
225225
refreshSuccess: 'Refresh Success',
226+
Download: 'Download'
226227
} as const;
227228

228229
export type I18nKeys = keyof typeof en;
@@ -249,7 +250,7 @@ const zh: Resources['translation'] = {
249250
Description: '描述',
250251
Storage: '存储类型',
251252
Please_input_the_description: '请输入描述',
252-
Please_select_the_storage:'请选择存储类型',
253+
Please_select_the_storage: '请选择存储类型',
253254
Next: '下一步',
254255
the_name_can_only_contain: '名称只能包含数字、字母、中文字符、-或_',
255256
Text: '文本',
@@ -452,6 +453,7 @@ const zh: Resources['translation'] = {
452453
Chinese: '中文',
453454
English: '英文',
454455
refreshSuccess: '刷新成功',
456+
Download: '下载',
455457
} as const;
456458

457459
i18n.use(initReactI18next).init({

web/components/chart/autoChart/charts/util.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,12 @@ export const sortData = ({ data, chartType, xField }: {
6666
}
6767
return sortedData
6868
}
69+
70+
/** 数据空值处理:后端返回的空数据为 '-', 在展示为图表时会有问题,修改为 null */
71+
export const processNilData = (data: Datum[], emptyValue = '-') => data.map((datum) => {
72+
const processedDatum: Record<string, string | number | null> = {};
73+
Object.keys(datum).forEach((key) => {
74+
processedDatum[key] = datum[key] === emptyValue ? null : datum[key];
75+
});
76+
return processedDatum;
77+
});

web/components/chart/autoChart/index.tsx

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
1-
import { Empty, Row, Col, Select, Tooltip } from 'antd';
2-
import { Advice, Advisor } from '@antv/ava';
3-
import { Chart } from '@berryv/g2-react';
1+
import { Empty, Row, Col, Select, Tooltip, Button, Space } from 'antd';
2+
import { Advice, Advisor, Datum } from '@antv/ava';
3+
import { Chart, ChartRef } from '@berryv/g2-react';
44
import i18n, { I18nKeys } from '@/app/i18n';
55
import { customizeAdvisor, getVisAdvices } from './advisor/pipeline';
6-
import { useContext, useEffect, useMemo, useState } from 'react';
6+
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
77
import { defaultAdvicesFilter } from './advisor/utils';
88
import { AutoChartProps, ChartType, CustomAdvisorConfig, CustomChart, Specification } from './types';
99
import { customCharts } from './charts';
1010
import { ChatContext } from '@/app/chat-context';
1111
import { compact, concat, uniq } from 'lodash';
12-
import { sortData } from './charts/util';
12+
import { processNilData, sortData } from './charts/util';
13+
import { downloadImage } from '../helpers/downloadChartImage';;
14+
import { DownloadOutlined } from '@ant-design/icons';
1315

1416
const { Option } = Select;
1517

1618
export const AutoChart = (props: AutoChartProps) => {
17-
const { data, chartType, scopeOfCharts, ruleConfig } = props;
19+
const { chartType, scopeOfCharts, ruleConfig, data: originalData } = props;
1820

21+
// 处理空值数据 (为'-'的数据)
22+
const data = processNilData(originalData) as Datum[];
1923
const { mode } = useContext(ChatContext);
2024

2125
const [advisor, setAdvisor] = useState<Advisor>();
2226
const [advices, setAdvices] = useState<Advice[]>([]);
2327
const [renderChartType, setRenderChartType] = useState<ChartType>();
28+
const chartRef = useRef<ChartRef>()
2429

2530
useEffect(() => {
2631
const input_charts: CustomChart[] = customCharts;
@@ -106,6 +111,7 @@ export const AutoChart = (props: AutoChartProps) => {
106111
autoFit: true,
107112
height: 300,
108113
}}
114+
ref={chartRef}
109115
/>
110116
);
111117
}
@@ -115,28 +121,38 @@ export const AutoChart = (props: AutoChartProps) => {
115121
if (renderChartType) {
116122
return (
117123
<div>
118-
<Row justify="start" className="mb-2">
119-
<Col>{i18n.t('Advices')}</Col>
120-
<Col style={{ marginLeft: 24 }}>
121-
<Select
122-
className="w-52"
123-
value={renderChartType}
124-
placeholder={'Chart Switcher'}
125-
onChange={(value) => setRenderChartType(value)}
126-
size={'small'}
127-
>
128-
{advices?.map((item) => {
129-
const name = i18n.t(item.type as I18nKeys);
130-
131-
return (
132-
<Option key={item.type} value={item.type}>
133-
<Tooltip title={name} placement={'right'}>
134-
<div>{name}</div>
135-
</Tooltip>
136-
</Option>
137-
);
138-
})}
139-
</Select>
124+
<Row justify='space-between' className="mb-2">
125+
<Col>
126+
<Space>
127+
<span>{i18n.t('Advices')}</span>
128+
<Select
129+
className="w-52"
130+
value={renderChartType}
131+
placeholder={'Chart Switcher'}
132+
onChange={(value) => setRenderChartType(value)}
133+
size={'small'}
134+
>
135+
{advices?.map((item) => {
136+
const name = i18n.t(item.type as I18nKeys);
137+
return (
138+
<Option key={item.type} value={item.type}>
139+
<Tooltip title={name} placement={'right'}>
140+
<div>{name}</div>
141+
</Tooltip>
142+
</Option>
143+
);
144+
})}
145+
</Select>
146+
</Space>
147+
</Col>
148+
<Col>
149+
<Tooltip title={i18n.t('Download')}>
150+
<Button
151+
onClick={() => downloadImage(chartRef.current, i18n.t(renderChartType as I18nKeys))}
152+
icon={<DownloadOutlined />}
153+
type='text'
154+
/>
155+
</Tooltip>
140156
</Col>
141157
</Row>
142158
<div className="auto-chart-content">{visComponent}</div>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ChartRef as G2Chart } from "@berryv/g2-react";
2+
3+
const getChartCanvas = (chart: G2Chart) => {
4+
if (!chart) return;
5+
const chartContainer = chart.getContainer();
6+
const canvasNode = chartContainer.getElementsByTagName('canvas')[0];
7+
return canvasNode
8+
}
9+
10+
/** 获得 g2 Chart 实例的 dataURL */
11+
function toDataURL(chart: G2Chart) {
12+
const canvasDom = getChartCanvas(chart);
13+
if (canvasDom) {
14+
const dataURL = canvasDom.toDataURL('image/png');
15+
return dataURL;
16+
}
17+
}
18+
19+
/**
20+
* 图表图片导出
21+
* @param chart chart 实例
22+
* @param name 图片名称
23+
*/
24+
export function downloadImage(chart: G2Chart, name: string = 'Chart') {
25+
const link = document.createElement('a');
26+
const filename = `${name}.png`;
27+
28+
setTimeout(() => {
29+
const dataURL = toDataURL(chart);
30+
if (dataURL) {
31+
link.addEventListener('click', () => {
32+
link.download = filename;
33+
link.href = dataURL;
34+
});
35+
const e = document.createEvent('MouseEvents');
36+
e.initEvent('click', false, false);
37+
link.dispatchEvent(e);
38+
}
39+
}, 16);
40+
}

0 commit comments

Comments
 (0)