Skip to content

Commit 4226d82

Browse files
authored
Merge branch 'master' into enxdev/fix/table-adhoc-column-cross-filters
2 parents 2d011b0 + 43816d7 commit 4226d82

10 files changed

Lines changed: 715 additions & 40 deletions

File tree

.github/workflows/ephemeral-env.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ jobs:
296296
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
297297
- name: Deploy Amazon ECS task definition
298298
id: deploy-task
299-
uses: aws-actions/amazon-ecs-deploy-task-definition@cbf54ec46642b86ff78c2f5793da6746954cf8ff # v2
299+
uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2
300300
with:
301301
task-definition: ${{ steps.task-def.outputs.task-definition }}
302302
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service

superset-frontend/package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

superset-frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"content-disposition": "^1.0.1",
163163
"d3-color": "^3.1.0",
164164
"d3-scale": "^4.0.2",
165-
"dayjs": "^1.11.19",
165+
"dayjs": "^1.11.20",
166166
"dom-to-image-more": "^3.7.2",
167167
"dom-to-pdf": "^0.3.2",
168168
"echarts": "^5.6.0",

superset-frontend/packages/superset-ui-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"csstype": "^3.2.3",
3737
"core-js": "^3.48.0",
3838
"d3-format": "^3.1.2",
39-
"dayjs": "^1.11.19",
39+
"dayjs": "^1.11.20",
4040
"d3-interpolate": "^3.0.1",
4141
"d3-scale": "^4.0.2",
4242
"d3-time": "^3.1.0",

superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,60 @@ import {
124124
import { safeParseEChartOptions } from '../utils/safeEChartOptionsParser';
125125
import { mergeCustomEChartOptions } from '../utils/mergeCustomEChartOptions';
126126

127+
const visibleDashPatterns: ([number, number] | 'dashed' | 'dotted')[] = [
128+
'dashed',
129+
'dotted',
130+
[6, 15], // narrow dashed
131+
[2, 10], // wide dotted
132+
[20, 3], // wide dashed
133+
];
134+
const visibleSymbols = [
135+
'rect',
136+
'triangle',
137+
'diamond',
138+
'roundRect',
139+
'pin',
140+
] as const;
141+
142+
function getSymbolMarker(symbol: string, color: string) {
143+
const size = 10;
144+
switch (symbol) {
145+
case 'circle':
146+
return `<span style="
147+
display:inline-block;width:${size}px;height:${size}px;
148+
border-radius:50%;background:${color};margin-right:5px"></span>`;
149+
case 'rect':
150+
return `<span style="
151+
display:inline-block;width:${size}px;height:${size}px;
152+
background:${color};margin-right:5px"></span>`;
153+
case 'roundRect':
154+
return `<span style="
155+
display:inline-block;width:${size}px;height:${size}px;border-radius:2px;
156+
background:${color};margin-right:5px"></span>`;
157+
case 'triangle':
158+
return `<span style="
159+
display:inline-block;width:0;height:0;
160+
border-left:${size / 2}px solid transparent;
161+
border-right:${size / 2}px solid transparent;
162+
border-bottom:${size}px solid ${color};
163+
margin-right:5px"></span>`;
164+
case 'diamond':
165+
return `<span style="
166+
display:inline-block;width:${size - 2}px;height:${size - 2}px;
167+
background:${color};transform: rotate(45deg) translateX(1px) translateY(-1px);
168+
margin-right:5px"></span>`;
169+
case 'pin':
170+
return `<span style="
171+
display:inline-block;width:${size - 2}px;height:${size - 2}px;
172+
background:${color};transform: rotate(45deg) translateX(1px) translateY(-1px);
173+
border-radius:50%;border-bottom-right-radius:0;margin-right:5px"></span>`;
174+
default:
175+
return `<span style="
176+
display:inline-block;width:${size}px;height:${size}px;
177+
border-radius:50%;background:${color};margin-right:5px"></span>`;
178+
}
179+
}
180+
127181
export default function transformProps(
128182
chartProps: EchartsTimeseriesChartProps,
129183
): TimeseriesChartTransformedProps {
@@ -346,7 +400,8 @@ export default function transformProps(
346400
);
347401

348402
const lineStyle: LineStyleOption = {};
349-
if (derivedSeries) {
403+
let lineSymbol;
404+
if (derivedSeries && timeShiftColor) {
350405
// Get the time offset for this series to assign different dash patterns
351406
const offset = getTimeOffset(entry, array) || seriesName;
352407
if (!offsetLineWidths[offset]) {
@@ -355,11 +410,11 @@ export default function transformProps(
355410
// Use visible dash patterns that vary by offset index
356411
// Pattern: [dash length, gap length] - scaled to be clearly visible
357412
const patternIndex = offsetLineWidths[offset];
358-
lineStyle.type = [
359-
(patternIndex % 5) + 4, // dash: 4-8px (visible)
360-
(patternIndex % 3) + 3, // gap: 3-5px (visible)
361-
];
413+
lineStyle.type =
414+
visibleDashPatterns[patternIndex % visibleDashPatterns.length];
415+
362416
lineStyle.opacity = OpacityEnum.DerivedSeries;
417+
lineSymbol = visibleSymbols[patternIndex % visibleSymbols.length];
363418
}
364419

365420
// Calculate min/max from data for horizontal bar charts
@@ -443,6 +498,7 @@ export default function transformProps(
443498
sliceId,
444499
isHorizontal,
445500
lineStyle,
501+
lineSymbol,
446502
timeCompare: array,
447503
timeShiftColor,
448504
theme,
@@ -934,10 +990,16 @@ export default function transformProps(
934990
if (value.observation === 0 && stack) {
935991
return;
936992
}
993+
const seriesForKey = series.find(s => s.name === key);
994+
const symbolForSeries = (seriesForKey as any)?.symbol || 'circle';
995+
const marker = value.color
996+
? getSymbolMarker(symbolForSeries, value.color)
997+
: value.marker;
937998
const row = formatForecastTooltipSeries({
938999
...value,
9391000
seriesName: key,
9401001
formatter,
1002+
marker,
9411003
});
9421004

9431005
const annotationRow = annotationLayers.some(

superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export function transformSeries(
221221
seriesKey?: OptionName;
222222
sliceId?: number;
223223
isHorizontal?: boolean;
224+
lineSymbol?: string;
224225
lineStyle?: LineStyleOption;
225226
queryIndex?: number;
226227
timeCompare?: string[];
@@ -359,8 +360,10 @@ export function transformSeries(
359360

360361
// Use filled circles in dark mode to avoid the white fill issue with hollow circles
361362
// Use emptyCircle explicitly in light mode
362-
const symbol =
363-
plotType === 'line' ? (isDarkMode ? 'circle' : 'emptyCircle') : undefined;
363+
let symbol;
364+
if (plotType === 'line') {
365+
symbol = opts.lineSymbol || (isDarkMode ? 'circle' : 'emptyCircle');
366+
}
364367

365368
return {
366369
...series,

superset-frontend/plugins/plugin-chart-echarts/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export type ForecastValue = {
9292
forecastTrend?: number;
9393
forecastLower?: number;
9494
forecastUpper?: number;
95+
color?: string;
9596
};
9697

9798
export type LegendFormData = {

superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const extractForecastValuesFromTooltipParams = (
6060
): Record<string, ForecastValue> => {
6161
const values: Record<string, ForecastValue> = {};
6262
params.forEach(param => {
63-
const { marker, seriesId, value } = param;
63+
const { marker, seriesId, value, color } = param;
6464
const context = extractForecastSeriesContext(seriesId);
6565
const numericValue = isHorizontal ? value[0] : value[1];
6666
if (typeof numericValue === 'number') {
@@ -69,6 +69,7 @@ export const extractForecastValuesFromTooltipParams = (
6969
marker: marker || '',
7070
};
7171
const forecastValues = values[context.name];
72+
forecastValues.color = color;
7273
if (context.type === ForecastSeriesEnum.Observation)
7374
forecastValues.observation = numericValue;
7475
if (context.type === ForecastSeriesEnum.ForecastTrend)

superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,7 @@ test('should apply dashed line style to time comparison series with single metri
955955
formData: {
956956
...timeCompareFormData,
957957
time_compare: ['1 week ago'],
958+
timeShiftColor: true,
958959
comparison_type: ComparisonType.Values,
959960
},
960961
queriesData: queriesDataWithTimeCompare,
@@ -974,18 +975,9 @@ test('should apply dashed line style to time comparison series with single metri
974975
expect(comparisonSeries).toBeDefined();
975976
// Main series should not have a dash pattern array
976977
expect(Array.isArray(mainSeries?.lineStyle?.type)).toBe(false);
977-
// Comparison series should have a visible dash pattern array [dash, gap]
978-
expect(Array.isArray(comparisonSeries?.lineStyle?.type)).toBe(true);
979-
expect(
980-
Array.isArray(comparisonSeries?.lineStyle?.type)
981-
? comparisonSeries.lineStyle.type[0]
982-
: undefined,
983-
).toBeGreaterThanOrEqual(4);
984-
expect(
985-
Array.isArray(comparisonSeries?.lineStyle?.type)
986-
? comparisonSeries.lineStyle.type[1]
987-
: undefined,
988-
).toBeGreaterThanOrEqual(3);
978+
expect(mainSeries?.lineStyle?.type).not.toBe('dotted');
979+
// Comparison series should have a visible dash pattern
980+
expect(comparisonSeries?.lineStyle?.type).toBe('dotted');
989981
});
990982

991983
test('should apply dashed line style to time comparison series with metric__offset pattern', () => {
@@ -1008,6 +1000,7 @@ test('should apply dashed line style to time comparison series with metric__offs
10081000
formData: {
10091001
...timeCompareFormData,
10101002
time_compare: ['1 week ago'],
1003+
timeShiftColor: true,
10111004
comparison_type: ComparisonType.Values,
10121005
},
10131006
queriesData: queriesDataWithTimeCompare,
@@ -1029,18 +1022,8 @@ test('should apply dashed line style to time comparison series with metric__offs
10291022
expect(comparisonSeries).toBeDefined();
10301023
// Main series should not have a dash pattern array
10311024
expect(Array.isArray(mainSeries?.lineStyle?.type)).toBe(false);
1032-
// Comparison series should have a visible dash pattern array [dash, gap]
1033-
expect(Array.isArray(comparisonSeries?.lineStyle?.type)).toBe(true);
1034-
expect(
1035-
Array.isArray(comparisonSeries?.lineStyle?.type)
1036-
? comparisonSeries.lineStyle.type[0]
1037-
: undefined,
1038-
).toBeGreaterThanOrEqual(4);
1039-
expect(
1040-
Array.isArray(comparisonSeries?.lineStyle?.type)
1041-
? comparisonSeries.lineStyle.type[1]
1042-
: undefined,
1043-
).toBeGreaterThanOrEqual(3);
1025+
// Comparison series should have a visible dash pattern
1026+
expect(comparisonSeries?.lineStyle?.type).toBe('dotted');
10441027
});
10451028

10461029
test('should apply connectNulls to time comparison series', () => {
@@ -1352,3 +1335,52 @@ test('should not apply axis bounds calculation when seriesType is not Bar for ho
13521335
// Should not have explicit max set when seriesType is not Bar
13531336
expect(xAxisRaw.max).toBeUndefined();
13541337
});
1338+
1339+
test('should assign distinct dash patterns for multiple time offsets consistently', () => {
1340+
const queriesDataWithMultipleOffsets = [
1341+
createTestQueryData([
1342+
{
1343+
sum__num: 100,
1344+
'1 year ago': 80,
1345+
'2 years ago': 60,
1346+
__timestamp: 599616000000,
1347+
},
1348+
{
1349+
sum__num: 150,
1350+
'1 year ago': 120,
1351+
'2 years ago': 90,
1352+
__timestamp: 599916000000,
1353+
},
1354+
]),
1355+
];
1356+
1357+
const chartProps = createTestChartProps({
1358+
formData: {
1359+
...timeCompareFormData,
1360+
time_compare: ['1 year ago', '2 years ago'],
1361+
comparison_type: ComparisonType.Values,
1362+
timeShiftColor: true,
1363+
},
1364+
queriesData: queriesDataWithMultipleOffsets,
1365+
});
1366+
1367+
const transformed = transformProps(chartProps);
1368+
const series = (transformed.echartOptions.series as SeriesOption[]) || [];
1369+
1370+
const series1 = series.find(s => s.name === '1 year ago') as any;
1371+
const series2 = series.find(s => s.name === '2 years ago') as any;
1372+
1373+
expect(series1).toBeDefined();
1374+
expect(series2).toBeDefined();
1375+
1376+
const pattern1 = series1.lineStyle?.type;
1377+
const symbol1 = series1.symbol;
1378+
const pattern2 = series2.lineStyle?.type;
1379+
const symbol2 = series2.symbol;
1380+
1381+
// must be different patterns
1382+
expect(pattern1).not.toEqual(pattern2);
1383+
1384+
// must be different patterns
1385+
expect(symbol1).not.toEqual(symbol2);
1386+
});

0 commit comments

Comments
 (0)