diff --git a/packages/ava/__tests__/unit/advisor/index.test.ts b/packages/ava/__tests__/unit/advisor/index.test.ts index 82e9f069..62f00e96 100644 --- a/packages/ava/__tests__/unit/advisor/index.test.ts +++ b/packages/ava/__tests__/unit/advisor/index.test.ts @@ -13,7 +13,7 @@ const myRule: RuleModule = { }, trigger: (args) => { const { chartType } = args; - return ['pie_chart'].includes(chartType); + return ['pie_chart'].includes(chartType!); }, validator: (args) => { let result = 1; @@ -61,8 +61,8 @@ describe('init Advisor', () => { test('data to advices with ckb config custom chart', () => { const splitAngleColor = (dataProps: BasicDataPropertyForAdvice[]) => { - const field4Color = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal'])); - const field4Angle = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); + const field4Color = dataProps.find((field) => hasSubset(field.levelOfMeasurements!, ['Nominal'])); + const field4Angle = dataProps.find((field) => hasSubset(field.levelOfMeasurements!, ['Interval'])); return [field4Color, field4Angle]; }; diff --git a/packages/ava/package.json b/packages/ava/package.json index 056909d0..32c8afcb 100644 --- a/packages/ava/package.json +++ b/packages/ava/package.json @@ -1,6 +1,6 @@ { "name": "@antv/ava", - "version": "3.4.1", + "version": "3.5.0-alpha.1", "description": "A framework for automated visual analytics.", "author": { "name": "AntV", @@ -55,14 +55,15 @@ }, "dependencies": { "@antv/antv-spec": "^0.1.0-alpha.18", + "@antv/color-schema": "^0.2.3", "@antv/g2": "^5.0.8", "@antv/smart-color": "^0.2.1", - "@antv/color-schema": "^0.2.3", "bayesian-changepoint": "^1.0.1", "csstype": "^3.1.2", "heap-js": "^2.1.6", "lodash": "^4.17.21", "regression": "^2.0.1", + "tapable": "^2.2.1", "tslib": "^2.3.1" }, "devDependencies": { diff --git a/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts b/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts index 41c45873..1dfd8b8a 100644 --- a/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts +++ b/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts @@ -1,16 +1,17 @@ import { cloneDeep, deepMix } from '../utils'; -import { getChartTypeSpec } from './spec-generator'; +import { getChartTypeSpec } from './plugin/presets/spec-generator'; import { DEFAULT_COLOR } from './constants'; -import { getChartTypeRecommendations } from './get-chart-Type'; -import { applyTheme, applyDesignRules, applySmartColor } from './spec-processors'; -import { getDataProps, getSelectedData } from './data-processors'; +import { getChartTypeRecommendations } from './plugin/presets/chart-type-recommend/get-chart-Type'; +import { applyTheme, applyDesignRules, applySmartColor } from './plugin/presets/spec-generator/spec-processors'; +import { getDataProps, getSelectedData } from './plugin/presets/data-processors'; import type { ScoringResultForChartType, Advice, AdviseResult, ChartAdviseParams } from '../types'; import type { RuleModule } from '../ruler/types'; import type { ChartKnowledgeBase } from '../../ckb'; /** + * @deprecated 已改造为 plugin 插件形式,之前的硬编码形式函数后续清理掉 * recommending charts given data and dataProps, based on CKB and RuleBase * * @param params input params for charts recommending @@ -40,13 +41,18 @@ export function dataToAdvices({ const chartTypeRecommendations: ScoringResultForChartType[] = getChartTypeRecommendations({ dataProps, ruleBase, - chartWIKI: ckb, + chartWiki: ckb, }); const list: Advice[] = chartTypeRecommendations.map((result) => { // step 2: generate spec for each chart type const { score, chartType } = result; - const chartTypeSpec = getChartTypeSpec(chartType, filteredData, dataProps, ckb[chartType]); + const chartTypeSpec = getChartTypeSpec({ + chartType, + data: filteredData, + dataProps, + chartKnowledge: ckb[chartType], + }); // step 3: apply spec processors such as design rules, theme, color, to improve spec if (chartTypeSpec && refine) { diff --git a/packages/ava/src/advisor/advise-pipeline/get-chart-Type.ts b/packages/ava/src/advisor/advise-pipeline/get-chart-Type.ts deleted file mode 100644 index b479bdf0..00000000 --- a/packages/ava/src/advisor/advise-pipeline/get-chart-Type.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { compareAdvices, scoreRules } from './score-calculator'; - -import type { ChartKnowledge } from '../../ckb'; -import type { ScoringResultForChartType, BasicDataPropertyForAdvice, RuleModule, AdvisorOptions } from '../types'; - -export const getChartTypeRecommendations = ({ - chartWIKI, - dataProps, - ruleBase, - options, -}: { - dataProps: BasicDataPropertyForAdvice[]; - chartWIKI: Record; - ruleBase: Record; - options?: AdvisorOptions; -}) => { - const chatTypes = Object.keys(chartWIKI); - const list: ScoringResultForChartType[] = chatTypes.map((chartType) => { - return scoreRules(chartType, chartWIKI, dataProps, ruleBase, options); - }); - - // filter and sorter - return list.filter((advice) => advice.score > 0).sort(compareAdvices); -}; diff --git a/packages/ava/src/advisor/advise-pipeline/index.ts b/packages/ava/src/advisor/advise-pipeline/index.ts index c5b16914..87d19592 100644 --- a/packages/ava/src/advisor/advise-pipeline/index.ts +++ b/packages/ava/src/advisor/advise-pipeline/index.ts @@ -1,3 +1,2 @@ export * from './data-to-advices'; -export * from './spec-generator'; -export * from './data-processors'; +export * from './plugin'; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/index.ts new file mode 100644 index 00000000..e8fbf9ed --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/index.ts @@ -0,0 +1 @@ +export * from './presets'; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/get-chart-Type.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/get-chart-Type.ts new file mode 100644 index 00000000..f2ce18fb --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/get-chart-Type.ts @@ -0,0 +1,32 @@ +import { compareAdvices, scoreRules } from '../../../score-calculator'; + +import type { ChartKnowledge } from '../../../../../ckb'; +import type { + ScoringResultForChartType, + BasicDataPropertyForAdvice, + RuleModule, + AdvisorOptions, + AdvisorPipelineContext, +} from '../../../../types'; + +export const getChartTypeRecommendations = ({ + chartWiki, + dataProps, + ruleBase, + options, + advisorContext, +}: { + dataProps: BasicDataPropertyForAdvice[]; + chartWiki: Record; + ruleBase: Record; + options?: AdvisorOptions; + advisorContext?: Pick; +}) => { + const chatTypes = Object.keys(chartWiki); + const list: ScoringResultForChartType[] = chatTypes.map((chartType) => { + return scoreRules(chartType, chartWiki, dataProps, ruleBase, options, advisorContext); + }); + + // filter and sorter + return list.filter((advice) => advice.score > 0).sort(compareAdvices); +}; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/index.ts new file mode 100644 index 00000000..69797740 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/index.ts @@ -0,0 +1,2 @@ +export * from './get-chart-Type'; +export { chartTypeRecommendPlugin } from './plugin-config'; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/plugin-config.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/plugin-config.ts new file mode 100644 index 00000000..2eed3380 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/plugin-config.ts @@ -0,0 +1,24 @@ +import { getChartTypeRecommendations } from './get-chart-Type'; + +import type { + AdvisorPipelineContext, + ChartTypeRecommendInput, + ChartTypeRecommendOutput, + AdvisorPluginType, +} from '../../../../types'; + +export const chartTypeRecommendPlugin: AdvisorPluginType = { + name: 'defaultChartTypeRecommend', + execute(input: ChartTypeRecommendInput, context?: AdvisorPipelineContext): ChartTypeRecommendOutput { + const { dataProps } = input; + const { advisor, options, extra } = context || {}; + const chartTypeRecommendations = getChartTypeRecommendations({ + dataProps, + chartWiki: advisor.ckb, + ruleBase: advisor.ruleBase, + options, + advisorContext: { extra }, + }); + return { chartTypeRecommendations }; + }, +}; diff --git a/packages/ava/src/advisor/advise-pipeline/data-processors/get-data-properties.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/get-data-properties.ts similarity index 88% rename from packages/ava/src/advisor/advise-pipeline/data-processors/get-data-properties.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/get-data-properties.ts index 6225483e..bfdde8a3 100644 --- a/packages/ava/src/advisor/advise-pipeline/data-processors/get-data-properties.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/get-data-properties.ts @@ -1,7 +1,7 @@ -import { DataFrame } from '../../../data'; +import { DataFrame } from '../../../../../data'; -import type { Data } from '../../../common/types'; -import type { BasicDataPropertyForAdvice } from '../../types'; +import type { Data } from '../../../../../common/types'; +import type { BasicDataPropertyForAdvice } from '../../../../types'; /** * A utility function that assemble dataProps with user input and DataFrame diff --git a/packages/ava/src/advisor/advise-pipeline/data-processors/get-selected-data.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/get-selected-data.ts similarity index 89% rename from packages/ava/src/advisor/advise-pipeline/data-processors/get-selected-data.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/get-selected-data.ts index f41cab2c..47d47aa0 100644 --- a/packages/ava/src/advisor/advise-pipeline/data-processors/get-selected-data.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/get-selected-data.ts @@ -1,4 +1,4 @@ -import { Data } from '../../../common/types'; +import { Data } from '../../../../../common/types'; /** filter out fields that are not included for advising */ export const getSelectedData = ({ data, fields }: { data: Data; fields?: string[] }) => { diff --git a/packages/ava/src/advisor/advise-pipeline/data-processors/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/index.ts similarity index 58% rename from packages/ava/src/advisor/advise-pipeline/data-processors/index.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/index.ts index baad8d61..0834041c 100644 --- a/packages/ava/src/advisor/advise-pipeline/data-processors/index.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/index.ts @@ -1,2 +1,3 @@ export * from './get-data-properties'; export * from './get-selected-data'; +export { dataProcessorPlugin } from './plugin-config'; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/plugin-config.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/plugin-config.ts new file mode 100644 index 00000000..0caa5d5e --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/plugin-config.ts @@ -0,0 +1,26 @@ +import { cloneDeep } from 'lodash'; + +import { getDataProps } from './get-data-properties'; +import { getSelectedData } from './get-selected-data'; + +import type { + AdvisorPipelineContext, + DataProcessorInput, + DataProcessorOutput, + AdvisorPluginType, +} from '../../../../types'; + +export const dataProcessorPlugin: AdvisorPluginType = { + name: 'defaultDataProcessor', + execute: (input: DataProcessorInput, context: AdvisorPipelineContext): DataProcessorOutput => { + const { data, customDataProps } = input; + const { fields } = context?.options || {}; + const copyData = cloneDeep(data); + const dataProps = getDataProps(copyData, fields, customDataProps); + const filteredData = getSelectedData({ data: copyData, fields }); + return { + data: filteredData, + dataProps, + }; + }, +}; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/index.ts new file mode 100644 index 00000000..b4f3fbb7 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/index.ts @@ -0,0 +1,4 @@ +export * from './chart-type-recommend'; +export * from './data-processors'; +export * from './spec-generator'; +export * from './visual-encoder'; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/area.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts similarity index 74% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/area.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts index 5692b012..632da5e4 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/area.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts @@ -1,8 +1,9 @@ -import { hasSubset, intersects } from '../../../utils'; -import { splitAreaXYSeries } from '../splitFields'; +import { hasSubset, intersects } from '../../../../../utils'; +import { splitAreaXYSeries } from '../../visual-encoder/split-fields'; +import { getLineSize } from '../../visual-encoder/utils'; -import type { Data } from '../../../../common/types'; -import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; +import type { Data, Datum } from '../../../../../../common/types'; +import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; export function areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const field4X = dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal'])); @@ -16,6 +17,10 @@ export function areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): encode: { x: field4X.name, y: field4Y.name, + size: (datum: Datum) => getLineSize(datum, data, { field4X }), + }, + legend: { + size: false, }, }; @@ -33,6 +38,10 @@ export function stackedAreaChart(data: Data, dataProps: BasicDataPropertyForAdvi x: field4X.name, y: field4Y.name, color: field4Series.name, + size: (datum: Datum) => getLineSize(datum, data, { field4Split: field4Series, field4X }), + }, + legend: { + size: false, }, transform: [{ type: 'stackY' }], }; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts similarity index 94% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts index d7adf008..8f0e54bf 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts @@ -1,7 +1,7 @@ -import { splitBarXYSeries } from '../splitFields'; +import { splitBarXYSeries } from '../../visual-encoder/split-fields'; -import type { Data } from '../../../../common/types'; -import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; +import type { Data } from '../../../../../../common/types'; +import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; export function barChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const [field4X, field4Y, field4Color] = splitBarXYSeries(dataProps); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/column.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts similarity index 89% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/column.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts index 7c6653ad..da66f8ad 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/column.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts @@ -1,7 +1,7 @@ -import { Data } from '../../../../common/types'; -import { Advice, BasicDataPropertyForAdvice } from '../../../types'; -import { compare, hasSubset } from '../../../utils'; -import { splitColumnXYSeries } from '../splitFields'; +import { Data } from '../../../../../../common/types'; +import { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; +import { compare, hasSubset } from '../../../../../utils'; +import { splitColumnXYSeries } from '../../visual-encoder/split-fields'; export function columnChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const nominalFields = dataProps.filter((field) => hasSubset(field.levelOfMeasurements, ['Nominal'])); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/heatmap.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts similarity index 83% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/heatmap.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts index 8e0d0daf..f6fdbbc0 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/heatmap.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts @@ -1,7 +1,7 @@ -import { intersects, compare, hasSubset } from '../../../utils'; +import { intersects, compare, hasSubset } from '../../../../../utils'; -import type { Data } from '../../../../common/types'; -import type { BasicDataPropertyForAdvice, Advice } from '../../../types'; +import type { Data } from '../../../../../../common/types'; +import type { BasicDataPropertyForAdvice, Advice } from '../../../../../types'; export function heatmap(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const axisFields = dataProps.filter((field) => intersects(field.levelOfMeasurements, ['Nominal', 'Ordinal'])); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/histogram.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts similarity index 78% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/histogram.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts index e19bf568..a5d8cf98 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/histogram.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts @@ -1,7 +1,7 @@ -import { hasSubset } from '../../../utils'; +import { hasSubset } from '../../../../../utils'; -import type { Data } from '../../../../common/types'; -import type { BasicDataPropertyForAdvice, Advice } from '../../../types'; +import type { Data } from '../../../../../../common/types'; +import type { BasicDataPropertyForAdvice, Advice } from '../../../../../types'; export function histogram(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const field = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/index.ts similarity index 100% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/index.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/index.ts diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/line.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/line.ts similarity index 68% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/line.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/line.ts index e79b90e5..e3bf3584 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/line.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/line.ts @@ -1,7 +1,8 @@ -import { splitLineXY } from '../splitFields'; +import { splitLineXY } from '../../visual-encoder/split-fields'; +import { getLineSize } from '../../visual-encoder/utils'; -import type { Data } from '../../../../common/types'; -import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; +import type { Data, Datum } from '../../../../../../common/types'; +import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; export function lineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const [field4X, field4Y, field4Color] = splitLineXY(dataProps); @@ -13,6 +14,10 @@ export function lineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): encode: { x: field4X.name, y: field4Y.name, + size: (datum: Datum) => getLineSize(datum, data, { field4X }), + }, + legend: { + size: false, }, }; @@ -34,6 +39,10 @@ export function stepLineChart(data: Data, dataProps: BasicDataPropertyForAdvice[ x: field4X.name, y: field4Y.name, shape: 'hvh', + size: (datum: Datum) => getLineSize(datum, data, { field4X }), + }, + legend: { + size: false, }, }; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts similarity index 87% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts index f86f41d3..08219c77 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts @@ -1,7 +1,7 @@ -import { splitAngleColor } from '../splitFields'; +import { splitAngleColor } from '../../visual-encoder/split-fields'; -import type { Data } from '../../../../common/types'; -import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; +import type { Data } from '../../../../../../common/types'; +import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; export function pieChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const [field4Color, field4Angle] = splitAngleColor(dataProps); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/scatter.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts similarity index 91% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/charts/scatter.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts index 4f1ecd5d..37931be5 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/scatter.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts @@ -1,8 +1,8 @@ -import { pearson } from '../../../../data'; -import { hasSubset, compare, intersects } from '../../../utils'; +import { pearson } from '../../../../../../data'; +import { hasSubset, compare, intersects } from '../../../../../utils'; -import type { BasicDataPropertyForAdvice, Advice } from '../../../types'; -import type { Data } from '../../../../common/types'; +import type { BasicDataPropertyForAdvice, Advice } from '../../../../../types'; +import type { Data } from '../../../../../../common/types'; export function scatterPlot(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const intervalFields = dataProps.filter((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts similarity index 84% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts index f5af2485..1d497798 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts @@ -1,4 +1,4 @@ -import { CHART_IDS } from '../../../ckb'; +import { CHART_IDS } from '../../../../../ckb'; import { pieChart, @@ -22,9 +22,10 @@ import { histogram, } from './charts'; -import type { Data } from '../../../common/types'; -import type { ChartId, ChartKnowledge } from '../../../ckb'; -import type { BasicDataPropertyForAdvice } from '../../ruler'; +import type { Data } from '../../../../../common/types'; +import type { ChartId, ChartKnowledge } from '../../../../../ckb'; +import type { BasicDataPropertyForAdvice } from '../../../../ruler'; +import type { Advice } from '../../../../types'; /** * Convert chartType + data to antv-spec @@ -36,12 +37,17 @@ import type { BasicDataPropertyForAdvice } from '../../ruler'; * @param chartKnowledge chart knowledge of a singble chart * @returns spec or null */ -export function getChartTypeSpec( - chartType: string, - data: Data, - dataProps: BasicDataPropertyForAdvice[], - chartKnowledge?: ChartKnowledge -) { +export function getChartTypeSpec({ + chartType, + data, + dataProps, + chartKnowledge, +}: { + chartType: string; + data: Data; + dataProps: BasicDataPropertyForAdvice[]; + chartKnowledge?: ChartKnowledge; +}): Advice['spec'] { // step 0: check whether the chartType is default in `ChartId` // if not, use customized `toSpec` function if (!CHART_IDS.includes(chartType as ChartId) && chartKnowledge) { diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/index.ts new file mode 100644 index 00000000..f9e80344 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/index.ts @@ -0,0 +1,2 @@ +export { getChartTypeSpec } from './get-chart-spec'; +export { specGeneratorPlugin } from './plugin-config'; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/plugin-config.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/plugin-config.ts new file mode 100644 index 00000000..b8b8a687 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/plugin-config.ts @@ -0,0 +1,60 @@ +import { deepMix } from '../../../../utils'; +import { DEFAULT_COLOR } from '../../../constants'; + +import { applyDesignRules, applySmartColor, applyTheme } from './spec-processors'; +import { getChartTypeSpec } from './get-chart-spec'; + +import type { + AdvisorPipelineContext, + SpecGeneratorInput, + SpecGeneratorOutput, + AdvisorPluginType, +} from '../../../../types'; + +// todo 内置的 visualEncode 和 spec generate 插件需要明确支持哪些图表类型 +export const specGeneratorPlugin: AdvisorPluginType = { + name: 'defaultSpecGenerator', + execute: (input: SpecGeneratorInput, context: AdvisorPipelineContext): SpecGeneratorOutput => { + const { chartTypeRecommendations, dataProps, data } = input; + const { options, advisor } = context || {}; + const { refine = false, theme, colorOptions, smartColor } = options || {}; + const { themeColor = DEFAULT_COLOR, colorSchemeType, simulationType } = colorOptions || {}; + const advices = chartTypeRecommendations + ?.map((chartTypeAdvice) => { + const { chartType } = chartTypeAdvice; + const chartKnowledge = advisor.ckb[chartType]; + const chartTypeSpec = + chartKnowledge?.toSpec?.(data, dataProps) ?? + getChartTypeSpec({ + chartType, + data, + dataProps, + chartKnowledge, + }); + + // step 3: apply spec processors such as design rules, theme, color, to improve spec + if (chartTypeSpec && refine) { + const partEncSpec = applyDesignRules(chartType, dataProps, advisor.ruleBase, chartTypeSpec, context); + deepMix(chartTypeSpec, partEncSpec); + } + + // step 4: custom theme + if (chartTypeSpec) { + if (theme && !smartColor) { + const partEncSpec = applyTheme(dataProps, chartTypeSpec, theme); + deepMix(chartTypeSpec, partEncSpec); + } else if (smartColor) { + const partEncSpec = applySmartColor(dataProps, chartTypeSpec, themeColor, colorSchemeType, simulationType); + deepMix(chartTypeSpec, partEncSpec); + } + } + return { + type: chartTypeAdvice.chartType, + spec: chartTypeSpec, + score: chartTypeAdvice.score, + }; + }) + .filter((advices) => advices.spec); + return { advices }; + }, +}; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-processors/apply-design-rules.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-design-rules.ts similarity index 57% rename from packages/ava/src/advisor/advise-pipeline/spec-processors/apply-design-rules.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-design-rules.ts index b18d84d4..7a601ae8 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-processors/apply-design-rules.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-design-rules.ts @@ -1,18 +1,25 @@ -import { BasicDataPropertyForAdvice, RuleModule, Specification, DesignRuleModule } from '../../types'; -import { deepMix } from '../../utils'; +import { + BasicDataPropertyForAdvice, + RuleModule, + Specification, + DesignRuleModule, + AdvisorPipelineContext, +} from '../../../../../types'; +import { deepMix } from '../../../../../utils'; export function applyDesignRules( chartType: string, dataProps: BasicDataPropertyForAdvice[], ruleBase: Record, - chartSpec: Specification + chartSpec: Specification, + advisorContext?: Pick ) { const toCheckRules = Object.values(ruleBase).filter( (rule: RuleModule) => - rule.type === 'DESIGN' && rule.trigger({ dataProps, chartType }) && !ruleBase[rule.id].option?.off + rule.type === 'DESIGN' && rule.trigger({ dataProps, chartType, advisorContext }) && !ruleBase[rule.id].option?.off ); const encodingSpec = toCheckRules.reduce((lastSpec, rule: RuleModule) => { - const relatedSpec = (rule as DesignRuleModule).optimizer(dataProps, chartSpec); + const relatedSpec = (rule as DesignRuleModule).optimizer(dataProps, chartSpec, advisorContext); return deepMix(lastSpec, relatedSpec); }, {}); return encodingSpec; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-processors/apply-smart-color.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-smart-color.ts similarity index 94% rename from packages/ava/src/advisor/advise-pipeline/spec-processors/apply-smart-color.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-smart-color.ts index 27ade3bb..c1d66318 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-processors/apply-smart-color.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-smart-color.ts @@ -1,8 +1,8 @@ import { ColorSchemeType, colorToHex } from '@antv/color-schema'; import { SimulationType, hexToColor, paletteGeneration, colorSimulation } from '@antv/smart-color'; -import { BasicDataPropertyForAdvice, Specification } from '../../types'; -import { getSpecWithEncodeType } from '../../utils/infer-data-type'; +import { BasicDataPropertyForAdvice, Specification } from '../../../../../types'; +import { getSpecWithEncodeType } from '../../../../../utils/infer-data-type'; export function applySmartColor( dataProps: BasicDataPropertyForAdvice[], diff --git a/packages/ava/src/advisor/advise-pipeline/spec-processors/apply-theme.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-theme.ts similarity index 92% rename from packages/ava/src/advisor/advise-pipeline/spec-processors/apply-theme.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-theme.ts index d29b1df0..993eb518 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-processors/apply-theme.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/apply-theme.ts @@ -1,9 +1,9 @@ import { type ColorSchemeType, colorToHex } from '@antv/color-schema'; import { hexToColor, paletteGeneration } from '@antv/smart-color'; -import { BasicDataPropertyForAdvice, Specification, Theme } from '../../types'; -import { getSpecWithEncodeType } from '../../utils/infer-data-type'; -import { DISCRETE_PALETTE_TYPES, CATEGORICAL_PALETTE_TYPES } from '../constants'; +import { BasicDataPropertyForAdvice, Specification, Theme } from '../../../../../types'; +import { getSpecWithEncodeType } from '../../../../../utils/infer-data-type'; +import { DISCRETE_PALETTE_TYPES, CATEGORICAL_PALETTE_TYPES } from '../../../../constants'; export function applyTheme(dataProps: BasicDataPropertyForAdvice[], chartSpec: Specification, theme: Theme) { const specWithEncodeType = getSpecWithEncodeType(chartSpec); diff --git a/packages/ava/src/advisor/advise-pipeline/spec-processors/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/index.ts similarity index 100% rename from packages/ava/src/advisor/advise-pipeline/spec-processors/index.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/spec-processors/index.ts diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/index.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/index.ts new file mode 100644 index 00000000..959fe62c --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/index.ts @@ -0,0 +1 @@ +export { visualEncoderPlugin } from './plugin-config'; diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/plugin-config.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/plugin-config.ts new file mode 100644 index 00000000..9e31e04d --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/plugin-config.ts @@ -0,0 +1,9 @@ +import type { AdvisorPluginType, VisualEncoderInput, VisualEncoderOutput } from '../../../../types'; + +export const visualEncoderPlugin: AdvisorPluginType = { + name: 'defaultVisualEncoder', + execute: (input) => { + // todo 从 spec-generator 中拆分出来核心 encode 部分 + return input; + }, +}; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/splitFields.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts similarity index 60% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/splitFields.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts index 738ebc27..a3a374a9 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/splitFields.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts @@ -1,19 +1,35 @@ -import { isParentChild } from '../../../data'; -import { BasicDataPropertyForAdvice } from '../../types'; -import { compare, hasSubset, intersects } from '../../utils'; +import { isParentChild } from '../../../../../data'; +import { compare, hasSubset, intersects } from '../../../../utils'; + +import type { BasicDataPropertyForAdvice } from '../../../../types'; type ReturnField = BasicDataPropertyForAdvice | undefined; export function splitAngleColor(dataProps: BasicDataPropertyForAdvice[]): [ReturnField, ReturnField] { - const field4Color = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal'])); - const field4Angle = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); + const field4Color = + dataProps.find((field) => intersects(field.levelOfMeasurements, ['Nominal'])) ?? + dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal'])) ?? + dataProps.find((field) => intersects(field.levelOfMeasurements, ['Interval'])); + const field4Angle = + dataProps + .filter((field) => field !== field4Color) + .find((field) => intersects(field.levelOfMeasurements, ['Interval'])) ?? + dataProps + .filter((field) => field !== field4Color) + .find((field) => intersects(field.levelOfMeasurements, ['Nominal', 'Time', 'Ordinal'])); return [field4Color, field4Angle]; } export function splitLineXY(dataProps: BasicDataPropertyForAdvice[]): [ReturnField, ReturnField, ReturnField] { - const field4X = dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal'])); - const field4Y = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); - const field4Color = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal'])); + const field4X = + dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal', 'Nominal'])) ?? + dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); + const field4Y = dataProps + .filter((field) => field !== field4X) + .find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); + const field4Color = dataProps + .filter((field) => field !== field4X && field !== field4Y) + .find((field) => intersects(field.levelOfMeasurements, ['Nominal', 'Ordinal', 'Time'])); return [field4X, field4Y, field4Color]; } diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/utils.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/utils.ts new file mode 100644 index 00000000..5671fd71 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/utils.ts @@ -0,0 +1,28 @@ +import { uniq } from 'lodash'; + +import type { Datum } from '../../../../../common/types'; +import type { FieldInfo } from '../../../../../data'; + +// 识别 x 轴是否只有一条数据(绘制的折线图是否只有一个点) +export const isUniqueXValue = ({ data, xField }: { xField: string; data: Datum[] }): boolean => { + const uniqXValues = uniq(data.map((datum) => datum[xField])); + return uniqXValues.length <= 1; +}; + +/** 获取线宽:当只有一条数据时,折线图需要特殊设置线宽,否则仅绘制 1px,看不见 */ +export const getLineSize = ( + datum: Datum, + allData: Datum[], + fields: { + field4Split?: FieldInfo; + field4X?: FieldInfo; + } +) => { + const { field4Split, field4X } = fields; + if (field4Split?.name && field4X?.name) { + const seriesValue = datum[field4Split.name]; + const splitData = allData.filter((item) => field4Split.name && item[field4Split.name] === seriesValue); + return isUniqueXValue({ data: splitData, xField: field4X.name }) ? 5 : undefined; + } + return field4X?.name && isUniqueXValue({ data: allData, xField: field4X.name }) ? 5 : undefined; +}; diff --git a/packages/ava/src/advisor/advise-pipeline/score-calculator/compute-score.ts b/packages/ava/src/advisor/advise-pipeline/score-calculator/compute-score.ts index 57e91ac9..95415968 100644 --- a/packages/ava/src/advisor/advise-pipeline/score-calculator/compute-score.ts +++ b/packages/ava/src/advisor/advise-pipeline/score-calculator/compute-score.ts @@ -1,7 +1,7 @@ import { Info, RuleModule } from '../../ruler'; import { DEFAULT_RULE_WEIGHTS } from '../../constants'; -import type { ScoringResultForRule } from '../../types'; +import type { AdvisorPipelineContext, ScoringResultForRule } from '../../types'; import type { ChartRuleModule } from '../../ruler/types'; import type { ChartId, ChartKnowledgeBase } from '../../../ckb'; @@ -9,11 +9,12 @@ const defaultWeights = DEFAULT_RULE_WEIGHTS; export const computeScore = ( chartType: ChartId | string, - chartWIKI: ChartKnowledgeBase, + chartWiki: ChartKnowledgeBase, ruleBase: Record, ruleType: 'HARD' | 'SOFT', info: Info, - log: ScoringResultForRule[] + log: ScoringResultForRule[], + advisorContext?: Pick ) => { // initial score is 1 for HARD rules and 0 for SOFT rules let computedScore = 1; @@ -21,12 +22,23 @@ export const computeScore = ( .filter((r: RuleModule) => { const weight = r.option?.weight || defaultWeights[r.id] || 1; const extra = r.option?.extra; - return r.type === ruleType && r.trigger({ ...info, weight, ...extra, chartType, chartWIKI }) && !r.option?.off; + return ( + r.type === ruleType && + r.trigger({ ...info, weight, ...extra, chartType, chartWIKI: chartWiki, advisorContext }) && + !r.option?.off + ); }) .forEach((r: RuleModule) => { const weight = r.option?.weight || defaultWeights[r.id] || 1; const extra = r.option?.extra; - const base = (r as ChartRuleModule).validator({ ...info, weight, ...extra, chartType, chartWIKI }) as number; + const base = (r as ChartRuleModule).validator({ + ...info, + weight, + ...extra, + chartType, + chartWIKI: chartWiki, + advisorContext, + }) as number; const score = weight * base; computedScore *= score; log.push({ phase: 'ADVISE', ruleId: r.id, score, base, weight, ruleType }); diff --git a/packages/ava/src/advisor/advise-pipeline/score-calculator/score-rules.ts b/packages/ava/src/advisor/advise-pipeline/score-calculator/score-rules.ts index 872d6ef9..10a76090 100644 --- a/packages/ava/src/advisor/advise-pipeline/score-calculator/score-rules.ts +++ b/packages/ava/src/advisor/advise-pipeline/score-calculator/score-rules.ts @@ -4,6 +4,7 @@ import { AdvisorOptions, ScoringResultForChartType, ScoringResultForRule, + AdvisorPipelineContext, } from '../../types'; import { computeScore } from './compute-score'; @@ -21,10 +22,11 @@ import type { ChartId, ChartKnowledgeBase } from '../../../ckb'; */ export function scoreRules( chartType: ChartId | string, - chartWIKI: ChartKnowledgeBase, + chartWiki: ChartKnowledgeBase, dataProps: BasicDataPropertyForAdvice[], ruleBase: Record, - options?: AdvisorOptions + options?: AdvisorOptions, + advisorContext?: Pick ): ScoringResultForChartType { const purpose = options ? options.purpose : ''; const preferences = options ? options.preferences : undefined; @@ -34,7 +36,7 @@ export function scoreRules( const info = { dataProps, chartType, purpose, preferences }; - const hardScore = computeScore(chartType, chartWIKI, ruleBase, 'HARD', info, log); + const hardScore = computeScore(chartType, chartWiki, ruleBase, 'HARD', info, log, advisorContext); // Hard-Rule pruning if (hardScore === 0) { @@ -42,7 +44,7 @@ export function scoreRules( return result; } - const softScore = computeScore(chartType, chartWIKI, ruleBase, 'SOFT', info, log); + const softScore = computeScore(chartType, chartWiki, ruleBase, 'SOFT', info, log, advisorContext); const score = hardScore * softScore; diff --git a/packages/ava/src/advisor/advisor.ts b/packages/ava/src/advisor/advisor.ts index b4adf599..333001ee 100644 --- a/packages/ava/src/advisor/advisor.ts +++ b/packages/ava/src/advisor/advisor.ts @@ -1,12 +1,27 @@ +import { forEach } from 'lodash'; + import { ckb } from '../ckb'; import { processRuleCfg } from './ruler'; import { dataToAdvices } from './advise-pipeline'; import { checkRules } from './lint-pipeline/check-rules'; +import { BaseComponent, Pipeline } from './pipeline'; import type { ChartKnowledgeBase } from '../ckb'; import type { RuleModule } from './ruler'; -import type { AdvisorConfig, Advice, AdviseParams, AdviseResult, LintResult, LintParams, Lint } from './types'; +import type { + AdvisorConfig, + Advice, + AdviseParams, + AdviseResult, + LintResult, + LintParams, + Lint, + AdvisorPipelineContext, + AdvisorPluginType, + DataProcessorInput, + SpecGeneratorOutput, +} from './types'; export class Advisor { /** @@ -19,11 +34,36 @@ export class Advisor { */ ruleBase: Record; - constructor(config: AdvisorConfig = {}) { + context: AdvisorPipelineContext; + + plugins: AdvisorPluginType[] = []; + + pipeline: Pipeline; + + constructor( + config: AdvisorConfig = {}, + custom: { + plugins?: (AdvisorPluginType & { componentName: string })[]; + components?: BaseComponent[]; + /** extra info to pass through the pipeline + * 额外透传到推荐 pipeline 中的业务信息 + */ + extra?: Record; + } = {} + ) { + // init + const { plugins, components, extra = {} } = custom; this.ckb = ckb(config.ckbCfg); this.ruleBase = processRuleCfg(config.ruleCfg); + this.context = { advisor: this, extra }; + this.pipeline = new Pipeline({ + components, + context: this.context, + }); + this.registerPlugins(plugins); } + // todo 暂时还在用旧链路 advise(params: AdviseParams): Advice[] { const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); return adviseResult.advices; @@ -34,6 +74,16 @@ export class Advisor { return adviseResult; } + // todo 补充 adviseAsyncWithLog? + async adviseAsync(params: AdviseParams): Promise { + this.context = { + ...this.context, + options: params.options, + }; + const adviseResult = await this.pipeline.execute(params); + return adviseResult.advices; + } + lint(params: LintParams): Lint[] { const lintResult = checkRules(params, this.ruleBase, this.ckb); return lintResult.lints; @@ -43,4 +93,12 @@ export class Advisor { const lintResult = checkRules(params, this.ruleBase, this.ckb); return lintResult; } + + registerPlugins(plugins: (AdvisorPluginType & { componentName: string })[] = []) { + forEach(plugins, (plugin) => { + this.plugins.push(plugin); + const pipelineComponent = this.pipeline.components.find((component) => component.name === plugin.componentName); + pipelineComponent.registerPlugin(plugin); + }); + } } diff --git a/packages/ava/src/advisor/constants.ts b/packages/ava/src/advisor/constants.ts index 261da84d..3513b27f 100644 --- a/packages/ava/src/advisor/constants.ts +++ b/packages/ava/src/advisor/constants.ts @@ -11,3 +11,11 @@ export const DEFAULT_RULE_WEIGHTS: Record = { 'purpose-check': 1.0, 'series-qty-limit': 0.8, }; + +/** 内置默认模块名 */ +export enum PresetComponentName { + dataAnalyzer, + chartTypeRecommender, + chartEncoder, + specGenerator, +} diff --git a/packages/ava/src/advisor/index.ts b/packages/ava/src/advisor/index.ts index 5c387194..c385ff6a 100644 --- a/packages/ava/src/advisor/index.ts +++ b/packages/ava/src/advisor/index.ts @@ -1,2 +1,3 @@ export { Advisor } from './advisor'; export * from './types'; +export { PresetComponentName } from './constants'; diff --git a/packages/ava/src/advisor/lint-pipeline/lintRules.ts b/packages/ava/src/advisor/lint-pipeline/lintRules.ts index c3e56b30..2df95567 100644 --- a/packages/ava/src/advisor/lint-pipeline/lintRules.ts +++ b/packages/ava/src/advisor/lint-pipeline/lintRules.ts @@ -1,7 +1,7 @@ import { Info, RuleModule } from '../ruler'; import type { Specification } from '../../common/types'; -import type { ScoringResultForRule, Lint } from '../types'; +import type { ScoringResultForRule, Lint, AdvisorPipelineContext } from '../types'; import type { ChartRuleModule, DesignRuleModule } from '../ruler'; import type { ChartKnowledgeBase } from '../../ckb'; @@ -12,7 +12,8 @@ export function lintRules( log: ScoringResultForRule[], lints: Lint[], ckb: ChartKnowledgeBase, - spec?: Specification + spec?: Specification, + advisorContext?: Pick ) { const judge = (type: 'HARD' | 'SOFT' | 'DESIGN') => { if (ruleTypeToLint === 'DESIGN') { @@ -24,20 +25,28 @@ export function lintRules( Object.values(ruleBase) .filter((r: RuleModule) => { const { weight, extra } = r.option || {}; - return judge(r.type) && !r.option?.off && r.trigger({ ...info, weight, ...extra, chartWIKI: ckb }); + return ( + judge(r.type) && !r.option?.off && r.trigger({ ...info, weight, ...extra, chartWIKI: ckb, advisorContext }) + ); }) .forEach((r: RuleModule) => { const { type, id, docs } = r; let score: number; if (ruleTypeToLint === 'DESIGN') { - const fix = (r as DesignRuleModule).optimizer(info.dataProps, spec); + const fix = (r as DesignRuleModule).optimizer(info.dataProps, spec, advisorContext); // no fix -> means no violation score = Object.keys(fix).length === 0 ? 1 : 0; lints.push({ type, id, score, fix, docs }); } else { const { weight, extra } = r.option || {}; // no weight for linter's result - score = (r as ChartRuleModule).validator({ ...info, weight, ...extra, chartWIKI: ckb }) as number; + score = (r as ChartRuleModule).validator({ + ...info, + weight, + ...extra, + chartWIKI: ckb, + advisorContext, + }) as number; lints.push({ type, id, score, docs }); } log.push({ phase: 'LINT', ruleId: id, score, base: score, weight: 1, ruleType: type }); diff --git a/packages/ava/src/advisor/pipeline/component.ts b/packages/ava/src/advisor/pipeline/component.ts new file mode 100644 index 00000000..075ede4a --- /dev/null +++ b/packages/ava/src/advisor/pipeline/component.ts @@ -0,0 +1,135 @@ +import { isFunction, last } from 'lodash'; + +import type { AdvisorPluginType, AdvisorPipelineContext } from '../types'; + +/** 收集多个 plugin 的输出结果 */ +type PluginResultMap = Record; + +interface BaseComponentType { + name: string; + /** 内部 plugin 数组的执行方式,目前仅并行执行,后续可扩展串行、瀑布型等,适应更多的插件编排方式 */ + executeType?: 'parallel'; + registerPlugin: (plugin: AdvisorPluginType) => void; + unloadPlugin: (pluginName: string) => void; + afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; + execute: (input: Input) => Output; + executeAsync: (input: Input) => Promise; +} + +export type ComponentOptions = { + plugins?: AdvisorPluginType[]; + afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; + context?: AdvisorPipelineContext; +}; + +export class BaseComponent implements BaseComponentType { + name: string; + + executeType: 'parallel'; + + readonly context?: AdvisorPipelineContext; + + private plugins: AdvisorPluginType[] = []; + + /** 是否存在异步插件 */ + private hasAsyncPlugin: boolean = false; + + afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; + + getPlugins() { + return this.plugins; + } + + get isAsync() { + return this.hasAsyncPlugin; + } + + constructor(name, options?: ComponentOptions) { + this.name = name; + this.afterPluginsExecute = options?.afterPluginsExecute ?? this.defaultAfterPluginsExecute; + this.context = options?.context; + this.hasAsyncPlugin = !!options?.plugins?.find((plugin) => this.isPluginAsync(plugin)); + options?.plugins?.forEach((plugin) => { + this.registerPlugin(plugin); + }); + } + + private defaultAfterPluginsExecute(params: Record) { + if (this.plugins.length) { + const lastPlugin = last(this.plugins); + return params[lastPlugin.name]; + } + return null; + } + + private isPluginAsync(plugin: AdvisorPluginType) { + // 检测插件是否为异步的 + if (plugin.type === 'async' || plugin.execute.constructor.name === 'AsyncFunction') { + return true; + } + return false; + } + + registerPlugin(plugin: AdvisorPluginType) { + plugin.onLoad?.(this.context); + if (this.isPluginAsync(plugin)) { + this.hasAsyncPlugin = true; + } + const existPlugin = this.plugins.find((item) => item.name === plugin.name); + if (existPlugin) { + this.unloadPlugin(existPlugin.name); + } + this.plugins.push(plugin); + } + + unloadPlugin(pluginName: string) { + const plugin = this.plugins.find((item) => item.name === pluginName); + if (plugin) { + plugin.onUnload?.(this.context); + this.plugins = this.plugins.filter((item) => item.name !== pluginName); + } + } + + private executeSinglePlugin(plugin: AdvisorPluginType, params: Input, result?: PluginResultMap) { + plugin.onBeforeExecute?.(params, this.context); + if (isFunction(plugin.condition) && !plugin.condition(params, this.context)) return null; + if (this.isPluginAsync(plugin)) { + return plugin.execute(params, this.context).then((output: Output) => { + plugin.onAfterExecute?.(output, this.context); + // eslint-disable-next-line no-param-reassign + result[plugin.name] = output; + }); + } + const output = plugin.execute(params, this.context) as Output; + plugin.onAfterExecute?.(output, this.context); + // eslint-disable-next-line no-param-reassign + result[plugin.name] = output; + return output; + } + + execute(params: Input): Output { + if (this.hasAsyncPlugin) { + // eslint-disable-next-line no-console + console.warn('存在异步执行的插件,请使用 executeAsync'); + } + const pluginsOutput = {}; + this.plugins.forEach((plugin) => { + this.executeSinglePlugin(plugin, params, pluginsOutput); + }); + return this.afterPluginsExecute?.(pluginsOutput, this.context); + } + + async executeAsync(params: Input): Promise { + if (!this.hasAsyncPlugin) { + return Promise.resolve(this.execute(params)); + } + const pluginsOutput = {}; + return Promise.all( + this.plugins.map(async (plugin) => { + await this.executeSinglePlugin(plugin, params, pluginsOutput); + }) + ).then(async () => { + return this.afterPluginsExecute?.(pluginsOutput, this.context); + }); + } +} diff --git a/packages/ava/src/advisor/pipeline/index.ts b/packages/ava/src/advisor/pipeline/index.ts new file mode 100644 index 00000000..7905130d --- /dev/null +++ b/packages/ava/src/advisor/pipeline/index.ts @@ -0,0 +1,2 @@ +export * from './component'; +export * from './pipeline'; diff --git a/packages/ava/src/advisor/pipeline/pipeline.ts b/packages/ava/src/advisor/pipeline/pipeline.ts new file mode 100644 index 00000000..e2f1383e --- /dev/null +++ b/packages/ava/src/advisor/pipeline/pipeline.ts @@ -0,0 +1,39 @@ +import { AsyncSeriesWaterfallHook } from 'tapable'; + +import { BaseComponent } from './component'; +import { getPresetPipelineComponents } from './presets/default-components'; + +import type { AdvisorPipelineContext } from '../types'; + +export class Pipeline { + components: BaseComponent[]; + + componentsManager: AsyncSeriesWaterfallHook; + + constructor({ components, context }: { components: BaseComponent[]; context: AdvisorPipelineContext }) { + this.components = components ?? getPresetPipelineComponents({ context }); + this.componentsManager = new AsyncSeriesWaterfallHook(['initialParams']); + + components.forEach((component) => { + if (!component) return; + this.componentsManager.tapPromise(component.name, async (previousResult) => { + const input = previousResult; + const componentOutput = await component.executeAsync(input || {}); + + return { + ...input, + ...componentOutput, + }; + }); + }); + } + + async execute(initialParams: Input): Promise { + const result = await this.componentsManager.promise(initialParams); + return result; + } + + getComponent(componentName: string) { + return this.components.find((component) => component.name === componentName); + } +} diff --git a/packages/ava/src/advisor/pipeline/presets/chart-encoder.ts b/packages/ava/src/advisor/pipeline/presets/chart-encoder.ts new file mode 100644 index 00000000..a4cb1908 --- /dev/null +++ b/packages/ava/src/advisor/pipeline/presets/chart-encoder.ts @@ -0,0 +1,12 @@ +import { visualEncoderPlugin } from '../../advise-pipeline'; +import { PresetComponentName } from '../../constants'; +import { BaseComponent, type ComponentOptions } from '../component'; + +export class ChartEncoder extends BaseComponent { + constructor(options: ComponentOptions) { + super(PresetComponentName.chartEncoder, { + plugins: [visualEncoderPlugin], + ...options, + }); + } +} diff --git a/packages/ava/src/advisor/pipeline/presets/chart-type-recommender.ts b/packages/ava/src/advisor/pipeline/presets/chart-type-recommender.ts new file mode 100644 index 00000000..d73c40da --- /dev/null +++ b/packages/ava/src/advisor/pipeline/presets/chart-type-recommender.ts @@ -0,0 +1,12 @@ +import { chartTypeRecommendPlugin } from '../../advise-pipeline'; +import { PresetComponentName } from '../../constants'; +import { BaseComponent, type ComponentOptions } from '../component'; + +export class ChartTypeRecommender extends BaseComponent { + constructor(options: ComponentOptions) { + super(PresetComponentName.chartTypeRecommender, { + plugins: [chartTypeRecommendPlugin], + ...options, + }); + } +} diff --git a/packages/ava/src/advisor/pipeline/presets/data-analyzer.ts b/packages/ava/src/advisor/pipeline/presets/data-analyzer.ts new file mode 100644 index 00000000..e6fe5e0c --- /dev/null +++ b/packages/ava/src/advisor/pipeline/presets/data-analyzer.ts @@ -0,0 +1,12 @@ +import { dataProcessorPlugin } from '../../advise-pipeline'; +import { PresetComponentName } from '../../constants'; +import { BaseComponent, type ComponentOptions } from '../component'; + +export class DataAnalyzer extends BaseComponent { + constructor(options: ComponentOptions) { + super(PresetComponentName.dataAnalyzer, { + plugins: [dataProcessorPlugin], + ...options, + }); + } +} diff --git a/packages/ava/src/advisor/pipeline/presets/default-components.ts b/packages/ava/src/advisor/pipeline/presets/default-components.ts new file mode 100644 index 00000000..a1932c4f --- /dev/null +++ b/packages/ava/src/advisor/pipeline/presets/default-components.ts @@ -0,0 +1,15 @@ +import { ChartEncoder } from './chart-encoder'; +import { ChartTypeRecommender } from './chart-type-recommender'; +import { DataAnalyzer } from './data-analyzer'; +import { SpecGenerator } from './spec-generator'; + +import type { ComponentOptions } from '../component'; + +export const getPresetPipelineComponents = (options: ComponentOptions) => { + return [ + new DataAnalyzer(options), + new ChartTypeRecommender(options), + new ChartEncoder(options), + new SpecGenerator(options), + ]; +}; diff --git a/packages/ava/src/advisor/pipeline/presets/spec-generator.ts b/packages/ava/src/advisor/pipeline/presets/spec-generator.ts new file mode 100644 index 00000000..7ae94f6b --- /dev/null +++ b/packages/ava/src/advisor/pipeline/presets/spec-generator.ts @@ -0,0 +1,12 @@ +import { specGeneratorPlugin } from '../../advise-pipeline'; +import { PresetComponentName } from '../../constants'; +import { BaseComponent, type ComponentOptions } from '../component'; + +export class SpecGenerator extends BaseComponent { + constructor(options: ComponentOptions) { + super(PresetComponentName.specGenerator, { + plugins: [specGeneratorPlugin], + ...options, + }); + } +} diff --git a/packages/ava/src/advisor/ruler/types.ts b/packages/ava/src/advisor/ruler/types.ts index 85a746c3..2e3c92e2 100644 --- a/packages/ava/src/advisor/ruler/types.ts +++ b/packages/ava/src/advisor/ruler/types.ts @@ -1,6 +1,7 @@ import type { FieldInfo } from '../../data'; import type { LevelOfMeasurement, ChartKnowledgeBase } from '../../ckb'; import type { Specification } from '../../common/types'; +import type { AdvisorPipelineContext } from '../types'; /** * Type of different rules. @@ -51,6 +52,7 @@ export interface Info { purpose?: string; preferences?: Preferences; customWeight?: number; + advisorContext?: Pick; [key: string]: any; } @@ -62,7 +64,8 @@ export type Trigger = (args: Info) => boolean; export type Optimizer = ( dataProps: BasicDataPropertyForAdvice[] | BasicDataPropertyForAdvice, - chartSpec: Specification + chartSpec: Specification, + advisorContext?: Pick ) => object; /** diff --git a/packages/ava/src/advisor/types/component.ts b/packages/ava/src/advisor/types/component.ts new file mode 100644 index 00000000..5ad1948f --- /dev/null +++ b/packages/ava/src/advisor/types/component.ts @@ -0,0 +1,62 @@ +import type { Data } from '@antv/g2'; +import type { Advice, ScoringResultForChartType, AdvisorPipelineContext } from './pipeline'; +import type { BasicDataPropertyForAdvice } from '../ruler'; +import type { ChartId } from '../../ckb'; +import type { MarkEncode } from './mark'; + +/** 基础插件接口定义 */ +export interface AdvisorPluginType { + /** 插件的唯一标识 */ + name: string; + type?: 'async' | 'sync'; + /** 插件所属的模块 * */ + componentName?: string; + execute: (input: Input, context: AdvisorPipelineContext) => Output | Promise; + /** 判断插件运行的条件 */ + condition?: (input?: Input, context?: AdvisorPipelineContext) => boolean; + // hooks + onBeforeExecute?: (input: Input, context: AdvisorPipelineContext) => void; + onAfterExecute?: (output: Output, context: AdvisorPipelineContext) => void; + onLoad?: (context: AdvisorPipelineContext) => void; + onUnload?: (context: AdvisorPipelineContext) => void; +} + +export type DataProcessorInput = { + data: Data; + customDataProps?: BasicDataPropertyForAdvice[]; +}; + +export type DataProcessorOutput = { + data: Data; + dataProps: BasicDataPropertyForAdvice[]; +}; + +export type ChartTypeRecommendInput = { + dataProps: BasicDataPropertyForAdvice[]; +}; + +export type ChartTypeRecommendOutput = { chartTypeRecommendations: ScoringResultForChartType[] }; + +export type SpecGeneratorInput = { + // todo 实际上不应该需要 score 信息 + chartTypeRecommendations: ScoringResultForChartType[]; + data: Data; + dataProps: BasicDataPropertyForAdvice[]; + encode?: MarkEncode; +}; +export type SpecGeneratorOutput = { + advices: (Omit & { + spec: Record | null; + })[]; +}; + +export type VisualEncoderInput = { + chartType: ChartId; + dataProps: BasicDataPropertyForAdvice[]; + chartTypeRecommendations: ScoringResultForChartType[]; +}; + +export type VisualEncoderOutput = { + encode?: MarkEncode; + chartTypeRecommendations?: ScoringResultForChartType[]; +}; diff --git a/packages/ava/src/advisor/types/index.ts b/packages/ava/src/advisor/types/index.ts new file mode 100644 index 00000000..b108e35e --- /dev/null +++ b/packages/ava/src/advisor/types/index.ts @@ -0,0 +1,3 @@ +export * from './component'; +export * from './mark'; +export * from './pipeline'; diff --git a/packages/ava/src/advisor/types/mark.ts b/packages/ava/src/advisor/types/mark.ts new file mode 100644 index 00000000..0bf79f6f --- /dev/null +++ b/packages/ava/src/advisor/types/mark.ts @@ -0,0 +1,38 @@ +/** g2-spec 相关types */ +import type { IntervalMark, RectMark, LineMark, PointMark, TextMark, CellMark, AreaMark } from '@antv/g2'; +import type { Specification } from '../../common/types'; + +export type Mark = IntervalMark | RectMark | LineMark | PointMark | TextMark | CellMark | AreaMark; + +export type Primitive = number | string | boolean | Date; + +export type TabularData = Record[]; + +export type Callback = (datum: Record, index: number, data: TabularData) => Primitive; + +export type DataType = 'quantitative' | 'categorical' | 'temporal'; + +/** Encode 的值 */ +export type Encode = Primitive | Callback; + +/** Encode 的对象 */ +export type MarkEncode = Record; + +/** 带有字段类型的 Encode 对象 */ +export type MarkEncodeWithType = Record; + +/** 原 G2 spec 去掉复杂 Encode 类型并添加简易版 Encode 类型 */ +export type G2ChartSpec = Omit & { encode: MarkEncode }; + +/** 原 G2 spec 去掉复杂 Encode 类型并添加简易版(带字段类型的) Encode 类型 */ +export type ChartSpecWithEncodeType = Omit & { encode: MarkEncodeWithType }; + +export type ChartEncoding = { + x?: string; + y?: string; + color?: string; + theta?: string; + size?: string; +}; + +export type { Specification }; diff --git a/packages/ava/src/advisor/types.ts b/packages/ava/src/advisor/types/pipeline.ts similarity index 75% rename from packages/ava/src/advisor/types.ts rename to packages/ava/src/advisor/types/pipeline.ts index 1c18ed80..6f8a3f7d 100644 --- a/packages/ava/src/advisor/types.ts +++ b/packages/ava/src/advisor/types/pipeline.ts @@ -1,12 +1,11 @@ -import { IntervalMark, RectMark, LineMark, PointMark, TextMark, CellMark, AreaMark } from '@antv/g2'; - import type { ColorSchemeType } from '@antv/color-schema'; import type { SimulationType } from '@antv/smart-color'; /** AVA 包内跨模块引用 */ -import type { Purpose, CkbConfig } from '../ckb'; -import type { Specification, Data } from '../common/types'; +import type { Purpose, CkbConfig } from '../../ckb'; +import type { Specification, Data } from '../../common/types'; /** Advisor 模块内引用 */ -import type { RuleConfig, BasicDataPropertyForAdvice, Preferences, RuleType } from './ruler/types'; +import type { RuleConfig, BasicDataPropertyForAdvice, Preferences, RuleType } from '../ruler/types'; +import type { Advisor } from '../advisor'; /** * Advisor config type @@ -66,13 +65,15 @@ export type ChartAdviseParams = { data: Data; /** customized data props to advise */ dataProps?: Partial[]; - /** data fields to focus, apply in `data` and `dataProps` */ + /** data fields to focus, apply in `data` and `dataProps` + * @todo 确认下为啥这里和 options 里面都有 是否应该废弃一处的 + */ fields?: string[]; /** advising options such as purpose, layout preferences */ options?: AdvisorOptions; - /** SmartColor mode on/off, optional props, default is off */ + /** @deprecated 移动到 options.smartColor, SmartColor mode on/off, optional props, default is off */ smartColor?: boolean; - /** smart color options, @see {@link SmartColorOptions} */ + /** @deprecated 移动到 options.colorOptions, smart color options, @see {@link SmartColorOptions} */ colorOptions?: SmartColorOptions; }; @@ -115,6 +116,10 @@ export type AdvisorOptions = { * only consider chart types with spec */ requireSpec?: boolean; + /** SmartColor mode on/off, optional props, default is off */ + smartColor?: boolean; + /** smart color options, @see {@link SmartColorOptions} */ + colorOptions?: SmartColorOptions; }; export type Theme = { @@ -196,7 +201,7 @@ export interface ScoringResultForChartType { log?: ScoringResultForRule[]; } -export * from './ruler/types'; +export * from '../ruler/types'; export interface LinterOptions { purpose?: Purpose; @@ -209,31 +214,13 @@ export interface LintParams { options?: LinterOptions; } -/** g2-spec 相关types */ - -export type Mark = IntervalMark | RectMark | LineMark | PointMark | TextMark | CellMark | AreaMark; - -export type Primitive = number | string | boolean | Date; - -export type TabularData = Record[]; - -export type Callback = (datum: Record, index: number, data: TabularData) => Primitive; - -export type DataType = 'quantitative' | 'categorical' | 'temporal'; - -/** Encode 的值 */ -export type Encode = Primitive | Callback; - -/** Encode 的对象 */ -export type MarkEncode = Record; - -/** 带有字段类型的 Encode 对象 */ -export type MarkEncodeWithType = Record; - -/** 原 G2 spec 去掉复杂 Encode 类型并添加简易版 Encode 类型 */ -export type G2ChartSpec = Omit & { encode: MarkEncode }; - -/** 原 G2 spec 去掉复杂 Encode 类型并添加简易版(带字段类型的) Encode 类型 */ -export type ChartSpecWithEncodeType = Omit & { encode: MarkEncodeWithType }; - -export type { Specification }; +/** 存储 pipeline 共用信息 */ +export type AdvisorPipelineContext = { + advisor: Advisor; + /** 推荐配置项 */ + options?: AdvisorOptions; + /** 业务自定义信息,上下文键/值存储 * */ + extra?: { + [key: string]: any; + }; +}; diff --git a/packages/ava/src/advisor/utils/infer-data-type.ts b/packages/ava/src/advisor/utils/infer-data-type.ts index 3607427a..595960e4 100644 --- a/packages/ava/src/advisor/utils/infer-data-type.ts +++ b/packages/ava/src/advisor/utils/infer-data-type.ts @@ -1,7 +1,15 @@ import { ScaleTypes } from '@antv/g2'; import { mapValues } from 'lodash'; -import { Callback, ChartSpecWithEncodeType, DataType, Encode, G2ChartSpec, Primitive, TabularData } from '../types'; +import type { + Callback, + ChartSpecWithEncodeType, + DataType, + Encode, + G2ChartSpec, + Primitive, + TabularData, +} from '../types'; function isQuantitative(d: any): boolean { return typeof d === 'number'; diff --git a/packages/ava/src/index.ts b/packages/ava/src/index.ts index 50e283f0..07a59d88 100644 --- a/packages/ava/src/index.ts +++ b/packages/ava/src/index.ts @@ -17,6 +17,14 @@ export type { LintParams, RuleModule, AdvisorConfig, + AdvisorPluginType, + AdvisorPipelineContext, + DataProcessorInput, + DataProcessorOutput, + ChartTypeRecommendInput, + ChartTypeRecommendOutput, + SpecGeneratorInput, + SpecGeneratorOutput, } from './advisor'; /* CKB */ diff --git a/site/examples/advice/advise-and-lint/demo/ca.jsx b/site/examples/advice/advise-and-lint/demo/ca.jsx index a302e35f..8e31484c 100644 --- a/site/examples/advice/advise-and-lint/demo/ca.jsx +++ b/site/examples/advice/advise-and-lint/demo/ca.jsx @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { PagList, JSONView } from 'antv-site-demo-rc'; -import { Advisor } from '@antv/ava'; +import { Advisor, PresetComponentName } from '@antv/ava'; // contants @@ -14,13 +14,34 @@ const defaultData = [ // usage const myChartAdvisor = new Advisor(); -const results = myChartAdvisor.advise({ data: defaultData }); - -const App = () => ( - } - /> -); + +const App = () => { + const [results, setResults] = useState(); + + useEffect(() => { + // 默认 pipeline 用法 + myChartAdvisor.adviseAsync({ data: defaultData }).then((results) => { + setResults(results); + }); + + // 单独使用 pipeline 中的部分环节 + const dataAnalyzer = myChartAdvisor.pipeline.getComponent(PresetComponentName.dataAnalyzer); + const specGenerator = myChartAdvisor.pipeline.getComponent(PresetComponentName.specGenerator); + const { dataProps, data: filteredData } = dataAnalyzer.execute({ data: defaultData }) || {}; + specGenerator.execute({ + dataProps, + data: filteredData, + chartTypeRecommendations: [{ chartType: 'pie_chart' }], + }); + // console.log('advices', advices) + }, []); + + return ( + } + /> + ); +}; ReactDOM.render(, document.getElementById('container'));