From a6af04df63387944acb3728c745d0ac6bfab5048 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 21 May 2024 16:53:52 +0800 Subject: [PATCH 1/6] refactor(ava/advisor): split visual-encoder and chart-type-recommend folders --- .../ava/__tests__/unit/advisor/index.test.ts | 6 +++--- .../get-chart-Type.ts | 6 +++--- .../chart-type-recommend/index.ts | 1 + .../advisor/advise-pipeline/data-to-advices.ts | 9 +++++++-- .../spec-generator/charts/area.ts | 2 +- .../spec-generator/charts/bar.ts | 2 +- .../spec-generator/charts/column.ts | 2 +- .../spec-generator/charts/line.ts | 2 +- .../spec-generator/charts/pie.ts | 2 +- .../advise-pipeline/spec-generator/index.ts | 18 ++++++++++++------ .../split-fields.ts} | 0 11 files changed, 31 insertions(+), 19 deletions(-) rename packages/ava/src/advisor/advise-pipeline/{ => chart-type-recommend}/get-chart-Type.ts (78%) create mode 100644 packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts rename packages/ava/src/advisor/advise-pipeline/{spec-generator/splitFields.ts => visual-encoder/split-fields.ts} (100%) 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/src/advisor/advise-pipeline/get-chart-Type.ts b/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/get-chart-Type.ts similarity index 78% rename from packages/ava/src/advisor/advise-pipeline/get-chart-Type.ts rename to packages/ava/src/advisor/advise-pipeline/chart-type-recommend/get-chart-Type.ts index b479bdf0..fac6bcf0 100644 --- a/packages/ava/src/advisor/advise-pipeline/get-chart-Type.ts +++ b/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/get-chart-Type.ts @@ -1,7 +1,7 @@ -import { compareAdvices, scoreRules } from './score-calculator'; +import { compareAdvices, scoreRules } from '../score-calculator'; -import type { ChartKnowledge } from '../../ckb'; -import type { ScoringResultForChartType, BasicDataPropertyForAdvice, RuleModule, AdvisorOptions } from '../types'; +import type { ChartKnowledge } from '../../../ckb'; +import type { ScoringResultForChartType, BasicDataPropertyForAdvice, RuleModule, AdvisorOptions } from '../../types'; export const getChartTypeRecommendations = ({ chartWIKI, diff --git a/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts b/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts new file mode 100644 index 00000000..ef0f2acb --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts @@ -0,0 +1 @@ +export * from './get-chart-Type'; 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..142d52fc 100644 --- a/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts +++ b/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts @@ -2,7 +2,7 @@ import { cloneDeep, deepMix } from '../utils'; import { getChartTypeSpec } from './spec-generator'; import { DEFAULT_COLOR } from './constants'; -import { getChartTypeRecommendations } from './get-chart-Type'; +import { getChartTypeRecommendations } from './chart-type-recommend/get-chart-Type'; import { applyTheme, applyDesignRules, applySmartColor } from './spec-processors'; import { getDataProps, getSelectedData } from './data-processors'; @@ -46,7 +46,12 @@ export function dataToAdvices({ 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/spec-generator/charts/area.ts b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/area.ts index 5692b012..fb872b54 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/area.ts +++ b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/area.ts @@ -1,5 +1,5 @@ import { hasSubset, intersects } from '../../../utils'; -import { splitAreaXYSeries } from '../splitFields'; +import { splitAreaXYSeries } from '../../visual-encoder/split-fields'; import type { Data } from '../../../../common/types'; import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts index d7adf008..bfcaae8b 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts +++ b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/bar.ts @@ -1,4 +1,4 @@ -import { splitBarXYSeries } from '../splitFields'; +import { splitBarXYSeries } from '../../visual-encoder/split-fields'; import type { Data } from '../../../../common/types'; import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/column.ts b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/column.ts index 7c6653ad..1fab8940 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/column.ts +++ b/packages/ava/src/advisor/advise-pipeline/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 { 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/line.ts b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/line.ts index e79b90e5..c8d81544 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/line.ts +++ b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/line.ts @@ -1,4 +1,4 @@ -import { splitLineXY } from '../splitFields'; +import { splitLineXY } from '../../visual-encoder/split-fields'; import type { Data } from '../../../../common/types'; import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts index f86f41d3..b5c3490f 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts +++ b/packages/ava/src/advisor/advise-pipeline/spec-generator/charts/pie.ts @@ -1,4 +1,4 @@ -import { splitAngleColor } from '../splitFields'; +import { splitAngleColor } from '../../visual-encoder/split-fields'; import type { Data } from '../../../../common/types'; import type { Advice, BasicDataPropertyForAdvice } from '../../../types'; diff --git a/packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts b/packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts index f5af2485..f63f005a 100644 --- a/packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts +++ b/packages/ava/src/advisor/advise-pipeline/spec-generator/index.ts @@ -25,6 +25,7 @@ import { 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/spec-generator/splitFields.ts b/packages/ava/src/advisor/advise-pipeline/visual-encoder/split-fields.ts similarity index 100% rename from packages/ava/src/advisor/advise-pipeline/spec-generator/splitFields.ts rename to packages/ava/src/advisor/advise-pipeline/visual-encoder/split-fields.ts From c35a204e265e7ba0cf2bfc3278308a6e182723b5 Mon Sep 17 00:00:00 2001 From: chenluli Date: Thu, 6 Jun 2024 12:12:36 +0800 Subject: [PATCH 2/6] refactor(ava/advisor): init new advisor pipeline & built-in modules to plugin-based architecture --- packages/ava/package.json | 5 +- .../chart-type-recommend/index.ts | 1 - .../advise-pipeline/data-to-advices.ts | 9 +- .../ava/src/advisor/advise-pipeline/index.ts | 4 +- .../advisor/advise-pipeline/plugin/index.ts | 1 + .../chart-type-recommend/get-chart-Type.ts | 11 +- .../presets/chart-type-recommend/index.ts | 2 + .../chart-type-recommend/plugin-config.ts | 24 ++++ .../data-processors/get-data-properties.ts | 6 +- .../data-processors/get-selected-data.ts | 2 +- .../presets}/data-processors/index.ts | 1 + .../presets/data-processors/plugin-config.ts | 22 ++++ .../advise-pipeline/plugin/presets/index.ts | 4 + .../presets}/spec-generator/charts/area.ts | 6 +- .../presets}/spec-generator/charts/bar.ts | 4 +- .../presets}/spec-generator/charts/column.ts | 6 +- .../presets}/spec-generator/charts/heatmap.ts | 6 +- .../spec-generator/charts/histogram.ts | 6 +- .../presets}/spec-generator/charts/index.ts | 0 .../presets}/spec-generator/charts/line.ts | 4 +- .../presets}/spec-generator/charts/pie.ts | 4 +- .../presets}/spec-generator/charts/scatter.ts | 8 +- .../presets/spec-generator/get-chart-spec.ts} | 10 +- .../plugin/presets/spec-generator/index.ts | 2 + .../presets/spec-generator/plugin-config.ts | 53 ++++++++ .../spec-processors/apply-design-rules.ts | 4 +- .../spec-processors/apply-smart-color.ts | 4 +- .../spec-processors/apply-theme.ts | 6 +- .../spec-generator}/spec-processors/index.ts | 0 .../plugin/presets/visual-encoder/index.ts | 1 + .../presets/visual-encoder/plugin-config.ts | 10 ++ .../presets}/visual-encoder/split-fields.ts | 28 +++-- packages/ava/src/advisor/advisor.ts | 95 ++++++++++++++- .../ava/src/advisor/pipeline/component.ts | 115 ++++++++++++++++++ packages/ava/src/advisor/pipeline/index.ts | 2 + packages/ava/src/advisor/pipeline/pipeline.ts | 32 +++++ packages/ava/src/advisor/types/component.ts | 53 ++++++++ packages/ava/src/advisor/types/index.ts | 3 + packages/ava/src/advisor/types/mark.ts | 38 ++++++ .../advisor/{types.ts => types/pipeline.ts} | 63 ++++------ .../ava/src/advisor/utils/infer-data-type.ts | 10 +- .../advice/advise-and-lint/demo/ca.jsx | 34 ++++-- 42 files changed, 592 insertions(+), 107 deletions(-) delete mode 100644 packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/index.ts rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/chart-type-recommend/get-chart-Type.ts (68%) create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/index.ts create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/plugin-config.ts rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/data-processors/get-data-properties.ts (88%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/data-processors/get-selected-data.ts (89%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/data-processors/index.ts (58%) create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/plugin-config.ts create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/index.ts rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/area.ts (91%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/bar.ts (97%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/column.ts (92%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/heatmap.ts (83%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/histogram.ts (78%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/index.ts (100%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/line.ts (92%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/pie.ts (93%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/spec-generator/charts/scatter.ts (91%) rename packages/ava/src/advisor/advise-pipeline/{spec-generator/index.ts => plugin/presets/spec-generator/get-chart-spec.ts} (91%) create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/index.ts create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/plugin-config.ts rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets/spec-generator}/spec-processors/apply-design-rules.ts (87%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets/spec-generator}/spec-processors/apply-smart-color.ts (94%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets/spec-generator}/spec-processors/apply-theme.ts (92%) rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets/spec-generator}/spec-processors/index.ts (100%) create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/index.ts create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/plugin-config.ts rename packages/ava/src/advisor/advise-pipeline/{ => plugin/presets}/visual-encoder/split-fields.ts (62%) create mode 100644 packages/ava/src/advisor/pipeline/component.ts create mode 100644 packages/ava/src/advisor/pipeline/index.ts create mode 100644 packages/ava/src/advisor/pipeline/pipeline.ts create mode 100644 packages/ava/src/advisor/types/component.ts create mode 100644 packages/ava/src/advisor/types/index.ts create mode 100644 packages/ava/src/advisor/types/mark.ts rename packages/ava/src/advisor/{types.ts => types/pipeline.ts} (75%) 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/chart-type-recommend/index.ts b/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts deleted file mode 100644 index ef0f2acb..00000000 --- a/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-chart-Type'; 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 142d52fc..250fd924 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 './chart-type-recommend/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 diff --git a/packages/ava/src/advisor/advise-pipeline/index.ts b/packages/ava/src/advisor/advise-pipeline/index.ts index c5b16914..77c6422c 100644 --- a/packages/ava/src/advisor/advise-pipeline/index.ts +++ b/packages/ava/src/advisor/advise-pipeline/index.ts @@ -1,3 +1,3 @@ export * from './data-to-advices'; -export * from './spec-generator'; -export * from './data-processors'; +export * from './plugin/presets/spec-generator'; +export * from './plugin/presets/data-processors'; 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/chart-type-recommend/get-chart-Type.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/get-chart-Type.ts similarity index 68% rename from packages/ava/src/advisor/advise-pipeline/chart-type-recommend/get-chart-Type.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/get-chart-Type.ts index fac6bcf0..266b78b8 100644 --- a/packages/ava/src/advisor/advise-pipeline/chart-type-recommend/get-chart-Type.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/chart-type-recommend/get-chart-Type.ts @@ -1,7 +1,12 @@ -import { compareAdvices, scoreRules } from '../score-calculator'; +import { compareAdvices, scoreRules } from '../../../score-calculator'; -import type { ChartKnowledge } from '../../../ckb'; -import type { ScoringResultForChartType, BasicDataPropertyForAdvice, RuleModule, AdvisorOptions } from '../../types'; +import type { ChartKnowledge } from '../../../../../ckb'; +import type { + ScoringResultForChartType, + BasicDataPropertyForAdvice, + RuleModule, + AdvisorOptions, +} from '../../../../types'; export const getChartTypeRecommendations = ({ chartWIKI, 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..5598c5d1 --- /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, + ChartTypeRecommendInputParams, + ChartTypeRecommendOutput, + PluginType, +} from '../../../../types'; + +export const chartTypeRecommendPlugin: PluginType = { + name: 'defaultChartTypeRecommend', + stage: ['chartTypeRecommend'], + execute(input: ChartTypeRecommendInputParams, context?: AdvisorPipelineContext): ChartTypeRecommendOutput { + const { dataProps } = input; + const { advisor, options } = context || {}; + const chartTypeRecommendations = getChartTypeRecommendations({ + dataProps, + chartWIKI: advisor.ckb, + ruleBase: advisor.ruleBase, + options, + }); + 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..a32208ab --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/data-processors/plugin-config.ts @@ -0,0 +1,22 @@ +import { cloneDeep } from 'lodash'; + +import { getDataProps } from './get-data-properties'; +import { getSelectedData } from './get-selected-data'; + +import type { AdvisorPipelineContext, DataProcessorInput, DataProcessorOutput, PluginType } from '../../../../types'; + +export const dataProcessorPlugin: PluginType = { + name: 'defaultDataProcessor', + stage: ['dataAnalyze'], + 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 91% 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 fb872b54..e1535daf 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,8 @@ -import { hasSubset, intersects } from '../../../utils'; +import { hasSubset, intersects } from '../../../../../utils'; import { splitAreaXYSeries } 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 areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const field4X = dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal'])); 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 97% 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 bfcaae8b..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 '../../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 92% 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 1fab8940..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,6 +1,6 @@ -import { Data } from '../../../../common/types'; -import { Advice, BasicDataPropertyForAdvice } from '../../../types'; -import { compare, hasSubset } from '../../../utils'; +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'] { 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 92% 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 c8d81544..fa53122a 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,7 @@ import { splitLineXY } 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 lineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { const [field4X, field4Y, field4Color] = splitLineXY(dataProps); 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 93% 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 b5c3490f..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 '../../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 91% 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 f63f005a..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,10 +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 { Advice } from '../../types'; +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 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..1787b011 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/plugin-config.ts @@ -0,0 +1,53 @@ +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, PluginType } from '../../../../types'; + +export const specGeneratorPlugin: PluginType = { + name: 'defaultSpecGenerator', + stage: ['specGenerate'], + // todo 目前上一步输出是一个图表列表数组,这里原子能力实际上应该是只生成 spec + 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 chartTypeSpec = getChartTypeSpec({ + chartType, + data, + dataProps, + chartKnowledge: advisor.ckb[chartType], + }); + + // 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); + 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 87% 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..b30f924a 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,5 +1,5 @@ -import { BasicDataPropertyForAdvice, RuleModule, Specification, DesignRuleModule } from '../../types'; -import { deepMix } from '../../utils'; +import { BasicDataPropertyForAdvice, RuleModule, Specification, DesignRuleModule } from '../../../../../types'; +import { deepMix } from '../../../../../utils'; export function applyDesignRules( chartType: string, 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..a2be2460 --- /dev/null +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/plugin-config.ts @@ -0,0 +1,10 @@ +import type { PluginType, VisualEncoderInput } from '../../../../types'; + +export const visualEncoderPlugin: PluginType = { + name: 'defaultVisualEncoder', + stage: ['encode'], + execute: (input) => { + // todo 从 spec-generator 中拆分出来核心 encode 部分 + return input; + }, +}; diff --git a/packages/ava/src/advisor/advise-pipeline/visual-encoder/split-fields.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts similarity index 62% rename from packages/ava/src/advisor/advise-pipeline/visual-encoder/split-fields.ts rename to packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts index 738ebc27..b942509d 100644 --- a/packages/ava/src/advisor/advise-pipeline/visual-encoder/split-fields.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts @@ -1,19 +1,31 @@ -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', '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) => hasSubset(field.levelOfMeasurements, ['Nominal', 'Ordinal', 'Time'])); return [field4X, field4Y, field4Color]; } diff --git a/packages/ava/src/advisor/advisor.ts b/packages/ava/src/advisor/advisor.ts index b4adf599..50ec34e5 100644 --- a/packages/ava/src/advisor/advisor.ts +++ b/packages/ava/src/advisor/advisor.ts @@ -3,10 +3,32 @@ import { ckb } from '../ckb'; import { processRuleCfg } from './ruler'; import { dataToAdvices } from './advise-pipeline'; import { checkRules } from './lint-pipeline/check-rules'; +import { BaseComponent } from './pipeline/component'; +import { Pipeline } from './pipeline/pipeline'; +import { dataProcessorPlugin, specGeneratorPlugin, chartTypeRecommendPlugin } from './advise-pipeline/plugin'; 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, + PipelineStageType, + PluginType, + DataProcessorInput, + DataProcessorOutput, + ChartTypeRecommendInputParams, + ChartTypeRecommendOutput, + VisualEncoderInput, + VisualEncoderOutput, + SpecGeneratorInput, + SpecGeneratorOutput, +} from './types'; export class Advisor { /** @@ -19,16 +41,69 @@ export class Advisor { */ ruleBase: Record; - constructor(config: AdvisorConfig = {}) { + dataAnalyzer: BaseComponent; + + chartTypeRecommender: BaseComponent; + + chartEncoder: BaseComponent; + + specGenerator: BaseComponent; + + context: AdvisorPipelineContext; + + plugins: PluginType[]; + + pipeline: Pipeline; + + constructor( + config: AdvisorConfig = {}, + custom: { + plugins?: PluginType[]; + components?: BaseComponent[]; + } = {} + ) { + // init this.ckb = ckb(config.ckbCfg); this.ruleBase = processRuleCfg(config.ruleCfg); + this.context = { advisor: this }; + this.initDefaultComponents(); + const defaultComponents = [this.dataAnalyzer, this.chartTypeRecommender, this.chartEncoder, this.specGenerator]; + const { plugins, components } = custom; + this.plugins = plugins; + this.pipeline = new Pipeline({ components: components ?? defaultComponents }); + } + + private initDefaultComponents() { + this.dataAnalyzer = new BaseComponent('data', { plugins: [dataProcessorPlugin], context: this.context }); + this.chartTypeRecommender = new BaseComponent('chartType', { + plugins: [chartTypeRecommendPlugin], + context: this.context, + }); + // this.chartEncoder = new BaseComponent('chartEncode', { plugins: [visualEncoderPlugin], context: this.context }); + this.specGenerator = new BaseComponent('specGenerate', { plugins: [specGeneratorPlugin], context: this.context }); } + // todo 定义多个链路串并联的方法 + // private definePipeline(components: BaseComponent[]) { + // this.pipeline.components = components; + // } + + // todo 暂时还在用旧链路,需要改造到新链路 advise(params: AdviseParams): Advice[] { const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); return adviseResult.advices; } + async adviseAsync(params: AdviseParams): Promise { + this.context = { + ...this.context, + data: params.data, + options: params.options, + }; + const adviseResult = await this.pipeline.execute(params); + return adviseResult.advices; + } + adviseWithLog(params: AdviseParams): AdviseResult { const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); return adviseResult; @@ -43,4 +118,20 @@ export class Advisor { const lintResult = checkRules(params, this.ruleBase, this.ckb); return lintResult; } + + registerPlugins(plugins: PluginType[]) { + const stage2Components: Record = { + dataAnalyze: this.dataAnalyzer, + chartTypeRecommend: this.chartTypeRecommender, + encode: this.chartEncoder, + specGenerate: this.specGenerator, + }; + + plugins.forEach((plugin) => { + if (typeof plugin.stage === 'string') { + const pipelineComponent = stage2Components[plugin.stage]; + pipelineComponent.registerPlugin(plugin); + } + }); + } } diff --git a/packages/ava/src/advisor/pipeline/component.ts b/packages/ava/src/advisor/pipeline/component.ts new file mode 100644 index 00000000..6c4bb3a8 --- /dev/null +++ b/packages/ava/src/advisor/pipeline/component.ts @@ -0,0 +1,115 @@ +import { AsyncParallelHook, SyncHook } from 'tapable'; +import { last } from 'lodash'; + +import type { PluginType, AdvisorPipelineContext } from '../types'; + +/** 收集多个 plugin 的输出结果 */ +type PluginResultMap = Record; + +export class BaseComponent { + name: string; + + plugins: PluginType[] = []; + + /** 是否存在异步插件 */ + private hasAsyncPlugin: boolean; + + pluginManager: AsyncParallelHook<[Input, PluginResultMap], Output>; + + syncPluginManager: SyncHook<[Input, PluginResultMap], Output>; + + afterPluginsExecute?: (params: PluginResultMap) => Output; + + context?: AdvisorPipelineContext; + + constructor( + name, + options?: { + plugins?: PluginType[]; + afterPluginsExecute?: (params: PluginResultMap) => Output; + context?: AdvisorPipelineContext; + } + ) { + this.name = name; + this.afterPluginsExecute = options?.afterPluginsExecute ?? this.defaultAfterPluginsExecute; + this.pluginManager = new AsyncParallelHook(['data', 'results']); + this.syncPluginManager = new SyncHook(['data', 'results']); + this.context = options?.context; + this.hasAsyncPlugin = !!options?.plugins?.find((plugin) => this.isPluginAsync(plugin)); + options?.plugins?.forEach((plugin) => { + this.registerPlugin(plugin); + }); + } + + private defaultAfterPluginsExecute(params: Record) { + return last(Object.values(params)); + } + + private isPluginAsync(plugin: PluginType) { + // 检测插件是否为异步的,并设置hasAsyncPlugin标志位 + if (plugin.execute.constructor.name === 'AsyncFunction') { + return true; + } + return false; + } + + // 处理 之前都是同步的插件,新追加注册一个异步的插件 的情况 -- 需要执行的地方就不能用 + registerPlugin(plugin: PluginType) { + plugin.onLoad?.(this.context); + this.plugins.push(plugin); + if (this.isPluginAsync(plugin)) { + this.hasAsyncPlugin = true; + } + + if (this.hasAsyncPlugin) { + this.pluginManager.tapPromise(plugin.name, async (input, outputs = {}) => { + plugin.onBeforeExecute?.(input, this.context); + const output = await plugin.execute(input, this.context); + plugin.onAfterExecute?.(output, this.context); + /* eslint-disable no-param-reassign */ + outputs[plugin.name] = output; + }); + } else { + this.syncPluginManager.tap(plugin.name, (input, outputs = {}) => { + plugin.onBeforeExecute?.(input, this.context); + const output = plugin.execute(input, this.context); + plugin.onAfterExecute?.(output, this.context); + /* eslint-disable no-param-reassign */ + outputs[plugin.name] = output; + return output; + }); + } + } + + 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); + } + } + + execute(params: Input): Output | Promise { + if (this.hasAsyncPlugin) { + // console.warn('存在异步执行的插件,建议使用 executeAsync') + const pluginsOutput = {}; + return this.pluginManager.promise(params, pluginsOutput).then(async () => { + return this.afterPluginsExecute?.(pluginsOutput); + }); + } + const pluginsOutput = {}; + this.syncPluginManager.call(params, pluginsOutput); + return this.afterPluginsExecute?.(pluginsOutput); + } + + // todo 是否应该区分同步和异步接口 + // executeAsync(params: Input): Promise { + // if(!this.hasAsyncPlugin) { + // console.warn('插件均同步执行,建议使用 execute') + // } + // const pluginsOutput = {}; + // return this.pluginManager.promise(params, pluginsOutput).then(async () => { + // return this.afterPluginsExecute?.(pluginsOutput); + // }); + // } +} 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..7be30117 --- /dev/null +++ b/packages/ava/src/advisor/pipeline/pipeline.ts @@ -0,0 +1,32 @@ +import { AsyncSeriesWaterfallHook } from 'tapable'; + +import { BaseComponent } from './component'; + +export class Pipeline { + components: BaseComponent[]; + + componentsManager: AsyncSeriesWaterfallHook; + + constructor({ components }: { components: BaseComponent[] }) { + this.components = components; + 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.execute(input || {}); + + return { + ...input, + ...componentOutput, + }; + }); + }); + } + + async execute(initialParams: any) { + const result = await this.componentsManager.promise(initialParams); + return result; + } +} diff --git a/packages/ava/src/advisor/types/component.ts b/packages/ava/src/advisor/types/component.ts new file mode 100644 index 00000000..f1af3863 --- /dev/null +++ b/packages/ava/src/advisor/types/component.ts @@ -0,0 +1,53 @@ +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 type PipelineStageType = 'dataAnalyze' | 'chartTypeRecommend' | 'encode' | 'specGenerate'; + +/** 基础插件接口定义 */ +export interface PluginType { + /** 插件的唯一标识 */ + name: string; + /** 插件运行的阶段,用于指定插件在 pipeline 的哪个环节运行 * */ + stage?: PipelineStageType | PipelineStageType[]; + execute: (data: Input, context: AdvisorPipelineContext) => Output | Promise; + // hooks + onBeforeExecute?: (input: Input, context: AdvisorPipelineContext) => void | Promise; + onAfterExecute?: (output: Output, context: AdvisorPipelineContext) => void | Promise; + onLoad?: (context: AdvisorPipelineContext) => void | Promise; + onUnload?: (context: AdvisorPipelineContext) => void | Promise; +} + +export type DataProcessorInput = { + data: Data; + customDataProps?: BasicDataPropertyForAdvice[]; +}; + +export type DataProcessorOutput = { + data: Data; + dataProps: BasicDataPropertyForAdvice[]; +}; + +export type ChartTypeRecommendInputParams = { + dataProps: BasicDataPropertyForAdvice[]; +}; + +export type ChartTypeRecommendOutput = { chartTypeRecommendations: ScoringResultForChartType[] }; + +export type SpecGeneratorInput = { + // todo 实际上不应该需要 score 信息 + chartTypeRecommendations: ScoringResultForChartType[]; + data: Data; + // 单独调用 SpecGenerator 时,还要额外计算 dataProps 么 + dataProps: BasicDataPropertyForAdvice[]; +}; +export type SpecGeneratorOutput = { advices: Advice[] }; + +export type VisualEncoderInput = { + chartType: ChartId; + dataProps: BasicDataPropertyForAdvice[]; +}; + +export type VisualEncoderOutput = MarkEncode; 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..88bc6cbd 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,13 @@ export type ChartAdviseParams = { data: Data; /** customized data props to advise */ dataProps?: Partial[]; - /** data fields to focus, apply in `data` and `dataProps` */ + /** @todo 确认下为啥这里和 options 里面都有 data fields to focus, apply in `data` and `dataProps` */ 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 +114,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 +199,7 @@ export interface ScoringResultForChartType { log?: ScoringResultForRule[]; } -export * from './ruler/types'; +export * from '../ruler/types'; export interface LinterOptions { purpose?: Purpose; @@ -209,31 +212,17 @@ 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 = { + /** 原始数据 */ + data?: Data; + advisor: Advisor; + /** 推荐配置项 */ + options?: AdvisorOptions; + /** 业务自定义信息,上下文键/值存储 * */ + extra?: { + [key: string]: any; + }; + /** 过程中的日志信息,开发调试用, todo 明确类型 */ + logs?: { [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/site/examples/advice/advise-and-lint/demo/ca.jsx b/site/examples/advice/advise-and-lint/demo/ca.jsx index a302e35f..52b0477f 100644 --- a/site/examples/advice/advise-and-lint/demo/ca.jsx +++ b/site/examples/advice/advise-and-lint/demo/ca.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { PagList, JSONView } from 'antv-site-demo-rc'; @@ -14,13 +14,29 @@ const defaultData = [ // usage const myChartAdvisor = new Advisor(); -const results = myChartAdvisor.advise({ data: defaultData }); - -const App = () => ( - } - /> -); + +const App = () => { + const [results, setResults] = useState(); + + useEffect(() => { + myChartAdvisor.adviseAsync({ data: defaultData }).then((results) => { + setResults(results); + }); + const { dataProps, data: filteredData } = myChartAdvisor.dataAnalyzer.execute({ data: defaultData }) || {}; + myChartAdvisor.specGenerator.execute({ + dataProps, + data: filteredData, + chartTypeRecommendations: [{ chartType: 'pie_chart' }], + }); + // console.log('advices', advices) + }, []); + + return ( + } + /> + ); +}; ReactDOM.render(, document.getElementById('container')); From a86960eae40eb8f625c2c8580e150af3271dcf4e Mon Sep 17 00:00:00 2001 From: chenluli Date: Fri, 7 Jun 2024 18:32:01 +0800 Subject: [PATCH 3/6] fix(ava/advisor): fix single datapoint line chart and area chart --- .../presets/spec-generator/charts/area.ts | 11 +++++++- .../presets/spec-generator/charts/line.ts | 11 +++++++- .../presets/visual-encoder/split-fields.ts | 12 +++++--- .../plugin/presets/visual-encoder/utils.ts | 28 +++++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/utils.ts diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts index e1535daf..632da5e4 100644 --- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/area.ts @@ -1,7 +1,8 @@ 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 { Data, Datum } from '../../../../../../common/types'; import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; export function areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { @@ -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/plugin/presets/spec-generator/charts/line.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/line.ts index fa53122a..e3bf3584 100644 --- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/line.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/line.ts @@ -1,6 +1,7 @@ import { splitLineXY } from '../../visual-encoder/split-fields'; +import { getLineSize } from '../../visual-encoder/utils'; -import type { Data } from '../../../../../../common/types'; +import type { Data, Datum } from '../../../../../../common/types'; import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types'; export function lineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] { @@ -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/plugin/presets/visual-encoder/split-fields.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts index b942509d..a3a374a9 100644 --- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts +++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/split-fields.ts @@ -10,9 +10,13 @@ export function splitAngleColor(dataProps: BasicDataPropertyForAdvice[]): [Retur 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', 'Nominal', 'Time', 'Ordinal'])); + 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]; } @@ -25,7 +29,7 @@ export function splitLineXY(dataProps: BasicDataPropertyForAdvice[]): [ReturnFie .find((field) => hasSubset(field.levelOfMeasurements, ['Interval'])); const field4Color = dataProps .filter((field) => field !== field4X && field !== field4Y) - .find((field) => hasSubset(field.levelOfMeasurements, ['Nominal', 'Ordinal', 'Time'])); + .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; +}; From 3d3244745b839df551252177c9a32d0649096625 Mon Sep 17 00:00:00 2001 From: chenluli Date: Tue, 11 Jun 2024 14:46:07 +0800 Subject: [PATCH 4/6] fix(ava/advisor): add advisor export types --- .../chart-type-recommend/plugin-config.ts | 8 ++--- .../presets/data-processors/plugin-config.ts | 9 ++++-- .../presets/spec-generator/plugin-config.ts | 26 +++++++++++------ .../presets/visual-encoder/plugin-config.ts | 4 +-- packages/ava/src/advisor/advisor.ts | 25 ++++++++-------- .../ava/src/advisor/pipeline/component.ts | 29 ++++++------------- packages/ava/src/advisor/types/component.ts | 10 +++++-- packages/ava/src/advisor/types/pipeline.ts | 6 ++-- packages/ava/src/index.ts | 8 +++++ 9 files changed, 69 insertions(+), 56 deletions(-) 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 index 5598c5d1..2f0fe737 100644 --- 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 @@ -2,15 +2,15 @@ import { getChartTypeRecommendations } from './get-chart-Type'; import type { AdvisorPipelineContext, - ChartTypeRecommendInputParams, + ChartTypeRecommendInput, ChartTypeRecommendOutput, - PluginType, + AdvisorPluginType, } from '../../../../types'; -export const chartTypeRecommendPlugin: PluginType = { +export const chartTypeRecommendPlugin: AdvisorPluginType = { name: 'defaultChartTypeRecommend', stage: ['chartTypeRecommend'], - execute(input: ChartTypeRecommendInputParams, context?: AdvisorPipelineContext): ChartTypeRecommendOutput { + execute(input: ChartTypeRecommendInput, context?: AdvisorPipelineContext): ChartTypeRecommendOutput { const { dataProps } = input; const { advisor, options } = context || {}; const chartTypeRecommendations = getChartTypeRecommendations({ 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 index a32208ab..7cf305a1 100644 --- 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 @@ -3,9 +3,14 @@ import { cloneDeep } from 'lodash'; import { getDataProps } from './get-data-properties'; import { getSelectedData } from './get-selected-data'; -import type { AdvisorPipelineContext, DataProcessorInput, DataProcessorOutput, PluginType } from '../../../../types'; +import type { + AdvisorPipelineContext, + DataProcessorInput, + DataProcessorOutput, + AdvisorPluginType, +} from '../../../../types'; -export const dataProcessorPlugin: PluginType = { +export const dataProcessorPlugin: AdvisorPluginType = { name: 'defaultDataProcessor', stage: ['dataAnalyze'], execute: (input: DataProcessorInput, context: AdvisorPipelineContext): DataProcessorOutput => { 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 index 1787b011..cc7aa432 100644 --- 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 @@ -4,12 +4,17 @@ import { DEFAULT_COLOR } from '../../../constants'; import { applyDesignRules, applySmartColor, applyTheme } from './spec-processors'; import { getChartTypeSpec } from './get-chart-spec'; -import type { AdvisorPipelineContext, SpecGeneratorInput, SpecGeneratorOutput, PluginType } from '../../../../types'; +import type { + AdvisorPipelineContext, + SpecGeneratorInput, + SpecGeneratorOutput, + AdvisorPluginType, +} from '../../../../types'; -export const specGeneratorPlugin: PluginType = { +// todo 内置的 visualEncode 和 spec generate 插件需要明确支持哪些图表类型 +export const specGeneratorPlugin: AdvisorPluginType = { name: 'defaultSpecGenerator', stage: ['specGenerate'], - // todo 目前上一步输出是一个图表列表数组,这里原子能力实际上应该是只生成 spec execute: (input: SpecGeneratorInput, context: AdvisorPipelineContext): SpecGeneratorOutput => { const { chartTypeRecommendations, dataProps, data } = input; const { options, advisor } = context || {}; @@ -18,12 +23,15 @@ export const specGeneratorPlugin: PluginType { const { chartType } = chartTypeAdvice; - const chartTypeSpec = getChartTypeSpec({ - chartType, - data, - dataProps, - chartKnowledge: advisor.ckb[chartType], - }); + 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) { 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 index a2be2460..c5654262 100644 --- 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 @@ -1,6 +1,6 @@ -import type { PluginType, VisualEncoderInput } from '../../../../types'; +import type { AdvisorPluginType, VisualEncoderInput } from '../../../../types'; -export const visualEncoderPlugin: PluginType = { +export const visualEncoderPlugin: AdvisorPluginType = { name: 'defaultVisualEncoder', stage: ['encode'], execute: (input) => { diff --git a/packages/ava/src/advisor/advisor.ts b/packages/ava/src/advisor/advisor.ts index 50ec34e5..c41f9d73 100644 --- a/packages/ava/src/advisor/advisor.ts +++ b/packages/ava/src/advisor/advisor.ts @@ -19,10 +19,10 @@ import type { Lint, AdvisorPipelineContext, PipelineStageType, - PluginType, + AdvisorPluginType, DataProcessorInput, DataProcessorOutput, - ChartTypeRecommendInputParams, + ChartTypeRecommendInput, ChartTypeRecommendOutput, VisualEncoderInput, VisualEncoderOutput, @@ -43,7 +43,7 @@ export class Advisor { dataAnalyzer: BaseComponent; - chartTypeRecommender: BaseComponent; + chartTypeRecommender: BaseComponent; chartEncoder: BaseComponent; @@ -51,24 +51,28 @@ export class Advisor { context: AdvisorPipelineContext; - plugins: PluginType[]; + plugins: AdvisorPluginType[]; pipeline: Pipeline; constructor( config: AdvisorConfig = {}, custom: { - plugins?: PluginType[]; + plugins?: AdvisorPluginType[]; 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 }; + this.context = { advisor: this, extra }; this.initDefaultComponents(); const defaultComponents = [this.dataAnalyzer, this.chartTypeRecommender, this.chartEncoder, this.specGenerator]; - const { plugins, components } = custom; this.plugins = plugins; this.pipeline = new Pipeline({ components: components ?? defaultComponents }); } @@ -83,11 +87,6 @@ export class Advisor { this.specGenerator = new BaseComponent('specGenerate', { plugins: [specGeneratorPlugin], context: this.context }); } - // todo 定义多个链路串并联的方法 - // private definePipeline(components: BaseComponent[]) { - // this.pipeline.components = components; - // } - // todo 暂时还在用旧链路,需要改造到新链路 advise(params: AdviseParams): Advice[] { const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); @@ -119,7 +118,7 @@ export class Advisor { return lintResult; } - registerPlugins(plugins: PluginType[]) { + registerPlugins(plugins: AdvisorPluginType[]) { const stage2Components: Record = { dataAnalyze: this.dataAnalyzer, chartTypeRecommend: this.chartTypeRecommender, diff --git a/packages/ava/src/advisor/pipeline/component.ts b/packages/ava/src/advisor/pipeline/component.ts index 6c4bb3a8..5e45b44f 100644 --- a/packages/ava/src/advisor/pipeline/component.ts +++ b/packages/ava/src/advisor/pipeline/component.ts @@ -1,7 +1,7 @@ import { AsyncParallelHook, SyncHook } from 'tapable'; import { last } from 'lodash'; -import type { PluginType, AdvisorPipelineContext } from '../types'; +import type { AdvisorPluginType, AdvisorPipelineContext } from '../types'; /** 收集多个 plugin 的输出结果 */ type PluginResultMap = Record; @@ -9,7 +9,7 @@ type PluginResultMap = Record; export class BaseComponent { name: string; - plugins: PluginType[] = []; + plugins: AdvisorPluginType[] = []; /** 是否存在异步插件 */ private hasAsyncPlugin: boolean; @@ -18,15 +18,15 @@ export class BaseComponent { syncPluginManager: SyncHook<[Input, PluginResultMap], Output>; - afterPluginsExecute?: (params: PluginResultMap) => Output; + afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; context?: AdvisorPipelineContext; constructor( name, options?: { - plugins?: PluginType[]; - afterPluginsExecute?: (params: PluginResultMap) => Output; + plugins?: AdvisorPluginType[]; + afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; context?: AdvisorPipelineContext; } ) { @@ -45,7 +45,7 @@ export class BaseComponent { return last(Object.values(params)); } - private isPluginAsync(plugin: PluginType) { + private isPluginAsync(plugin: AdvisorPluginType) { // 检测插件是否为异步的,并设置hasAsyncPlugin标志位 if (plugin.execute.constructor.name === 'AsyncFunction') { return true; @@ -54,7 +54,7 @@ export class BaseComponent { } // 处理 之前都是同步的插件,新追加注册一个异步的插件 的情况 -- 需要执行的地方就不能用 - registerPlugin(plugin: PluginType) { + registerPlugin(plugin: AdvisorPluginType) { plugin.onLoad?.(this.context); this.plugins.push(plugin); if (this.isPluginAsync(plugin)) { @@ -94,22 +94,11 @@ export class BaseComponent { // console.warn('存在异步执行的插件,建议使用 executeAsync') const pluginsOutput = {}; return this.pluginManager.promise(params, pluginsOutput).then(async () => { - return this.afterPluginsExecute?.(pluginsOutput); + return this.afterPluginsExecute?.(pluginsOutput, this.context); }); } const pluginsOutput = {}; this.syncPluginManager.call(params, pluginsOutput); - return this.afterPluginsExecute?.(pluginsOutput); + return this.afterPluginsExecute?.(pluginsOutput, this.context); } - - // todo 是否应该区分同步和异步接口 - // executeAsync(params: Input): Promise { - // if(!this.hasAsyncPlugin) { - // console.warn('插件均同步执行,建议使用 execute') - // } - // const pluginsOutput = {}; - // return this.pluginManager.promise(params, pluginsOutput).then(async () => { - // return this.afterPluginsExecute?.(pluginsOutput); - // }); - // } } diff --git a/packages/ava/src/advisor/types/component.ts b/packages/ava/src/advisor/types/component.ts index f1af3863..7d7ef79a 100644 --- a/packages/ava/src/advisor/types/component.ts +++ b/packages/ava/src/advisor/types/component.ts @@ -7,7 +7,7 @@ import type { MarkEncode } from './mark'; export type PipelineStageType = 'dataAnalyze' | 'chartTypeRecommend' | 'encode' | 'specGenerate'; /** 基础插件接口定义 */ -export interface PluginType { +export interface AdvisorPluginType { /** 插件的唯一标识 */ name: string; /** 插件运行的阶段,用于指定插件在 pipeline 的哪个环节运行 * */ @@ -30,7 +30,7 @@ export type DataProcessorOutput = { dataProps: BasicDataPropertyForAdvice[]; }; -export type ChartTypeRecommendInputParams = { +export type ChartTypeRecommendInput = { dataProps: BasicDataPropertyForAdvice[]; }; @@ -43,7 +43,11 @@ export type SpecGeneratorInput = { // 单独调用 SpecGenerator 时,还要额外计算 dataProps 么 dataProps: BasicDataPropertyForAdvice[]; }; -export type SpecGeneratorOutput = { advices: Advice[] }; +export type SpecGeneratorOutput = { + advices: (Omit & { + spec: Record | null; + })[]; +}; export type VisualEncoderInput = { chartType: ChartId; diff --git a/packages/ava/src/advisor/types/pipeline.ts b/packages/ava/src/advisor/types/pipeline.ts index 88bc6cbd..f6a08d8b 100644 --- a/packages/ava/src/advisor/types/pipeline.ts +++ b/packages/ava/src/advisor/types/pipeline.ts @@ -65,7 +65,9 @@ export type ChartAdviseParams = { data: Data; /** customized data props to advise */ dataProps?: Partial[]; - /** @todo 确认下为啥这里和 options 里面都有 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; @@ -223,6 +225,4 @@ export type AdvisorPipelineContext = { extra?: { [key: string]: any; }; - /** 过程中的日志信息,开发调试用, todo 明确类型 */ - logs?: { [key: string]: any }[]; }; 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 */ From 85cc0ce50363cc6474e1f29fbe9354cf68db4b4f Mon Sep 17 00:00:00 2001 From: chenluli Date: Mon, 17 Jun 2024 16:59:39 +0800 Subject: [PATCH 5/6] feat(ava/advisor): add async and condition type to advisor plugin --- .../chart-type-recommend/get-chart-Type.ts | 5 +- .../chart-type-recommend/plugin-config.ts | 3 +- .../presets/spec-generator/plugin-config.ts | 4 +- .../spec-processors/apply-design-rules.ts | 15 +++- .../presets/visual-encoder/plugin-config.ts | 4 +- .../score-calculator/compute-score.ts | 20 ++++- .../score-calculator/score-rules.ts | 8 +- packages/ava/src/advisor/advisor.ts | 39 ++++++--- .../src/advisor/lint-pipeline/lintRules.ts | 19 +++-- .../ava/src/advisor/pipeline/component.ts | 79 +++++++++---------- packages/ava/src/advisor/pipeline/pipeline.ts | 6 +- packages/ava/src/advisor/ruler/types.ts | 5 +- packages/ava/src/advisor/types/component.ts | 10 ++- 13 files changed, 139 insertions(+), 78 deletions(-) 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 index 266b78b8..826bd4aa 100644 --- 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 @@ -6,6 +6,7 @@ import type { BasicDataPropertyForAdvice, RuleModule, AdvisorOptions, + AdvisorPipelineContext, } from '../../../../types'; export const getChartTypeRecommendations = ({ @@ -13,15 +14,17 @@ export const getChartTypeRecommendations = ({ 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); + return scoreRules(chartType, chartWIKI, dataProps, ruleBase, options, advisorContext); }); // filter and sorter 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 index 2f0fe737..ad966ce7 100644 --- 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 @@ -12,12 +12,13 @@ export const chartTypeRecommendPlugin: AdvisorPluginType, - 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/plugin/presets/visual-encoder/plugin-config.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/plugin-config.ts index c5654262..b5cc147f 100644 --- 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 @@ -1,6 +1,6 @@ -import type { AdvisorPluginType, VisualEncoderInput } from '../../../../types'; +import type { AdvisorPluginType, VisualEncoderInput, VisualEncoderOutput } from '../../../../types'; -export const visualEncoderPlugin: AdvisorPluginType = { +export const visualEncoderPlugin: AdvisorPluginType = { name: 'defaultVisualEncoder', stage: ['encode'], execute: (input) => { 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..27384095 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'; @@ -13,7 +13,8 @@ export const computeScore = ( 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, 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, + 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..93086108 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'; @@ -24,7 +25,8 @@ export function scoreRules( 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 c41f9d73..d2cca061 100644 --- a/packages/ava/src/advisor/advisor.ts +++ b/packages/ava/src/advisor/advisor.ts @@ -1,3 +1,5 @@ +import { forEach, size } from 'lodash'; + import { ckb } from '../ckb'; import { processRuleCfg } from './ruler'; @@ -5,7 +7,12 @@ import { dataToAdvices } from './advise-pipeline'; import { checkRules } from './lint-pipeline/check-rules'; import { BaseComponent } from './pipeline/component'; import { Pipeline } from './pipeline/pipeline'; -import { dataProcessorPlugin, specGeneratorPlugin, chartTypeRecommendPlugin } from './advise-pipeline/plugin'; +import { + dataProcessorPlugin, + specGeneratorPlugin, + chartTypeRecommendPlugin, + visualEncoderPlugin, +} from './advise-pipeline/plugin'; import type { ChartKnowledgeBase } from '../ckb'; import type { RuleModule } from './ruler'; @@ -51,7 +58,7 @@ export class Advisor { context: AdvisorPipelineContext; - plugins: AdvisorPluginType[]; + plugins: AdvisorPluginType[] = []; pipeline: Pipeline; @@ -73,8 +80,10 @@ export class Advisor { this.context = { advisor: this, extra }; this.initDefaultComponents(); const defaultComponents = [this.dataAnalyzer, this.chartTypeRecommender, this.chartEncoder, this.specGenerator]; - this.plugins = plugins; - this.pipeline = new Pipeline({ components: components ?? defaultComponents }); + this.registerPlugins(plugins); + this.pipeline = new Pipeline({ + components: components ?? defaultComponents, + }); } private initDefaultComponents() { @@ -83,7 +92,7 @@ export class Advisor { plugins: [chartTypeRecommendPlugin], context: this.context, }); - // this.chartEncoder = new BaseComponent('chartEncode', { plugins: [visualEncoderPlugin], context: this.context }); + this.chartEncoder = new BaseComponent('chartEncode', { plugins: [visualEncoderPlugin], context: this.context }); this.specGenerator = new BaseComponent('specGenerate', { plugins: [specGeneratorPlugin], context: this.context }); } @@ -93,6 +102,11 @@ export class Advisor { return adviseResult.advices; } + adviseWithLog(params: AdviseParams): AdviseResult { + const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); + return adviseResult; + } + async adviseAsync(params: AdviseParams): Promise { this.context = { ...this.context, @@ -103,10 +117,7 @@ export class Advisor { return adviseResult.advices; } - adviseWithLog(params: AdviseParams): AdviseResult { - const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); - return adviseResult; - } + // todo 补充 adviseAsyncWithLog lint(params: LintParams): Lint[] { const lintResult = checkRules(params, this.ruleBase, this.ckb); @@ -118,7 +129,7 @@ export class Advisor { return lintResult; } - registerPlugins(plugins: AdvisorPluginType[]) { + registerPlugins(plugins: AdvisorPluginType[] = []) { const stage2Components: Record = { dataAnalyze: this.dataAnalyzer, chartTypeRecommend: this.chartTypeRecommender, @@ -126,10 +137,16 @@ export class Advisor { specGenerate: this.specGenerator, }; - plugins.forEach((plugin) => { + forEach(plugins, (plugin) => { + this.plugins.push(plugin); if (typeof plugin.stage === 'string') { const pipelineComponent = stage2Components[plugin.stage]; pipelineComponent.registerPlugin(plugin); + return; + } + if (size(plugin.stage) === 1) { + const pipelineComponent = stage2Components[plugin.stage[0]]; + pipelineComponent.registerPlugin(plugin); } }); } 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 index 5e45b44f..ff156d29 100644 --- a/packages/ava/src/advisor/pipeline/component.ts +++ b/packages/ava/src/advisor/pipeline/component.ts @@ -1,5 +1,4 @@ -import { AsyncParallelHook, SyncHook } from 'tapable'; -import { last } from 'lodash'; +import { isFunction, last } from 'lodash'; import type { AdvisorPluginType, AdvisorPipelineContext } from '../types'; @@ -12,11 +11,7 @@ export class BaseComponent { plugins: AdvisorPluginType[] = []; /** 是否存在异步插件 */ - private hasAsyncPlugin: boolean; - - pluginManager: AsyncParallelHook<[Input, PluginResultMap], Output>; - - syncPluginManager: SyncHook<[Input, PluginResultMap], Output>; + private hasAsyncPlugin: boolean = false; afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; @@ -32,8 +27,6 @@ export class BaseComponent { ) { this.name = name; this.afterPluginsExecute = options?.afterPluginsExecute ?? this.defaultAfterPluginsExecute; - this.pluginManager = new AsyncParallelHook(['data', 'results']); - this.syncPluginManager = new SyncHook(['data', 'results']); this.context = options?.context; this.hasAsyncPlugin = !!options?.plugins?.find((plugin) => this.isPluginAsync(plugin)); options?.plugins?.forEach((plugin) => { @@ -41,44 +34,29 @@ export class BaseComponent { }); } - private defaultAfterPluginsExecute(params: Record) { - return last(Object.values(params)); + private defaultAfterPluginsExecute(params: Record) { + if (this.plugins.length) { + const lastPlugin = last(this.plugins); + return params[lastPlugin.name]; + } + return null; } private isPluginAsync(plugin: AdvisorPluginType) { - // 检测插件是否为异步的,并设置hasAsyncPlugin标志位 - if (plugin.execute.constructor.name === 'AsyncFunction') { + // 检测插件是否为异步的 + if (plugin.type === 'async' || plugin.execute.constructor.name === 'AsyncFunction') { return true; } return false; } - // 处理 之前都是同步的插件,新追加注册一个异步的插件 的情况 -- 需要执行的地方就不能用 registerPlugin(plugin: AdvisorPluginType) { plugin.onLoad?.(this.context); - this.plugins.push(plugin); if (this.isPluginAsync(plugin)) { this.hasAsyncPlugin = true; } - if (this.hasAsyncPlugin) { - this.pluginManager.tapPromise(plugin.name, async (input, outputs = {}) => { - plugin.onBeforeExecute?.(input, this.context); - const output = await plugin.execute(input, this.context); - plugin.onAfterExecute?.(output, this.context); - /* eslint-disable no-param-reassign */ - outputs[plugin.name] = output; - }); - } else { - this.syncPluginManager.tap(plugin.name, (input, outputs = {}) => { - plugin.onBeforeExecute?.(input, this.context); - const output = plugin.execute(input, this.context); - plugin.onAfterExecute?.(output, this.context); - /* eslint-disable no-param-reassign */ - outputs[plugin.name] = output; - return output; - }); - } + this.plugins.push(plugin); } unloadPlugin(pluginName: string) { @@ -89,16 +67,37 @@ export class BaseComponent { } } - execute(params: Input): Output | Promise { + execute(params: Input): Output { if (this.hasAsyncPlugin) { - // console.warn('存在异步执行的插件,建议使用 executeAsync') - const pluginsOutput = {}; - return this.pluginManager.promise(params, pluginsOutput).then(async () => { - return this.afterPluginsExecute?.(pluginsOutput, this.context); - }); + // eslint-disable-next-line no-console + console.warn('存在异步执行的插件,请使用 executeAsync'); } const pluginsOutput = {}; - this.syncPluginManager.call(params, pluginsOutput); + this.plugins.forEach((plugin) => { + plugin.onBeforeExecute?.(params, this.context); + if (isFunction(plugin.condition) && !plugin.condition(params, this.context)) return; + const output = plugin.execute(params, this.context) as Output; + plugin.onAfterExecute?.(output, this.context); + pluginsOutput[plugin.name] = output; + }); 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) => { + plugin.onBeforeExecute?.(params, this.context); + if (isFunction(plugin.condition) && !plugin.condition(params, this.context)) return; + const output = (await plugin.execute(params, this.context)) as Output; + plugin.onAfterExecute?.(output, this.context); + pluginsOutput[plugin.name] = output; + }) + ).then(async () => { + return this.afterPluginsExecute?.(pluginsOutput, this.context); + }); + } } diff --git a/packages/ava/src/advisor/pipeline/pipeline.ts b/packages/ava/src/advisor/pipeline/pipeline.ts index 7be30117..8a3f32fe 100644 --- a/packages/ava/src/advisor/pipeline/pipeline.ts +++ b/packages/ava/src/advisor/pipeline/pipeline.ts @@ -2,7 +2,7 @@ import { AsyncSeriesWaterfallHook } from 'tapable'; import { BaseComponent } from './component'; -export class Pipeline { +export class Pipeline { components: BaseComponent[]; componentsManager: AsyncSeriesWaterfallHook; @@ -15,7 +15,7 @@ export class Pipeline { if (!component) return; this.componentsManager.tapPromise(component.name, async (previousResult) => { const input = previousResult; - const componentOutput = await component.execute(input || {}); + const componentOutput = await component.executeAsync(input || {}); return { ...input, @@ -25,7 +25,7 @@ export class Pipeline { }); } - async execute(initialParams: any) { + async execute(initialParams: Input): Promise { const result = await this.componentsManager.promise(initialParams); return result; } 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 index 7d7ef79a..6c046522 100644 --- a/packages/ava/src/advisor/types/component.ts +++ b/packages/ava/src/advisor/types/component.ts @@ -12,7 +12,10 @@ export interface AdvisorPluginType { name: string; /** 插件运行的阶段,用于指定插件在 pipeline 的哪个环节运行 * */ stage?: PipelineStageType | PipelineStageType[]; + type?: 'async' | 'sync'; execute: (data: Input, context: AdvisorPipelineContext) => Output | Promise; + /** 判断插件运行的条件 */ + condition?: (data?: Input, context?: AdvisorPipelineContext) => boolean | Promise; // hooks onBeforeExecute?: (input: Input, context: AdvisorPipelineContext) => void | Promise; onAfterExecute?: (output: Output, context: AdvisorPipelineContext) => void | Promise; @@ -42,6 +45,7 @@ export type SpecGeneratorInput = { data: Data; // 单独调用 SpecGenerator 时,还要额外计算 dataProps 么 dataProps: BasicDataPropertyForAdvice[]; + encode?: MarkEncode; }; export type SpecGeneratorOutput = { advices: (Omit & { @@ -52,6 +56,10 @@ export type SpecGeneratorOutput = { export type VisualEncoderInput = { chartType: ChartId; dataProps: BasicDataPropertyForAdvice[]; + chartTypeRecommendations: ScoringResultForChartType[]; }; -export type VisualEncoderOutput = MarkEncode; +export type VisualEncoderOutput = { + encode?: MarkEncode; + chartTypeRecommendations?: ScoringResultForChartType[]; +}; From 70b5899a462c6cdad2556d51972b0baf6b45e296 Mon Sep 17 00:00:00 2001 From: chenluli Date: Mon, 8 Jul 2024 19:48:29 +0800 Subject: [PATCH 6/6] refactor(ava/advisor): adjustments to the concepts and APIs of components and pipeline --- .../advise-pipeline/data-to-advices.ts | 2 +- .../ava/src/advisor/advise-pipeline/index.ts | 3 +- .../chart-type-recommend/get-chart-Type.ts | 8 +- .../chart-type-recommend/plugin-config.ts | 3 +- .../presets/data-processors/plugin-config.ts | 1 - .../presets/spec-generator/plugin-config.ts | 1 - .../presets/visual-encoder/plugin-config.ts | 1 - .../score-calculator/compute-score.ts | 6 +- .../score-calculator/score-rules.ts | 6 +- packages/ava/src/advisor/advisor.ts | 69 +++-------------- packages/ava/src/advisor/constants.ts | 8 ++ packages/ava/src/advisor/index.ts | 1 + .../ava/src/advisor/pipeline/component.ts | 76 +++++++++++++------ packages/ava/src/advisor/pipeline/pipeline.ts | 11 ++- .../advisor/pipeline/presets/chart-encoder.ts | 12 +++ .../presets/chart-type-recommender.ts | 12 +++ .../advisor/pipeline/presets/data-analyzer.ts | 12 +++ .../pipeline/presets/default-components.ts | 15 ++++ .../pipeline/presets/spec-generator.ts | 12 +++ packages/ava/src/advisor/types/component.ts | 19 ++--- packages/ava/src/advisor/types/pipeline.ts | 2 - .../advice/advise-and-lint/demo/ca.jsx | 11 ++- 22 files changed, 174 insertions(+), 117 deletions(-) create mode 100644 packages/ava/src/advisor/pipeline/presets/chart-encoder.ts create mode 100644 packages/ava/src/advisor/pipeline/presets/chart-type-recommender.ts create mode 100644 packages/ava/src/advisor/pipeline/presets/data-analyzer.ts create mode 100644 packages/ava/src/advisor/pipeline/presets/default-components.ts create mode 100644 packages/ava/src/advisor/pipeline/presets/spec-generator.ts 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 250fd924..1dfd8b8a 100644 --- a/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts +++ b/packages/ava/src/advisor/advise-pipeline/data-to-advices.ts @@ -41,7 +41,7 @@ export function dataToAdvices({ const chartTypeRecommendations: ScoringResultForChartType[] = getChartTypeRecommendations({ dataProps, ruleBase, - chartWIKI: ckb, + chartWiki: ckb, }); const list: Advice[] = chartTypeRecommendations.map((result) => { diff --git a/packages/ava/src/advisor/advise-pipeline/index.ts b/packages/ava/src/advisor/advise-pipeline/index.ts index 77c6422c..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 './plugin/presets/spec-generator'; -export * from './plugin/presets/data-processors'; +export * from './plugin'; 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 index 826bd4aa..f2ce18fb 100644 --- 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 @@ -10,21 +10,21 @@ import type { } from '../../../../types'; export const getChartTypeRecommendations = ({ - chartWIKI, + chartWiki, dataProps, ruleBase, options, advisorContext, }: { dataProps: BasicDataPropertyForAdvice[]; - chartWIKI: Record; + chartWiki: Record; ruleBase: Record; options?: AdvisorOptions; advisorContext?: Pick; }) => { - const chatTypes = Object.keys(chartWIKI); + const chatTypes = Object.keys(chartWiki); const list: ScoringResultForChartType[] = chatTypes.map((chartType) => { - return scoreRules(chartType, chartWIKI, dataProps, ruleBase, options, advisorContext); + return scoreRules(chartType, chartWiki, dataProps, ruleBase, options, advisorContext); }); // filter and sorter 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 index ad966ce7..2eed3380 100644 --- 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 @@ -9,13 +9,12 @@ import type { export const chartTypeRecommendPlugin: AdvisorPluginType = { name: 'defaultChartTypeRecommend', - stage: ['chartTypeRecommend'], execute(input: ChartTypeRecommendInput, context?: AdvisorPipelineContext): ChartTypeRecommendOutput { const { dataProps } = input; const { advisor, options, extra } = context || {}; const chartTypeRecommendations = getChartTypeRecommendations({ dataProps, - chartWIKI: advisor.ckb, + chartWiki: advisor.ckb, ruleBase: advisor.ruleBase, options, advisorContext: { extra }, 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 index 7cf305a1..0caa5d5e 100644 --- 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 @@ -12,7 +12,6 @@ import type { export const dataProcessorPlugin: AdvisorPluginType = { name: 'defaultDataProcessor', - stage: ['dataAnalyze'], execute: (input: DataProcessorInput, context: AdvisorPipelineContext): DataProcessorOutput => { const { data, customDataProps } = input; const { fields } = context?.options || {}; 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 index 8fa71094..b8b8a687 100644 --- 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 @@ -14,7 +14,6 @@ import type { // todo 内置的 visualEncode 和 spec generate 插件需要明确支持哪些图表类型 export const specGeneratorPlugin: AdvisorPluginType = { name: 'defaultSpecGenerator', - stage: ['specGenerate'], execute: (input: SpecGeneratorInput, context: AdvisorPipelineContext): SpecGeneratorOutput => { const { chartTypeRecommendations, dataProps, data } = input; const { options, advisor } = context || {}; 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 index b5cc147f..9e31e04d 100644 --- 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 @@ -2,7 +2,6 @@ import type { AdvisorPluginType, VisualEncoderInput, VisualEncoderOutput } from export const visualEncoderPlugin: AdvisorPluginType = { name: 'defaultVisualEncoder', - stage: ['encode'], execute: (input) => { // todo 从 spec-generator 中拆分出来核心 encode 部分 return input; 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 27384095..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 @@ -9,7 +9,7 @@ const defaultWeights = DEFAULT_RULE_WEIGHTS; export const computeScore = ( chartType: ChartId | string, - chartWIKI: ChartKnowledgeBase, + chartWiki: ChartKnowledgeBase, ruleBase: Record, ruleType: 'HARD' | 'SOFT', info: Info, @@ -24,7 +24,7 @@ export const computeScore = ( const extra = r.option?.extra; return ( r.type === ruleType && - r.trigger({ ...info, weight, ...extra, chartType, chartWIKI, advisorContext }) && + r.trigger({ ...info, weight, ...extra, chartType, chartWIKI: chartWiki, advisorContext }) && !r.option?.off ); }) @@ -36,7 +36,7 @@ export const computeScore = ( weight, ...extra, chartType, - chartWIKI, + chartWIKI: chartWiki, advisorContext, }) as number; const score = weight * base; 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 93086108..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 @@ -22,7 +22,7 @@ import type { ChartId, ChartKnowledgeBase } from '../../../ckb'; */ export function scoreRules( chartType: ChartId | string, - chartWIKI: ChartKnowledgeBase, + chartWiki: ChartKnowledgeBase, dataProps: BasicDataPropertyForAdvice[], ruleBase: Record, options?: AdvisorOptions, @@ -36,7 +36,7 @@ export function scoreRules( const info = { dataProps, chartType, purpose, preferences }; - const hardScore = computeScore(chartType, chartWIKI, ruleBase, 'HARD', info, log, advisorContext); + const hardScore = computeScore(chartType, chartWiki, ruleBase, 'HARD', info, log, advisorContext); // Hard-Rule pruning if (hardScore === 0) { @@ -44,7 +44,7 @@ export function scoreRules( return result; } - const softScore = computeScore(chartType, chartWIKI, ruleBase, 'SOFT', info, log, advisorContext); + 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 d2cca061..333001ee 100644 --- a/packages/ava/src/advisor/advisor.ts +++ b/packages/ava/src/advisor/advisor.ts @@ -1,18 +1,11 @@ -import { forEach, size } from 'lodash'; +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 } from './pipeline/component'; -import { Pipeline } from './pipeline/pipeline'; -import { - dataProcessorPlugin, - specGeneratorPlugin, - chartTypeRecommendPlugin, - visualEncoderPlugin, -} from './advise-pipeline/plugin'; +import { BaseComponent, Pipeline } from './pipeline'; import type { ChartKnowledgeBase } from '../ckb'; import type { RuleModule } from './ruler'; @@ -25,15 +18,8 @@ import type { LintParams, Lint, AdvisorPipelineContext, - PipelineStageType, AdvisorPluginType, DataProcessorInput, - DataProcessorOutput, - ChartTypeRecommendInput, - ChartTypeRecommendOutput, - VisualEncoderInput, - VisualEncoderOutput, - SpecGeneratorInput, SpecGeneratorOutput, } from './types'; @@ -48,14 +34,6 @@ export class Advisor { */ ruleBase: Record; - dataAnalyzer: BaseComponent; - - chartTypeRecommender: BaseComponent; - - chartEncoder: BaseComponent; - - specGenerator: BaseComponent; - context: AdvisorPipelineContext; plugins: AdvisorPluginType[] = []; @@ -65,7 +43,7 @@ export class Advisor { constructor( config: AdvisorConfig = {}, custom: { - plugins?: AdvisorPluginType[]; + plugins?: (AdvisorPluginType & { componentName: string })[]; components?: BaseComponent[]; /** extra info to pass through the pipeline * 额外透传到推荐 pipeline 中的业务信息 @@ -78,25 +56,14 @@ export class Advisor { this.ckb = ckb(config.ckbCfg); this.ruleBase = processRuleCfg(config.ruleCfg); this.context = { advisor: this, extra }; - this.initDefaultComponents(); - const defaultComponents = [this.dataAnalyzer, this.chartTypeRecommender, this.chartEncoder, this.specGenerator]; - this.registerPlugins(plugins); this.pipeline = new Pipeline({ - components: components ?? defaultComponents, - }); - } - - private initDefaultComponents() { - this.dataAnalyzer = new BaseComponent('data', { plugins: [dataProcessorPlugin], context: this.context }); - this.chartTypeRecommender = new BaseComponent('chartType', { - plugins: [chartTypeRecommendPlugin], + components, context: this.context, }); - this.chartEncoder = new BaseComponent('chartEncode', { plugins: [visualEncoderPlugin], context: this.context }); - this.specGenerator = new BaseComponent('specGenerate', { plugins: [specGeneratorPlugin], context: this.context }); + this.registerPlugins(plugins); } - // todo 暂时还在用旧链路,需要改造到新链路 + // todo 暂时还在用旧链路 advise(params: AdviseParams): Advice[] { const adviseResult = dataToAdvices({ adviseParams: params, ckb: this.ckb, ruleBase: this.ruleBase }); return adviseResult.advices; @@ -107,18 +74,16 @@ export class Advisor { return adviseResult; } + // todo 补充 adviseAsyncWithLog? async adviseAsync(params: AdviseParams): Promise { this.context = { ...this.context, - data: params.data, options: params.options, }; const adviseResult = await this.pipeline.execute(params); return adviseResult.advices; } - // todo 补充 adviseAsyncWithLog - lint(params: LintParams): Lint[] { const lintResult = checkRules(params, this.ruleBase, this.ckb); return lintResult.lints; @@ -129,25 +94,11 @@ export class Advisor { return lintResult; } - registerPlugins(plugins: AdvisorPluginType[] = []) { - const stage2Components: Record = { - dataAnalyze: this.dataAnalyzer, - chartTypeRecommend: this.chartTypeRecommender, - encode: this.chartEncoder, - specGenerate: this.specGenerator, - }; - + registerPlugins(plugins: (AdvisorPluginType & { componentName: string })[] = []) { forEach(plugins, (plugin) => { this.plugins.push(plugin); - if (typeof plugin.stage === 'string') { - const pipelineComponent = stage2Components[plugin.stage]; - pipelineComponent.registerPlugin(plugin); - return; - } - if (size(plugin.stage) === 1) { - const pipelineComponent = stage2Components[plugin.stage[0]]; - pipelineComponent.registerPlugin(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/pipeline/component.ts b/packages/ava/src/advisor/pipeline/component.ts index ff156d29..075ede4a 100644 --- a/packages/ava/src/advisor/pipeline/component.ts +++ b/packages/ava/src/advisor/pipeline/component.ts @@ -5,26 +5,46 @@ import type { AdvisorPluginType, AdvisorPipelineContext } from '../types'; /** 收集多个 plugin 的输出结果 */ type PluginResultMap = Record; -export class BaseComponent { +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; +}; - plugins: AdvisorPluginType[] = []; +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; - context?: AdvisorPipelineContext; + getPlugins() { + return this.plugins; + } - constructor( - name, - options?: { - plugins?: AdvisorPluginType[]; - afterPluginsExecute?: (params: PluginResultMap, context?: AdvisorPipelineContext) => Output; - context?: AdvisorPipelineContext; - } - ) { + get isAsync() { + return this.hasAsyncPlugin; + } + + constructor(name, options?: ComponentOptions) { this.name = name; this.afterPluginsExecute = options?.afterPluginsExecute ?? this.defaultAfterPluginsExecute; this.context = options?.context; @@ -55,7 +75,10 @@ export class BaseComponent { 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); } @@ -67,6 +90,23 @@ export class BaseComponent { } } + 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 @@ -74,11 +114,7 @@ export class BaseComponent { } const pluginsOutput = {}; this.plugins.forEach((plugin) => { - plugin.onBeforeExecute?.(params, this.context); - if (isFunction(plugin.condition) && !plugin.condition(params, this.context)) return; - const output = plugin.execute(params, this.context) as Output; - plugin.onAfterExecute?.(output, this.context); - pluginsOutput[plugin.name] = output; + this.executeSinglePlugin(plugin, params, pluginsOutput); }); return this.afterPluginsExecute?.(pluginsOutput, this.context); } @@ -90,11 +126,7 @@ export class BaseComponent { const pluginsOutput = {}; return Promise.all( this.plugins.map(async (plugin) => { - plugin.onBeforeExecute?.(params, this.context); - if (isFunction(plugin.condition) && !plugin.condition(params, this.context)) return; - const output = (await plugin.execute(params, this.context)) as Output; - plugin.onAfterExecute?.(output, this.context); - pluginsOutput[plugin.name] = output; + await this.executeSinglePlugin(plugin, params, pluginsOutput); }) ).then(async () => { return this.afterPluginsExecute?.(pluginsOutput, this.context); diff --git a/packages/ava/src/advisor/pipeline/pipeline.ts b/packages/ava/src/advisor/pipeline/pipeline.ts index 8a3f32fe..e2f1383e 100644 --- a/packages/ava/src/advisor/pipeline/pipeline.ts +++ b/packages/ava/src/advisor/pipeline/pipeline.ts @@ -1,14 +1,17 @@ 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 }: { components: BaseComponent[] }) { - this.components = components; + constructor({ components, context }: { components: BaseComponent[]; context: AdvisorPipelineContext }) { + this.components = components ?? getPresetPipelineComponents({ context }); this.componentsManager = new AsyncSeriesWaterfallHook(['initialParams']); components.forEach((component) => { @@ -29,4 +32,8 @@ export class Pipeline { 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/types/component.ts b/packages/ava/src/advisor/types/component.ts index 6c046522..5ad1948f 100644 --- a/packages/ava/src/advisor/types/component.ts +++ b/packages/ava/src/advisor/types/component.ts @@ -4,23 +4,21 @@ import type { BasicDataPropertyForAdvice } from '../ruler'; import type { ChartId } from '../../ckb'; import type { MarkEncode } from './mark'; -export type PipelineStageType = 'dataAnalyze' | 'chartTypeRecommend' | 'encode' | 'specGenerate'; - /** 基础插件接口定义 */ export interface AdvisorPluginType { /** 插件的唯一标识 */ name: string; - /** 插件运行的阶段,用于指定插件在 pipeline 的哪个环节运行 * */ - stage?: PipelineStageType | PipelineStageType[]; type?: 'async' | 'sync'; - execute: (data: Input, context: AdvisorPipelineContext) => Output | Promise; + /** 插件所属的模块 * */ + componentName?: string; + execute: (input: Input, context: AdvisorPipelineContext) => Output | Promise; /** 判断插件运行的条件 */ - condition?: (data?: Input, context?: AdvisorPipelineContext) => boolean | Promise; + condition?: (input?: Input, context?: AdvisorPipelineContext) => boolean; // hooks - onBeforeExecute?: (input: Input, context: AdvisorPipelineContext) => void | Promise; - onAfterExecute?: (output: Output, context: AdvisorPipelineContext) => void | Promise; - onLoad?: (context: AdvisorPipelineContext) => void | Promise; - onUnload?: (context: AdvisorPipelineContext) => void | Promise; + onBeforeExecute?: (input: Input, context: AdvisorPipelineContext) => void; + onAfterExecute?: (output: Output, context: AdvisorPipelineContext) => void; + onLoad?: (context: AdvisorPipelineContext) => void; + onUnload?: (context: AdvisorPipelineContext) => void; } export type DataProcessorInput = { @@ -43,7 +41,6 @@ export type SpecGeneratorInput = { // todo 实际上不应该需要 score 信息 chartTypeRecommendations: ScoringResultForChartType[]; data: Data; - // 单独调用 SpecGenerator 时,还要额外计算 dataProps 么 dataProps: BasicDataPropertyForAdvice[]; encode?: MarkEncode; }; diff --git a/packages/ava/src/advisor/types/pipeline.ts b/packages/ava/src/advisor/types/pipeline.ts index f6a08d8b..6f8a3f7d 100644 --- a/packages/ava/src/advisor/types/pipeline.ts +++ b/packages/ava/src/advisor/types/pipeline.ts @@ -216,8 +216,6 @@ export interface LintParams { /** 存储 pipeline 共用信息 */ export type AdvisorPipelineContext = { - /** 原始数据 */ - data?: Data; advisor: Advisor; /** 推荐配置项 */ options?: AdvisorOptions; diff --git a/site/examples/advice/advise-and-lint/demo/ca.jsx b/site/examples/advice/advise-and-lint/demo/ca.jsx index 52b0477f..8e31484c 100644 --- a/site/examples/advice/advise-and-lint/demo/ca.jsx +++ b/site/examples/advice/advise-and-lint/demo/ca.jsx @@ -2,7 +2,7 @@ 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 @@ -19,11 +19,16 @@ const App = () => { const [results, setResults] = useState(); useEffect(() => { + // 默认 pipeline 用法 myChartAdvisor.adviseAsync({ data: defaultData }).then((results) => { setResults(results); }); - const { dataProps, data: filteredData } = myChartAdvisor.dataAnalyzer.execute({ data: defaultData }) || {}; - myChartAdvisor.specGenerator.execute({ + + // 单独使用 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' }],