Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7a9f687
Test fork commit
nelsondrew Apr 9, 2025
1cec095
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 16, 2025
696d5d9
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 25, 2025
672c16f
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 29, 2025
3df058e
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 29, 2025
dc5820d
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 1, 2025
dce9fe3
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 4, 2025
7f51dca
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 11, 2025
e88e98d
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 14, 2025
4e92bb6
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 16, 2025
6d6d237
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 18, 2025
0e4927a
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 20, 2025
e0fd65f
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 22, 2025
b374343
Radar chart normalisation
amaannawab923 May 22, 2025
9daf1a6
Getter method for safely obtaining denormalised values
amaannawab923 May 22, 2025
0c1f934
column config visiblity
amaannawab923 May 22, 2025
d0e4da2
Removing any
amaannawab923 May 22, 2025
11f3ba2
cosistency with normalize
amaannawab923 May 22, 2025
009dec2
is normalized
amaannawab923 May 22, 2025
11dd197
Transform props test radar chart
amaannawab923 May 22, 2025
0cc2183
Updated description
amaannawab923 May 22, 2025
4cd764d
Resolved comments
amaannawab923 May 23, 2025
022ed85
Apache license
amaannawab923 May 23, 2025
b7d608a
Remove normalisation control & apply normalisation as default
amaannawab923 May 23, 2025
e0959d2
Correcting unit test
amaannawab923 May 23, 2025
a6a13a6
removing logic of metric value as max
amaannawab923 May 24, 2025
9c6606e
handling min value
amaannawab923 May 24, 2025
66707a8
type
amaannawab923 May 24, 2025
a82c59d
Normalized tooltip as tsx
amaannawab923 May 26, 2025
edde6c9
using is defined
amaannawab923 May 26, 2025
b158611
No use before for metrics with custom bounds
amaannawab923 May 26, 2025
bf2fb5e
Using diff variable
amaannawab923 May 26, 2025
86478bd
string formatter tooltip
amaannawab923 May 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'is_normalised',
config: {
type: 'CheckboxControl',
label: t('Normalise Values'),
renderTrigger: true,
default: false,
description: t(
'Normalise the radar chart values between 0 & 1 to get consistent plotting.',
),
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated
},
},
],
],
},
{
Expand Down Expand Up @@ -210,6 +224,8 @@ const config: ControlPanelConfig = {
columnsPropsObject: { colnames, coltypes },
};
},
visibility: ({ controls }) =>
Boolean(!controls?.is_normalised?.value),
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
EchartsRadarFormData,
EchartsRadarLabelType,
RadarChartTransformedProps,
SeriesNormalizedMap,
} from './types';
import { DEFAULT_LEGEND_FORM_DATA, OpacityEnum } from '../constants';
import {
Expand All @@ -51,13 +52,24 @@ export function formatLabel({
params,
labelType,
numberFormatter,
getDenormalisedSeriesValue,
isNormalised,
}: {
params: CallbackDataParams;
labelType: EchartsRadarLabelType;
numberFormatter: NumberFormatter;
getDenormalisedSeriesValue: (
seriesName: string,
normalisedValue: string,
) => number;
isNormalised: boolean;
}): string {
const { name = '', value } = params;
const formattedValue = numberFormatter(value as number);
const formattedValue = numberFormatter(
isNormalised
? (getDenormalisedSeriesValue(name, String(value)) as number)
: (value as number),
);
Comment on lines 59 to +75

This comment was marked as resolved.


switch (labelType) {
case EchartsRadarLabelType.Value:
Expand Down Expand Up @@ -103,6 +115,7 @@ export default function transformProps(
isCircle,
columnConfig,
sliceId,
isNormalised,
}: EchartsRadarFormData = {
...DEFAULT_LEGEND_FORM_DATA,
...DEFAULT_RADAR_FORM_DATA,
Expand All @@ -111,11 +124,36 @@ export default function transformProps(
const { setDataMask = () => {}, onContextMenu } = hooks;
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
const numberFormatter = getNumberFormatter(numberFormat);
const denormalizedSeriesValues: SeriesNormalizedMap = {};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use an Object to store the denormalised values belonging to each series because after normalising we need to pass the denormalised values to the label formatter & tooltip formatter to keep the chart consistent & preserve original values

const getDenormalisedSeriesValue = (
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated
seriesName: string,
normalisedValue: string,
): number => {
if (
Object.prototype.hasOwnProperty.call(
denormalizedSeriesValues,
seriesName,
) &&
Object.prototype.hasOwnProperty.call(
denormalizedSeriesValues[seriesName],
normalisedValue,
)
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated
) {

This comment was marked as resolved.

return denormalizedSeriesValues[seriesName][normalisedValue];
}

// Fallback: return the normalised value itself as a number
return Number(normalisedValue);
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getDenormalisedSeriesValue is getter function for safely getting the denormalised value , if we dont find the value we fallback to normalised value, this shouldnt happen but we do this to prevent crashes & unexpected errors which can crash the user experience


const formatter = (params: CallbackDataParams) =>
formatLabel({
params,
numberFormatter,
labelType,
getDenormalisedSeriesValue,
isNormalised,
});

const metricLabels = metrics.map(getMetricLabel);
Expand All @@ -124,7 +162,7 @@ export default function transformProps(
const metricLabelAndMaxValueMap = new Map<string, number>();
const metricLabelAndMinValueMap = new Map<string, number>();
const columnsLabelMap = new Map<string, string[]>();
const transformedData: RadarSeriesDataItemOption[] = [];
let transformedData: RadarSeriesDataItemOption[] = [];
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated
data.forEach(datum => {
const joinedName = extractGroupbyLabel({
datum,
Expand Down Expand Up @@ -212,7 +250,40 @@ export default function transformProps(
{},
);

// Add normalization function
const normalizeArray = (arr: number[], decimals = 10, seriesName: string) => {
const max = Math.max(...arr);
return arr.map(value => {
const normalisedValue = Number((value / max).toFixed(decimals));
denormalizedSeriesValues[seriesName][String(normalisedValue)] = value;
return normalisedValue;
});
};

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalise function gets max value from each series & consider it as 1.0 and then calculates the normalised value & then goes onto put a reference of normalised -> denormalised value in our denormalised values map


if (isNormalised) {
// Normalize the transformed data
transformedData = transformedData.map(series => {
if (Array.isArray(series.value)) {
const seriesName = String(series?.name || '');
denormalizedSeriesValues[seriesName] = {};

return {
...series,
value: normalizeArray(series.value as number[], 10, seriesName),
};
}
return series;
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We normalise the values for each series only if we have isNormalised as true

}

const indicator = metricLabels.map(metricLabel => {
if (isNormalised) {
return {
name: metricLabel,
max: 1,
min: 0,
};
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the values are normalised we consider min & max as 0 & 1 respectively & hence we dont need to calculate the rest as they are only in case when isNormalised is not true & some column config is provided

const maxValueInControl = columnConfig?.[metricLabel]?.radarMetricMaxValue;
const minValueInControl = columnConfig?.[metricLabel]?.radarMetricMinValue;

Expand Down Expand Up @@ -259,6 +330,39 @@ export default function transformProps(
},
];

const NormalisedTooltipFormater = (
params: CallbackDataParams & {
color: string;
name: string;
value: number[];
},
) => {
const { color } = params;
const seriesName = params.name || '';
const values = params.value;

const colorDot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:5px;height:5px;background-color:${color}"></span>`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could move NormalisedTooltipFormatter to a separate .tsx file and use JSX instead of strings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes thanks ... Made a seperate tooltip component

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Echarts expects a string and renderToStaticMarkup causes a performance hit. See #33501. We should probably revert this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// Start with series name without dot
let tooltip = `<div style="font-weight:bold;margin-bottom:5px;">${seriesName || 'series0'}</div>`;
Comment thread
amaannawab923 marked this conversation as resolved.
Outdated

metricLabels.forEach((metric, index) => {
const normalizedValue = values[index];
const originalValue = getDenormalisedSeriesValue(
seriesName,
String(normalizedValue),
);
tooltip += `
<div style="display:flex;">
<div>${colorDot}${metric}:</div>
<div style="font-weight:bold;margin-left:auto;">${originalValue}</div>
</div>
`;
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we need to override the denormalised values with the normalised values in tooltip we create the tooltip from scratch with the tooltip formatter in the format that echarts expect which is html string


return tooltip;
};

const echartOptions: EChartsCoreOption = {
grid: {
...defaultGrid,
Expand All @@ -267,6 +371,9 @@ export default function transformProps(
...getDefaultTooltip(refs),
show: !inContextMenu,
trigger: 'item',
...(isNormalised && {
formatter: NormalisedTooltipFormater,
}),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this normalised tooltip formatter only when normlalised is true

},
legend: {
...getLegendProps(legendType, legendOrientation, showLegend, theme),
Expand Down
15 changes: 15 additions & 0 deletions superset-frontend/plugins/plugin-chart-echarts/src/Radar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type EchartsRadarFormData = QueryFormData &
isCircle: boolean;
numberFormat: string;
dateFormat: string;
isNormalised: boolean;
};

export enum EchartsRadarLabelType {
Expand Down Expand Up @@ -83,3 +84,17 @@ export type RadarChartTransformedProps =
BaseTransformedProps<EchartsRadarFormData> &
ContextMenuTransformedProps &
CrossFilterTransformedProps;

/**
* Represents a mapping from a normalized value (as string) to an original numeric value.
*/
interface NormalizedValueMap {
[normalized: string]: number;
}

/**
* Represents a collection of series, each containing its own NormalizedValueMap.
*/
export interface SeriesNormalizedMap {
[seriesName: string]: NormalizedValueMap;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interfaces of Series -> Normalised -> denormalised values

Loading