mirror of https://github.com/apache/superset.git
feat: conditional coloring for big number chart (#23064)
Co-authored-by: Gerold Busch <gerold.busch@valtech.com>
This commit is contained in:
parent
fd3030fc14
commit
61d8a0bd12
|
@ -62,6 +62,7 @@ export const getColorFunction = (
|
|||
colorScheme,
|
||||
}: ConditionalFormattingConfig,
|
||||
columnValues: number[],
|
||||
alpha?: boolean,
|
||||
) => {
|
||||
let minOpacity = MIN_OPACITY_BOUNDED;
|
||||
const maxOpacity = MAX_OPACITY;
|
||||
|
@ -176,10 +177,13 @@ export const getColorFunction = (
|
|||
const compareResult = comparatorFunction(value, columnValues);
|
||||
if (compareResult === false) return undefined;
|
||||
const { cutoffValue, extremeValue } = compareResult;
|
||||
return addAlpha(
|
||||
colorScheme,
|
||||
getOpacity(value, cutoffValue, extremeValue, minOpacity, maxOpacity),
|
||||
);
|
||||
if (alpha === undefined || alpha) {
|
||||
return addAlpha(
|
||||
colorScheme,
|
||||
getOpacity(value, cutoffValue, extremeValue, minOpacity, maxOpacity),
|
||||
);
|
||||
}
|
||||
return colorScheme;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -187,6 +191,7 @@ export const getColorFormatters = memoizeOne(
|
|||
(
|
||||
columnConfig: ConditionalFormattingConfig[] | undefined,
|
||||
data: DataRecord[],
|
||||
alpha?: boolean,
|
||||
) =>
|
||||
columnConfig?.reduce(
|
||||
(acc: ColorFormatters, config: ConditionalFormattingConfig) => {
|
||||
|
@ -204,6 +209,7 @@ export const getColorFormatters = memoizeOne(
|
|||
getColorFromValue: getColorFunction(
|
||||
config,
|
||||
data.map(row => row[config.column!] as number),
|
||||
alpha,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -188,6 +188,25 @@ describe('getColorFunction()', () => {
|
|||
expect(colorFunction(150)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('getColorFunction BETWEEN_OR_EQUAL without opacity', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
operator: COMPARATOR.BETWEEN_OR_EQUAL,
|
||||
targetValueLeft: 50,
|
||||
targetValueRight: 100,
|
||||
colorScheme: '#FF0000',
|
||||
column: 'count',
|
||||
},
|
||||
countValues,
|
||||
false,
|
||||
);
|
||||
expect(colorFunction(25)).toBeUndefined();
|
||||
expect(colorFunction(50)).toEqual('#FF0000');
|
||||
expect(colorFunction(75)).toEqual('#FF0000');
|
||||
expect(colorFunction(100)).toEqual('#FF0000');
|
||||
expect(colorFunction(125)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('getColorFunction BETWEEN_OR_LEFT_EQUAL', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { smartDateFormatter, t } from '@superset-ui/core';
|
||||
import { GenericDataType, smartDateFormatter, t } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
D3_FORMAT_DOCS,
|
||||
D3_TIME_FORMAT_OPTIONS,
|
||||
Dataset,
|
||||
getStandardizedControls,
|
||||
sections,
|
||||
} from '@superset-ui/chart-controls';
|
||||
|
@ -89,6 +90,45 @@ export default {
|
|||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'conditional_formatting',
|
||||
config: {
|
||||
type: 'ConditionalFormattingControl',
|
||||
renderTrigger: true,
|
||||
label: t('Conditional Formatting'),
|
||||
description: t('Apply conditional color formatting to metric'),
|
||||
shouldMapStateToProps() {
|
||||
return true;
|
||||
},
|
||||
mapStateToProps(explore, _, chart) {
|
||||
const verboseMap = explore?.datasource?.hasOwnProperty(
|
||||
'verbose_map',
|
||||
)
|
||||
? (explore?.datasource as Dataset)?.verbose_map
|
||||
: explore?.datasource?.columns ?? {};
|
||||
const { colnames, coltypes } =
|
||||
chart?.queriesResponse?.[0] ?? {};
|
||||
const numericColumns =
|
||||
Array.isArray(colnames) && Array.isArray(coltypes)
|
||||
? colnames
|
||||
.filter(
|
||||
(colname: string, index: number) =>
|
||||
coltypes[index] === GenericDataType.NUMERIC,
|
||||
)
|
||||
.map(colname => ({
|
||||
value: colname,
|
||||
label: verboseMap[colname] ?? colname,
|
||||
}))
|
||||
: [];
|
||||
return {
|
||||
columnOptions: numericColumns,
|
||||
verboseMap,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
ColorFormatters,
|
||||
getColorFormatters,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import {
|
||||
getNumberFormatter,
|
||||
GenericDataType,
|
||||
|
@ -40,6 +44,7 @@ export default function transformProps(
|
|||
forceTimestampFormatting,
|
||||
timeFormat,
|
||||
yAxisFormat,
|
||||
conditionalFormatting,
|
||||
} = formData;
|
||||
const refs: Refs = {};
|
||||
const { data = [], coltypes = [] } = queriesData[0];
|
||||
|
@ -71,6 +76,12 @@ export default function transformProps(
|
|||
|
||||
const { onContextMenu } = hooks;
|
||||
|
||||
const defaultColorFormatters = [] as ColorFormatters;
|
||||
|
||||
const colorThresholdFormatters =
|
||||
getColorFormatters(conditionalFormatting, data, false) ??
|
||||
defaultColorFormatters;
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
|
@ -81,5 +92,6 @@ export default function transformProps(
|
|||
subheader: formattedSubheader,
|
||||
onContextMenu,
|
||||
refs,
|
||||
colorThresholdFormatters,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -128,10 +128,29 @@ class BigNumberVis extends React.PureComponent<BigNumberVizProps> {
|
|||
}
|
||||
|
||||
renderHeader(maxHeight: number) {
|
||||
const { bigNumber, headerFormatter, width } = this.props;
|
||||
const { bigNumber, headerFormatter, width, colorThresholdFormatters } =
|
||||
this.props;
|
||||
// @ts-ignore
|
||||
const text = bigNumber === null ? t('No data') : headerFormatter(bigNumber);
|
||||
|
||||
const hasThresholdColorFormatter =
|
||||
Array.isArray(colorThresholdFormatters) &&
|
||||
colorThresholdFormatters.length > 0;
|
||||
|
||||
let numberColor;
|
||||
if (hasThresholdColorFormatter) {
|
||||
colorThresholdFormatters!.forEach(formatter => {
|
||||
const formatterResult = bigNumber
|
||||
? formatter.getColorFromValue(bigNumber as number)
|
||||
: false;
|
||||
if (formatterResult) {
|
||||
numberColor = formatterResult;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
numberColor = 'black';
|
||||
}
|
||||
|
||||
const container = this.createTemporaryContainer();
|
||||
document.body.append(container);
|
||||
const fontSize = computeMaxFontSize({
|
||||
|
@ -156,6 +175,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVizProps> {
|
|||
style={{
|
||||
fontSize,
|
||||
height: maxHeight,
|
||||
color: numberColor,
|
||||
}}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
QueryFormMetric,
|
||||
TimeFormatter,
|
||||
} from '@superset-ui/core';
|
||||
import { ColorFormatters } from '@superset-ui/chart-controls';
|
||||
import { BaseChartProps, Refs } from '../types';
|
||||
|
||||
export interface BigNumberDatum {
|
||||
|
@ -94,4 +95,5 @@ export type BigNumberVizProps = {
|
|||
xValueFormatter?: TimeFormatter;
|
||||
formData?: BigNumberWithTrendlineFormData;
|
||||
refs: Refs;
|
||||
colorThresholdFormatters?: ColorFormatters;
|
||||
};
|
||||
|
|
|
@ -39,9 +39,12 @@ const JustifyEnd = styled.div`
|
|||
`;
|
||||
|
||||
const colorSchemeOptions = (theme: SupersetTheme) => [
|
||||
{ value: theme.colors.success.light1, label: t('green') },
|
||||
{ value: theme.colors.alert.light1, label: t('yellow') },
|
||||
{ value: theme.colors.error.light1, label: t('red') },
|
||||
{ value: theme.colors.success.light1, label: t('success') },
|
||||
{ value: theme.colors.alert.light1, label: t('alert') },
|
||||
{ value: theme.colors.error.light1, label: t('error') },
|
||||
{ value: theme.colors.success.dark1, label: t('success dark') },
|
||||
{ value: theme.colors.alert.dark1, label: t('alert dark') },
|
||||
{ value: theme.colors.error.dark1, label: t('error dark') },
|
||||
];
|
||||
|
||||
const operatorOptions = [
|
||||
|
|
Loading…
Reference in New Issue