Skip to content

Commit

Permalink
feat: support chart advisor in fillSpecWithData
Browse files Browse the repository at this point in the history
  • Loading branch information
da730 committed Jul 4, 2024
1 parent 73fd5b1 commit c2abfbb
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 39 deletions.
2 changes: 1 addition & 1 deletion packages/chart-advisor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { isNil } from '@visactor/vutils';
import { isNaN } from './dataUtil';

export { fold, omit } from './fieldUtils';
export { FOLD_NAME, FOLD_VALUE, COLOR_FIELD } from './constant';
export { FOLD_NAME, FOLD_VALUE, COLOR_FIELD, FOLD_VALUE_MAIN, FOLD_VALUE_SUB, GROUP_FIELD } from './constant';

export function chartAdvisor(params: AdviserParams): AdviseResult {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export function DataInput(props: IPropsType) {
? fieldInfo.map(info => ({ fieldName: info.fieldName, role: info.role, type: info.type }))
: fieldInfo;

const finalDataset = specTemplateTest ? undefined : dataset;
const finalDataset = specTemplateTest && model !== Model.CHART_ADVISOR ? undefined : dataset;

const startTime = new Date().getTime();
const chartGenerationRes = await vmind.generateChart(describe, finalFieldInfo, finalDataset, {
Expand All @@ -153,20 +153,24 @@ export function DataInput(props: IPropsType) {
const endTime = new Date().getTime();
console.log(chartGenerationRes);
if (isArray(chartGenerationRes)) {
props.onSpecListGenerate(chartGenerationRes.map(res => res.spec));
const resNew = chartGenerationRes.map(res => {
const { spec, cell } = res;
specTemplateTest && (spec.data = undefined);
const finalSpec = specTemplateTest ? vmind.fillSpecWithData(spec, dataset, cell) : spec;
return finalSpec;
});
props.onSpecListGenerate(resNew);
} else {
const { spec, time, cell } = chartGenerationRes;

const finalSpec = specTemplateTest
? vmind.fillSpecWithData(spec, dataset, cell, finalFieldInfo, time.totalTime)
: spec;
const finalSpec = specTemplateTest ? vmind.fillSpecWithData(spec, dataset) : spec;

const costTime = endTime - startTime;
props.onSpecGenerate(finalSpec, time as any, costTime);
}

setLoading(false);
}, [vmind, csv, describe, props]);
}, [vmind, csv, model, describe, props]);

return (
<div className="left-sider">
Expand Down
151 changes: 125 additions & 26 deletions packages/vmind/src/common/specUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ import {
sequenceData,
wordCloudData
} from '../../applications/chartGeneration/taskNodes/getChartSpec/VChart/transformers';
import { isArray, uniqArray } from '@visactor/vutils';
import { isArray, isString, uniqArray } from '@visactor/vutils';
import { getFieldInfoFromDataset } from '../dataProcess';
import { foldDatasetByYField } from '../utils/utils';
import { FOLD_NAME, FOLD_VALUE } from '@visactor/chart-advisor';
import {
FOLD_NAME,
FOLD_VALUE,
COLOR_FIELD,
FOLD_VALUE_MAIN,
FOLD_VALUE_SUB,
GROUP_FIELD
} from '@visactor/chart-advisor';
import type { DataItem } from '../typings';
import { ChartType, type SimpleFieldInfo, type VMindDataset } from '../typings';
import type { Cell } from '../../applications/chartGeneration/types';

Expand All @@ -22,16 +30,25 @@ import type { Cell } from '../../applications/chartGeneration/types';
* @returns
*/
const removeInvalidFieldFromCell = (cell: Cell, fieldInfo: SimpleFieldInfo[]) => {
const fieldList = fieldInfo.map(f => f.fieldName);
const cellNew = {
const fieldList = fieldInfo
.map(f => f.fieldName)
.concat([
FOLD_NAME.toString(),
FOLD_VALUE.toString(),
FOLD_VALUE_MAIN.toString(),
FOLD_VALUE_SUB.toString(),
COLOR_FIELD.toString(),
GROUP_FIELD.toString()
]);
const cellNew: any = {
...cell
};
Object.keys(cellNew).forEach(key => {
const fields = cellNew[key];
if (isArray(fields)) {
const filteredFields = fields.filter(field => fieldList.includes(field));
cellNew[key] = filteredFields.length === 0 ? undefined : filteredFields;
} else {
} else if (isString(fields)) {
cellNew[key] = fieldList.includes(fields) ? fields : undefined;
}
});
Expand Down Expand Up @@ -132,17 +149,30 @@ export const getCellFromSpec = (spec: Spec) => {
* @param totalTime
* @returns
*/
export const fillSpecTemplateWithData = (template: Spec, dataset: VMindDataset, totalTime?: number) => {
export const fillSpecTemplateWithData = (
template: Spec,
dataset: VMindDataset,
propsCell?: any,
totalTime?: number
) => {
const { type } = template;
const fieldInfo = getFieldInfoFromDataset(dataset);
const tempCell = getCellFromSpec(template);
const tempCell = propsCell ?? getCellFromSpec(template);

let cellNew = { ...tempCell };
let datasetNew = dataset;

const cell = removeInvalidFieldFromCell(tempCell, fieldInfo);
const cellNew = { ...cell };
//check if the spec is generated using fold dataset
const hasFold = isArray(cellNew.y)
? cellNew.y[0] === FOLD_VALUE.toString() ||
(cellNew.y[0] === FOLD_VALUE_MAIN.toString() && cellNew.y[1] === FOLD_VALUE_SUB.toString())
: cellNew.y === FOLD_VALUE.toString();

cellNew = removeInvalidFieldFromCell(cellNew, fieldInfo);

const context: any = {
spec: template,
dataset,
dataset: datasetNew,
cell: cellNew,
totalTime
};
Expand All @@ -153,18 +183,23 @@ export const fillSpecTemplateWithData = (template: Spec, dataset: VMindDataset,
return spec;
}
if (['bar', 'line'].includes(type)) {
let datasetNew = dataset;

if (isArray(cellNew.y) && cellNew.y.length > 1) {
if (hasFold) {
//bar chart and line chart can visualize multiple y fields
datasetNew = foldDatasetByYField(datasetNew, cellNew.y, fieldInfo);
cellNew.y = FOLD_VALUE.toString();
cellNew.color = FOLD_NAME.toString();
template.yField = cellNew.y;
template.seriesField = cellNew.color;
template.xField = isArray(template.xField)
? [...template.xField, cellNew.color]
: [template.xField, cellNew.color];
const { foldInfo } = cellNew;
const { foldMap } = foldInfo;
datasetNew = foldDatasetByYField(datasetNew, Object.keys(foldMap), fieldInfo);
}

if (cellNew.color === COLOR_FIELD.toString()) {
const { cartesianInfo } = cellNew;
const colorFields = cartesianInfo.fieldList;
datasetNew = datasetNew.map((data: DataItem) => {
const colorItem = colorFields.map((field: string) => data[field]).join('-');
return {
...data,
[COLOR_FIELD]: colorItem
};
});
}

const contextNew: any = {
Expand All @@ -175,20 +210,84 @@ export const fillSpecTemplateWithData = (template: Spec, dataset: VMindDataset,
};
const { spec: spec1 } = data(contextNew);
const { spec } = legend({ ...contextNew, spec: spec1 });

return spec;
}
if (['pie', 'scatter', 'rose', 'radar', 'waterfall', 'boxPlot'].includes(type)) {
const { spec } = data(context);
if (hasFold) {
const { foldInfo } = cellNew;
const { foldMap } = foldInfo;
datasetNew = foldDatasetByYField(datasetNew, Object.keys(foldMap), fieldInfo);
}
if (cellNew.color === COLOR_FIELD.toString()) {
const { cartesianInfo } = cellNew;
const colorFields = cartesianInfo.fieldList;
datasetNew = datasetNew.map((data: DataItem) => {
const colorItem = colorFields.map((field: string) => data[field]).join('-');
return {
...data,
[COLOR_FIELD]: colorItem
};
});
}

const contextNew: any = {
spec: template,
dataset: datasetNew,
cell: cellNew,
totalTime
};
const { spec } = data(contextNew);
return spec;
}
if ('common' === type) {
//dual-axis chart
let mainSeriesData = datasetNew;
let subSeriesData = datasetNew;

if (hasFold) {
//bar chart and line chart can visualize multiple y fields
const { foldInfo } = cellNew;
const { foldMap } = foldInfo;
mainSeriesData = foldDatasetByYField(
datasetNew,
[Object.keys(foldMap)[0]],
fieldInfo,
FOLD_NAME,
FOLD_VALUE_MAIN
);
subSeriesData = foldDatasetByYField(datasetNew, [Object.keys(foldMap)[1]], fieldInfo, FOLD_NAME, FOLD_VALUE_SUB);
}

const { spec: spec1 } = data(context);
const { spec: spec2 } = legend({ ...context, spec: spec1 });
const { spec: finalSpec } = legend({ ...context, spec: spec1 });

const { spec } = dualAxisSeries({ ...context, spec: spec2 });
return spec;
//const { spec } = dualAxisSeries({ ...context, spec: spec2 });
const { cartesianInfo, y } = cellNew;
if (finalSpec.series && finalSpec.series[0]) {
finalSpec.series[0].seriesField = COLOR_FIELD;

const colorFields = cartesianInfo ? cartesianInfo.fieldList : undefined;
finalSpec.series[0].data = {
id: finalSpec.data.id + '_bar',
values: mainSeriesData.map((d: any) => {
const colorItem = isArray(colorFields) ? colorFields.map((field: string) => d[field]).join('-') : y[0];
return { ...d, [COLOR_FIELD]: colorItem };
})
};
}
if (finalSpec.series && finalSpec.series[1]) {
finalSpec.series[1].seriesField = COLOR_FIELD;

const colorFields = cartesianInfo ? cartesianInfo.fieldList : undefined;
finalSpec.series[1].data = {
id: finalSpec.data.id + '_line',
values: subSeriesData.map((d: any) => {
const colorItem = isArray(colorFields) ? colorFields.map((field: string) => d[field]).join('-') : y[1];
return { ...d, [COLOR_FIELD]: colorItem };
})
};
}
return finalSpec;
}
if (type === 'wordCloud') {
const { spec } = wordCloudData(context);
Expand Down
10 changes: 8 additions & 2 deletions packages/vmind/src/common/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,16 @@ export const getFieldByDataType = (fields: SimpleFieldInfo[], dataTypeList: Data
return fields.find(f => dataTypeList.includes(f.type));
};

export const foldDatasetByYField = (dataset: DataItem[], yFieldList: string[], fieldInfo: SimpleFieldInfo[]) => {
export const foldDatasetByYField = (
dataset: DataItem[],
yFieldList: string[],
fieldInfo: SimpleFieldInfo[],
foldName: any = FOLD_NAME,
foldValue: any = FOLD_VALUE
) => {
const aliasMap = Object.fromEntries(fieldInfo.map(d => [d.fieldName, d.fieldName]));

return fold(dataset as any, yFieldList, FOLD_NAME, FOLD_VALUE, aliasMap, false);
return fold(dataset as any, yFieldList, foldName, foldValue, aliasMap, false);
};

export function getObjectProperties(e: Error): {} {
Expand Down
9 changes: 5 additions & 4 deletions packages/vmind/src/core/VMind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import type {
DataItem,
OuterPackages,
VMindDataset,
ChartType
ChartType,
ChartTheme
} from '../common/typings';
import { Model, ModelType, ChartTheme } from '../common/typings';
import { Model, ModelType } from '../common/typings';
import { getFieldInfoFromDataset, parseCSVData as parseCSVDataWithRule } from '../common/dataProcess';
import type { VMindApplicationMap } from './types';
import type {
Expand Down Expand Up @@ -266,8 +267,8 @@ class VMind {
* @param dataset
* @returns
*/
fillSpecWithData(spec: any, dataset: VMindDataset, totalTime?: number) {
return fillSpecTemplateWithData(spec, dataset, totalTime);
fillSpecWithData(spec: any, dataset: VMindDataset, cell?: any) {
return fillSpecTemplateWithData(spec, dataset, cell);
}

async exportVideo(spec: any, time: TimeType, outerPackages: OuterPackages, mode?: 'node' | 'desktop-browser') {
Expand Down

0 comments on commit c2abfbb

Please sign in to comment.