From 2836018b0ea414c4cabefced0f9eacf73b9e9244 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Wed, 9 Mar 2022 12:49:10 +0200 Subject: [PATCH] [Gauge] Custom color support (#126882) * Fixed case if uiState is undefined. * Fixed wrong behavior of the coloring in the percentage mode * Update src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx Co-authored-by: Stratoula Kalafateli * Update src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx Co-authored-by: Stratoula Kalafateli * Fixed bug, related to the formatting behavior while picking overrided color. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../common/types/expression_renderers.ts | 2 + .../components/gauge_component.test.tsx | 11 ++++ .../public/components/gauge_component.tsx | 62 ++++++++++++++++--- .../expression_renderers/gauge_renderer.tsx | 2 + 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/plugins/chart_expressions/expression_gauge/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_gauge/common/types/expression_renderers.ts index 9e72a0eabef65..4c2133e8572f8 100644 --- a/src/plugins/chart_expressions/expression_gauge/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_gauge/common/types/expression_renderers.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { PersistedState } from '../../../../visualizations/public'; import type { ChartsPluginSetup, PaletteRegistry } from '../../../../charts/public'; import type { IFieldFormat, SerializedFieldFormat } from '../../../../field_formats/common'; import type { GaugeExpressionProps } from './expression_functions'; @@ -16,6 +17,7 @@ export type GaugeRenderProps = GaugeExpressionProps & { formatFactory: FormatFactory; chartsThemeService: ChartsPluginSetup['theme']; paletteService: PaletteRegistry; + uiState: PersistedState; }; export interface ColorStop { diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx index c71b95a95d02c..e7e1e47ca65f2 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx @@ -79,6 +79,16 @@ const createData = ( }; }; +const mockState = new Map(); +const uiState = { + get: jest + .fn() + .mockImplementation((key, fallback) => (mockState.has(key) ? mockState.get(key) : fallback)), + set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)), + emit: jest.fn(), + setSilent: jest.fn(), +} as any; + describe('GaugeComponent', function () { let wrapperProps: GaugeRenderProps; @@ -89,6 +99,7 @@ describe('GaugeComponent', function () { args, formatFactory: formatService.deserialize, paletteService: await paletteThemeService.getPalettes(), + uiState, }; }); diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx index dfb560a33b092..9db6b81acefce 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx @@ -8,6 +8,7 @@ import React, { FC, memo, useCallback } from 'react'; import { Chart, Goal, Settings } from '@elastic/charts'; import { FormattedMessage } from '@kbn/i18n-react'; +import { FieldFormat } from '../../../../field_formats/common'; import type { CustomPaletteState, PaletteOutput } from '../../../../charts/public'; import { EmptyPlaceholder } from '../../../../charts/public'; import { isVisDimension } from '../../../../visualizations/common/utils'; @@ -183,8 +184,23 @@ const calculateRealRangeValueMax = ( return max; }; +const getPreviousSectionValue = (value: number, bands: number[]) => { + // bands value is equal to the stop. The purpose of this value is coloring the previous section, which is smaller, then the band. + // So, the smaller value should be taken. For the first element -1, for the next - middle value of the previous section. + + let prevSectionValue = value - 1; + const valueIndex = bands.indexOf(value); + const prevBand = bands[valueIndex - 1]; + const curBand = bands[valueIndex]; + if (valueIndex > 0) { + prevSectionValue = value - (curBand - prevBand) / 2; + } + + return prevSectionValue; +}; + export const GaugeComponent: FC = memo( - ({ data, args, formatFactory, paletteService, chartsThemeService }) => { + ({ data, args, uiState, formatFactory, paletteService, chartsThemeService }) => { const { shape: gaugeType, palette, @@ -230,6 +246,34 @@ export const GaugeComponent: FC = memo( [paletteService] ); + // Legacy chart was not formatting numbers, when was forming overrideColors. + // To support the behavior of the color overriding, it is required to skip all the formatting, except percent. + const overrideColor = useCallback( + (value: number, bands: number[], formatter?: FieldFormat) => { + const overrideColors = uiState?.get('vis.colors') ?? {}; + const valueIndex = bands.findIndex((band, index, allBands) => { + if (index === allBands.length - 1) { + return false; + } + + return value >= band && value < allBands[index + 1]; + }); + + if (valueIndex < 0 || valueIndex === bands.length - 1) { + return undefined; + } + const curValue = bands[valueIndex]; + const nextValue = bands[valueIndex + 1]; + + return overrideColors[ + `${formatter?.convert(curValue) ?? curValue} - ${ + formatter?.convert(nextValue) ?? nextValue + }` + ]; + }, + [uiState] + ); + const table = data; const accessors = getAccessorsFromArgs(args, table.columns); @@ -353,12 +397,16 @@ export const GaugeComponent: FC = memo( bandFillColor={ colorMode === GaugeColorModes.PALETTE ? (val) => { - // bands value is equal to the stop. The purpose of this value is coloring the previous section, which is smaller, then the band. - // So, the smaller value should be taken. For the first element -1, for the next - middle value of the previous section. - let value = val.value - 1; - const valueIndex = bands.indexOf(val.value); - if (valueIndex > 0) { - value = val.value - (bands[valueIndex] - bands[valueIndex - 1]) / 2; + const value = getPreviousSectionValue(val.value, bands); + + const overridedColor = overrideColor( + value, + args.percentageMode ? bands : args.palette?.params?.stops ?? [], + args.percentageMode ? tickFormatter : undefined + ); + + if (overridedColor) { + return overridedColor; } return args.palette diff --git a/src/plugins/chart_expressions/expression_gauge/public/expression_renderers/gauge_renderer.tsx b/src/plugins/chart_expressions/expression_gauge/public/expression_renderers/gauge_renderer.tsx index ebd2aaa6fc948..c75f60f75c004 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/expression_renderers/gauge_renderer.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/expression_renderers/gauge_renderer.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { PersistedState } from '../../../../visualizations/public'; import { ThemeServiceStart } from '../../../../../core/public'; import { KibanaThemeProvider } from '../../../../kibana_react/public'; import { ExpressionRenderDefinition } from '../../../../expressions/common/expression_renderers'; @@ -40,6 +41,7 @@ export const gaugeRenderer: ( formatFactory={getFormatService().deserialize} chartsThemeService={getThemeService()} paletteService={getPaletteService()} + uiState={handlers.uiState as PersistedState} /> ,