codeant-ai-for-open-source[bot] commented on code in PR #40967:
URL: https://github.com/apache/superset/pull/40967#discussion_r3400608057


##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts:
##########
@@ -175,3 +175,283 @@ describe('Scatter Chart X-axis Number Formatting', () => {
     },
   );
 });
+
+describe('Scatter Chart Orientation and Dot Size Metric', () => {
+  const baseFormData: EchartsTimeseriesFormData = {
+    ...DEFAULT_FORM_DATA,
+    colorScheme: 'supersetColors',
+    datasource: '1__table',
+    metrics: ['sum_val'],
+    x_axis: 'category_col',
+    groupby: [],
+    viz_type: 'echarts_timeseries_scatter',
+    seriesType: EchartsTimeseriesSeriesType.Scatter,
+  };
+
+  const categoricalData = [
+    {
+      data: [
+        { category_col: 'A', sum_val: 1, size_metric: 10 },
+        { category_col: 'B', sum_val: 2, size_metric: 25 },
+        { category_col: 'C', sum_val: 3, size_metric: 40 },
+      ],
+      colnames: ['category_col', 'sum_val', 'size_metric'],
+      coltypes: [
+        GenericDataType.String,
+        GenericDataType.Numeric,
+        GenericDataType.Numeric,
+      ],
+      label_map: {
+        category_col: ['category_col'],
+        sum_val: ['sum_val'],
+        size_metric: ['size_metric'],
+      },
+    },
+  ];
+
+  const baseChartPropsConfig = {
+    width: 800,
+    height: 600,
+    theme: supersetTheme,
+  };
+
+  const getScatterSeries = (props: ReturnType<typeof transformProps>) =>
+    (props.echartOptions.series as any[]).filter(s => s.type === 'scatter');
+
+  const singleMetricData = [
+    {
+      ...categoricalData[0],
+      data: categoricalData[0].data.map(row => ({
+        category_col: row.category_col,
+        sum_val: row.sum_val,
+      })),
+      colnames: ['category_col', 'sum_val'],
+      coltypes: [GenericDataType.String, GenericDataType.Numeric],
+    },
+  ];
+
+  test('horizontal orientation swaps the dimension axis onto the y-axis', () 
=> {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: { ...baseFormData, orientation: 'horizontal' },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;
+    expect(yAxis.type).toBe('category');
+    expect(xAxis.type).toBe('value');
+
+    const series = getScatterSeries(transformedProps);
+    expect(series).toHaveLength(1);
+    // data points are flipped to [metric, dimension]
+    expect(series[0].data[0]).toEqual([1, 'A']);
+  });
+
+  test('vertical orientation keeps the dimension axis on the x-axis', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: baseFormData,
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;

Review Comment:
   **Suggestion:** Remove the second `any` cast for axis destructuring by 
typing the chart options object with an explicit ECharts options interface. 
[custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is another direct `any` cast in TypeScript test code. The custom rule 
requires avoiding `any`, so the violation is real.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=c3606d0397594e199df03b4d20e79bb9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=c3606d0397594e199df03b4d20e79bb9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts
   **Line:** 263:263
   **Comment:**
        *Custom Rule: Remove the second `any` cast for axis destructuring by 
typing the chart options object with an explicit ECharts options interface.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=c01420d9b1bb9952dd1d6edca3144fd7f0455bd78aab0d54914fa7e8a3fe5a9d&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=c01420d9b1bb9952dd1d6edca3144fd7f0455bd78aab0d54914fa7e8a3fe5a9d&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts:
##########
@@ -148,3 +148,59 @@ test('x_axis_number_format control should be hidden for 
non-numeric data types',
   expect(isNumberVisible(null, null)).toBe(false);
   expect(isNumberVisible('time_column', GenericDataType.Temporal)).toBe(false);
 });
+
+// tests for orientation and dot size controls
+const orientationControl: any = getControl('orientation');
+const sizeControl: any = getControl('size');
+const minMarkerSizeControl: any = getControl('minMarkerSize');
+const maxMarkerSizeControl: any = getControl('maxMarkerSize');
+
+test('scatter chart control panel should include an orientation control 
defaulting to vertical', () => {
+  expect(orientationControl).toBeDefined();
+  expect(orientationControl.config.default).toBe('vertical');
+  expect(orientationControl.config.options).toEqual([
+    ['vertical', expect.anything()],
+    ['horizontal', expect.anything()],
+  ]);
+});
+
+test('scatter chart control panel should include an optional dot size metric 
control', () => {
+  expect(sizeControl).toBeDefined();
+  expect(sizeControl.config.validators).toEqual([]);
+  expect(sizeControl.config.default).toBeNull();
+});
+
+const mockSizeControls = (
+  sizeValue: string | null,
+): ControlPanelsContainerProps =>
+  ({
+    controls: {
+      size: { value: sizeValue },
+      markerEnabled: { value: true },
+    },
+  }) as unknown as ControlPanelsContainerProps;
+
+test('dot size range controls should only be visible when a size metric is 
set', () => {
+  expect(minMarkerSizeControl.config.visibility(mockSizeControls(null))).toBe(
+    false,
+  );
+  expect(maxMarkerSizeControl.config.visibility(mockSizeControls(null))).toBe(
+    false,
+  );
+  expect(
+    minMarkerSizeControl.config.visibility(mockSizeControls('size_metric')),
+  ).toBe(true);
+  expect(
+    maxMarkerSizeControl.config.visibility(mockSizeControls('size_metric')),
+  ).toBe(true);
+});
+
+test('fixed marker size control should hide when a size metric is set', () => {
+  const markerSizeControl: any = getControl('markerSize');

Review Comment:
   **Suggestion:** Change this `any` usage to the same concrete control type 
used elsewhere in the test file to avoid bypassing type checks in visibility 
assertions. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a direct `any` annotation in a modified TypeScript test file, so it 
violates the custom rule against using `any` in new or changed TS/TSX code.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=287f0bf684ba49938d07a3b0808b58cc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=287f0bf684ba49938d07a3b0808b58cc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts
   **Line:** 199:199
   **Comment:**
        *Custom Rule: Change this `any` usage to the same concrete control type 
used elsewhere in the test file to avoid bypassing type checks in visibility 
assertions.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=58291e70b650a86c9c76b46093943267b291a40189c0926dba161863cad1f061&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=58291e70b650a86c9c76b46093943267b291a40189c0926dba161863cad1f061&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts:
##########
@@ -175,3 +175,283 @@ describe('Scatter Chart X-axis Number Formatting', () => {
     },
   );
 });
+
+describe('Scatter Chart Orientation and Dot Size Metric', () => {
+  const baseFormData: EchartsTimeseriesFormData = {
+    ...DEFAULT_FORM_DATA,
+    colorScheme: 'supersetColors',
+    datasource: '1__table',
+    metrics: ['sum_val'],
+    x_axis: 'category_col',
+    groupby: [],
+    viz_type: 'echarts_timeseries_scatter',
+    seriesType: EchartsTimeseriesSeriesType.Scatter,
+  };
+
+  const categoricalData = [
+    {
+      data: [
+        { category_col: 'A', sum_val: 1, size_metric: 10 },
+        { category_col: 'B', sum_val: 2, size_metric: 25 },
+        { category_col: 'C', sum_val: 3, size_metric: 40 },
+      ],
+      colnames: ['category_col', 'sum_val', 'size_metric'],
+      coltypes: [
+        GenericDataType.String,
+        GenericDataType.Numeric,
+        GenericDataType.Numeric,
+      ],
+      label_map: {
+        category_col: ['category_col'],
+        sum_val: ['sum_val'],
+        size_metric: ['size_metric'],
+      },
+    },
+  ];
+
+  const baseChartPropsConfig = {
+    width: 800,
+    height: 600,
+    theme: supersetTheme,
+  };
+
+  const getScatterSeries = (props: ReturnType<typeof transformProps>) =>
+    (props.echartOptions.series as any[]).filter(s => s.type === 'scatter');

Review Comment:
   **Suggestion:** Replace the `any[]` cast on the series collection with a 
concrete ECharts series type so scatter filtering is type-safe. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is TypeScript test code and it uses `any[]` directly in a cast. The 
custom rule explicitly flags any new or modified TypeScript/TSX code that uses 
`any`, so this is a real violation.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ae7feb05f8fa440e92527820d58dd0c8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ae7feb05f8fa440e92527820d58dd0c8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts
   **Line:** 218:219
   **Comment:**
        *Custom Rule: Replace the `any[]` cast on the series collection with a 
concrete ECharts series type so scatter filtering is type-safe.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=8be97440ad089f23634ec6575640b5352aa1727fe444ffa4350ddcca405c8710&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=8be97440ad089f23634ec6575640b5352aa1727fe444ffa4350ddcca405c8710&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts:
##########
@@ -148,3 +148,59 @@ test('x_axis_number_format control should be hidden for 
non-numeric data types',
   expect(isNumberVisible(null, null)).toBe(false);
   expect(isNumberVisible('time_column', GenericDataType.Temporal)).toBe(false);
 });
+
+// tests for orientation and dot size controls
+const orientationControl: any = getControl('orientation');
+const sizeControl: any = getControl('size');
+const minMarkerSizeControl: any = getControl('minMarkerSize');
+const maxMarkerSizeControl: any = getControl('maxMarkerSize');

Review Comment:
   **Suggestion:** Replace these `any` annotations with a concrete control type 
(or a shared reusable control interface from the chart controls types) so the 
assertions on `config` and `visibility` are type-safe. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The file is a TypeScript test file, and these newly added declarations use 
the banned `any` type directly. This matches the custom rule requiring a 
concrete, reusable, or narrower type instead of `any`.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1357b71bf0644e18b35999d4fdd14711&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1357b71bf0644e18b35999d4fdd14711&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts
   **Line:** 152:156
   **Comment:**
        *Custom Rule: Replace these `any` annotations with a concrete control 
type (or a shared reusable control interface from the chart controls types) so 
the assertions on `config` and `visibility` are type-safe.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=bca55730d1805d4943eccfddec623a0d84e58155ed273c896ed4bd3da08c2898&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=bca55730d1805d4943eccfddec623a0d84e58155ed273c896ed4bd3da08c2898&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts:
##########
@@ -175,3 +175,283 @@ describe('Scatter Chart X-axis Number Formatting', () => {
     },
   );
 });
+
+describe('Scatter Chart Orientation and Dot Size Metric', () => {
+  const baseFormData: EchartsTimeseriesFormData = {
+    ...DEFAULT_FORM_DATA,
+    colorScheme: 'supersetColors',
+    datasource: '1__table',
+    metrics: ['sum_val'],
+    x_axis: 'category_col',
+    groupby: [],
+    viz_type: 'echarts_timeseries_scatter',
+    seriesType: EchartsTimeseriesSeriesType.Scatter,
+  };
+
+  const categoricalData = [
+    {
+      data: [
+        { category_col: 'A', sum_val: 1, size_metric: 10 },
+        { category_col: 'B', sum_val: 2, size_metric: 25 },
+        { category_col: 'C', sum_val: 3, size_metric: 40 },
+      ],
+      colnames: ['category_col', 'sum_val', 'size_metric'],
+      coltypes: [
+        GenericDataType.String,
+        GenericDataType.Numeric,
+        GenericDataType.Numeric,
+      ],
+      label_map: {
+        category_col: ['category_col'],
+        sum_val: ['sum_val'],
+        size_metric: ['size_metric'],
+      },
+    },
+  ];
+
+  const baseChartPropsConfig = {
+    width: 800,
+    height: 600,
+    theme: supersetTheme,
+  };
+
+  const getScatterSeries = (props: ReturnType<typeof transformProps>) =>
+    (props.echartOptions.series as any[]).filter(s => s.type === 'scatter');
+
+  const singleMetricData = [
+    {
+      ...categoricalData[0],
+      data: categoricalData[0].data.map(row => ({
+        category_col: row.category_col,
+        sum_val: row.sum_val,
+      })),
+      colnames: ['category_col', 'sum_val'],
+      coltypes: [GenericDataType.String, GenericDataType.Numeric],
+    },
+  ];
+
+  test('horizontal orientation swaps the dimension axis onto the y-axis', () 
=> {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: { ...baseFormData, orientation: 'horizontal' },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;

Review Comment:
   **Suggestion:** Avoid casting axis config to `any`; use the proper ECharts 
axis option type (or a narrowed union) for the destructured axis objects. 
[custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The code casts the chart options object to `any`, which is explicitly 
disallowed by the rule for TypeScript/TSX changes. This is a direct and present 
violation.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=fd5a87534e5f4d5aaa37fad3a3cb57ad&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=fd5a87534e5f4d5aaa37fad3a3cb57ad&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts
   **Line:** 243:243
   **Comment:**
        *Custom Rule: Avoid casting axis config to `any`; use the proper 
ECharts axis option type (or a narrowed union) for the destructured axis 
objects.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=23ec214352420cb43514444aaa1b7dc7f05a03b2f4741d77ffab9e38464069e0&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=23ec214352420cb43514444aaa1b7dc7f05a03b2f4741d77ffab9e38464069e0&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts:
##########
@@ -175,3 +175,283 @@ describe('Scatter Chart X-axis Number Formatting', () => {
     },
   );
 });
+
+describe('Scatter Chart Orientation and Dot Size Metric', () => {
+  const baseFormData: EchartsTimeseriesFormData = {
+    ...DEFAULT_FORM_DATA,
+    colorScheme: 'supersetColors',
+    datasource: '1__table',
+    metrics: ['sum_val'],
+    x_axis: 'category_col',
+    groupby: [],
+    viz_type: 'echarts_timeseries_scatter',
+    seriesType: EchartsTimeseriesSeriesType.Scatter,
+  };
+
+  const categoricalData = [
+    {
+      data: [
+        { category_col: 'A', sum_val: 1, size_metric: 10 },
+        { category_col: 'B', sum_val: 2, size_metric: 25 },
+        { category_col: 'C', sum_val: 3, size_metric: 40 },
+      ],
+      colnames: ['category_col', 'sum_val', 'size_metric'],
+      coltypes: [
+        GenericDataType.String,
+        GenericDataType.Numeric,
+        GenericDataType.Numeric,
+      ],
+      label_map: {
+        category_col: ['category_col'],
+        sum_val: ['sum_val'],
+        size_metric: ['size_metric'],
+      },
+    },
+  ];
+
+  const baseChartPropsConfig = {
+    width: 800,
+    height: 600,
+    theme: supersetTheme,
+  };
+
+  const getScatterSeries = (props: ReturnType<typeof transformProps>) =>
+    (props.echartOptions.series as any[]).filter(s => s.type === 'scatter');
+
+  const singleMetricData = [
+    {
+      ...categoricalData[0],
+      data: categoricalData[0].data.map(row => ({
+        category_col: row.category_col,
+        sum_val: row.sum_val,
+      })),
+      colnames: ['category_col', 'sum_val'],
+      coltypes: [GenericDataType.String, GenericDataType.Numeric],
+    },
+  ];
+
+  test('horizontal orientation swaps the dimension axis onto the y-axis', () 
=> {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: { ...baseFormData, orientation: 'horizontal' },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;
+    expect(yAxis.type).toBe('category');
+    expect(xAxis.type).toBe('value');
+
+    const series = getScatterSeries(transformedProps);
+    expect(series).toHaveLength(1);
+    // data points are flipped to [metric, dimension]
+    expect(series[0].data[0]).toEqual([1, 'A']);
+  });
+
+  test('vertical orientation keeps the dimension axis on the x-axis', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: baseFormData,
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;
+    expect(xAxis.type).toBe('category');
+    expect(yAxis.type).toBe('value');
+
+    const series = getScatterSeries(transformedProps);
+    expect(series[0].data[0]).toEqual(['A', 1]);
+  });
+
+  test('size metric series is not rendered or shown in the legend', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: categoricalData,
+      formData: { ...baseFormData, size: 'size_metric' },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const series = getScatterSeries(transformedProps);
+    expect(series).toHaveLength(1);
+    expect(series[0].name).toBe('sum_val');
+    expect(transformedProps.legendData).toEqual(['sum_val']);
+  });
+
+  test('size metric scales marker areas between min and max dot size', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: categoricalData,
+      formData: {
+        ...baseFormData,
+        size: 'size_metric',
+        minMarkerSize: 5,
+        maxMarkerSize: 30,
+      },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const [series] = getScatterSeries(transformedProps);
+    expect(typeof series.symbolSize).toBe('function');
+    // smallest size value -> min dot size, largest -> max dot size
+    expect(series.symbolSize(['A', 1])).toBe(5);
+    expect(series.symbolSize(['C', 3])).toBe(30);
+    // midpoint value -> midpoint *area*, not midpoint diameter
+    expect(series.symbolSize(['B', 2]) ** 2).toBeCloseTo(
+      (5 ** 2 + 30 ** 2) / 2,
+    );
+  });
+
+  test('size metric lookups follow the dimension key when grouped', () => {
+    const groupedData = [
+      {
+        data: [
+          {
+            category_col: 'A',
+            'sum_val, g1': 1,
+            'size_metric, g1': 10,
+            'sum_val, g2': 2,
+            'size_metric, g2': 40,
+          },
+        ],
+        colnames: [
+          'category_col',
+          'sum_val, g1',
+          'size_metric, g1',
+          'sum_val, g2',
+          'size_metric, g2',
+        ],
+        coltypes: [
+          GenericDataType.String,
+          GenericDataType.Numeric,
+          GenericDataType.Numeric,
+          GenericDataType.Numeric,
+          GenericDataType.Numeric,
+        ],
+        label_map: {
+          category_col: ['category_col'],
+          'sum_val, g1': ['sum_val', 'g1'],
+          'size_metric, g1': ['size_metric', 'g1'],
+          'sum_val, g2': ['sum_val', 'g2'],
+          'size_metric, g2': ['size_metric', 'g2'],
+        },
+      },
+    ];
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: groupedData,
+      formData: {
+        ...baseFormData,
+        groupby: ['group_col'],
+        size: 'size_metric',
+        minMarkerSize: 5,
+        maxMarkerSize: 30,
+      },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const series = getScatterSeries(transformedProps);
+    expect(series.map((s: any) => s.name).sort()).toEqual([
+      'sum_val, g1',
+      'sum_val, g2',
+    ]);
+    const g1 = series.find((s: any) => s.name === 'sum_val, g1');
+    const g2 = series.find((s: any) => s.name === 'sum_val, g2');

Review Comment:
   **Suggestion:** Type the `find` callback parameter with the same series type 
used elsewhere rather than `any` to enforce safe property access. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   Both callback parameters are typed as `any` in TypeScript test code. That 
directly violates the rule requiring concrete or narrower types instead of 
`any`.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=78950423cd8446fd8b890fa01a58066e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=78950423cd8446fd8b890fa01a58066e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts
   **Line:** 368:369
   **Comment:**
        *Custom Rule: Type the `find` callback parameter with the same series 
type used elsewhere rather than `any` to enforce safe property access.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=01d159edd6e7a94ec0a5c2854c7ccdf6cea56b5fc538aad2102493d1aa992393&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=01d159edd6e7a94ec0a5c2854c7ccdf6cea56b5fc538aad2102493d1aa992393&reaction=dislike'>👎</a>



##########
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts:
##########
@@ -175,3 +175,283 @@ describe('Scatter Chart X-axis Number Formatting', () => {
     },
   );
 });
+
+describe('Scatter Chart Orientation and Dot Size Metric', () => {
+  const baseFormData: EchartsTimeseriesFormData = {
+    ...DEFAULT_FORM_DATA,
+    colorScheme: 'supersetColors',
+    datasource: '1__table',
+    metrics: ['sum_val'],
+    x_axis: 'category_col',
+    groupby: [],
+    viz_type: 'echarts_timeseries_scatter',
+    seriesType: EchartsTimeseriesSeriesType.Scatter,
+  };
+
+  const categoricalData = [
+    {
+      data: [
+        { category_col: 'A', sum_val: 1, size_metric: 10 },
+        { category_col: 'B', sum_val: 2, size_metric: 25 },
+        { category_col: 'C', sum_val: 3, size_metric: 40 },
+      ],
+      colnames: ['category_col', 'sum_val', 'size_metric'],
+      coltypes: [
+        GenericDataType.String,
+        GenericDataType.Numeric,
+        GenericDataType.Numeric,
+      ],
+      label_map: {
+        category_col: ['category_col'],
+        sum_val: ['sum_val'],
+        size_metric: ['size_metric'],
+      },
+    },
+  ];
+
+  const baseChartPropsConfig = {
+    width: 800,
+    height: 600,
+    theme: supersetTheme,
+  };
+
+  const getScatterSeries = (props: ReturnType<typeof transformProps>) =>
+    (props.echartOptions.series as any[]).filter(s => s.type === 'scatter');
+
+  const singleMetricData = [
+    {
+      ...categoricalData[0],
+      data: categoricalData[0].data.map(row => ({
+        category_col: row.category_col,
+        sum_val: row.sum_val,
+      })),
+      colnames: ['category_col', 'sum_val'],
+      coltypes: [GenericDataType.String, GenericDataType.Numeric],
+    },
+  ];
+
+  test('horizontal orientation swaps the dimension axis onto the y-axis', () 
=> {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: { ...baseFormData, orientation: 'horizontal' },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;
+    expect(yAxis.type).toBe('category');
+    expect(xAxis.type).toBe('value');
+
+    const series = getScatterSeries(transformedProps);
+    expect(series).toHaveLength(1);
+    // data points are flipped to [metric, dimension]
+    expect(series[0].data[0]).toEqual([1, 'A']);
+  });
+
+  test('vertical orientation keeps the dimension axis on the x-axis', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: singleMetricData,
+      formData: baseFormData,
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const { xAxis, yAxis } = transformedProps.echartOptions as any;
+    expect(xAxis.type).toBe('category');
+    expect(yAxis.type).toBe('value');
+
+    const series = getScatterSeries(transformedProps);
+    expect(series[0].data[0]).toEqual(['A', 1]);
+  });
+
+  test('size metric series is not rendered or shown in the legend', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: categoricalData,
+      formData: { ...baseFormData, size: 'size_metric' },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const series = getScatterSeries(transformedProps);
+    expect(series).toHaveLength(1);
+    expect(series[0].name).toBe('sum_val');
+    expect(transformedProps.legendData).toEqual(['sum_val']);
+  });
+
+  test('size metric scales marker areas between min and max dot size', () => {
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: categoricalData,
+      formData: {
+        ...baseFormData,
+        size: 'size_metric',
+        minMarkerSize: 5,
+        maxMarkerSize: 30,
+      },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const [series] = getScatterSeries(transformedProps);
+    expect(typeof series.symbolSize).toBe('function');
+    // smallest size value -> min dot size, largest -> max dot size
+    expect(series.symbolSize(['A', 1])).toBe(5);
+    expect(series.symbolSize(['C', 3])).toBe(30);
+    // midpoint value -> midpoint *area*, not midpoint diameter
+    expect(series.symbolSize(['B', 2]) ** 2).toBeCloseTo(
+      (5 ** 2 + 30 ** 2) / 2,
+    );
+  });
+
+  test('size metric lookups follow the dimension key when grouped', () => {
+    const groupedData = [
+      {
+        data: [
+          {
+            category_col: 'A',
+            'sum_val, g1': 1,
+            'size_metric, g1': 10,
+            'sum_val, g2': 2,
+            'size_metric, g2': 40,
+          },
+        ],
+        colnames: [
+          'category_col',
+          'sum_val, g1',
+          'size_metric, g1',
+          'sum_val, g2',
+          'size_metric, g2',
+        ],
+        coltypes: [
+          GenericDataType.String,
+          GenericDataType.Numeric,
+          GenericDataType.Numeric,
+          GenericDataType.Numeric,
+          GenericDataType.Numeric,
+        ],
+        label_map: {
+          category_col: ['category_col'],
+          'sum_val, g1': ['sum_val', 'g1'],
+          'size_metric, g1': ['size_metric', 'g1'],
+          'sum_val, g2': ['sum_val', 'g2'],
+          'size_metric, g2': ['size_metric', 'g2'],
+        },
+      },
+    ];
+    const chartProps = new ChartProps({
+      ...baseChartPropsConfig,
+      queriesData: groupedData,
+      formData: {
+        ...baseFormData,
+        groupby: ['group_col'],
+        size: 'size_metric',
+        minMarkerSize: 5,
+        maxMarkerSize: 30,
+      },
+    });
+
+    const transformedProps = transformProps(
+      chartProps as EchartsTimeseriesChartProps,
+    );
+    const series = getScatterSeries(transformedProps);
+    expect(series.map((s: any) => s.name).sort()).toEqual([

Review Comment:
   **Suggestion:** Replace the callback parameter type `any` with a typed 
scatter series object so `.name` access is validated by TypeScript. 
[custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The callback parameter is explicitly typed as `any`, which falls under the 
custom rule banning `any` in TypeScript/TSX code.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=86d471c2415e47db9c39ace481e8ba29&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=86d471c2415e47db9c39ace481e8ba29&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/transformProps.test.ts
   **Line:** 364:364
   **Comment:**
        *Custom Rule: Replace the callback parameter type `any` with a typed 
scatter series object so `.name` access is validated by TypeScript.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=8ddf532e349c77cc1700f3f39bc45bd4fda1f6d6de29c8d1ee7d45ada0d0e45a&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40967&comment_hash=8ddf532e349c77cc1700f3f39bc45bd4fda1f6d6de29c8d1ee7d45ada0d0e45a&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to