Skip to content

Commit

Permalink
[TSVB] Integrates the color service (elastic#93749)
Browse files Browse the repository at this point in the history
* [TSVB] Integrates the color service

* Fix i18n failure

* Sync colors :)

* Fix unit tests

* Apply the multiple colors also for gauge

* Fix

* More unit tests

* Cleanup

* Be backwards compatible

* Fetch palettesService on vis renderer

* Fix eslint

* Fix jest test

* Fix color mapping for empty labels

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
stratoula and kibanamachine committed Mar 23, 2021
1 parent 432649b commit d1b6c8f
Show file tree
Hide file tree
Showing 23 changed files with 556 additions and 231 deletions.
7 changes: 6 additions & 1 deletion src/plugins/vis_type_timeseries/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type PanelSchema = TypeOf<typeof panel>;
export type VisPayload = TypeOf<typeof visPayloadSchema>;
export type FieldObject = TypeOf<typeof fieldObject>;

interface PanelData {
export interface PanelData {
id: string;
label: string;
data: Array<[number, number]>;
Expand Down Expand Up @@ -57,3 +57,8 @@ export interface SanitizedFieldType {
type: string;
label?: string;
}

export enum PALETTES {
GRADIENT = 'gradient',
RAINBOW = 'rainbow',
}
4 changes: 4 additions & 0 deletions src/plugins/vis_type_timeseries/common/vis_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ export const seriesItems = schema.object({
series_interval: stringOptionalNullable,
series_drop_last_bucket: numberIntegerOptional,
split_color_mode: stringOptionalNullable,
palette: schema.object({
type: stringRequired,
name: stringRequired,
}),
split_filters: schema.maybe(schema.arrayOf(splitFiltersItems)),
split_mode: stringRequired,
stacked: stringRequired,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { mountWithIntl } from '@kbn/test/jest';
import { ReactWrapper } from 'enzyme';
import { PalettePicker, PalettePickerProps } from './palette_picker';
import { chartPluginMock } from '../../../../charts/public/mocks';
import { EuiColorPalettePicker } from '@elastic/eui';
import { PALETTES } from '../../../common/types';

describe('PalettePicker', function () {
let props: PalettePickerProps;
let component: ReactWrapper<PalettePickerProps>;

beforeAll(() => {
props = {
palettes: chartPluginMock.createPaletteRegistry(),
activePalette: {
type: 'palette',
name: 'kibana_palette',
},
setPalette: jest.fn(),
color: '#68BC00',
};
});

it('renders the EuiPalettePicker', () => {
component = mountWithIntl(<PalettePicker {...props} />);
expect(component.find(EuiColorPalettePicker).length).toBe(1);
});

it('renders the default palette if not activePalette is given', function () {
const { activePalette, ...newProps } = props;
component = mountWithIntl(<PalettePicker {...newProps} />);
const palettePicker = component.find(EuiColorPalettePicker);
expect(palettePicker.props().valueOfSelected).toBe('default');
});

it('renders the activePalette palette if given', function () {
component = mountWithIntl(<PalettePicker {...props} />);
const palettePicker = component.find(EuiColorPalettePicker);
expect(palettePicker.props().valueOfSelected).toBe('kibana_palette');
});

it('renders two additional palettes, rainbow and gradient', function () {
component = mountWithIntl(<PalettePicker {...props} />);
const palettePicker = component.find(EuiColorPalettePicker);
expect(palettePicker.props().palettes).toEqual(
expect.arrayContaining([
expect.objectContaining({
value: PALETTES.RAINBOW,
}),
expect.objectContaining({
value: PALETTES.GRADIENT,
}),
])
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
import { EuiColorPalettePicker } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { rainbowColors } from '../lib/rainbow_colors';
import { computeGradientFinalColor } from '../lib/compute_gradient_final_color';
import { PALETTES } from '../../../common/types';

export interface PalettePickerProps {
activePalette?: PaletteOutput;
palettes: PaletteRegistry;
setPalette: (value: PaletteOutput) => void;
color: string;
}

export function PalettePicker({ activePalette, palettes, setPalette, color }: PalettePickerProps) {
const finalGradientColor = computeGradientFinalColor(color);

return (
<EuiColorPalettePicker
fullWidth
data-test-subj="visEditorPalettePicker"
compressed
palettes={[
...palettes
.getAll()
.filter(({ internal }) => !internal)
.map(({ id, title, getColors }) => {
return {
value: id,
title,
type: 'fixed' as const,
palette: getColors(10),
};
}),
{
value: PALETTES.GRADIENT,
title: i18n.translate('visTypeTimeseries.timeSeries.gradientLabel', {
defaultMessage: 'Gradient',
}),
type: 'fixed',
palette: palettes
.get('custom')
.getColors(10, { colors: [color, finalGradientColor], gradient: true }),
},
{
value: PALETTES.RAINBOW,
title: i18n.translate('visTypeTimeseries.timeSeries.rainbowLabel', {
defaultMessage: 'Rainbow',
}),
type: 'fixed',
palette: palettes
.get('custom')
.getColors(10, { colors: rainbowColors.slice(0, 10), gradient: false }),
},
]}
onChange={(newPalette) => {
if (newPalette === PALETTES.RAINBOW) {
setPalette({
type: 'palette',
name: PALETTES.RAINBOW,
params: {
colors: rainbowColors,
gradient: false,
},
});
} else if (newPalette === PALETTES.GRADIENT) {
setPalette({
type: 'palette',
name: PALETTES.GRADIENT,
params: {
colors: [color, finalGradientColor],
gradient: true,
},
});
} else {
setPalette({
type: 'palette',
name: newPalette,
});
}
}}
valueOfSelected={activePalette?.name || 'default'}
selectionDisplay={'palette'}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import React, { useCallback, useEffect } from 'react';
import { IUiSettingsClient } from 'src/core/public';
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { PersistedState } from 'src/plugins/visualizations/public';
import { PaletteRegistry } from 'src/plugins/charts/public';

// @ts-expect-error
import { ErrorComponent } from './error';
Expand All @@ -25,6 +26,8 @@ interface TimeseriesVisualizationProps {
model: TimeseriesVisParams;
visData: TimeseriesVisData;
uiState: PersistedState;
syncColors: boolean;
palettesService: PaletteRegistry;
}

function TimeseriesVisualization({
Expand All @@ -34,6 +37,8 @@ function TimeseriesVisualization({
handlers,
uiState,
getConfig,
syncColors,
palettesService,
}: TimeseriesVisualizationProps) {
const onBrush = useCallback(
(gte: string, lte: string) => {
Expand Down Expand Up @@ -91,6 +96,8 @@ function TimeseriesVisualization({
uiState={uiState}
onBrush={onBrush}
onUiState={handleUiState}
syncColors={syncColors}
palettesService={palettesService}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, { lazy } from 'react';

import { IUiSettingsClient } from 'src/core/public';
import { PersistedState } from 'src/plugins/visualizations/public';
import { PaletteRegistry } from 'src/plugins/charts/public';

import { TimeseriesVisParams } from '../../../types';
import { TimeseriesVisData } from '../../../../common/types';
Expand Down Expand Up @@ -54,4 +55,6 @@ export interface TimeseriesVisProps {
uiState: PersistedState;
visData: TimeseriesVisData;
getConfig: IUiSettingsClient['get'];
syncColors: boolean;
palettesService: PaletteRegistry;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import PropTypes from 'prop-types';
import React from 'react';
import React, { useState, useEffect } from 'react';
import { DataFormatPicker } from '../../data_format_picker';
import { createSelectHandler } from '../../lib/create_select_handler';
import { YesNo } from '../../yes_no';
Expand All @@ -28,7 +28,8 @@ import {
} from '@elastic/eui';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import { SeriesConfigQueryBarWithIgnoreGlobalFilter } from '../../series_config_query_bar_with_ignore_global_filter';

import { PalettePicker } from '../../palette_picker';
import { getChartsSetup } from '../../../../services';
import { isPercentDisabled } from '../../lib/stacked';
import { STACKED_OPTIONS } from '../../../visualizations/constants/chart';

Expand All @@ -41,7 +42,6 @@ export const TimeseriesConfig = injectI18n(function (props) {
point_size: '',
value_template: '{{value}}',
offset_time: '',
split_color_mode: 'kibana',
axis_min: '',
axis_max: '',
stacked: STACKED_OPTIONS.NONE,
Expand Down Expand Up @@ -124,33 +124,23 @@ export const TimeseriesConfig = injectI18n(function (props) {
const selectedChartTypeOption = chartTypeOptions.find((option) => {
return model.chart_type === option.value;
});
const { palettes } = getChartsSetup();
const [palettesRegistry, setPalettesRegistry] = useState(null);

const splitColorOptions = [
{
label: intl.formatMessage({
id: 'visTypeTimeseries.timeSeries.defaultPaletteLabel',
defaultMessage: 'Default palette',
}),
value: 'kibana',
},
{
label: intl.formatMessage({
id: 'visTypeTimeseries.timeSeries.rainbowLabel',
defaultMessage: 'Rainbow',
}),
value: 'rainbow',
},
{
label: intl.formatMessage({
id: 'visTypeTimeseries.timeSeries.gradientLabel',
defaultMessage: 'Gradient',
}),
value: 'gradient',
},
];
const selectedSplitColorOption = splitColorOptions.find((option) => {
return model.split_color_mode === option.value;
});
useEffect(() => {
const fetchPalettes = async () => {
const palettesService = await palettes.getPalettes();
setPalettesRegistry(palettesService);
};
fetchPalettes();
}, [palettes]);

const handlePaletteChange = (val) => {
props.onChange({
split_color_mode: null,
palette: val,
});
};

let type;

Expand Down Expand Up @@ -342,6 +332,14 @@ export const TimeseriesConfig = injectI18n(function (props) {
? props.model.series_index_pattern
: props.indexPatternForQuery;

const initialPalette = {
...model.palette,
name:
model.split_color_mode === 'kibana'
? 'kibana_palette'
: model.split_color_mode || model.palette.name,
};

return (
<div className="tvbAggRow">
<EuiFlexGroup gutterSize="s">
Expand Down Expand Up @@ -420,25 +418,26 @@ export const TimeseriesConfig = injectI18n(function (props) {
<EuiSpacer size="s" />
<YesNo value={model.hide_in_legend} name="hide_in_legend" onChange={props.onChange} />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiFormRow
id={htmlId('splitColor')}
label={
<FormattedMessage
id="visTypeTimeseries.timeSeries.splitColorThemeLabel"
defaultMessage="Split color theme"
{palettesRegistry && (
<EuiFlexItem grow={true}>
<EuiFormRow
id={htmlId('splitColor')}
label={
<FormattedMessage
id="visTypeTimeseries.timeSeries.splitColorThemeLabel"
defaultMessage="Split color theme"
/>
}
>
<PalettePicker
palettes={palettesRegistry}
activePalette={initialPalette}
setPalette={handlePaletteChange}
color={model.color}
/>
}
>
<EuiComboBox
isClearable={false}
options={splitColorOptions}
selectedOptions={selectedSplitColorOption ? [selectedSplitColorOption] : []}
onChange={handleSelectChange('split_color_mode')}
singleSelection={{ asPlainText: true }}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFormRow>
</EuiFlexItem>
)}
</EuiFlexGroup>

<EuiHorizontalRule margin="s" />
Expand Down
Loading

0 comments on commit d1b6c8f

Please sign in to comment.