fix: 动态条形图动画修复&badcase修复

This commit is contained in:
da730 2023-11-15 14:59:15 +08:00
parent 5c887fb8d7
commit 5543defaba
8 changed files with 128 additions and 27 deletions

2
.gitignore vendored
View File

@ -124,3 +124,5 @@ packages/wx-vchart/miniprogram/miniprogram_npm/
docs/public/documents
docs/public/vchart/preview/failedPreviewLists.json
packages/vchart/__tests__/runtime/node/**.png
# env files
.env.local

View File

@ -3566,7 +3566,8 @@ export const mockUserInput9 = {
Step1,100,1
Step2,80,1
Step3,60,1
Step4,40,1`
Step4,40,1`,
input: '展示各个流程转化率'
};
/**
@ -3580,7 +3581,8 @@ export const mockUserInput11 = {
,10,12
,13,15
,10,15
,12,14`
,12,14`,
input: '展示男女早餐饭量不同'
};
/**
@ -3657,5 +3659,6 @@ South Asia,9.4,10.06,10.75,11.56,12.5
Middle East & North Africa,9.54,10.6,11.05,11.5,11.92
Latin America & Caribbean,8.74,9.46,10.35,10.94,12.21
East Asia & Pacific,7.8,8.95,10.18,11.57,13.25
Europe & Central Asia,9.52,10.39,10.93,11.69,12.63`
Europe & Central Asia,9.52,10.39,10.93,11.69,12.63`,
input: '看下各地区gdp情况'
};

View File

@ -1,6 +1,6 @@
import React, { useState, useCallback } from 'react';
import './index.scss';
import { Avatar, Input, Divider, Button, InputNumber, Upload, Message } from '@arco-design/web-react';
import { Avatar, Input, Divider, Button, InputNumber, Upload, Message, Select } from '@arco-design/web-react';
import {
mockUserInput10,
mockUserInput2,
@ -9,12 +9,26 @@ import {
mockUserInput6,
mockUserInput6Eng,
mockUserInput8,
carSaleMockData
carSaleMockData,
mockUserInput15,
acceptRatioData,
mallSalesData,
hotWordsData,
mockUserInput4,
mockUserInput5,
mockUserInput9,
mockUserInput11,
mockUserInput12,
mockUserInput13,
mockUserInput14,
mockUserInput16
} from '../constants/mockData';
import { excel2csv } from '../../../../src/excel';
import VMind from '../../../../src/index';
const TextArea = Input.TextArea;
const Option = Select.Option;
type IPropsType = {
onSpecGenerate: (
spec: any,
@ -24,21 +38,45 @@ type IPropsType = {
}
) => void;
};
const demoDataList: { [key: string]: any } = {
pie: mockUserInput2,
'dynamic bar zh_cn': mockUserInput6,
line: mockUserInput8,
column: mockUserInput3,
column2: mockUserInput10,
wordcloud: hotWordsData,
wordcloud2: mockUserInput5,
'scatter plot': mockUserInput4,
funnel: mockUserInput9,
'dual-axis': mockUserInput11,
waterfall: mockUserInput12,
rose: mockUserInput13,
radar: mockUserInput14,
sankey: mockUserInput15,
'box-plot': mockUserInput16,
'Electric vehicle sales': carSaleMockData,
'College entrance examination': acceptRatioData,
'Shopping Mall Sales Performance': mallSalesData,
'Global GDP': mockUserInput6Eng,
'Sales of different drinkings': mockUserInput3Eng
};
export function LeftInput(props: IPropsType) {
const [describe, setDescribe] = useState<string>(mockUserInput2.input);
const [csv, setCsv] = useState<string>(mockUserInput2.csv);
const defaultDataKey = Object.keys(demoDataList)[0];
const [describe, setDescribe] = useState<string>(demoDataList[defaultDataKey].input);
const [csv, setCsv] = useState<string>(demoDataList[defaultDataKey].csv);
const [spec, setSpec] = useState<string>('');
const [time, setTime] = useState<number>(1000);
const [loading, setLoading] = useState<boolean>(false);
const vmind = new VMind(import.meta.OPENAI_KEY!);
const vmind = new VMind(import.meta.env.OPENAI_KEY!, {
url: import.meta.env.VITE_OPENAI_URL ?? undefined
});
const askGPT = useCallback(async () => {
setLoading(true);
const { spec, time } = await vmind.generateChart(csv, describe);
props.onSpecGenerate(spec, time as any);
setLoading(false);
}, [describe, csv]);
}, [vmind, csv, describe, props]);
const isAcceptFile = useCallback((file, accept) => {
if (accept && file) {
@ -68,6 +106,23 @@ export function LeftInput(props: IPropsType) {
return (
<div className="left-sider">
<Select
style={{
width: '100%'
}}
defaultValue={defaultDataKey}
onChange={v => {
const dataObj = demoDataList[v];
setDescribe(dataObj.input);
setCsv(dataObj.csv);
}}
>
{Object.keys(demoDataList).map(name => (
<Option key={name} value={name}>
{name}
</Option>
))}
</Select>
<div>
<p>
<Avatar size={18} style={{ backgroundColor: '#3370ff' }}>
@ -78,7 +133,7 @@ export function LeftInput(props: IPropsType) {
<TextArea
placeholder={describe}
defaultValue={describe}
value={describe}
onChange={v => setDescribe(v)}
style={{ minHeight: 160, marginTop: 20, background: 'transparent', border: '1px solid #eee' }}
/>

View File

@ -139,7 +139,7 @@ export const getSchemaFromFieldInfo = (dataProcessResJson: GPTDataProcessResult)
const usefulFields = dataProcessResJson.USEFUL_FIELDS;
const schema = {
fields: fieldInfo
.filter(d => usefulFields.includes(d.fieldName))
//.filter(d => usefulFields.includes(d.fieldName))
.map(d => ({
id: d.fieldName,
alias: d.fieldName,

View File

@ -489,13 +489,13 @@ Respone in the following format:
"FIELD_MAP": { // Visual channels and the fields mapped to them, available visual channels: ["x", "y", "color", "size", "angle", "time"]
"x": the field mapped to the x-axis, can be empty. Can Only has one field.
"y": the field mapped to the y-axis, can be empty. Can only has one field.
"color": the field mapped to the color channel. Can't be empty in Word Cloud, Pie Chart and Rose Chart
"size": the field mapped to the size channel, can be empty
"angle": the field mapped to the angle channel of the pie chart, can be empty
"color": the field mapped to the color channel. Must use a string field. Can't be empty in Word Cloud, Pie Chart and Rose Chart.
"size": the field mapped to the size channel. Must use a number field. Can be empty
"angle": the field mapped to the angle channel of the pie chart, can be empty.
"time": This is usually a date field and can be used only in Dynamic Bar Chart. Can't be empty in Dynamic Bar Chart.
"source": the field mapped to the source channel. Can't be empty in Sankey Chart
"target": the field mapped to the target channel. Can't be empty in Sankey Chart
"value": the field mapped to the value channel. Can't be empty in Sankey Chart
"source": the field mapped to the source channel. Can't be empty in Sankey Chart.
"target": the field mapped to the target channel. Can't be empty in Sankey Chart.
"value": the field mapped to the value channel. Can't be empty in Sankey Chart.
},
"Reason": the reason for selecting the chart type and visual mapping.
}
@ -505,7 +505,7 @@ Constraints:
1. No user assistance.
2. The selected chart type in CHART_TYPE must be in the list of supported charts.
3. Just ignore the user's request about duration and style in their input.
4. All fields in the data must be mapped to at least one visual channel in FIELD_MAP. DO NOT change or translate the field names in FIELD_MAP.
4. DO NOT change or translate the field names in FIELD_MAP.
5. The keys in FIELD_MAP must be selected from the list of available visual channels.
6. Wrap the reply content using \`\`\`, and the returned content must be directly parsed by JSON.parse() in JavaScript.

View File

@ -880,6 +880,10 @@ export const rankingBarLabel = (spec: any, context: Context) => {
style: {
fill: '#FFFFFF',
stroke: null
},
animation: {
duration: spec.animationUpdate.axis.duration,
easing: 'linear'
}
};
return spec;

View File

@ -131,3 +131,14 @@ export const patchUserInput = (userInput: string) => {
'严格按照prompt中的格式回复不要有任何多余内容。 Use the original fieldName and DO NOT change or translate any word of the data fields in the response.';
return finalStr;
};
export const CARTESIAN_CHART_LIST = [
'Dynamic Bar Chart',
'Bar Chart',
'Line Chart',
'Scatter Plot',
'Funnel Chart',
'Dual Axis Chart',
'Waterfall Chart',
'Box Plot Chart'
];

View File

@ -46,7 +46,7 @@ import {
} from './pipes';
import { Cell, ChartType, Context, Pipe } from '../typings';
import { DataView } from '@visactor/vdataset';
import { detectAxesType } from './utils';
import { CARTESIAN_CHART_LIST, detectAxesType } from './utils';
export const vizDataToSpec = (
dataView: DataView,
@ -67,7 +67,7 @@ export const vizDataToSpec = (
return { spec, chartTypeNew };
};
const patchChartTypeAndCell = (chartType: string, cell: any, dataView: DataView) => {
const patchChartTypeAndCell = (chartTypeOutter: string, cell: any, dataView: DataView) => {
//对GPT返回结果进行修正
//某些时候由于用户输入的意图不明确GPT返回的cell中可能缺少字段。
//此时需要根据规则补全
@ -75,6 +75,7 @@ const patchChartTypeAndCell = (chartType: string, cell: any, dataView: DataView)
const { x, y } = cell;
let chartType = chartTypeOutter;
// y轴字段有多个时处理方式:
// 1. 图表类型为: 箱型图, 图表类型不做矫正
// 2. 图表类型为: 柱状图 或 折线图, 图表类型矫正为双轴图
@ -87,12 +88,7 @@ const patchChartTypeAndCell = (chartType: string, cell: any, dataView: DataView)
};
}
if (chartType === 'BAR CHART' || chartType === 'LINE CHART') {
return {
chartTypeNew: 'DUAL AXIS CHART',
cellNew: {
...cell
}
};
chartType = 'DUAL AXIS CHART';
} else {
return {
chartTypeNew: 'SCATTER PLOT',
@ -105,6 +101,13 @@ const patchChartTypeAndCell = (chartType: string, cell: any, dataView: DataView)
};
}
}
//双轴图 订正yLeft和yRight
if (chartType === 'DUAL AXIS CHART' && cell.yLeft && cell.yRight) {
return {
chartTypeNew: chartType,
cellNew: { ...cell, y: [cell.yLeft, cell.yRight] }
};
}
//饼图 必须有color字段和angle字段
if (chartType === 'PIE CHART') {
const cellNew = { ...cell };
@ -215,6 +218,29 @@ const patchChartTypeAndCell = (chartType: string, cell: any, dataView: DataView)
cellNew
};
}
//直角坐标图表 必须有x字段
if (CARTESIAN_CHART_LIST.map(chart => chart.toUpperCase()).includes(chartType)) {
const cellNew = { ...cell };
if (!cellNew.x) {
const usedFields = Object.values(cell);
const dataFields = Object.keys(dataView.latestData[0]);
const remainedFields = dataFields.filter(f => !usedFields.includes(f));
//没有分配x字段从剩下的字段里选择一个离散字段分配到x上
const xField = remainedFields.find(f => {
const fieldType = detectAxesType(dataView.latestData, f);
return fieldType === 'band';
});
if (xField) {
cellNew.x = xField;
} else {
cellNew.x = remainedFields[0];
}
}
return {
chartTypeNew: chartType,
cellNew
};
}
return {
chartTypeNew: chartType,