mirror of
https://github.com/apache/superset.git
synced 2026-04-15 06:04:48 +00:00
360 lines
9.9 KiB
TypeScript
360 lines
9.9 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import {
|
|
QueryFormMetric,
|
|
CategoricalColorNamespace,
|
|
CategoricalColorScale,
|
|
DataRecord,
|
|
getMetricLabel,
|
|
getColumnLabel,
|
|
getValueFormatter,
|
|
} from '@superset-ui/core';
|
|
import { EChartsCoreOption, GaugeSeriesOption } from 'echarts';
|
|
import { GaugeDataItemOption } from 'echarts/types/src/chart/gauge/GaugeSeries';
|
|
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
|
import range from 'lodash/range';
|
|
import { parseNumbersList } from '../utils/controls';
|
|
import {
|
|
DEFAULT_FORM_DATA as DEFAULT_GAUGE_FORM_DATA,
|
|
EchartsGaugeFormData,
|
|
AxisTickLineStyle,
|
|
GaugeChartTransformedProps,
|
|
EchartsGaugeChartProps,
|
|
} from './types';
|
|
import {
|
|
defaultGaugeSeriesOption,
|
|
INTERVAL_GAUGE_SERIES_OPTION,
|
|
OFFSETS,
|
|
FONT_SIZE_MULTIPLIERS,
|
|
} from './constants';
|
|
import { OpacityEnum } from '../constants';
|
|
import { getDefaultTooltip } from '../utils/tooltip';
|
|
import { Refs } from '../types';
|
|
import { getColtypesMapping } from '../utils/series';
|
|
|
|
const setIntervalBoundsAndColors = (
|
|
intervals: string,
|
|
intervalColorIndices: string,
|
|
colorFn: CategoricalColorScale,
|
|
normalizer: number,
|
|
): Array<[number, string]> => {
|
|
let intervalBoundsNonNormalized;
|
|
let intervalColorIndicesArray;
|
|
try {
|
|
intervalBoundsNonNormalized = parseNumbersList(intervals, ',');
|
|
intervalColorIndicesArray = parseNumbersList(intervalColorIndices, ',');
|
|
} catch (error) {
|
|
intervalBoundsNonNormalized = [] as number[];
|
|
intervalColorIndicesArray = [] as number[];
|
|
}
|
|
|
|
const intervalBounds = intervalBoundsNonNormalized.map(
|
|
bound => bound / normalizer,
|
|
);
|
|
const intervalColors = intervalColorIndicesArray.map(
|
|
ind => colorFn.colors[(ind - 1) % colorFn.colors.length],
|
|
);
|
|
|
|
return intervalBounds.map((val, idx) => {
|
|
const color = intervalColors[idx];
|
|
return [val, color || colorFn.colors[idx]];
|
|
});
|
|
};
|
|
|
|
const calculateAxisLineWidth = (
|
|
data: DataRecord[],
|
|
fontSize: number,
|
|
overlap: boolean,
|
|
): number => (overlap ? fontSize : data.length * fontSize);
|
|
|
|
const calculateMin = (data: GaugeDataItemOption[]) =>
|
|
2 * Math.min(...data.map(d => d.value as number).concat([0]));
|
|
|
|
const calculateMax = (data: GaugeDataItemOption[]) =>
|
|
2 * Math.max(...data.map(d => d.value as number).concat([0]));
|
|
|
|
export default function transformProps(
|
|
chartProps: EchartsGaugeChartProps,
|
|
): GaugeChartTransformedProps {
|
|
const {
|
|
width,
|
|
height,
|
|
formData,
|
|
queriesData,
|
|
hooks,
|
|
filterState,
|
|
theme,
|
|
emitCrossFilters,
|
|
datasource,
|
|
} = chartProps;
|
|
|
|
const gaugeSeriesOptions = defaultGaugeSeriesOption(theme);
|
|
const {
|
|
verboseMap = {},
|
|
currencyFormats = {},
|
|
columnFormats = {},
|
|
} = datasource;
|
|
const {
|
|
groupby,
|
|
metric,
|
|
minVal,
|
|
maxVal,
|
|
colorScheme,
|
|
fontSize,
|
|
numberFormat,
|
|
currencyFormat,
|
|
animation,
|
|
showProgress,
|
|
overlap,
|
|
roundCap,
|
|
showAxisTick,
|
|
showSplitLine,
|
|
splitNumber,
|
|
startAngle,
|
|
endAngle,
|
|
showPointer,
|
|
intervals,
|
|
intervalColorIndices,
|
|
valueFormatter,
|
|
sliceId,
|
|
}: EchartsGaugeFormData = { ...DEFAULT_GAUGE_FORM_DATA, ...formData };
|
|
const refs: Refs = {};
|
|
const data = (queriesData[0]?.data || []) as DataRecord[];
|
|
const coltypeMapping = getColtypesMapping(queriesData[0]);
|
|
const numberFormatter = getValueFormatter(
|
|
metric,
|
|
currencyFormats,
|
|
columnFormats,
|
|
numberFormat,
|
|
currencyFormat,
|
|
);
|
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
|
const axisLineWidth = calculateAxisLineWidth(data, fontSize, overlap);
|
|
const groupbyLabels = groupby.map(getColumnLabel);
|
|
const formatValue = (value: number) =>
|
|
valueFormatter.replace('{value}', numberFormatter(value));
|
|
const axisTickLength = FONT_SIZE_MULTIPLIERS.axisTickLength * fontSize;
|
|
const splitLineLength = FONT_SIZE_MULTIPLIERS.splitLineLength * fontSize;
|
|
const titleOffsetFromTitle =
|
|
FONT_SIZE_MULTIPLIERS.titleOffsetFromTitle * fontSize;
|
|
const detailOffsetFromTitle =
|
|
FONT_SIZE_MULTIPLIERS.detailOffsetFromTitle * fontSize;
|
|
const columnsLabelMap = new Map<string, string[]>();
|
|
|
|
const transformedData: GaugeDataItemOption[] = data.map(
|
|
(data_point, index) => {
|
|
const name = groupbyLabels
|
|
.map(column => `${verboseMap[column] || column}: ${data_point[column]}`)
|
|
.join(', ');
|
|
columnsLabelMap.set(
|
|
name,
|
|
groupbyLabels.map(col => data_point[col] as string),
|
|
);
|
|
let item: GaugeDataItemOption = {
|
|
value: data_point[getMetricLabel(metric as QueryFormMetric)] as number,
|
|
name,
|
|
itemStyle: {
|
|
color: colorFn(index, sliceId),
|
|
},
|
|
title: {
|
|
offsetCenter: [
|
|
'0%',
|
|
`${index * titleOffsetFromTitle + OFFSETS.titleFromCenter}%`,
|
|
],
|
|
fontSize,
|
|
},
|
|
detail: {
|
|
offsetCenter: [
|
|
'0%',
|
|
`${
|
|
index * titleOffsetFromTitle +
|
|
OFFSETS.titleFromCenter +
|
|
detailOffsetFromTitle
|
|
}%`,
|
|
],
|
|
fontSize: FONT_SIZE_MULTIPLIERS.detailFontSize * fontSize,
|
|
},
|
|
};
|
|
if (
|
|
filterState.selectedValues &&
|
|
!filterState.selectedValues.includes(name)
|
|
) {
|
|
item = {
|
|
...item,
|
|
itemStyle: {
|
|
color: colorFn(index, sliceId),
|
|
opacity: OpacityEnum.SemiTransparent,
|
|
},
|
|
detail: {
|
|
show: false,
|
|
},
|
|
title: {
|
|
show: false,
|
|
},
|
|
};
|
|
}
|
|
return item;
|
|
},
|
|
);
|
|
|
|
const { setDataMask = () => {}, onContextMenu } = hooks;
|
|
|
|
const min = minVal ?? calculateMin(transformedData);
|
|
const max = maxVal ?? calculateMax(transformedData);
|
|
const axisLabels = range(min, max, (max - min) / splitNumber);
|
|
const axisLabelLength = Math.max(
|
|
...axisLabels.map(label => numberFormatter(label).length).concat([1]),
|
|
);
|
|
const normalizer = max;
|
|
const intervalBoundsAndColors = setIntervalBoundsAndColors(
|
|
intervals,
|
|
intervalColorIndices,
|
|
colorFn,
|
|
normalizer,
|
|
);
|
|
const splitLineDistance =
|
|
axisLineWidth + splitLineLength + OFFSETS.ticksFromLine;
|
|
const axisLabelDistance =
|
|
FONT_SIZE_MULTIPLIERS.axisLabelDistance *
|
|
fontSize *
|
|
FONT_SIZE_MULTIPLIERS.axisLabelLength *
|
|
axisLabelLength +
|
|
(showSplitLine ? splitLineLength : 0) +
|
|
(showAxisTick ? axisTickLength : 0) +
|
|
OFFSETS.ticksFromLine -
|
|
axisLineWidth;
|
|
const axisTickDistance =
|
|
axisLineWidth + axisTickLength + OFFSETS.ticksFromLine;
|
|
|
|
const progress = {
|
|
show: showProgress,
|
|
overlap,
|
|
roundCap,
|
|
width: fontSize,
|
|
};
|
|
const splitLine = {
|
|
show: showSplitLine,
|
|
distance: -splitLineDistance,
|
|
length: splitLineLength,
|
|
lineStyle: {
|
|
width: FONT_SIZE_MULTIPLIERS.splitLineWidth * fontSize,
|
|
color: gaugeSeriesOptions.splitLine?.lineStyle?.color,
|
|
},
|
|
};
|
|
const axisLine = {
|
|
roundCap,
|
|
lineStyle: {
|
|
width: axisLineWidth,
|
|
color: gaugeSeriesOptions.axisLine?.lineStyle?.color,
|
|
},
|
|
};
|
|
const axisLabel = {
|
|
distance: -axisLabelDistance,
|
|
fontSize,
|
|
formatter: numberFormatter,
|
|
color: gaugeSeriesOptions.axisLabel?.color,
|
|
};
|
|
const axisTick = {
|
|
show: showAxisTick,
|
|
distance: -axisTickDistance,
|
|
length: axisTickLength,
|
|
lineStyle: gaugeSeriesOptions.axisTick?.lineStyle as AxisTickLineStyle,
|
|
};
|
|
const detail = {
|
|
valueAnimation: animation,
|
|
formatter: (value: number) => formatValue(value),
|
|
color: gaugeSeriesOptions.detail?.color,
|
|
};
|
|
const tooltip = {
|
|
...getDefaultTooltip(refs),
|
|
formatter: (params: CallbackDataParams) => {
|
|
const { name, value } = params;
|
|
return `${name} : ${formatValue(value as number)}`;
|
|
},
|
|
};
|
|
|
|
let pointer;
|
|
if (intervalBoundsAndColors.length) {
|
|
splitLine.lineStyle.color =
|
|
INTERVAL_GAUGE_SERIES_OPTION.splitLine?.lineStyle?.color;
|
|
axisTick.lineStyle.color = INTERVAL_GAUGE_SERIES_OPTION?.axisTick?.lineStyle
|
|
?.color as string;
|
|
axisLabel.color = INTERVAL_GAUGE_SERIES_OPTION.axisLabel?.color;
|
|
axisLine.lineStyle.color = intervalBoundsAndColors;
|
|
pointer = {
|
|
show: showPointer,
|
|
showAbove: false,
|
|
itemStyle: INTERVAL_GAUGE_SERIES_OPTION.pointer?.itemStyle,
|
|
};
|
|
} else {
|
|
pointer = {
|
|
show: showPointer,
|
|
showAbove: false,
|
|
};
|
|
}
|
|
|
|
const series: GaugeSeriesOption[] = [
|
|
{
|
|
type: 'gauge',
|
|
startAngle,
|
|
endAngle,
|
|
min,
|
|
max,
|
|
progress,
|
|
animation,
|
|
axisLine: axisLine as GaugeSeriesOption['axisLine'],
|
|
splitLine,
|
|
splitNumber,
|
|
axisLabel,
|
|
axisTick,
|
|
pointer,
|
|
detail,
|
|
// @ts-ignore
|
|
tooltip,
|
|
radius:
|
|
Math.min(width, height) / 2 - axisLabelDistance - axisTickDistance,
|
|
center: ['50%', '55%'],
|
|
data: transformedData,
|
|
},
|
|
];
|
|
|
|
const echartOptions: EChartsCoreOption = {
|
|
tooltip: {
|
|
...getDefaultTooltip(refs),
|
|
trigger: 'item',
|
|
},
|
|
series,
|
|
};
|
|
|
|
return {
|
|
formData,
|
|
width,
|
|
height,
|
|
echartOptions,
|
|
setDataMask,
|
|
emitCrossFilters,
|
|
labelMap: Object.fromEntries(columnsLabelMap),
|
|
groupby,
|
|
selectedValues: filterState.selectedValues || [],
|
|
onContextMenu,
|
|
refs,
|
|
coltypeMapping,
|
|
};
|
|
}
|