Copilot commented on code in PR #36128:
URL: https://github.com/apache/superset/pull/36128#discussion_r2539247606
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -372,6 +391,38 @@ export default function transformProps(
}
}
});
+
+ // ----- ensure series data are sorted naturally on the x-value -----
+// Run after all series have been created so each series.data is complete.
Review Comment:
The comment formatting is inconsistent. The comment on line 395 should start
with `//` on the same line as the code block begins, or the entire comment
block (lines 395-396) should be properly indented. Currently, line 395 has
trailing whitespace and line 396 starts a new comment without proper
indentation alignment.
```suggestion
// Run after all series have been created so each series.data is complete.
```
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -372,6 +391,38 @@ export default function transformProps(
}
}
});
+
+ // ----- ensure series data are sorted naturally on the x-value -----
+// Run after all series have been created so each series.data is complete.
+series.forEach((s: SeriesOption) => {
+ const dataArr = (s as any).data;
+ if (!Array.isArray(dataArr) || dataArr.length <= 1) return;
+
+ (s as any).data = dataArr.sort((row1: any, row2: any) => {
+ // extract the raw x values (support both [x,y] and { x, y } shapes)
+ const rawX1 = Array.isArray(row1) ? row1[0] : row1?.x;
+ const rawX2 = Array.isArray(row2) ? row2[0] : row2?.x;
+
+ // If this chart's x-axis is temporal, coerce to timestamps (numbers) for
sorting.
+ // Fallback to original raw values if parsing fails.
+ const getComparableX = (raw: any) => {
+ if (xAxisType === AxisType.Time) {
+ // If it's already a number, use it. Otherwise try to coerce to Date
timestamp.
+ if (typeof raw === 'number' && isFinite(raw)) return raw;
+ const parsed = new Date(String(raw)).getTime();
+ return isFinite(parsed) ? parsed : String(raw);
+ }
+ return raw;
Review Comment:
The `getComparableX` function has inconsistent return types. When `xAxisType
=== AxisType.Time`, it can return either a number (timestamp) or a string
(fallback). For non-temporal axes, it returns the raw value which could be any
type. This type inconsistency makes the sorting behavior less predictable.
Consider explicitly documenting the expected return types or normalizing all
values to a consistent comparable type.
```suggestion
// For non-temporal axes, always coerce to string for consistent
comparison
return String(raw);
```
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -344,7 +363,7 @@ export default function transformProps(
metrics,
labelMap?.[seriesName]?.[0],
) ?? defaultFormatter),
- showValue,
+ showValue:Boolean(showValue),
Review Comment:
Missing space after colon. Should be `showValue: Boolean(showValue)` with a
space for consistent formatting.
```suggestion
showValue: Boolean(showValue),
```
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -372,6 +391,38 @@ export default function transformProps(
}
}
});
+
+ // ----- ensure series data are sorted naturally on the x-value -----
+// Run after all series have been created so each series.data is complete.
+series.forEach((s: SeriesOption) => {
+ const dataArr = (s as any).data;
+ if (!Array.isArray(dataArr) || dataArr.length <= 1) return;
+
+ (s as any).data = dataArr.sort((row1: any, row2: any) => {
+ // extract the raw x values (support both [x,y] and { x, y } shapes)
+ const rawX1 = Array.isArray(row1) ? row1[0] : row1?.x;
+ const rawX2 = Array.isArray(row2) ? row2[0] : row2?.x;
+
+ // If this chart's x-axis is temporal, coerce to timestamps (numbers) for
sorting.
+ // Fallback to original raw values if parsing fails.
+ const getComparableX = (raw: any) => {
+ if (xAxisType === AxisType.Time) {
+ // If it's already a number, use it. Otherwise try to coerce to Date
timestamp.
+ if (typeof raw === 'number' && isFinite(raw)) return raw;
+ const parsed = new Date(String(raw)).getTime();
+ return isFinite(parsed) ? parsed : String(raw);
+ }
+ return raw;
+ };
+
+ const x1 = getComparableX(rawX1);
+ const x2 = getComparableX(rawX2);
+
+ // naturalCompare already prefers numeric comparison when possible
+ return naturalCompare(x1, x2);
+ });
+});
Review Comment:
Using `(s as any)` type assertions multiple times (lines 398, 401) bypasses
TypeScript's type safety. Consider defining a proper interface or type for the
series data structure, or at least use a more specific type than `any` to
maintain type safety and code clarity.
```suggestion
// ----- ensure series data are sorted naturally on the x-value -----
// Run after all series have been created so each series.data is complete.
// Define a type for series with a data property
type SeriesWithData = SeriesOption & { data: any[] };
series.forEach((s: SeriesOption) => {
const dataArr = (s as SeriesWithData).data;
if (!Array.isArray(dataArr) || dataArr.length <= 1) return;
(s as SeriesWithData).data = dataArr.sort((row1: any, row2: any) => {
// extract the raw x values (support both [x,y] and { x, y } shapes)
const rawX1 = Array.isArray(row1) ? row1[0] : row1?.x;
const rawX2 = Array.isArray(row2) ? row2[0] : row2?.x;
// If this chart's x-axis is temporal, coerce to timestamps (numbers)
for sorting.
// Fallback to original raw values if parsing fails.
const getComparableX = (raw: any) => {
if (xAxisType === AxisType.Time) {
// If it's already a number, use it. Otherwise try to coerce to
Date timestamp.
if (typeof raw === 'number' && isFinite(raw)) return raw;
const parsed = new Date(String(raw)).getTime();
return isFinite(parsed) ? parsed : String(raw);
}
return raw;
};
const x1 = getComparableX(rawX1);
const x2 = getComparableX(rawX2);
// naturalCompare already prefers numeric comparison when possible
return naturalCompare(x1, x2);
});
});
```
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -486,9 +537,7 @@ export default function transformProps(
const xAxisFormatter =
xAxisDataType === GenericDataType.Temporal
? getXAxisFormatter(xAxisTimeFormat)
- : xAxisDataType === GenericDataType.Numeric
- ? getNumberFormatter(xAxisNumberFormat)
- : String;
+ : String;
Review Comment:
The removal of numeric axis formatting (`xAxisNumberFormat` with
`getNumberFormatter`) means that numeric x-axes will now always use `String()`
for formatting, losing the ability to format numbers with thousands separators,
decimal precision, or other numeric formats. This is a breaking change that
could negatively affect charts with numeric x-axes (e.g., scatter plots with
numeric dimensions). Consider keeping the numeric formatter for
`GenericDataType.Numeric` x-axes.
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -372,6 +391,38 @@ export default function transformProps(
}
}
});
+
+ // ----- ensure series data are sorted naturally on the x-value -----
+// Run after all series have been created so each series.data is complete.
+series.forEach((s: SeriesOption) => {
+ const dataArr = (s as any).data;
+ if (!Array.isArray(dataArr) || dataArr.length <= 1) return;
+
+ (s as any).data = dataArr.sort((row1: any, row2: any) => {
Review Comment:
The series data is being mutated in place with `Array.sort()`, which
modifies the original array. This could cause issues if the original data array
is referenced elsewhere. Consider creating a sorted copy instead: `(s as
any).data = [...dataArr].sort(...)` to avoid unintended side effects.
```suggestion
(s as any).data = [...dataArr].sort((row1: any, row2: any) => {
```
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -110,6 +110,26 @@ import {
getYAxisFormatter,
} from '../utils/formatters';
+
+// ----- natural sort helper -----
+// Try numeric comparison first for numeric-like strings, fallback to
localeCompare.
+function naturalCompare(a: any, b: any): number {
+ const sa = a === undefined || a === null ? '' : String(a);
+ const sb = b === undefined || b === null ? '' : String(b);
+ const na = Number(sa);
+ const nb = Number(sb);
+
+ // If both parse as finite numbers, do numeric sort
+ if (isFinite(na) && isFinite(nb)) {
+ return na - nb;
+ }
Review Comment:
Converting empty strings to numbers will result in `0` (since `Number('')
=== 0`), which will pass the `isFinite()` check. This means null/undefined
values will be treated as `0` in numeric comparisons, causing them to sort
alongside actual zero values rather than being handled distinctly. This could
lead to incorrect ordering when the data contains both null values and zeros.
##########
superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts:
##########
@@ -110,6 +110,26 @@ import {
getYAxisFormatter,
} from '../utils/formatters';
+
+// ----- natural sort helper -----
+// Try numeric comparison first for numeric-like strings, fallback to
localeCompare.
+function naturalCompare(a: any, b: any): number {
+ const sa = a === undefined || a === null ? '' : String(a);
+ const sb = b === undefined || b === null ? '' : String(b);
+ const na = Number(sa);
+ const nb = Number(sb);
+
+ // If both parse as finite numbers, do numeric sort
+ if (isFinite(na) && isFinite(nb)) {
+ return na - nb;
+ }
+
+ // Otherwise fallback to lexicographic
+ return sa.localeCompare(sb);
+}
Review Comment:
The `naturalCompare` function will incorrectly sort empty strings before
numbers. When `a` or `b` is `null` or `undefined`, they are converted to empty
strings (`''`), which will then be compared using `localeCompare`. This means
null/undefined values will appear before numeric values, which may not be the
intended behavior. Consider handling null/undefined values explicitly by
placing them at the end of the sort order.
--
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]