Copilot commented on code in PR #37407:
URL: https://github.com/apache/superset/pull/37407#discussion_r2728777259


##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx:
##########
@@ -252,12 +312,18 @@ export default function EchartsTimeseries({
           });
         });
 
+        // Provide cross-filter for dimensions OR categorical X-axis (issue 
#25334)
+        let crossFilter;
+        if (hasDimensions) {
+          crossFilter = getCrossFilterDataMask(seriesName);
+        } else if (canCrossFilterByXAxis && data) {
+          crossFilter = getXAxisCrossFilterDataMask(data[0]);

Review Comment:
   There's no validation that the X-axis value exists before using it. If data 
is an empty array or data[0] is undefined, the code will convert undefined to 
the string "undefined" and attempt to filter on it, which will likely cause 
incorrect behavior.
   
   Consider adding a check to ensure the X-axis value is not undefined before 
proceeding with the cross-filter operation.



##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx:
##########
@@ -154,6 +154,43 @@ export default function EchartsTimeseries({
     [groupby, labelMap, selectedValues],
   );
 
+  // Cross-filter using X-axis value when no dimensions are set (issue #25334)
+  const getXAxisCrossFilterDataMask = useCallback(
+    (xAxisValue: string | number) => {
+      const stringValue = String(xAxisValue);
+      const selected: string[] = Object.values(selectedValues);
+      let values: string[];
+      if (selected.includes(stringValue)) {
+        values = selected.filter(v => v !== stringValue);
+      } else {
+        values = [stringValue];
+      }
+      return {
+        dataMask: {
+          extraFormData: {
+            filters:
+              values.length === 0
+                ? []
+                : [
+                    {
+                      col: xAxis.label,
+                      op: 'IN' as const,
+                      val: values,
+                    },
+                  ],
+          },
+          filterState: {
+            label: values.length ? values : undefined,
+            value: values.length ? values : null,
+            selectedValues: values.length ? values : null,
+          },
+        },
+        isCurrentValueSelected: selected.includes(stringValue),
+      };
+    },
+    [selectedValues, xAxis.label],
+  );

Review Comment:
   When X-axis cross-filtering is active, the selectedValues will contain 
X-axis values (e.g., "Product A") rather than series names (e.g., "Sales"). 
This causes a visual feedback issue in transformers.ts (around line 241) where 
the code checks `!filterState?.selectedValues.includes(name)` to determine if a 
series should be dimmed.
   
   Since series names (metric names like "Sales") won't match X-axis values 
(like "Product A"), all series will be incorrectly marked as filtered and 
rendered with semi-transparent opacity. This doesn't break the actual filtering 
functionality (which happens via extraFormData), but provides incorrect visual 
feedback to users - all bars/lines will appear dimmed when X-axis filtering is 
active.
   
   Consider either:
   1. Disabling the opacity-based visual feedback when using X-axis filtering 
(by not setting selectedValues in filterState for X-axis filters)
   2. Implementing a different visual feedback mechanism for X-axis filtering
   3. Adding a flag to filterState to distinguish between dimension-based and 
X-axis-based filtering



##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx:
##########
@@ -154,6 +154,43 @@ export default function EchartsTimeseries({
     [groupby, labelMap, selectedValues],
   );
 
+  // Cross-filter using X-axis value when no dimensions are set (issue #25334)
+  const getXAxisCrossFilterDataMask = useCallback(
+    (xAxisValue: string | number) => {
+      const stringValue = String(xAxisValue);
+      const selected: string[] = Object.values(selectedValues);
+      let values: string[];
+      if (selected.includes(stringValue)) {
+        values = selected.filter(v => v !== stringValue);
+      } else {
+        values = [stringValue];
+      }
+      return {
+        dataMask: {
+          extraFormData: {
+            filters:
+              values.length === 0
+                ? []
+                : [
+                    {
+                      col: xAxis.label,
+                      op: 'IN' as const,
+                      val: values,
+                    },
+                  ],
+          },
+          filterState: {
+            label: values.length ? values : undefined,
+            value: values.length ? values : null,
+            selectedValues: values.length ? values : null,
+          },
+        },
+        isCurrentValueSelected: selected.includes(stringValue),

Review Comment:
   Converting the X-axis value to a string may cause filtering issues when the 
X-axis column contains numeric values. The filter value type should match the 
column's data type. For example, if the category column contains years as 
numbers (2020, 2021, etc.), converting them to strings ('2020', '2021') may not 
match the column type in the database, causing the filter to fail.
   
   Consider preserving the original type of xAxisValue instead of converting it 
to a string. The selectedValues comparison on line 163 could use String() for 
comparison purposes, but the actual filter value should retain its original 
type.
   ```suggestion
         const selected = Object.values(
           selectedValues,
         ) as (string | number)[];
         const selectedStrings = selected.map(v => String(v));
   
         let filterValues: (string | number)[];
         let stateValues: string[];
   
         if (selectedStrings.includes(stringValue)) {
           // Deselect current value
           filterValues = selected.filter(v => String(v) !== stringValue);
           stateValues = selectedStrings.filter(v => v !== stringValue);
         } else {
           // Select current value
           filterValues = [...selected, xAxisValue];
           stateValues = [...selectedStrings, stringValue];
         }
   
         return {
           dataMask: {
             extraFormData: {
               filters:
                 filterValues.length === 0
                   ? []
                   : [
                       {
                         col: xAxis.label,
                         op: 'IN' as const,
                         val: filterValues,
                       },
                     ],
             },
             filterState: {
               label: stateValues.length ? stateValues : undefined,
               value: stateValues.length ? stateValues : null,
               selectedValues: stateValues.length ? stateValues : null,
             },
           },
           isCurrentValueSelected: selectedStrings.includes(stringValue),
   ```



##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/EchartsTimeseries.tsx:
##########
@@ -164,18 +201,41 @@ export default function EchartsTimeseries({
     [emitCrossFilters, setDataMask, getCrossFilterDataMask],
   );
 
+  // Handle cross-filter using X-axis value when no dimensions (issue #25334)
+  const handleXAxisChange = useCallback(
+    (xAxisValue: string | number) => {
+      if (!emitCrossFilters) {
+        return;
+      }
+      setDataMask(getXAxisCrossFilterDataMask(xAxisValue).dataMask);
+    },
+    [emitCrossFilters, setDataMask, getXAxisCrossFilterDataMask],
+  );
+
+  // Determine if X-axis can be used for cross-filtering (categorical axis 
without dimensions)
+  const canCrossFilterByXAxis =
+    !hasDimensions && xAxis.type === AxisType.Category;
+
   const eventHandlers: EventHandlers = {
     click: props => {
-      if (!hasDimensions) {
+      // Allow cross-filter by dimensions OR by categorical X-axis (issue 
#25334)
+      if (!hasDimensions && !canCrossFilterByXAxis) {
         return;
       }
       if (clickTimer.current) {
         clearTimeout(clickTimer.current);
       }
       // Ensure that double-click events do not trigger single click event. So 
we put it in the timer.
       clickTimer.current = setTimeout(() => {
-        const { seriesName: name } = props;
-        handleChange(name);
+        if (hasDimensions) {
+          // Cross-filter by dimension (original behavior)
+          const { seriesName: name } = props;
+          handleChange(name);
+        } else if (canCrossFilterByXAxis && props.data) {
+          // Cross-filter by X-axis value when no dimensions (issue #25334)
+          const xAxisValue = props.data[0];
+          handleXAxisChange(xAxisValue);

Review Comment:
   There's no validation that the X-axis value exists before using it. If 
props.data is an empty array or data[0] is undefined, the code will convert 
undefined to the string "undefined" and attempt to filter on it, which will 
likely cause incorrect behavior.
   
   Consider adding a check to ensure the X-axis value is not undefined before 
proceeding with the cross-filter operation.
   ```suggestion
           } else if (
             canCrossFilterByXAxis &&
             Array.isArray(props.data) &&
             props.data.length > 0
           ) {
             // Cross-filter by X-axis value when no dimensions (issue #25334)
             const xAxisValue = props.data[0];
             if (xAxisValue !== undefined && xAxisValue !== null) {
               handleXAxisChange(xAxisValue);
             }
   ```



-- 
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