[Lens] Configurable color syncing (#86180)

This commit is contained in:
Joe Reuter 2020-12-22 17:35:27 +01:00 committed by GitHub
parent 9c74a1090e
commit 57a72a78f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 574 additions and 560 deletions

View file

@ -17,5 +17,6 @@ export declare type EmbeddableInput = {
disabledActions?: string[]; disabledActions?: string[];
disableTriggers?: boolean; disableTriggers?: boolean;
searchSessionId?: string; searchSessionId?: string;
syncColors?: boolean;
}; };
``` ```

View file

@ -9,7 +9,7 @@ Constructs a new instance of the `ExpressionRenderHandler` class
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams); constructor(element: HTMLElement, { onRenderError, renderMode, syncColors, hasCompatibleActions, }?: ExpressionRenderHandlerParams);
``` ```
## Parameters ## Parameters
@ -17,5 +17,5 @@ constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActi
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| element | <code>HTMLElement</code> | | | element | <code>HTMLElement</code> | |
| { onRenderError, renderMode, hasCompatibleActions, } | <code>ExpressionRenderHandlerParams</code> | | | { onRenderError, renderMode, syncColors, hasCompatibleActions, } | <code>ExpressionRenderHandlerParams</code> | |

View file

@ -14,7 +14,7 @@ export declare class ExpressionRenderHandler
| Constructor | Modifiers | Description | | Constructor | Modifiers | Description |
| --- | --- | --- | | --- | --- | --- |
| [(constructor)(element, { onRenderError, renderMode, hasCompatibleActions, })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the <code>ExpressionRenderHandler</code> class | | [(constructor)(element, { onRenderError, renderMode, syncColors, hasCompatibleActions, })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the <code>ExpressionRenderHandler</code> class |
## Properties ## Properties

View file

@ -25,6 +25,7 @@ export interface IExpressionLoaderParams
| [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | <code>RenderMode</code> | | | [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | <code>RenderMode</code> | |
| [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | <code>SerializableState</code> | | | [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | <code>SerializableState</code> | |
| [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | <code>string</code> | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | <code>string</code> | |
| [syncColors](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md) | <code>boolean</code> | |
| [uiState](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.uistate.md) | <code>unknown</code> | | | [uiState](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.uistate.md) | <code>unknown</code> | |
| [variables](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.variables.md) | <code>Record&lt;string, any&gt;</code> | | | [variables](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.variables.md) | <code>Record&lt;string, any&gt;</code> | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) &gt; [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) &gt; [syncColors](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md)
## IExpressionLoaderParams.syncColors property
<b>Signature:</b>
```typescript
syncColors?: boolean;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) &gt; [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md) &gt; [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md)
## IInterpreterRenderHandlers.isSyncColorsEnabled property
<b>Signature:</b>
```typescript
isSyncColorsEnabled: () => boolean;
```

View file

@ -18,6 +18,7 @@ export interface IInterpreterRenderHandlers
| [event](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.event.md) | <code>(event: any) =&gt; void</code> | | | [event](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.event.md) | <code>(event: any) =&gt; void</code> | |
| [getRenderMode](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.getrendermode.md) | <code>() =&gt; RenderMode</code> | | | [getRenderMode](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.getrendermode.md) | <code>() =&gt; RenderMode</code> | |
| [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | <code>(event: any) =&gt; Promise&lt;boolean&gt;</code> | | | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | <code>(event: any) =&gt; Promise&lt;boolean&gt;</code> | |
| [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.issynccolorsenabled.md) | <code>() =&gt; boolean</code> | |
| [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | <code>(fn: () =&gt; void) =&gt; void</code> | | | [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | <code>(fn: () =&gt; void) =&gt; void</code> | |
| [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | <code>() =&gt; void</code> | | | [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | <code>() =&gt; void</code> | |
| [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | <code>unknown</code> | This uiState interface is actually <code>PersistedState</code> from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | | [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | <code>unknown</code> | This uiState interface is actually <code>PersistedState</code> from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) &gt; [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md) &gt; [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md)
## IInterpreterRenderHandlers.isSyncColorsEnabled property
<b>Signature:</b>
```typescript
isSyncColorsEnabled: () => boolean;
```

View file

@ -18,6 +18,7 @@ export interface IInterpreterRenderHandlers
| [event](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.event.md) | <code>(event: any) =&gt; void</code> | | | [event](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.event.md) | <code>(event: any) =&gt; void</code> | |
| [getRenderMode](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.getrendermode.md) | <code>() =&gt; RenderMode</code> | | | [getRenderMode](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.getrendermode.md) | <code>() =&gt; RenderMode</code> | |
| [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | <code>(event: any) =&gt; Promise&lt;boolean&gt;</code> | | | [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | <code>(event: any) =&gt; Promise&lt;boolean&gt;</code> | |
| [isSyncColorsEnabled](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.issynccolorsenabled.md) | <code>() =&gt; boolean</code> | |
| [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | <code>(fn: () =&gt; void) =&gt; void</code> | | | [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | <code>(fn: () =&gt; void) =&gt; void</code> | |
| [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | <code>() =&gt; void</code> | | | [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | <code>() =&gt; void</code> | |
| [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | <code>unknown</code> | This uiState interface is actually <code>PersistedState</code> from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | | [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | <code>unknown</code> | This uiState interface is actually <code>PersistedState</code> from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. |

View file

@ -81,6 +81,21 @@ Put the dashboard in *Edit* mode, then use the following options:
* To delete, open the panel menu, then select *Delete from dashboard*. When you delete a panel from the dashboard, the * To delete, open the panel menu, then select *Delete from dashboard*. When you delete a panel from the dashboard, the
visualization or saved search from the panel is still available in Kibana. visualization or saved search from the panel is still available in Kibana.
[float]
[[sync-colors]]
=== Synchronize colors
By default, dashboard panels that share a non-gradient based color palette will synchronize their color assignment to improve readability.
Color assignment is based on the series name, and the total number of colors is based on the number of unique series names.
The color synchronizing logic can make the dashboard less readable when there are too many unique series names. It is possible to disable the synchronization behavior:
. Put the dashboard in *Edit* mode.
. Click the "Options" button in the top navigation bar.
. Disable "Sync color palettes across panels".
[float] [float]
[[clone-panels]] [[clone-panels]]
=== Clone panels === Clone panels

View file

@ -37,15 +37,15 @@ export class MappedColors {
private _mapping: any; private _mapping: any;
constructor( constructor(
private uiSettings: CoreSetup['uiSettings'], private uiSettings?: CoreSetup['uiSettings'],
private colorPaletteFn: (num: number) => string[] = createColorPalette private colorPaletteFn: (num: number) => string[] = createColorPalette
) { ) {
this._oldMap = {}; this._oldMap = {};
this._mapping = {}; this._mapping = {};
} }
private getConfigColorMapping() { private getConfigColorMapping(): Record<string, string> {
return _.mapValues(this.uiSettings.get(COLOR_MAPPING_SETTING), standardizeColor); return _.mapValues(this.uiSettings?.get(COLOR_MAPPING_SETTING) || {}, standardizeColor);
} }
public get oldMap(): any { public get oldMap(): any {

View file

@ -18,9 +18,11 @@
*/ */
import { coreMock } from '../../../../../core/public/mocks'; import { coreMock } from '../../../../../core/public/mocks';
import { createColorPalette as createLegacyColorPalette } from '../../../../../../src/plugins/charts/public';
import { PaletteDefinition } from './types'; import { PaletteDefinition } from './types';
import { buildPalettes } from './palettes'; import { buildPalettes } from './palettes';
import { colorsServiceMock } from '../legacy_colors/mock'; import { colorsServiceMock } from '../legacy_colors/mock';
import { euiPaletteColorBlind, euiPaletteColorBlindBehindText } from '@elastic/eui';
describe('palettes', () => { describe('palettes', () => {
const palettes: Record<string, PaletteDefinition> = buildPalettes( const palettes: Record<string, PaletteDefinition> = buildPalettes(
@ -28,6 +30,7 @@ describe('palettes', () => {
colorsServiceMock colorsServiceMock
); );
describe('default palette', () => { describe('default palette', () => {
describe('syncColors: false', () => {
it('should return different colors based on behind text flag', () => { it('should return different colors based on behind text flag', () => {
const palette = palettes.default; const palette = palettes.default;
@ -104,6 +107,183 @@ describe('palettes', () => {
}); });
}); });
describe('syncColors: true', () => {
it('should return different colors based on behind text flag', () => {
const palette = palettes.default;
const color1 = palette.getColor(
[
{
name: 'abc',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
],
{
syncColors: true,
}
);
const color2 = palette.getColor(
[
{
name: 'abc',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
],
{
behindText: true,
syncColors: true,
}
);
expect(color1).not.toEqual(color2);
});
it('should return different colors for different keys', () => {
const palette = palettes.default;
const color1 = palette.getColor(
[
{
name: 'abc',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
],
{
syncColors: true,
}
);
const color2 = palette.getColor(
[
{
name: 'def',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
],
{
syncColors: true,
}
);
expect(color1).not.toEqual(color2);
});
it('should return the same color for the same key, irregardless of rank', () => {
const palette = palettes.default;
const color1 = palette.getColor(
[
{
name: 'hij',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
],
{
syncColors: true,
}
);
const color2 = palette.getColor(
[
{
name: 'hij',
rankAtDepth: 5,
totalSeriesAtDepth: 5,
},
],
{
syncColors: true,
}
);
expect(color1).toEqual(color2);
});
it('should return the same color for different positions on outer series layers', () => {
const palette = palettes.default;
const color1 = palette.getColor(
[
{
name: 'klm',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
{
name: 'def',
rankAtDepth: 0,
totalSeriesAtDepth: 2,
},
],
{
syncColors: true,
}
);
const color2 = palette.getColor(
[
{
name: 'klm',
rankAtDepth: 3,
totalSeriesAtDepth: 5,
},
{
name: 'ghj',
rankAtDepth: 1,
totalSeriesAtDepth: 1,
},
],
{
syncColors: true,
}
);
expect(color1).toEqual(color2);
});
it('should return the same index of the behind text palette for same key', () => {
const palette = palettes.default;
const color1 = palette.getColor(
[
{
name: 'klm',
rankAtDepth: 0,
totalSeriesAtDepth: 5,
},
{
name: 'def',
rankAtDepth: 0,
totalSeriesAtDepth: 2,
},
],
{
syncColors: true,
}
);
const color2 = palette.getColor(
[
{
name: 'klm',
rankAtDepth: 3,
totalSeriesAtDepth: 5,
},
{
name: 'ghj',
rankAtDepth: 1,
totalSeriesAtDepth: 1,
},
],
{
syncColors: true,
behindText: true,
}
);
const color1Index = euiPaletteColorBlind({ rotations: 2 }).indexOf(color1!);
const color2Index = euiPaletteColorBlindBehindText({ rotations: 2 }).indexOf(color2!);
expect(color1Index).toEqual(color2Index);
});
});
});
describe('gradient palette', () => { describe('gradient palette', () => {
const palette = palettes.warm; const palette = palettes.warm;
@ -136,20 +316,67 @@ describe('palettes', () => {
(colorsServiceMock.mappedColors.get as jest.Mock).mockClear(); (colorsServiceMock.mappedColors.get as jest.Mock).mockClear();
}); });
it('should query legacy color service', () => { describe('syncColors: false', () => {
palette.getColor([ it('should not query legacy color service', () => {
palette.getColor(
[
{ {
name: 'abc', name: 'abc',
rankAtDepth: 0, rankAtDepth: 0,
totalSeriesAtDepth: 10, totalSeriesAtDepth: 10,
}, },
]); ],
{
syncColors: false,
}
);
expect(colorsServiceMock.mappedColors.mapKeys).not.toHaveBeenCalled();
expect(colorsServiceMock.mappedColors.get).not.toHaveBeenCalled();
});
it('should return a color from the legacy palette based on position of first series', () => {
const result = palette.getColor(
[
{
name: 'abc',
rankAtDepth: 2,
totalSeriesAtDepth: 10,
},
{
name: 'def',
rankAtDepth: 0,
totalSeriesAtDepth: 10,
},
],
{
syncColors: false,
}
);
expect(result).toEqual(createLegacyColorPalette(20)[2]);
});
});
describe('syncColors: true', () => {
it('should query legacy color service', () => {
palette.getColor(
[
{
name: 'abc',
rankAtDepth: 0,
totalSeriesAtDepth: 10,
},
],
{
syncColors: true,
}
);
expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']);
expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc');
}); });
it('should always use root series', () => { it('should always use root series', () => {
palette.getColor([ palette.getColor(
[
{ {
name: 'abc', name: 'abc',
rankAtDepth: 0, rankAtDepth: 0,
@ -160,13 +387,18 @@ describe('palettes', () => {
rankAtDepth: 0, rankAtDepth: 0,
totalSeriesAtDepth: 10, totalSeriesAtDepth: 10,
}, },
]); ],
{
syncColors: true,
}
);
expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledTimes(1); expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledTimes(1);
expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']);
expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledTimes(1); expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledTimes(1);
expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc');
}); });
}); });
});
describe('custom palette', () => { describe('custom palette', () => {
const palette = palettes.custom; const palette = palettes.custom;

View file

@ -28,26 +28,45 @@ import {
euiPaletteNegative, euiPaletteNegative,
euiPalettePositive, euiPalettePositive,
euiPaletteWarm, euiPaletteWarm,
euiPaletteColorBlindBehindText,
euiPaletteForStatus, euiPaletteForStatus,
euiPaletteForTemperature, euiPaletteForTemperature,
euiPaletteComplimentary, euiPaletteComplimentary,
euiPaletteColorBlindBehindText,
} from '@elastic/eui'; } from '@elastic/eui';
import { ChartsPluginSetup } from '../../../../../../src/plugins/charts/public'; import { flatten, zip } from 'lodash';
import {
ChartsPluginSetup,
createColorPalette as createLegacyColorPalette,
} from '../../../../../../src/plugins/charts/public';
import { lightenColor } from './lighten_color'; import { lightenColor } from './lighten_color';
import { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from './types'; import { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from './types';
import { LegacyColorsService } from '../legacy_colors'; import { LegacyColorsService } from '../legacy_colors';
import { MappedColors } from '../mapped_colors';
function buildRoundRobinCategoricalWithMappedColors(): Omit<PaletteDefinition, 'title'> { function buildRoundRobinCategoricalWithMappedColors(): Omit<PaletteDefinition, 'title'> {
const colors = euiPaletteColorBlind({ rotations: 2 }); const colors = euiPaletteColorBlind({ rotations: 2 });
const behindTextColors = euiPaletteColorBlindBehindText({ rotations: 2 }); const behindTextColors = euiPaletteColorBlindBehindText({ rotations: 2 });
const behindTextColorMap: Record<string, string> = Object.fromEntries(
zip(colors, behindTextColors)
);
const mappedColors = new MappedColors(undefined, (num: number) => {
return flatten(new Array(Math.ceil(num / 10)).fill(colors)).map((color) => color.toLowerCase());
});
function getColor( function getColor(
series: SeriesLayer[], series: SeriesLayer[],
chartConfiguration: ChartColorConfiguration = { behindText: false } chartConfiguration: ChartColorConfiguration = { behindText: false }
) { ) {
const outputColor = chartConfiguration.behindText let outputColor: string;
if (chartConfiguration.syncColors) {
const colorKey = series[0].name;
mappedColors.mapKeys([colorKey]);
const mappedColor = mappedColors.get(colorKey);
outputColor = chartConfiguration.behindText ? behindTextColorMap[mappedColor] : mappedColor;
} else {
outputColor = chartConfiguration.behindText
? behindTextColors[series[0].rankAtDepth % behindTextColors.length] ? behindTextColors[series[0].rankAtDepth % behindTextColors.length]
: colors[series[0].rankAtDepth % colors.length]; : colors[series[0].rankAtDepth % colors.length];
}
if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) { if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) {
return outputColor; return outputColor;
@ -115,9 +134,15 @@ function buildGradient(
function buildSyncedKibanaPalette( function buildSyncedKibanaPalette(
colors: ChartsPluginSetup['legacyColors'] colors: ChartsPluginSetup['legacyColors']
): Omit<PaletteDefinition, 'title'> { ): Omit<PaletteDefinition, 'title'> {
const staticColors = createLegacyColorPalette(20);
function getColor(series: SeriesLayer[], chartConfiguration: ChartColorConfiguration = {}) { function getColor(series: SeriesLayer[], chartConfiguration: ChartColorConfiguration = {}) {
let outputColor: string;
if (chartConfiguration.syncColors) {
colors.mappedColors.mapKeys([series[0].name]); colors.mappedColors.mapKeys([series[0].name]);
const outputColor = colors.mappedColors.get(series[0].name); outputColor = colors.mappedColors.get(series[0].name);
} else {
outputColor = staticColors[series[0].rankAtDepth % staticColors.length];
}
if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) { if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) {
return outputColor; return outputColor;

View file

@ -55,6 +55,11 @@ export interface ChartColorConfiguration {
* adjust colors for better a11y. Might be ignored depending on the palette. * adjust colors for better a11y. Might be ignored depending on the palette.
*/ */
behindText?: boolean; behindText?: boolean;
/**
* Flag whether a color assignment to a given key should be remembered and re-used the next time the key shows up.
* This setting might be ignored based on the palette.
*/
syncColors?: boolean;
} }
/** /**

View file

@ -151,6 +151,7 @@ export const getDashboardContainerInput = ({
description: dashboardStateManager.getDescription(), description: dashboardStateManager.getDescription(),
id: dashboardStateManager.savedDashboard.id || '', id: dashboardStateManager.savedDashboard.id || '',
useMargins: dashboardStateManager.getUseMargins(), useMargins: dashboardStateManager.getUseMargins(),
syncColors: dashboardStateManager.getSyncColors(),
viewMode: dashboardStateManager.getViewMode(), viewMode: dashboardStateManager.getViewMode(),
filters: query.filterManager.getFilters(), filters: query.filterManager.getFilters(),
query: dashboardStateManager.getQuery(), query: dashboardStateManager.getQuery(),

View file

@ -68,6 +68,7 @@ describe('DashboardState', function () {
query: {} as DashboardContainerInput['query'], query: {} as DashboardContainerInput['query'],
timeRange: {} as DashboardContainerInput['timeRange'], timeRange: {} as DashboardContainerInput['timeRange'],
useMargins: true, useMargins: true,
syncColors: false,
title: 'ultra awesome test dashboard', title: 'ultra awesome test dashboard',
isFullScreenMode: false, isFullScreenMode: false,
panels: {} as DashboardContainerInput['panels'], panels: {} as DashboardContainerInput['panels'],

View file

@ -404,6 +404,15 @@ export class DashboardStateManager {
this.stateContainer.transitions.setOption('useMargins', useMargins); this.stateContainer.transitions.setOption('useMargins', useMargins);
} }
public getSyncColors() {
// Existing dashboards that don't define this should default to true.
return this.appState.options.syncColors === undefined ? true : this.appState.options.syncColors;
}
public setSyncColors(syncColors: boolean) {
this.stateContainer.transitions.setOption('syncColors', syncColors);
}
public getHidePanelTitles() { public getHidePanelTitles() {
return this.appState.options.hidePanelTitles; return this.appState.options.hidePanelTitles;
} }

View file

@ -59,6 +59,7 @@ export interface DashboardContainerInput extends ContainerInput {
timeRange: TimeRange; timeRange: TimeRange;
description?: string; description?: string;
useMargins: boolean; useMargins: boolean;
syncColors?: boolean;
viewMode: ViewMode; viewMode: ViewMode;
filters: Filter[]; filters: Filter[];
title: string; title: string;
@ -93,6 +94,7 @@ export interface InheritedChildInput extends IndexSignature {
hidePanelTitles?: boolean; hidePanelTitles?: boolean;
id: string; id: string;
searchSessionId?: string; searchSessionId?: string;
syncColors?: boolean;
} }
export type DashboardReactContextValue = KibanaReactContextValue<DashboardContainerServices>; export type DashboardReactContextValue = KibanaReactContextValue<DashboardContainerServices>;
@ -269,6 +271,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
hidePanelTitles, hidePanelTitles,
filters, filters,
searchSessionId, searchSessionId,
syncColors,
} = this.input; } = this.input;
return { return {
filters, filters,
@ -279,6 +282,7 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
viewMode, viewMode,
id, id,
searchSessionId, searchSessionId,
syncColors,
}; };
} }
} }

View file

@ -62,6 +62,7 @@ export class DashboardContainerFactoryDefinition
isEmbeddedExternally: false, isEmbeddedExternally: false,
isFullScreenMode: false, isFullScreenMode: false,
useMargins: true, useMargins: true,
syncColors: true,
}; };
} }

View file

@ -348,6 +348,10 @@ export function DashboardTopNav({
onUseMarginsChange: (isChecked: boolean) => { onUseMarginsChange: (isChecked: boolean) => {
dashboardStateManager.setUseMargins(isChecked); dashboardStateManager.setUseMargins(isChecked);
}, },
syncColors: dashboardStateManager.getSyncColors(),
onSyncColorsChange: (isChecked: boolean) => {
dashboardStateManager.setSyncColors(isChecked);
},
hidePanelTitles: dashboardStateManager.getHidePanelTitles(), hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
onHidePanelTitlesChange: (isChecked: boolean) => { onHidePanelTitlesChange: (isChecked: boolean) => {
dashboardStateManager.setHidePanelTitles(isChecked); dashboardStateManager.setHidePanelTitles(isChecked);

View file

@ -27,17 +27,21 @@ interface Props {
onUseMarginsChange: (useMargins: boolean) => void; onUseMarginsChange: (useMargins: boolean) => void;
hidePanelTitles: boolean; hidePanelTitles: boolean;
onHidePanelTitlesChange: (hideTitles: boolean) => void; onHidePanelTitlesChange: (hideTitles: boolean) => void;
syncColors: boolean;
onSyncColorsChange: (syncColors: boolean) => void;
} }
interface State { interface State {
useMargins: boolean; useMargins: boolean;
hidePanelTitles: boolean; hidePanelTitles: boolean;
syncColors: boolean;
} }
export class OptionsMenu extends Component<Props, State> { export class OptionsMenu extends Component<Props, State> {
state = { state = {
useMargins: this.props.useMargins, useMargins: this.props.useMargins,
hidePanelTitles: this.props.hidePanelTitles, hidePanelTitles: this.props.hidePanelTitles,
syncColors: this.props.syncColors,
}; };
constructor(props: Props) { constructor(props: Props) {
@ -56,6 +60,12 @@ export class OptionsMenu extends Component<Props, State> {
this.setState({ hidePanelTitles: isChecked }); this.setState({ hidePanelTitles: isChecked });
}; };
handleSyncColorsChange = (evt: any) => {
const isChecked = evt.target.checked;
this.props.onSyncColorsChange(isChecked);
this.setState({ syncColors: isChecked });
};
render() { render() {
return ( return (
<EuiForm data-test-subj="dashboardOptionsMenu"> <EuiForm data-test-subj="dashboardOptionsMenu">
@ -80,6 +90,17 @@ export class OptionsMenu extends Component<Props, State> {
data-test-subj="dashboardPanelTitlesCheckbox" data-test-subj="dashboardPanelTitlesCheckbox"
/> />
</EuiFormRow> </EuiFormRow>
<EuiFormRow>
<EuiSwitch
label={i18n.translate('dashboard.topNav.options.syncColorsBetweenPanelsSwitchLabel', {
defaultMessage: 'Sync color palettes across panels',
})}
checked={this.state.syncColors}
onChange={this.handleSyncColorsChange}
data-test-subj="dashboardSyncColorsCheckbox"
/>
</EuiFormRow>
</EuiForm> </EuiForm>
); );
} }

View file

@ -39,10 +39,14 @@ export function showOptionsPopover({
onUseMarginsChange, onUseMarginsChange,
hidePanelTitles, hidePanelTitles,
onHidePanelTitlesChange, onHidePanelTitlesChange,
syncColors,
onSyncColorsChange,
}: { }: {
anchorElement: HTMLElement; anchorElement: HTMLElement;
useMargins: boolean; useMargins: boolean;
onUseMarginsChange: (useMargins: boolean) => void; onUseMarginsChange: (useMargins: boolean) => void;
syncColors: boolean;
onSyncColorsChange: (syncColors: boolean) => void;
hidePanelTitles: boolean; hidePanelTitles: boolean;
onHidePanelTitlesChange: (hideTitles: boolean) => void; onHidePanelTitlesChange: (hideTitles: boolean) => void;
}) { }) {
@ -62,6 +66,8 @@ export function showOptionsPopover({
onUseMarginsChange={onUseMarginsChange} onUseMarginsChange={onUseMarginsChange}
hidePanelTitles={hidePanelTitles} hidePanelTitles={hidePanelTitles}
onHidePanelTitlesChange={onHidePanelTitlesChange} onHidePanelTitlesChange={onHidePanelTitlesChange}
syncColors={syncColors}
onSyncColorsChange={onSyncColorsChange}
/> />
</EuiWrappingPopover> </EuiWrappingPopover>
</I18nProvider> </I18nProvider>

View file

@ -78,6 +78,7 @@ export interface DashboardAppState {
options: { options: {
hidePanelTitles: boolean; hidePanelTitles: boolean;
useMargins: boolean; useMargins: boolean;
syncColors?: boolean;
}; };
query: Query | string; query: Query | string;
filters: Filter[]; filters: Filter[];

View file

@ -55,6 +55,11 @@ export type EmbeddableInput = {
* Search session id to group searches * Search session id to group searches
*/ */
searchSessionId?: string; searchSessionId?: string;
/**
* Flag whether colors should be synced with other panels
*/
syncColors?: boolean;
}; };
export interface PanelState<E extends EmbeddableInput & { id: string } = { id: string }> { export interface PanelState<E extends EmbeddableInput & { id: string } = { id: string }> {

View file

@ -410,6 +410,7 @@ export type EmbeddableInput = {
disabledActions?: string[]; disabledActions?: string[];
disableTriggers?: boolean; disableTriggers?: boolean;
searchSessionId?: string; searchSessionId?: string;
syncColors?: boolean;
}; };
// Warning: (ae-missing-release-tag) "EmbeddableInstanceConfiguration" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // Warning: (ae-missing-release-tag) "EmbeddableInstanceConfiguration" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)

View file

@ -82,6 +82,7 @@ export interface IInterpreterRenderHandlers {
event: (event: any) => void; event: (event: any) => void;
hasCompatibleActions?: (event: any) => Promise<boolean>; hasCompatibleActions?: (event: any) => Promise<boolean>;
getRenderMode: () => RenderMode; getRenderMode: () => RenderMode;
isSyncColorsEnabled: () => boolean;
/** /**
* This uiState interface is actually `PersistedState` from the visualizations plugin, * This uiState interface is actually `PersistedState` from the visualizations plugin,
* but expressions cannot know about vis or it creates a mess of circular dependencies. * but expressions cannot know about vis or it creates a mess of circular dependencies.

View file

@ -64,6 +64,7 @@ export class ExpressionLoader {
this.renderHandler = new ExpressionRenderHandler(element, { this.renderHandler = new ExpressionRenderHandler(element, {
onRenderError: params && params.onRenderError, onRenderError: params && params.onRenderError,
renderMode: params?.renderMode, renderMode: params?.renderMode,
syncColors: params?.syncColors,
hasCompatibleActions: params?.hasCompatibleActions, hasCompatibleActions: params?.hasCompatibleActions,
}); });
this.render$ = this.renderHandler.render$; this.render$ = this.renderHandler.render$;

View file

@ -531,7 +531,7 @@ export interface ExpressionRenderError extends Error {
// @public (undocumented) // @public (undocumented)
export class ExpressionRenderHandler { export class ExpressionRenderHandler {
// Warning: (ae-forgotten-export) The symbol "ExpressionRenderHandlerParams" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ExpressionRenderHandlerParams" needs to be exported by the entry point index.d.ts
constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams); constructor(element: HTMLElement, { onRenderError, renderMode, syncColors, hasCompatibleActions, }?: ExpressionRenderHandlerParams);
// (undocumented) // (undocumented)
destroy: () => void; destroy: () => void;
// (undocumented) // (undocumented)
@ -903,6 +903,8 @@ export interface IExpressionLoaderParams {
// (undocumented) // (undocumented)
searchSessionId?: string; searchSessionId?: string;
// (undocumented) // (undocumented)
syncColors?: boolean;
// (undocumented)
uiState?: unknown; uiState?: unknown;
// (undocumented) // (undocumented)
variables?: Record<string, any>; variables?: Record<string, any>;
@ -920,6 +922,8 @@ export interface IInterpreterRenderHandlers {
// (undocumented) // (undocumented)
hasCompatibleActions?: (event: any) => Promise<boolean>; hasCompatibleActions?: (event: any) => Promise<boolean>;
// (undocumented) // (undocumented)
isSyncColorsEnabled: () => boolean;
// (undocumented)
onDestroy: (fn: () => void) => void; onDestroy: (fn: () => void) => void;
// (undocumented) // (undocumented)
reload: () => void; reload: () => void;

View file

@ -170,7 +170,12 @@ export const ReactExpressionRenderer = ({
errorRenderHandlerRef.current = null; errorRenderHandlerRef.current = null;
}; };
}, [hasCustomRenderErrorHandler, onEvent]); }, [
hasCustomRenderErrorHandler,
onEvent,
expressionLoaderOptions.renderMode,
expressionLoaderOptions.syncColors,
]);
useEffect(() => { useEffect(() => {
const subscription = reload$?.subscribe(() => { const subscription = reload$?.subscribe(() => {

View file

@ -31,6 +31,7 @@ export type IExpressionRendererExtraHandlers = Record<string, any>;
export interface ExpressionRenderHandlerParams { export interface ExpressionRenderHandlerParams {
onRenderError?: RenderErrorHandlerFnType; onRenderError?: RenderErrorHandlerFnType;
renderMode?: RenderMode; renderMode?: RenderMode;
syncColors?: boolean;
hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise<boolean>; hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise<boolean>;
} }
@ -63,6 +64,7 @@ export class ExpressionRenderHandler {
{ {
onRenderError, onRenderError,
renderMode, renderMode,
syncColors,
hasCompatibleActions = async () => false, hasCompatibleActions = async () => false,
}: ExpressionRenderHandlerParams = {} }: ExpressionRenderHandlerParams = {}
) { ) {
@ -101,6 +103,9 @@ export class ExpressionRenderHandler {
getRenderMode: () => { getRenderMode: () => {
return renderMode || 'display'; return renderMode || 'display';
}, },
isSyncColorsEnabled: () => {
return syncColors || false;
},
hasCompatibleActions, hasCompatibleActions,
}; };
} }

View file

@ -57,6 +57,7 @@ export interface IExpressionLoaderParams {
onRenderError?: RenderErrorHandlerFnType; onRenderError?: RenderErrorHandlerFnType;
searchSessionId?: string; searchSessionId?: string;
renderMode?: RenderMode; renderMode?: RenderMode;
syncColors?: boolean;
hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions'];
} }

View file

@ -737,6 +737,8 @@ export interface IInterpreterRenderHandlers {
// (undocumented) // (undocumented)
hasCompatibleActions?: (event: any) => Promise<boolean>; hasCompatibleActions?: (event: any) => Promise<boolean>;
// (undocumented) // (undocumented)
isSyncColorsEnabled: () => boolean;
// (undocumented)
onDestroy: (fn: () => void) => void; onDestroy: (fn: () => void) => void;
// (undocumented) // (undocumented)
reload: () => void; reload: () => void;

View file

@ -12,6 +12,7 @@ export const defaultHandlers: RendererHandlers = {
getElementId: () => 'element-id', getElementId: () => 'element-id',
getFilter: () => 'filter', getFilter: () => 'filter',
getRenderMode: () => 'display', getRenderMode: () => 'display',
isSyncColorsEnabled: () => false,
onComplete: (fn) => undefined, onComplete: (fn) => undefined,
onEmbeddableDestroyed: action('onEmbeddableDestroyed'), onEmbeddableDestroyed: action('onEmbeddableDestroyed'),
onEmbeddableInputChange: action('onEmbeddableInputChange'), onEmbeddableInputChange: action('onEmbeddableInputChange'),

View file

@ -26,6 +26,9 @@ export const createHandlers = (): RendererHandlers => ({
getRenderMode() { getRenderMode() {
return 'display'; return 'display';
}, },
isSyncColorsEnabled() {
return false;
},
onComplete(fn: () => void) { onComplete(fn: () => void) {
this.done = fn; this.done = fn;
}, },

View file

@ -260,6 +260,7 @@ export class Embeddable
handleEvent={this.handleEvent} handleEvent={this.handleEvent}
onData$={this.updateActiveData} onData$={this.updateActiveData}
renderMode={input.renderMode} renderMode={input.renderMode}
syncColors={input.syncColors}
hasCompatibleActions={this.hasCompatibleActions} hasCompatibleActions={this.hasCompatibleActions}
/>, />,
domNode domNode

View file

@ -29,6 +29,7 @@ export interface ExpressionWrapperProps {
inspectorAdapters?: Partial<DefaultInspectorAdapters> | undefined inspectorAdapters?: Partial<DefaultInspectorAdapters> | undefined
) => void; ) => void;
renderMode?: RenderMode; renderMode?: RenderMode;
syncColors?: boolean;
hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions']; hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions'];
} }
@ -41,6 +42,7 @@ export function ExpressionWrapper({
searchSessionId, searchSessionId,
onData$, onData$,
renderMode, renderMode,
syncColors,
hasCompatibleActions, hasCompatibleActions,
}: ExpressionWrapperProps) { }: ExpressionWrapperProps) {
return ( return (
@ -70,6 +72,7 @@ export function ExpressionWrapper({
searchSessionId={searchSessionId} searchSessionId={searchSessionId}
onData$={onData$} onData$={onData$}
renderMode={renderMode} renderMode={renderMode}
syncColors={syncColors}
renderError={(errorMessage, error) => ( renderError={(errorMessage, error) => (
<div data-test-subj="expression-renderer-error"> <div data-test-subj="expression-renderer-error">
<EuiFlexGroup direction="column" alignItems="center" justifyContent="center"> <EuiFlexGroup direction="column" alignItems="center" justifyContent="center">

View file

@ -140,6 +140,7 @@ export const getPieRenderer = (dependencies: {
paletteService={dependencies.paletteService} paletteService={dependencies.paletteService}
onClickValue={onClickValue} onClickValue={onClickValue}
renderMode={handlers.getRenderMode()} renderMode={handlers.getRenderMode()}
syncColors={handlers.isSyncColorsEnabled()}
/> />
</I18nProvider>, </I18nProvider>,
domNode, domNode,

View file

@ -71,6 +71,7 @@ describe('PieVisualization component', () => {
chartsThemeService, chartsThemeService,
paletteService: chartPluginMock.createPaletteRegistry(), paletteService: chartPluginMock.createPaletteRegistry(),
renderMode: 'display' as const, renderMode: 'display' as const,
syncColors: false,
}; };
} }
@ -172,6 +173,7 @@ describe('PieVisualization component', () => {
{ {
maxDepth: 2, maxDepth: 2,
totalSeries: 5, totalSeries: 5,
syncColors: false,
behindText: true, behindText: true,
}, },
undefined undefined

View file

@ -47,12 +47,13 @@ export function PieComponent(
paletteService: PaletteRegistry; paletteService: PaletteRegistry;
onClickValue: (data: LensFilterEvent['data']) => void; onClickValue: (data: LensFilterEvent['data']) => void;
renderMode: RenderMode; renderMode: RenderMode;
syncColors: boolean;
} }
) { ) {
const [firstTable] = Object.values(props.data.tables); const [firstTable] = Object.values(props.data.tables);
const formatters: Record<string, ReturnType<FormatFactory>> = {}; const formatters: Record<string, ReturnType<FormatFactory>> = {};
const { chartsThemeService, paletteService, onClickValue } = props; const { chartsThemeService, paletteService, syncColors, onClickValue } = props;
const { const {
shape, shape,
groups, groups,
@ -145,6 +146,7 @@ export function PieComponent(
behindText: categoryDisplay !== 'hide', behindText: categoryDisplay !== 'hide',
maxDepth: bucketColumns.length, maxDepth: bucketColumns.length,
totalSeries: totalSeriesCount, totalSeries: totalSeriesCount,
syncColors,
}, },
palette.params palette.params
); );

View file

@ -18,7 +18,13 @@ import {
Fit, Fit,
} from '@elastic/charts'; } from '@elastic/charts';
import { PaletteOutput } from 'src/plugins/charts/public'; import { PaletteOutput } from 'src/plugins/charts/public';
import { calculateMinInterval, xyChart, XYChart, XYChartProps } from './expression'; import {
calculateMinInterval,
xyChart,
XYChart,
XYChartProps,
XYChartRenderProps,
} from './expression';
import { LensMultiTable } from '../types'; import { LensMultiTable } from '../types';
import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public'; import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public';
import React from 'react'; import React from 'react';
@ -382,6 +388,7 @@ describe('xy_expression', () => {
describe('XYChart component', () => { describe('XYChart component', () => {
let getFormatSpy: jest.Mock; let getFormatSpy: jest.Mock;
let convertSpy: jest.Mock; let convertSpy: jest.Mock;
let defaultProps: Omit<XYChartRenderProps, 'data' | 'args'>;
const dataWithoutFormats: LensMultiTable = { const dataWithoutFormats: LensMultiTable = {
type: 'lens_multitable', type: 'lens_multitable',
@ -421,26 +428,25 @@ describe('xy_expression', () => {
}; };
const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => { const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => {
return shallow( return shallow(<XYChart {...defaultProps} data={data} args={args} />);
<XYChart
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
}; };
beforeEach(() => { beforeEach(() => {
convertSpy = jest.fn((x) => x); convertSpy = jest.fn((x) => x);
getFormatSpy = jest.fn(); getFormatSpy = jest.fn();
getFormatSpy.mockReturnValue({ convert: convertSpy }); getFormatSpy.mockReturnValue({ convert: convertSpy });
defaultProps = {
formatFactory: getFormatSpy,
timeZone: 'UTC',
renderMode: 'display',
chartsThemeService,
paletteService,
minInterval: 50,
onClickValue,
onSelectRange,
syncColors: false,
};
}); });
test('it renders line', () => { test('it renders line', () => {
@ -448,16 +454,9 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'line' }] }} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'line' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -493,6 +492,7 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ data={{
...data, ...data,
dateRange: { dateRange: {
@ -504,14 +504,7 @@ describe('xy_expression', () => {
...args, ...args,
layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'time' }], layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'time' }],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={undefined} minInterval={undefined}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(` expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
@ -534,6 +527,7 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ data={{
...data, ...data,
dateRange: { dateRange: {
@ -542,14 +536,6 @@ describe('xy_expression', () => {
}, },
}} }}
args={multiLayerArgs} args={multiLayerArgs}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -569,6 +555,7 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ data={{
...data, ...data,
dateRange: { dateRange: {
@ -580,14 +567,6 @@ describe('xy_expression', () => {
...args, ...args,
layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'linear' }], layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'linear' }],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(Settings).prop('xDomain')).toBeUndefined(); expect(component.find(Settings).prop('xDomain')).toBeUndefined();
@ -597,16 +576,9 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar' }] }} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -619,16 +591,9 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area' }] }} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -641,16 +606,9 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_horizontal' }] }} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_horizontal' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -666,20 +624,7 @@ describe('xy_expression', () => {
// send empty data to the chart // send empty data to the chart
data.tables.first.rows = []; data.tables.first.rows = [];
const component = shallow( const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
<XYChart
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
expect(component.find(BarSeries)).toHaveLength(0); expect(component.find(BarSeries)).toHaveLength(0);
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined(); expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
@ -690,19 +635,12 @@ describe('xy_expression', () => {
const wrapper = mountWithIntl( const wrapper = mountWithIntl(
<XYChart <XYChart
{...defaultProps}
data={dateHistogramData} data={dateHistogramData}
args={{ args={{
...args, ...args,
layers: [dateHistogramLayer], layers: [dateHistogramLayer],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -776,19 +714,12 @@ describe('xy_expression', () => {
const wrapper = mountWithIntl( const wrapper = mountWithIntl(
<XYChart <XYChart
{...defaultProps}
data={numberHistogramData} data={numberHistogramData}
args={{ args={{
...args, ...args,
layers: [numberLayer], layers: [numberLayer],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -806,18 +737,7 @@ describe('xy_expression', () => {
const { args, data } = sampleArgs(); const { args, data } = sampleArgs();
const wrapper = mountWithIntl( const wrapper = mountWithIntl(
<XYChart <XYChart {...defaultProps} data={data} args={args} renderMode="noInteractivity" />
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="noInteractivity"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(wrapper.find(Settings).first().prop('onBrushEnd')).toBeUndefined(); expect(wrapper.find(Settings).first().prop('onBrushEnd')).toBeUndefined();
@ -837,6 +757,7 @@ describe('xy_expression', () => {
const wrapper = mountWithIntl( const wrapper = mountWithIntl(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ args={{
...args, ...args,
@ -855,14 +776,6 @@ describe('xy_expression', () => {
}, },
], ],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -892,18 +805,7 @@ describe('xy_expression', () => {
const { args, data } = sampleArgs(); const { args, data } = sampleArgs();
const wrapper = mountWithIntl( const wrapper = mountWithIntl(
<XYChart <XYChart {...defaultProps} data={data} args={args} renderMode="noInteractivity" />
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="noInteractivity"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(wrapper.find(Settings).first().prop('onElementClick')).toBeUndefined(); expect(wrapper.find(Settings).first().prop('onElementClick')).toBeUndefined();
@ -913,16 +815,9 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }] }} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -935,16 +830,9 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area_stacked' }] }} args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area_stacked' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -957,19 +845,12 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ args={{
...args, ...args,
layers: [{ ...args.layers[0], seriesType: 'bar_horizontal_stacked' }], layers: [{ ...args.layers[0], seriesType: 'bar_horizontal_stacked' }],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component).toMatchSnapshot(); expect(component).toMatchSnapshot();
@ -984,6 +865,7 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ args={{
...args, ...args,
@ -996,14 +878,6 @@ describe('xy_expression', () => {
}, },
], ],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -1014,18 +888,7 @@ describe('xy_expression', () => {
test('it passes time zone to the series', () => { test('it passes time zone to the series', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={data} args={args} timeZone="CEST" />
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="CEST"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(component.find(LineSeries).at(0).prop('timeZone')).toEqual('CEST'); expect(component.find(LineSeries).at(0).prop('timeZone')).toEqual('CEST');
expect(component.find(LineSeries).at(1).prop('timeZone')).toEqual('CEST'); expect(component.find(LineSeries).at(1).prop('timeZone')).toEqual('CEST');
@ -1041,18 +904,7 @@ describe('xy_expression', () => {
}; };
delete firstLayer.splitAccessor; delete firstLayer.splitAccessor;
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={data} args={{ ...args, layers: [firstLayer] }} />
data={data}
args={{ ...args, layers: [firstLayer] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true);
}); });
@ -1062,18 +914,7 @@ describe('xy_expression', () => {
const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true }; const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true };
delete firstLayer.splitAccessor; delete firstLayer.splitAccessor;
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={data} args={{ ...args, layers: [firstLayer] }} />
data={data}
args={{ ...args, layers: [firstLayer] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false);
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(false); expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(false);
@ -1087,16 +928,9 @@ describe('xy_expression', () => {
delete secondLayer.splitAccessor; delete secondLayer.splitAccessor;
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [firstLayer, secondLayer] }} args={{ ...args, layers: [firstLayer, secondLayer] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(LineSeries).at(0).prop('enableHistogramMode')).toEqual(true); expect(component.find(LineSeries).at(0).prop('enableHistogramMode')).toEqual(true);
@ -1107,6 +941,7 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ args={{
...args, ...args,
@ -1118,14 +953,6 @@ describe('xy_expression', () => {
}, },
], ],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true);
@ -1136,19 +963,12 @@ describe('xy_expression', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ args={{
...args, ...args,
layers: [{ ...args.layers[0], seriesType: 'bar', isHistogram: true }], layers: [{ ...args.layers[0], seriesType: 'bar', isHistogram: true }],
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false); expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false);
@ -1541,16 +1361,9 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], xScaleType: 'ordinal' }] }} args={{ ...args, layers: [{ ...args.layers[0], xScaleType: 'ordinal' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(LineSeries).at(0).prop('xScaleType')).toEqual(ScaleType.Ordinal); expect(component.find(LineSeries).at(0).prop('xScaleType')).toEqual(ScaleType.Ordinal);
@ -1562,16 +1375,9 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={data} data={data}
args={{ ...args, layers: [{ ...args.layers[0], yScaleType: 'sqrt' }] }} args={{ ...args, layers: [{ ...args.layers[0], yScaleType: 'sqrt' }] }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(component.find(LineSeries).at(0).prop('yScaleType')).toEqual(ScaleType.Sqrt); expect(component.find(LineSeries).at(0).prop('yScaleType')).toEqual(ScaleType.Sqrt);
@ -1581,20 +1387,7 @@ describe('xy_expression', () => {
test('it gets the formatter for the x axis', () => { test('it gets the formatter for the x axis', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
shallow( shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
<XYChart
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' }); expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' });
}); });
@ -1604,16 +1397,9 @@ describe('xy_expression', () => {
shallow( shallow(
<XYChart <XYChart
{...defaultProps}
data={{ ...data }} data={{ ...data }}
args={{ ...args, layers: [{ ...args.layers[0], accessors: ['a'] }] }} args={{ ...args, layers: [{ ...args.layers[0], accessors: ['a'] }] }}
formatFactory={getFormatSpy}
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
timeZone="UTC"
renderMode="display"
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
expect(getFormatSpy).toHaveBeenCalledWith({ expect(getFormatSpy).toHaveBeenCalledWith({
@ -1625,20 +1411,7 @@ describe('xy_expression', () => {
test('it should pass the formatter function to the axis', () => { test('it should pass the formatter function to the axis', () => {
const { data, args } = sampleArgs(); const { data, args } = sampleArgs();
const instance = shallow( const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
<XYChart
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const tickFormatter = instance.find(Axis).first().prop('tickFormat'); const tickFormatter = instance.find(Axis).first().prop('tickFormat');
@ -1661,20 +1434,7 @@ describe('xy_expression', () => {
type: 'lens_xy_tickLabelsConfig', type: 'lens_xy_tickLabelsConfig',
}; };
const instance = shallow( const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
<XYChart
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const axisStyle = instance.find(Axis).first().prop('style'); const axisStyle = instance.find(Axis).first().prop('style');
@ -1695,20 +1455,7 @@ describe('xy_expression', () => {
type: 'lens_xy_tickLabelsConfig', type: 'lens_xy_tickLabelsConfig',
}; };
const instance = shallow( const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
<XYChart
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const axisStyle = instance.find(Axis).at(1).prop('style'); const axisStyle = instance.find(Axis).at(1).prop('style');
@ -1729,20 +1476,7 @@ describe('xy_expression', () => {
type: 'lens_xy_tickLabelsConfig', type: 'lens_xy_tickLabelsConfig',
}; };
const instance = shallow( const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
<XYChart
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const axisStyle = instance.find(Axis).first().prop('style'); const axisStyle = instance.find(Axis).first().prop('style');
@ -1763,20 +1497,7 @@ describe('xy_expression', () => {
type: 'lens_xy_tickLabelsConfig', type: 'lens_xy_tickLabelsConfig',
}; };
const instance = shallow( const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
<XYChart
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const axisStyle = instance.find(Axis).at(1).prop('style'); const axisStyle = instance.find(Axis).at(1).prop('style');
@ -1864,20 +1585,7 @@ describe('xy_expression', () => {
], ],
}; };
const component = shallow( const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
<XYChart
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const series = component.find(LineSeries); const series = component.find(LineSeries);
@ -1939,20 +1647,7 @@ describe('xy_expression', () => {
], ],
}; };
const component = shallow( const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
<XYChart
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
const series = component.find(LineSeries); const series = component.find(LineSeries);
@ -2012,20 +1707,7 @@ describe('xy_expression', () => {
], ],
}; };
const component = shallow( const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
<XYChart
data={data}
args={args}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
);
expect(component.find(Settings).prop('showLegend')).toEqual(true); expect(component.find(Settings).prop('showLegend')).toEqual(true);
}); });
@ -2035,20 +1717,13 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ ...data }} data={{ ...data }}
args={{ args={{
...args, ...args,
layers: [{ ...args.layers[0], accessors: ['a'], splitAccessor: undefined }], layers: [{ ...args.layers[0], accessors: ['a'], splitAccessor: undefined }],
legend: { ...args.legend, isVisible: true, showSingleSeries: true }, legend: { ...args.legend, isVisible: true, showSingleSeries: true },
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -2060,19 +1735,12 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ ...data }} data={{ ...data }}
args={{ args={{
...args, ...args,
legend: { ...args.legend, isVisible: false }, legend: { ...args.legend, isVisible: false },
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -2084,19 +1752,12 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ ...data }} data={{ ...data }}
args={{ args={{
...args, ...args,
legend: { ...args.legend, position: 'top' }, legend: { ...args.legend, position: 'top' },
}} }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -2123,16 +1784,9 @@ describe('xy_expression', () => {
const component = shallow( const component = shallow(
<XYChart <XYChart
{...defaultProps}
data={{ ...data }} data={{ ...data }}
args={{ ...args, fittingFunction: 'Carry' }} args={{ ...args, fittingFunction: 'Carry' }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/> />
); );
@ -2150,18 +1804,7 @@ describe('xy_expression', () => {
args.layers[0].accessors = ['a']; args.layers[0].accessors = ['a'];
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.None }); expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.None });
@ -2173,18 +1816,7 @@ describe('xy_expression', () => {
args.xTitle = 'My custom x-axis title'; args.xTitle = 'My custom x-axis title';
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(component.find(Axis).at(0).prop('title')).toEqual('My custom x-axis title'); expect(component.find(Axis).at(0).prop('title')).toEqual('My custom x-axis title');
@ -2201,18 +1833,7 @@ describe('xy_expression', () => {
}; };
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
const axisStyle = component.find(Axis).first().prop('style'); const axisStyle = component.find(Axis).first().prop('style');
@ -2235,18 +1856,7 @@ describe('xy_expression', () => {
}; };
const component = shallow( const component = shallow(
<XYChart <XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
data={{ ...data }}
args={{ ...args }}
formatFactory={getFormatSpy}
timeZone="UTC"
renderMode="display"
chartsThemeService={chartsThemeService}
paletteService={paletteService}
minInterval={50}
onClickValue={onClickValue}
onSelectRange={onSelectRange}
/>
); );
expect(component.find(Axis).at(0).prop('gridLine')).toMatchObject({ expect(component.find(Axis).at(0).prop('gridLine')).toMatchObject({

View file

@ -76,7 +76,7 @@ export interface XYRender {
value: XYChartProps; value: XYChartProps;
} }
type XYChartRenderProps = XYChartProps & { export type XYChartRenderProps = XYChartProps & {
chartsThemeService: ChartsPluginSetup['theme']; chartsThemeService: ChartsPluginSetup['theme'];
paletteService: PaletteRegistry; paletteService: PaletteRegistry;
formatFactory: FormatFactory; formatFactory: FormatFactory;
@ -85,6 +85,7 @@ type XYChartRenderProps = XYChartProps & {
onClickValue: (data: LensFilterEvent['data']) => void; onClickValue: (data: LensFilterEvent['data']) => void;
onSelectRange: (data: LensBrushEvent['data']) => void; onSelectRange: (data: LensBrushEvent['data']) => void;
renderMode: RenderMode; renderMode: RenderMode;
syncColors: boolean;
}; };
export const xyChart: ExpressionFunctionDefinition< export const xyChart: ExpressionFunctionDefinition<
@ -240,6 +241,7 @@ export const getXyChartRenderer = (dependencies: {
onClickValue={onClickValue} onClickValue={onClickValue}
onSelectRange={onSelectRange} onSelectRange={onSelectRange}
renderMode={handlers.getRenderMode()} renderMode={handlers.getRenderMode()}
syncColors={handlers.isSyncColorsEnabled()}
/> />
</I18nProvider>, </I18nProvider>,
domNode, domNode,
@ -309,6 +311,7 @@ export function XYChart({
onClickValue, onClickValue,
onSelectRange, onSelectRange,
renderMode, renderMode,
syncColors,
}: XYChartRenderProps) { }: XYChartRenderProps) {
const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args; const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args;
const chartTheme = chartsThemeService.useChartsTheme(); const chartTheme = chartsThemeService.useChartsTheme();
@ -681,6 +684,7 @@ export function XYChart({
maxDepth: 1, maxDepth: 1,
behindText: false, behindText: false,
totalSeries: colorAssignment.totalSeriesCount, totalSeries: colorAssignment.totalSeriesCount,
syncColors,
}, },
palette.params palette.params
); );