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 632da5e4..e9dc89c4 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,13 +1,18 @@
-import { hasSubset, intersects } from '../../../../../utils';
-import { splitAreaXYSeries } from '../../visual-encoder/split-fields';
+import { find } from 'lodash';
+
 import { getLineSize } from '../../visual-encoder/utils';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { areaEncodeRequirement } from '../../../../../../ckb/encode';
 
-import type { Data, Datum } from '../../../../../../common/types';
-import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types';
+import type { Datum } from '../../../../../../common/types';
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
-export function areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const field4X = dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal']));
-  const field4Y = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval']));
+export function areaChart({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: areaEncodeRequirement });
+  const field4X = encode.x?.[0];
+  const field4Y = encode.y?.[0];
 
   if (!field4X || !field4Y) return null;
 
@@ -15,9 +20,9 @@ export function areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]):
     type: 'area',
     data,
     encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      size: (datum: Datum) => getLineSize(datum, data, { field4X }),
+      x: field4X,
+      y: field4Y,
+      size: (datum: Datum) => getLineSize(datum, data, { field4X: find(dataProps, ['name', field4X]) }),
     },
     legend: {
       size: false,
@@ -27,42 +32,44 @@ export function areaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]):
   return spec;
 }
 
-export function stackedAreaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Series] = splitAreaXYSeries(dataProps);
-  if (!field4X || !field4Y || !field4Series) return null;
+export function stackedAreaChart({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: areaEncodeRequirement });
+  const [field4X, field4Y, field4Series] = [encode.x?.[0], encode.y?.[0], encode.color?.[0]];
+  if (!field4X || !field4Y) return null;
 
   const spec: Advice['spec'] = {
     type: 'area',
     data,
     encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      color: field4Series.name,
-      size: (datum: Datum) => getLineSize(datum, data, { field4Split: field4Series, field4X }),
+      x: field4X,
+      y: field4Y,
+      size: (datum: Datum) =>
+        getLineSize(datum, data, {
+          field4Split: find(dataProps, ['name', field4Series]),
+          field4X: find(dataProps, ['name', field4X]),
+        }),
     },
     legend: {
       size: false,
     },
-    transform: [{ type: 'stackY' }],
   };
 
+  if (field4Series) {
+    spec.encode.color = field4Series;
+    spec.transform = [{ type: 'stackY' }];
+  }
+
   return spec;
 }
 
-export function percentStackedAreaChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Series] = splitAreaXYSeries(dataProps);
-  if (!field4X || !field4Y || !field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'area',
-    data,
-    encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      color: field4Series.name,
-    },
-    transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
-  };
+export function percentStackedAreaChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  const spec = stackedAreaChart({ data, dataProps, encode });
+  if (spec?.transform) {
+    spec.transform.push({ type: 'normalizeY' });
+  } else {
+    spec.transform = [{ type: 'normalizeY' }];
+  }
 
   return spec;
 }
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts
index 8f0e54bf..689f0444 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/bar.ts
@@ -1,10 +1,14 @@
 import { splitBarXYSeries } from '../../visual-encoder/split-fields';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { barEncodeRequirement } from '../../../../../../ckb/encode';
 
-import type { Data } from '../../../../../../common/types';
-import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types';
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
-export function barChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Color] = splitBarXYSeries(dataProps);
+export function barChart({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: barEncodeRequirement });
+  const [field4X, field4Y, field4Color] = [encode.x?.[0], encode.y?.[0], encode.color?.[0]];
 
   if (!field4X || !field4Y) return null;
 
@@ -14,8 +18,8 @@ export function barChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): A
     // G2's implementation converts column chart (vertical bar) and bar chart (horizontal bar) by transpose, so the x and y fields need to be swapped.
     // 由于g2的实现是通过transpose来转换 column chart(竖着的bar)和bar chart(横着的bar),所以x和y的字段需要做交换
     encode: {
-      x: field4Y.name,
-      y: field4X.name,
+      x: field4Y,
+      y: field4X,
     },
     coordinate: {
       transform: [{ type: 'transpose' }],
@@ -23,72 +27,34 @@ export function barChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): A
   };
 
   if (field4Color) {
-    spec.encode.color = field4Color.name;
+    spec.encode.color = field4Color;
     spec.transform = [{ type: 'stackY' }];
   }
 
   return spec;
 }
 
-export function groupedBarChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Series] = splitBarXYSeries(dataProps);
-  if (!field4X || !field4Y || !field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      x: field4Y.name,
-      y: field4X.name,
-      color: field4Series.name,
-    },
-    transform: [{ type: 'dodgeX' }],
-    coordinate: {
-      transform: [{ type: 'transpose' }],
-    },
-  };
+export function groupedBarChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  const spec = barChart({ data, dataProps, encode });
 
+  if (spec?.encode?.color) {
+    spec.transform = [{ type: 'dodgeX' }];
+  }
   return spec;
 }
 
-export function stackedBarChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Series] = splitBarXYSeries(dataProps);
-  if (!field4X || !field4Y || !field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      x: field4Y.name,
-      y: field4X.name,
-      color: field4Series.name,
-    },
-    transform: [{ type: 'stackY' }],
-    coordinate: {
-      transform: [{ type: 'transpose' }],
-    },
-  };
-
-  return spec;
+export function stackedBarChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  return barChart({ data, dataProps, encode });
 }
 
-export function percentStackedBarChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
+export function percentStackedBarChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
   const [field4X, field4Y, field4Series] = splitBarXYSeries(dataProps);
   if (!field4X || !field4Y || !field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      x: field4Y.name,
-      y: field4X.name,
-      color: field4Series.name,
-    },
-    transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
-    coordinate: {
-      transform: [{ type: 'transpose' }],
-    },
-  };
-
+  const spec = barChart({ data, dataProps, encode });
+  if (spec?.transform) {
+    spec.transform.push({ type: 'normalizeY' });
+  } else {
+    spec.transform = [{ type: 'normalizeY' }];
+  }
   return spec;
 }
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts
index da66f8ad..17ae8831 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/column.ts
@@ -1,84 +1,50 @@
-import { Data } from '../../../../../../common/types';
-import { Advice, BasicDataPropertyForAdvice } from '../../../../../types';
-import { compare, hasSubset } from '../../../../../utils';
-import { splitColumnXYSeries } from '../../visual-encoder/split-fields';
+import { columnEncodeRequirement } from '../../../../../../ckb/encode';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
 
-export function columnChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const nominalFields = dataProps.filter((field) => hasSubset(field.levelOfMeasurements, ['Nominal']));
-  const sortedNominalFields = nominalFields.sort(compare);
-  const field4X = sortedNominalFields[0];
-  const field4Color = sortedNominalFields[1];
-  const field4Y = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval']));
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
+export function columnChart({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: columnEncodeRequirement });
+  const [field4X, field4Y, field4Color] = [encode.x?.[0], encode.y?.[0], encode.color?.[0]];
   if (!field4X || !field4Y) return null;
 
   const spec: Advice['spec'] = {
     type: 'interval',
     data,
     encode: {
-      x: field4X.name,
-      y: field4Y.name,
+      x: field4X,
+      y: field4Y,
     },
   };
 
   if (field4Color) {
-    spec.encode.color = field4Color.name;
+    spec.encode.color = field4Color;
     spec.transform = [{ type: 'stackY' }];
   }
 
   return spec;
 }
 
-export function groupedColumnChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Series] = splitColumnXYSeries(dataProps);
-  if (!field4X || !field4Y || !field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      color: field4Series.name,
-    },
-    transform: [{ type: 'dodgeX' }],
-  };
-
+export function groupedColumnChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  const spec = columnChart({ data, dataProps, encode });
+  if (spec?.encode?.color) {
+    spec.transform = [{ type: 'dodgeX' }];
+  }
   return spec;
 }
 
-export function stackedColumnChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, Field4Series] = splitColumnXYSeries(dataProps);
-  if (!field4X || !field4Y || !Field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      color: Field4Series.name,
-    },
-    transform: [{ type: 'stackY' }],
-  };
-
-  return spec;
+export function stackedColumnChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  return columnChart({ data, dataProps, encode });
 }
 
-export function percentStackedColumnChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, Field4Series] = splitColumnXYSeries(dataProps);
-  if (!field4X || !field4Y || !Field4Series) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      color: Field4Series.name,
-    },
-    transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
-  };
-
+export function percentStackedColumnChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  const spec = columnChart({ data, dataProps, encode });
+  if (spec?.transform) {
+    spec.transform.push({ type: 'normalizeY' });
+  } else {
+    spec.transform = [{ type: 'normalizeY' }];
+  }
   return spec;
 }
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts
index f6fdbbc0..f56e0bab 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/heatmap.ts
@@ -1,14 +1,15 @@
-import { intersects, compare, hasSubset } from '../../../../../utils';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { heatmapEncodeRequirement } from '../../../../../../ckb/encode';
 
-import type { Data } from '../../../../../../common/types';
-import type { BasicDataPropertyForAdvice, Advice } from '../../../../../types';
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
-export function heatmap(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const axisFields = dataProps.filter((field) => intersects(field.levelOfMeasurements, ['Nominal', 'Ordinal']));
-  const sortedFields = axisFields.sort(compare);
-  const field4X = sortedFields[0];
-  const field4Y = sortedFields[1];
-  const field4Color = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval']));
+export function heatmap({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: heatmapEncodeRequirement });
+  const field4X = encode?.x?.[0];
+  const field4Y = encode?.y?.[0];
+  const field4Color = encode?.color?.[0];
 
   if (!field4X || !field4Y || !field4Color) return null;
 
@@ -16,9 +17,9 @@ export function heatmap(data: Data, dataProps: BasicDataPropertyForAdvice[]): Ad
     type: 'cell',
     data,
     encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      color: field4Color.name,
+      x: field4X,
+      y: field4Y,
+      color: field4Color,
     },
   };
 
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts
index a5d8cf98..9b676561 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/histogram.ts
@@ -1,17 +1,20 @@
-import { hasSubset } from '../../../../../utils';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { histogramEncodeRequirement } from '../../../../../../ckb/encode';
 
-import type { Data } from '../../../../../../common/types';
-import type { BasicDataPropertyForAdvice, Advice } from '../../../../../types';
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
-export function histogram(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const field = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Interval']));
+export function histogram({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: histogramEncodeRequirement });
+  const field = encode.x?.[0];
   if (!field) return null;
 
   const spec: Advice['spec'] = {
     type: 'rect',
     data,
     encode: {
-      x: field.name,
+      x: field,
     },
     transform: [{ type: 'binX', y: 'count' }],
   };
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 e3bf3584..75210199 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,20 +1,26 @@
-import { splitLineXY } from '../../visual-encoder/split-fields';
+import { find } from 'lodash';
+
 import { getLineSize } from '../../visual-encoder/utils';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { lineEncodeRequirement } from '../../../../../../ckb/encode';
 
-import type { Data, Datum } from '../../../../../../common/types';
-import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types';
+import type { Datum } from '../../../../../../common/types';
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
-export function lineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Color] = splitLineXY(dataProps);
+export function lineChart({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: lineEncodeRequirement });
+  const [field4X, field4Y, field4Color] = [encode.x?.[0], encode.y?.[0], encode.color?.[0]];
   if (!field4X || !field4Y) return null;
 
   const spec: Advice['spec'] = {
     type: 'line',
     data,
     encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      size: (datum: Datum) => getLineSize(datum, data, { field4X }),
+      x: field4X,
+      y: field4Y,
+      size: (datum: Datum) => getLineSize(datum, data, { field4X: find(dataProps, ['name', field4X]) }),
     },
     legend: {
       size: false,
@@ -22,33 +28,16 @@ export function lineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]):
   };
 
   if (field4Color) {
-    spec.encode.color = field4Color.name;
+    spec.encode.color = field4Color;
   }
 
   return spec;
 }
 
-export function stepLineChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4X, field4Y, field4Color] = splitLineXY(dataProps);
-  if (!field4X || !field4Y) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'line',
-    data,
-    encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      shape: 'hvh',
-      size: (datum: Datum) => getLineSize(datum, data, { field4X }),
-    },
-    legend: {
-      size: false,
-    },
-  };
-
-  if (field4Color) {
-    spec.encode.color = field4Color.name;
+export function stepLineChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  const spec = lineChart({ data, dataProps, encode });
+  if (spec?.encode) {
+    spec.encode.shape = 'hvh';
   }
-
   return spec;
 }
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts
index 08219c77..f2f6fc20 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/pie.ts
@@ -1,18 +1,21 @@
-import { splitAngleColor } from '../../visual-encoder/split-fields';
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { pieEncodeRequirement } from '../../../../../../ckb/encode';
 
-import type { Data } from '../../../../../../common/types';
-import type { Advice, BasicDataPropertyForAdvice } from '../../../../../types';
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
 
-export function pieChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4Color, field4Angle] = splitAngleColor(dataProps);
+export function pieChart({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: pieEncodeRequirement });
+  const [field4Angle, field4Color] = [encode.y?.[0], encode.color?.[0]];
   if (!field4Angle || !field4Color) return null;
 
   const spec: Advice['spec'] = {
     type: 'interval',
     data,
     encode: {
-      color: field4Color.name,
-      y: field4Angle.name,
+      color: field4Color,
+      y: field4Angle,
     },
     transform: [{ type: 'stackY' }],
     coordinate: { type: 'theta' },
@@ -20,19 +23,10 @@ export function pieChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): A
   return spec;
 }
 
-export function donutChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const [field4Color, field4Angle] = splitAngleColor(dataProps);
-  if (!field4Angle || !field4Color) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'interval',
-    data,
-    encode: {
-      color: field4Color.name,
-      y: field4Angle.name,
-    },
-    transform: [{ type: 'stackY' }],
-    coordinate: { type: 'theta', innerRadius: 0.6 },
-  };
+export function donutChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  const spec = pieChart({ data, dataProps, encode });
+  if (spec?.coordinate?.type === 'theta') {
+    spec.coordinate.innerRadius = 0.6;
+  }
   return spec;
 }
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts
index 37931be5..d68506da 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/charts/scatter.ts
@@ -1,15 +1,18 @@
-import { pearson } from '../../../../../../data';
-import { hasSubset, compare, intersects } from '../../../../../utils';
-
-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']));
-  const sortedIntervalFields = intervalFields.sort(compare);
-  const field4X = sortedIntervalFields[0];
-  const field4Y = sortedIntervalFields[1];
-  const field4Color = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal']));
+import { mapFieldsToVisualEncode } from '../../visual-encoder/encode-mapping';
+import { scatterEncodeRequirement } from '../../../../../../ckb/encode';
+
+import type { Advice } from '../../../../../types';
+import type { GenerateChartSpecParams } from '../types';
+
+export function scatterPlot({ data, dataProps, encode: customEncode }: GenerateChartSpecParams): Advice['spec'] {
+  const encode =
+    customEncode ?? mapFieldsToVisualEncode({ fields: dataProps, encodeRequirements: scatterEncodeRequirement });
+  const [field4X, field4Y, field4Color, field4Size] = [
+    encode?.x?.[0],
+    encode?.y?.[0],
+    encode?.color?.[0],
+    encode?.size?.[0],
+  ];
 
   if (!field4X || !field4Y) return null;
 
@@ -17,61 +20,22 @@ export function scatterPlot(data: Data, dataProps: BasicDataPropertyForAdvice[])
     type: 'point',
     data,
     encode: {
-      x: field4X.name,
-      y: field4Y.name,
+      x: field4X,
+      y: field4Y,
     },
   };
 
   if (field4Color) {
-    spec.encode.color = field4Color.name;
+    spec.encode.color = field4Color;
   }
 
-  return spec;
-}
-
-export function bubbleChart(data: Data, dataProps: BasicDataPropertyForAdvice[]): Advice['spec'] {
-  const intervalFields = dataProps.filter((field) => hasSubset(field.levelOfMeasurements, ['Interval']));
-
-  const triple = {
-    x: intervalFields[0],
-    y: intervalFields[1],
-    corr: 0,
-    size: intervalFields[2],
-  };
-  for (let i = 0; i < intervalFields.length; i += 1) {
-    for (let j = i + 1; j < intervalFields.length; j += 1) {
-      const p = pearson(intervalFields[i].rawData, intervalFields[j].rawData);
-      if (Math.abs(p) > triple.corr) {
-        triple.x = intervalFields[i];
-        triple.y = intervalFields[j];
-        triple.corr = p;
-        triple.size = intervalFields[[...Array(intervalFields.length).keys()].find((e) => e !== i && e !== j) || 0];
-      }
-    }
-  }
-
-  const field4X = triple.x;
-  const field4Y = triple.y;
-  const field4Size = triple.size;
-
-  const field4Color = dataProps.find((field) => intersects(field.levelOfMeasurements, ['Nominal']));
-
-  // require x,y,size,color at the same time
-  if (!field4X || !field4Y || !field4Size) return null;
-
-  const spec: Advice['spec'] = {
-    type: 'point',
-    data,
-    encode: {
-      x: field4X.name,
-      y: field4Y.name,
-      size: field4Size.name,
-    },
-  };
-
-  if (field4Color) {
-    spec.encode.color = field4Color.name;
+  if (field4Size) {
+    spec.encode.size = field4Size;
   }
 
   return spec;
 }
+
+export function bubbleChart({ data, dataProps, encode }: GenerateChartSpecParams): Advice['spec'] {
+  return scatterPlot({ data, dataProps, encode });
+}
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts
index 1d497798..98255c99 100644
--- a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/get-chart-spec.ts
@@ -1,3 +1,5 @@
+import { isFunction } from 'lodash';
+
 import { CHART_IDS } from '../../../../../ckb';
 
 import {
@@ -25,7 +27,7 @@ import {
 import type { Data } from '../../../../../common/types';
 import type { ChartId, ChartKnowledge } from '../../../../../ckb';
 import type { BasicDataPropertyForAdvice } from '../../../../ruler';
-import type { Advice } from '../../../../types';
+import type { Advice, ChartEncodeMapping } from '../../../../types';
 
 /**
  * Convert chartType + data to antv-spec
@@ -41,69 +43,71 @@ export function getChartTypeSpec({
   chartType,
   data,
   dataProps,
+  encode,
   chartKnowledge,
 }: {
   chartType: string;
   data: Data;
   dataProps: BasicDataPropertyForAdvice[];
+  encode?: ChartEncodeMapping;
   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) {
-    if (chartKnowledge.toSpec) {
-      const spec = chartKnowledge.toSpec(data, dataProps);
-      return spec;
-    }
+  if (isFunction(chartKnowledge.toSpec)) {
+    const spec = chartKnowledge.toSpec(data, dataProps, encode);
+    return spec;
+  }
+
+  if (!CHART_IDS.includes(chartType as ChartId)) {
     return null;
   }
+
   switch (chartType) {
     // pie
     case 'pie_chart':
-      return pieChart(data, dataProps);
+      return pieChart({ data, dataProps, encode });
     case 'donut_chart':
-      return donutChart(data, dataProps);
+      return donutChart({ data, dataProps, encode });
     // line
     case 'line_chart':
-      return lineChart(data, dataProps);
+      return lineChart({ data, dataProps, encode });
     case 'step_line_chart':
-      return stepLineChart(data, dataProps);
+      return stepLineChart({ data, dataProps, encode });
     // area
     case 'area_chart':
-      return areaChart(data, dataProps);
+      return areaChart({ data, dataProps, encode });
     case 'stacked_area_chart':
-      return stackedAreaChart(data, dataProps);
+      return stackedAreaChart({ data, dataProps, encode });
     case 'percent_stacked_area_chart':
-      return percentStackedAreaChart(data, dataProps);
+      return percentStackedAreaChart({ data, dataProps, encode });
     // bar
     case 'bar_chart':
-      return barChart(data, dataProps);
+      return barChart({ data, dataProps, encode });
     case 'grouped_bar_chart':
-      return groupedBarChart(data, dataProps);
+      return groupedBarChart({ data, dataProps, encode });
     case 'stacked_bar_chart':
-      return stackedBarChart(data, dataProps);
+      return stackedBarChart({ data, dataProps, encode });
     case 'percent_stacked_bar_chart':
-      return percentStackedBarChart(data, dataProps);
+      return percentStackedBarChart({ data, dataProps, encode });
     // column
     case 'column_chart':
-      return columnChart(data, dataProps);
+      return columnChart({ data, dataProps, encode });
     case 'grouped_column_chart':
-      return groupedColumnChart(data, dataProps);
+      return groupedColumnChart({ data, dataProps, encode });
     case 'stacked_column_chart':
-      return stackedColumnChart(data, dataProps);
+      return stackedColumnChart({ data, dataProps, encode });
     case 'percent_stacked_column_chart':
-      return percentStackedColumnChart(data, dataProps);
+      return percentStackedColumnChart({ data, dataProps, encode });
     // scatter
     case 'scatter_plot':
-      return scatterPlot(data, dataProps);
+      return scatterPlot({ data, dataProps, encode });
     // bubble
     case 'bubble_chart':
-      return bubbleChart(data, dataProps);
+      return bubbleChart({ data, dataProps, encode });
     // histogram
     case 'histogram':
-      return histogram(data, dataProps);
+      return histogram({ data, dataProps, encode });
     case 'heatmap':
-      return heatmap(data, dataProps);
+      return heatmap({ data, dataProps, encode });
     // TODO other case 'kpi_panel' & 'table'
     // // FIXME kpi_panel and table spec to be null temporarily
     // const customChartType = ['kpi_panel', 'table'];
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..e62e6894 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
@@ -9,53 +9,81 @@ import type {
   SpecGeneratorInput,
   SpecGeneratorOutput,
   AdvisorPluginType,
+  Specification,
 } from '../../../../types';
 
-// todo 内置的 visualEncode 和 spec generate 插件需要明确支持哪些图表类型
+const refineSpec = (
+  params: SpecGeneratorInput & {
+    chartType: string;
+    spec: Specification;
+  },
+  context: AdvisorPipelineContext
+) => {
+  const { chartType, dataProps, spec } = params;
+  const { options, advisor } = context;
+  const { refine = false, theme, colorOptions, smartColor } = options || {};
+  const { themeColor = DEFAULT_COLOR, colorSchemeType, simulationType } = colorOptions || {};
+  // apply spec processors such as design rules, theme, color, to improve spec
+  if (spec && refine) {
+    const partEncSpec = applyDesignRules(chartType, dataProps, advisor.ruleBase, spec, context);
+    deepMix(spec, partEncSpec);
+  }
+
+  // custom theme
+  if (spec) {
+    if (theme && !smartColor) {
+      const partEncSpec = applyTheme(dataProps, spec, theme);
+      deepMix(spec, partEncSpec);
+    } else if (smartColor) {
+      const partEncSpec = applySmartColor(dataProps, spec, themeColor, colorSchemeType, simulationType);
+      deepMix(spec, partEncSpec);
+    }
+  }
+};
+
+const generateSpecForChartType = (
+  input: SpecGeneratorInput & {
+    chartType: string;
+  },
+  context: AdvisorPipelineContext
+) => {
+  const { dataProps, data, chartType, encode } = input;
+  const chartKnowledge = context?.advisor?.ckb?.[chartType];
+  const spec = getChartTypeSpec({
+    chartType,
+    data,
+    dataProps,
+    encode,
+    chartKnowledge,
+  });
+
+  refineSpec({ ...input, spec, chartType }, context);
+  return spec;
+};
+
 export const specGeneratorPlugin: AdvisorPluginType<SpecGeneratorInput, SpecGeneratorOutput> = {
   name: 'defaultSpecGenerator',
   stage: ['specGenerate'],
   execute: (input: SpecGeneratorInput, context: AdvisorPipelineContext): SpecGeneratorOutput => {
-    const { chartTypeRecommendations, dataProps, data } = input;
-    const { options, advisor } = context || {};
-    const { refine = false, theme, colorOptions, smartColor } = options || {};
-    const { themeColor = DEFAULT_COLOR, colorSchemeType, simulationType } = colorOptions || {};
-    const advices = chartTypeRecommendations
-      ?.map((chartTypeAdvice) => {
-        const { chartType } = chartTypeAdvice;
-        const chartKnowledge = advisor.ckb[chartType];
-        const chartTypeSpec =
-          chartKnowledge?.toSpec?.(data, dataProps) ??
-          getChartTypeSpec({
-            chartType,
-            data,
-            dataProps,
-            chartKnowledge,
-          });
-
-        // step 3: apply spec processors such as design rules, theme, color, to improve spec
-        if (chartTypeSpec && refine) {
-          const partEncSpec = applyDesignRules(chartType, dataProps, advisor.ruleBase, chartTypeSpec, context);
-          deepMix(chartTypeSpec, partEncSpec);
-        }
-
-        // step 4: custom theme
-        if (chartTypeSpec) {
-          if (theme && !smartColor) {
-            const partEncSpec = applyTheme(dataProps, chartTypeSpec, theme);
-            deepMix(chartTypeSpec, partEncSpec);
-          } else if (smartColor) {
-            const partEncSpec = applySmartColor(dataProps, chartTypeSpec, themeColor, colorSchemeType, simulationType);
-            deepMix(chartTypeSpec, partEncSpec);
-          }
-        }
-        return {
-          type: chartTypeAdvice.chartType,
-          spec: chartTypeSpec,
-          score: chartTypeAdvice.score,
-        };
-      })
-      .filter((advices) => advices.spec);
+    const chartTypeRecommendations =
+      input.chartTypeRecommendations ??
+      (input.chartType
+        ? [
+            {
+              chartType: input.chartType,
+              encode: input.encode,
+            },
+          ]
+        : []);
+    const advices = chartTypeRecommendations?.map((chartTypeAdvice) => {
+      const { chartType, encode } = chartTypeAdvice;
+      const spec = generateSpecForChartType({ ...input, chartType, encode }, context);
+      return {
+        ...chartTypeAdvice,
+        spec,
+      };
+    });
+
     return { advices };
   },
 };
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/types.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/types.ts
new file mode 100644
index 00000000..206318bf
--- /dev/null
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/spec-generator/types.ts
@@ -0,0 +1,8 @@
+import type { Data } from '../../../../../common/types';
+import type { BasicDataPropertyForAdvice, ChartEncodeMapping } from '../../../../types';
+
+export type GenerateChartSpecParams = {
+  data: Data;
+  dataProps: BasicDataPropertyForAdvice[];
+  encode: ChartEncodeMapping;
+};
diff --git a/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/encode-mapping.ts b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/encode-mapping.ts
new file mode 100644
index 00000000..be94b6d6
--- /dev/null
+++ b/packages/ava/src/advisor/advise-pipeline/plugin/presets/visual-encoder/encode-mapping.ts
@@ -0,0 +1,132 @@
+import { map, mapValues, size } from 'lodash';
+
+import { compare, intersects } from '../../../../utils';
+import { isParentChild } from '../../../../../data';
+import { findTopCorrFields } from '../../../../utils/top-corr-fields';
+
+import type { DataPrerequisite } from '../../../../../ckb';
+import type { EncodeRequirements } from '../../../../../ckb/encode';
+import type { BasicDataPropertyForAdvice, ChartEncodeMapping } from '../../../../types';
+
+// @todo chenluli encoding mapping 也考虑纳入规则系统中,可用规则系统来约束推荐逻辑
+/** find matching fields for encode requirement */
+function findAndMarkFields({
+  requirement,
+  fields,
+  fieldUsage,
+}: {
+  requirement: DataPrerequisite;
+  fields: BasicDataPropertyForAdvice[];
+  fieldUsage: Set<string>;
+}) {
+  const matchingFields: BasicDataPropertyForAdvice[] = [];
+  let count = requirement.minQty;
+  requirement.fieldConditions.forEach((targetLoM) => {
+    if (count <= 0) return;
+    const filteredFields = fields
+      .filter((field) => !fieldUsage.has(field.name) && field.levelOfMeasurements.includes(targetLoM))
+      .slice(0, count);
+    count -= filteredFields.length;
+    matchingFields.push(...filteredFields);
+    filteredFields.forEach((field) => fieldUsage.add(field.name));
+  });
+
+  // Use other type of fields if not enough matching fields
+  if (count > 0) {
+    const remainingFields = fields.filter((field) => !fieldUsage.has(field.name)).slice(0, count);
+    matchingFields.push(...remainingFields);
+    remainingFields.forEach((field) => fieldUsage.add(field.name));
+  }
+  return matchingFields;
+}
+
+export function mapFieldsToVisualEncode({
+  fields = [],
+  encodeRequirements = {},
+}: {
+  fields: BasicDataPropertyForAdvice[];
+  encodeRequirements: EncodeRequirements;
+}): ChartEncodeMapping {
+  const encodeMapping: Record<string, BasicDataPropertyForAdvice[]> = {};
+  const fieldUsage: Set<string> = new Set();
+  const sortedFields = fields.sort(compare);
+
+  // Iterate through each encode requirement
+  // 遍历每个 encode 的满足条件,先满足每个 encode key 对字段的最小数目要求
+  Object.entries(encodeRequirements).forEach(([encodeKey, requirement]) => {
+    encodeMapping[encodeKey] = findAndMarkFields({ requirement, fields: sortedFields, fieldUsage });
+  });
+
+  // assign remaining fields to the available slot
+  // 如果还有剩余字段没有映射上,则填充至最大数量还能满足的部分
+  const isAcceptableQty = (encodeKey: string) => {
+    const currentCount = size(encodeMapping[encodeKey]);
+    const maxQty = encodeRequirements[encodeKey]?.maxQty;
+    return maxQty === '*' || currentCount < maxQty;
+  };
+  sortedFields
+    .filter((field) => !fieldUsage.has(field.name))
+    .forEach((field) => {
+      const matchingEncodeKey =
+        Object.keys(encodeRequirements).find(
+          (encodeKey) =>
+            isAcceptableQty(encodeKey) &&
+            intersects(field.levelOfMeasurements, encodeRequirements[encodeKey]?.fieldConditions)
+        ) ?? Object.keys(encodeRequirements).find((encodeKey) => isAcceptableQty(encodeKey));
+
+      if (matchingEncodeKey) {
+        encodeMapping[matchingEncodeKey].push(field);
+        fieldUsage.add(field.name);
+      }
+    });
+
+  // todo 填充 slots 完成后,进一步优化调整
+  // 1. 如果 x 轴和 series 都是 nominal 类型字段:distinct 大的作为 x 轴;如果有层级关系,则使用 parent 作为 x 轴;
+  const xField = encodeMapping.x?.[0];
+  const colorField = encodeMapping.color?.[0];
+  if (
+    size(encodeMapping.x) === 1 &&
+    size(encodeMapping.color) === 1 &&
+    xField.levelOfMeasurements?.includes('Nominal') &&
+    colorField.levelOfMeasurements?.includes('Nominal')
+  ) {
+    if (isParentChild(colorField?.rawData, xField?.rawData) || colorField.distinct > xField.distinct) {
+      encodeMapping.x = [colorField];
+      encodeMapping.color = [xField];
+    }
+  }
+  // 2. 如果 x, y, size 都是 interval 类型字段,则选 correlation 较大的作为 x,y 另一个作为 size
+  const yField = encodeMapping.y?.[0];
+  const sizeField = encodeMapping.size?.[0];
+  if (
+    size(encodeMapping.x) === 1 &&
+    size(encodeMapping.y) === 1 &&
+    size(encodeMapping.size) === 1 &&
+    xField.levelOfMeasurements?.includes('Interval') &&
+    yField.levelOfMeasurements?.includes('Interval') &&
+    sizeField.levelOfMeasurements?.includes('Interval')
+  ) {
+    const { x, y, size } = findTopCorrFields([xField, yField, colorField]);
+    encodeMapping.x = [x];
+    encodeMapping.y = [y];
+    encodeMapping.size = [size];
+  }
+
+  // check for unmet encoding requirements
+  Object.entries(encodeRequirements).forEach(([key, requirement]) => {
+    if (encodeMapping[key].length < requirement.minQty) {
+      // eslint-disable-next-line no-console
+      console.warn(
+        `Requirement for ${key} not satisfied. Minimum required fields: ${requirement.minQty}, but got ${encodeMapping[key].length}.`
+      );
+    }
+    const remainingFields = sortedFields.filter((field) => !fieldUsage.has(field.name));
+    if (remainingFields.length) {
+      // eslint-disable-next-line no-console
+      console.warn(
+        `No available visual encoding slots for remaining ${remainingFields.length} fields. Excess fields are ignored.`
+      );
+    }
+  });
+  return mapValues(encodeMapping, (fields) => map(fields, 'name'));
+}
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..9cdcff82 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,10 +1,65 @@
-import type { AdvisorPluginType, VisualEncoderInput, VisualEncoderOutput } from '../../../../types';
+import { isFunction } from 'lodash';
+
+import { chartType2EncodeRequirement } from '../../../../../ckb/encode';
+
+import { mapFieldsToVisualEncode } from './encode-mapping';
+
+import type {
+  AdvisorPipelineContext,
+  AdvisorPluginType,
+  ChartEncodeMapping,
+  ScoringResultForChartType,
+  VisualEncoderInput,
+  VisualEncoderOutput,
+} from '../../../../types';
+
+const getEncodeMapping = (
+  params: VisualEncoderInput & {
+    chartType: string;
+  },
+  context: AdvisorPipelineContext
+): ChartEncodeMapping => {
+  const { chartType, dataProps, data } = params;
+  const chartKnowledgeBase = context?.advisor?.ckb;
+  if (isFunction(chartKnowledgeBase[chartType]?.toEncode)) {
+    return chartKnowledgeBase[chartType]?.toEncode({ data, dataProps, context });
+  }
+  const encode = mapFieldsToVisualEncode({
+    fields: dataProps,
+    encodeRequirements: chartKnowledgeBase[chartType]?.encodePres ?? chartType2EncodeRequirement[chartType],
+  });
+  return encode;
+};
 
 export const visualEncoderPlugin: AdvisorPluginType<VisualEncoderInput, VisualEncoderOutput> = {
   name: 'defaultVisualEncoder',
   stage: ['encode'],
-  execute: (input) => {
-    // todo 从 spec-generator 中拆分出来核心 encode 部分
-    return input;
+  execute: (input, context) => {
+    const { chartType } = input;
+    const chartTypeRecommendations: ScoringResultForChartType[] =
+      input?.chartTypeRecommendations ??
+      (chartType
+        ? [
+            {
+              chartType,
+              score: 1,
+            },
+          ]
+        : []);
+
+    const advices = chartTypeRecommendations.map((chartTypeAdvice) => {
+      const encode = getEncodeMapping(
+        {
+          ...input,
+          chartType: chartTypeAdvice.chartType,
+        },
+        context
+      );
+      return {
+        ...chartTypeAdvice,
+        encode,
+      };
+    });
+    return { chartTypeRecommendations: advices };
   },
 };
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 a3a374a9..18a79e92 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
@@ -1,22 +1,41 @@
 import { isParentChild } from '../../../../../data';
 import { compare, hasSubset, intersects } from '../../../../utils';
+import { LEVEL_OF_MEASUREMENTS, type LevelOfMeasurement } from '../../../../../ckb';
 
 import type { BasicDataPropertyForAdvice } from '../../../../types';
 
 type ReturnField = BasicDataPropertyForAdvice | undefined;
 
+export const getFieldByLoM = ({
+  dataProps = [],
+  levelOfMeasurements,
+  count = 1,
+}: {
+  dataProps: BasicDataPropertyForAdvice[];
+  levelOfMeasurements: LevelOfMeasurement[];
+  count?: number;
+}) => {
+  return dataProps.filter((field) => intersects(field.levelOfMeasurements, levelOfMeasurements)).slice(0, count);
+};
+
+export const splitFields = (dataProps: BasicDataPropertyForAdvice[] = [], filteredFieldNames: string[] = []) => {
+  const filteredFields = dataProps.filter((field) => !filteredFieldNames.includes(field.name));
+  const levelOfMeasurementFieldMap: Partial<Record<LevelOfMeasurement, BasicDataPropertyForAdvice>> = {};
+  LEVEL_OF_MEASUREMENTS.forEach((levelOfMeasurement) => {
+    const [field] = getFieldByLoM({ dataProps: filteredFields, levelOfMeasurements: ['Nominal'] });
+    levelOfMeasurementFieldMap[levelOfMeasurement] = field;
+  });
+  return levelOfMeasurementFieldMap;
+};
+
 export function splitAngleColor(dataProps: BasicDataPropertyForAdvice[]): [ReturnField, ReturnField] {
+  const splittedFields = splitFields(dataProps);
   const field4Color =
-    dataProps.find((field) => intersects(field.levelOfMeasurements, ['Nominal'])) ??
-    dataProps.find((field) => intersects(field.levelOfMeasurements, ['Time', 'Ordinal'])) ??
-    dataProps.find((field) => intersects(field.levelOfMeasurements, ['Interval']));
+    splittedFields.Nominal ?? splittedFields.Ordinal ?? splittedFields.Time ?? splittedFields.Interval;
+  const usedFieldKey = field4Color?.name ? [field4Color?.name] : [];
+  const filteredFieldsMap = splitFields(dataProps, usedFieldKey);
   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']));
+    filteredFieldsMap.Interval ?? filteredFieldsMap.Nominal ?? filteredFieldsMap.Ordinal ?? splittedFields.Time;
   return [field4Color, field4Angle];
 }
 
diff --git a/packages/ava/src/advisor/advisor.ts b/packages/ava/src/advisor/advisor.ts
index d2cca061..5cab8f55 100644
--- a/packages/ava/src/advisor/advisor.ts
+++ b/packages/ava/src/advisor/advisor.ts
@@ -114,6 +114,9 @@ export class Advisor {
       options: params.options,
     };
     const adviseResult = await this.pipeline.execute(params);
+    if (params.options?.requireSpec !== false) {
+      return adviseResult.advices?.filter((advice) => advice.spec);
+    }
     return adviseResult.advices;
   }
 
diff --git a/packages/ava/src/advisor/pipeline/component.ts b/packages/ava/src/advisor/pipeline/component.ts
index ff156d29..79f4cd5c 100644
--- a/packages/ava/src/advisor/pipeline/component.ts
+++ b/packages/ava/src/advisor/pipeline/component.ts
@@ -70,7 +70,7 @@ export class BaseComponent<Input = any, Output = any> {
   execute(params: Input): Output {
     if (this.hasAsyncPlugin) {
       // eslint-disable-next-line no-console
-      console.warn('存在异步执行的插件,请使用 executeAsync');
+      console.warn('async plugins detected, please use executeAsync');
     }
     const pluginsOutput = {};
     this.plugins.forEach((plugin) => {
diff --git a/packages/ava/src/advisor/types/component.ts b/packages/ava/src/advisor/types/component.ts
index 6c046522..321cff9e 100644
--- a/packages/ava/src/advisor/types/component.ts
+++ b/packages/ava/src/advisor/types/component.ts
@@ -2,7 +2,6 @@ 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';
 
@@ -39,27 +38,37 @@ export type ChartTypeRecommendInput = {
 
 export type ChartTypeRecommendOutput = { chartTypeRecommendations: ScoringResultForChartType[] };
 
-export type SpecGeneratorInput = {
-  // todo 实际上不应该需要 score 信息
-  chartTypeRecommendations: ScoringResultForChartType[];
+export type VisualEncoderInput = {
   data: Data;
-  // 单独调用 SpecGenerator 时,还要额外计算 dataProps 么
   dataProps: BasicDataPropertyForAdvice[];
-  encode?: MarkEncode;
+  chartType?: string;
+  chartTypeRecommendations?: ScoringResultForChartType[];
 };
-export type SpecGeneratorOutput = {
-  advices: (Omit<Advice, 'spec'> & {
-    spec: Record<string, any> | null;
-  })[];
+
+export type ChartEncodeMapping = {
+  x?: string[];
+  y?: string[];
+  color?: string[];
+  size?: string[];
+  [key: string]: string[];
 };
 
-export type VisualEncoderInput = {
-  chartType: ChartId;
+export type VisualEncoderOutput = {
+  encode?: ChartEncodeMapping;
+  chartTypeRecommendations?: (ScoringResultForChartType & { encode?: ChartEncodeMapping })[];
+};
+
+export type SpecGeneratorInput = {
+  data: Data;
   dataProps: BasicDataPropertyForAdvice[];
-  chartTypeRecommendations: ScoringResultForChartType[];
+  encode?: ChartEncodeMapping;
+  chartType?: ChartId;
+  // todo 实际上不应该需要 score 信息
+  chartTypeRecommendations: (ScoringResultForChartType & { encode?: ChartEncodeMapping })[];
 };
 
-export type VisualEncoderOutput = {
-  encode?: MarkEncode;
-  chartTypeRecommendations?: ScoringResultForChartType[];
+export type SpecGeneratorOutput = {
+  advices: (Omit<Advice, 'spec'> & {
+    spec: Record<string, any> | null;
+  })[];
 };
diff --git a/packages/ava/src/advisor/types/mark.ts b/packages/ava/src/advisor/types/mark.ts
index 0bf79f6f..67c50f15 100644
--- a/packages/ava/src/advisor/types/mark.ts
+++ b/packages/ava/src/advisor/types/mark.ts
@@ -27,12 +27,4 @@ export type G2ChartSpec = Omit<Mark, 'encode'> & { encode: MarkEncode };
 /** 原 G2 spec 去掉复杂 Encode 类型并添加简易版(带字段类型的) Encode 类型 */
 export type ChartSpecWithEncodeType = Omit<Mark, 'encode'> & { 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/utils/top-corr-fields.ts b/packages/ava/src/advisor/utils/top-corr-fields.ts
new file mode 100644
index 00000000..b0b7c6e2
--- /dev/null
+++ b/packages/ava/src/advisor/utils/top-corr-fields.ts
@@ -0,0 +1,33 @@
+import { filter } from 'lodash';
+
+import { pearson } from '../../data';
+import { BasicDataPropertyForAdvice } from '../ruler';
+
+export const findTopCorrFields = (fields: BasicDataPropertyForAdvice[]) => {
+  const intervalFields = filter(fields, (field) => field.levelOfMeasurements.includes('Interval'));
+  if (intervalFields.length < 3) {
+    return {
+      x: intervalFields[0],
+      y: intervalFields[1],
+    };
+  }
+  const triple = {
+    x: intervalFields[0],
+    y: intervalFields[1],
+    corr: 0,
+    size: intervalFields[2],
+  };
+  for (let i = 0; i < intervalFields.length; i += 1) {
+    for (let j = i + 1; j < intervalFields.length; j += 1) {
+      const p = pearson(intervalFields[i].rawData, intervalFields[j].rawData);
+      if (Math.abs(p) > triple.corr) {
+        triple.x = intervalFields[i];
+        triple.y = intervalFields[j];
+        triple.corr = p;
+        triple.size = intervalFields[[...Array(intervalFields.length).keys()].find((e) => e !== i && e !== j) || 0];
+      }
+    }
+  }
+
+  return { x: triple.x, y: triple.y, size: triple.size };
+};
diff --git a/packages/ava/src/ckb/encode.ts b/packages/ava/src/ckb/encode.ts
new file mode 100644
index 00000000..e03f701a
--- /dev/null
+++ b/packages/ava/src/ckb/encode.ts
@@ -0,0 +1,132 @@
+import { ChartId, EncodePrerequisite } from './types';
+
+export type EncodeRequirements = Record<string, EncodePrerequisite>;
+
+// todo @chenluli 全部迁移进入 ckb
+export const lineEncodeRequirement: EncodeRequirements = {
+  x: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Time', 'Ordinal', 'Nominal'],
+  },
+  y: {
+    maxQty: '*',
+    minQty: 1,
+    fieldConditions: ['Interval'],
+  },
+  color: {
+    maxQty: '*',
+    minQty: 0,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time'],
+  },
+};
+
+export const pieEncodeRequirement: EncodeRequirements = {
+  y: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Interval'],
+  },
+  color: {
+    minQty: 1,
+    maxQty: 1, // todo 是否限制为 1 个还是允许多个;多个切片维度时,实际上会变成旭日图
+    fieldConditions: ['Nominal', 'Ordinal', 'Time'],
+  },
+};
+
+export const barEncodeRequirement: EncodeRequirements = {
+  x: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time'],
+  },
+  y: {
+    maxQty: '*',
+    minQty: 1,
+    fieldConditions: ['Interval'],
+  },
+  color: {
+    maxQty: '*',
+    minQty: 0,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time'],
+  },
+};
+
+export const scatterEncodeRequirement: EncodeRequirements = {
+  x: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Interval'],
+  },
+  y: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Interval'],
+  },
+  size: {
+    maxQty: 1,
+    minQty: 0,
+    fieldConditions: ['Interval'],
+  },
+  color: {
+    maxQty: '*',
+    minQty: 0,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time'],
+  },
+};
+
+export const histogramEncodeRequirement: EncodeRequirements = {
+  x: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Interval'],
+  },
+  color: {
+    maxQty: '*',
+    minQty: 0,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time'],
+  },
+};
+
+export const heatmapEncodeRequirement: EncodeRequirements = {
+  x: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time', 'Interval'],
+  },
+  y: {
+    maxQty: 1,
+    minQty: 1,
+    fieldConditions: ['Nominal', 'Ordinal', 'Time', 'Interval'],
+  },
+  color: {
+    maxQty: '*',
+    minQty: 0,
+    fieldConditions: ['Interval'],
+  },
+};
+
+export const areaEncodeRequirement = lineEncodeRequirement;
+export const columnEncodeRequirement = barEncodeRequirement;
+
+export const chartType2EncodeRequirement: Partial<Record<ChartId, EncodeRequirements>> = {
+  line_chart: lineEncodeRequirement,
+  pie_chart: pieEncodeRequirement,
+  donut_chart: pieEncodeRequirement,
+  step_line_chart: lineEncodeRequirement,
+  area_chart: areaEncodeRequirement,
+  stacked_area_chart: areaEncodeRequirement,
+  percent_stacked_area_chart: areaEncodeRequirement,
+  bar_chart: barEncodeRequirement,
+  grouped_bar_chart: barEncodeRequirement,
+  stacked_bar_chart: barEncodeRequirement,
+  percent_stacked_bar_chart: barEncodeRequirement,
+  column_chart: barEncodeRequirement,
+  grouped_column_chart: columnEncodeRequirement,
+  stacked_column_chart: columnEncodeRequirement,
+  percent_stacked_column_chart: columnEncodeRequirement,
+  scatter_plot: scatterEncodeRequirement,
+  bubble_chart: scatterEncodeRequirement,
+  histogram: histogramEncodeRequirement,
+  heatmap: heatmapEncodeRequirement,
+};
diff --git a/packages/ava/src/ckb/types.ts b/packages/ava/src/ckb/types.ts
index b71f9e2a..382319e4 100644
--- a/packages/ava/src/ckb/types.ts
+++ b/packages/ava/src/ckb/types.ts
@@ -1,6 +1,7 @@
 import * as constants from './constants';
 
 import type { Data, Specification } from '../common/types';
+import type { AdvisorPipelineContext, BasicDataPropertyForAdvice, ChartEncodeMapping } from '../advisor';
 
 /**
  * TS type of standard IDs for each chart type.
@@ -98,6 +99,13 @@ export type DataPrerequisite = {
   fieldConditions: LevelOfMeasurement[];
 };
 
+/**
+ *  TS type of A prerequisite for being able to mapping to a specific chart visual encode channel
+ *
+ * 图表视觉映射所需的字段类型,例如折线图 x 轴需要1个日期型字段,y 轴需要至少1个数值型字段
+ */
+export type EncodePrerequisite = DataPrerequisite;
+
 /**
  * TS type of channels.
  *
@@ -135,6 +143,7 @@ export type PureChartKnowledge = {
   coord: CoordinateSystem[];
   category: GraphicCategory[];
   shape: Shape[];
+  encodePres?: Record<string, EncodePrerequisite>;
   dataPres: DataPrerequisite[];
   channel: Channel[];
   recRate: RecommendRating;
@@ -151,17 +160,32 @@ export type PureChartKnowledge = {
 export type ChartKnowledge = {
   id: string;
   name: string;
-  alias: string[];
-  family: string[];
-  def: string;
-  purpose: string[];
-  coord: string[];
-  category: string[];
-  shape: string[];
+  alias?: string[];
+  family?: string[];
+  def?: string;
+  purpose?: string[];
+  coord?: string[];
+  category?: string[];
+  shape?: string[];
+  encodePres?: Record<string, EncodePrerequisite>;
   dataPres: (Omit<DataPrerequisite, 'fieldConditions'> & { fieldConditions: string[] })[];
-  channel: string[];
-  recRate: string;
-  toSpec?: (data: Data, dataProps: any) => Specification | null;
+  channel?: string[];
+  recRate?: string;
+  toEncode?: ({
+    data,
+    dataProps,
+    context,
+  }: {
+    data?: Data;
+    dataProps?: BasicDataPropertyForAdvice[];
+    context?: AdvisorPipelineContext;
+  }) => Record<string, string[]>;
+  toSpec?: (
+    data: Data,
+    dataProps: any,
+    encode?: ChartEncodeMapping,
+    context?: AdvisorPipelineContext
+  ) => Specification | null;
 };
 
 /**