mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Step 1] Move CustomizablePalette from Lens into @kbn/coloring package (#129291)
* [Step 1] Move Coloring into Shared-ux-Components * Fix Joe's comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
8a0cc6efcd
commit
ac50a30eb6
116 changed files with 2369 additions and 1697 deletions
|
@ -66,6 +66,7 @@
|
|||
"share": "src/plugins/share",
|
||||
"sharedUX": "src/plugins/shared_ux",
|
||||
"sharedUXComponents": "packages/kbn-shared-ux-components/src",
|
||||
"coloring": "packages/kbn-coloring/src",
|
||||
"statusPage": "src/legacy/core_plugins/status_page",
|
||||
"telemetry": [
|
||||
"src/plugins/telemetry",
|
||||
|
@ -94,4 +95,4 @@
|
|||
"unifiedSearch": "src/plugins/unified_search"
|
||||
},
|
||||
"translations": []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
"@kbn/analytics": "link:bazel-bin/packages/kbn-analytics",
|
||||
"@kbn/apm-config-loader": "link:bazel-bin/packages/kbn-apm-config-loader",
|
||||
"@kbn/apm-utils": "link:bazel-bin/packages/kbn-apm-utils",
|
||||
"@kbn/coloring": "link:bazel-bin/packages/kbn-coloring",
|
||||
"@kbn/config": "link:bazel-bin/packages/kbn-config",
|
||||
"@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema",
|
||||
"@kbn/crypto": "link:bazel-bin/packages/kbn-crypto",
|
||||
|
@ -590,6 +591,7 @@
|
|||
"@types/kbn__axe-config": "link:bazel-bin/packages/kbn-axe-config/npm_module_types",
|
||||
"@types/kbn__bazel-packages": "link:bazel-bin/packages/kbn-bazel-packages/npm_module_types",
|
||||
"@types/kbn__cli-dev-mode": "link:bazel-bin/packages/kbn-cli-dev-mode/npm_module_types",
|
||||
"@types/kbn__coloring": "link:bazel-bin/packages/kbn-coloring/npm_module_types",
|
||||
"@types/kbn__config": "link:bazel-bin/packages/kbn-config/npm_module_types",
|
||||
"@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types",
|
||||
"@types/kbn__crypto": "link:bazel-bin/packages/kbn-crypto/npm_module_types",
|
||||
|
|
|
@ -26,6 +26,7 @@ filegroup(
|
|||
"//packages/kbn-babel-preset:build",
|
||||
"//packages/kbn-bazel-packages:build",
|
||||
"//packages/kbn-cli-dev-mode:build",
|
||||
"//packages/kbn-coloring:build",
|
||||
"//packages/kbn-config-schema:build",
|
||||
"//packages/kbn-config:build",
|
||||
"//packages/kbn-crypto:build",
|
||||
|
@ -111,6 +112,7 @@ filegroup(
|
|||
"//packages/kbn-axe-config:build_types",
|
||||
"//packages/kbn-bazel-packages:build_types",
|
||||
"//packages/kbn-cli-dev-mode:build_types",
|
||||
"//packages/kbn-coloring:build_types",
|
||||
"//packages/kbn-config-schema:build_types",
|
||||
"//packages/kbn-config:build_types",
|
||||
"//packages/kbn-crypto:build_types",
|
||||
|
|
9
packages/kbn-coloring/.storybook/main.js
Normal file
9
packages/kbn-coloring/.storybook/main.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = require('@kbn/storybook').defaultConfig;
|
146
packages/kbn-coloring/BUILD.bazel
Normal file
146
packages/kbn-coloring/BUILD.bazel
Normal file
|
@ -0,0 +1,146 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_config")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
|
||||
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
|
||||
|
||||
PKG_DIRNAME = "kbn-coloring"
|
||||
PKG_REQUIRE_NAME = "@kbn/coloring"
|
||||
|
||||
SOURCE_FILES = glob(
|
||||
[
|
||||
"src/**/*.scss",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
],
|
||||
exclude = [
|
||||
"**/*.test.*",
|
||||
"**/*.stories.*",
|
||||
],
|
||||
)
|
||||
|
||||
SRCS = SOURCE_FILES
|
||||
|
||||
filegroup(
|
||||
name = "srcs",
|
||||
srcs = SRCS,
|
||||
)
|
||||
|
||||
NPM_MODULE_EXTRA_FILES = [
|
||||
"package.json",
|
||||
]
|
||||
|
||||
# In this array place runtime dependencies, including other packages and NPM packages
|
||||
# which must be available for this code to run.
|
||||
#
|
||||
# To reference other packages use:
|
||||
# "//repo/relative/path/to/package"
|
||||
# eg. "//packages/kbn-utils"
|
||||
#
|
||||
# To reference a NPM package use:
|
||||
# "@npm//name-of-package"
|
||||
# eg. "@npm//lodash"
|
||||
RUNTIME_DEPS = [
|
||||
"//packages/kbn-i18n",
|
||||
"//packages/kbn-i18n-react",
|
||||
"//packages/kbn-shared-ux-storybook",
|
||||
"//packages/kbn-interpreter",
|
||||
"//packages/kbn-utility-types",
|
||||
"//packages/kbn-shared-ux-utility",
|
||||
"@npm//chroma-js",
|
||||
"@npm//@elastic/eui",
|
||||
"@npm//react-use",
|
||||
"@npm//react",
|
||||
]
|
||||
|
||||
# In this array place dependencies necessary to build the types, which will include the
|
||||
# :npm_module_types target of other packages and packages from NPM, including @types/*
|
||||
# packages.
|
||||
#
|
||||
# To reference the types for another package use:
|
||||
# "//repo/relative/path/to/package:npm_module_types"
|
||||
# eg. "//packages/kbn-utils:npm_module_types"
|
||||
#
|
||||
# References to NPM packages work the same as RUNTIME_DEPS
|
||||
TYPES_DEPS = [
|
||||
"//packages/kbn-i18n:npm_module_types",
|
||||
"//packages/kbn-i18n-react:npm_module_types",
|
||||
"//packages/kbn-shared-ux-storybook:npm_module_types",
|
||||
"//packages/kbn-interpreter:npm_module_types",
|
||||
"//packages/kbn-utility-types:npm_module_types",
|
||||
"//packages/kbn-shared-ux-utility:npm_module_types",
|
||||
"@npm//@types/chroma-js",
|
||||
"@npm//@types/react",
|
||||
"@npm//@elastic/eui",
|
||||
"@npm//react-use",
|
||||
]
|
||||
|
||||
jsts_transpiler(
|
||||
name = "target_node",
|
||||
srcs = SRCS,
|
||||
build_pkg_name = package_name(),
|
||||
)
|
||||
|
||||
jsts_transpiler(
|
||||
name = "target_web",
|
||||
srcs = SRCS,
|
||||
build_pkg_name = package_name(),
|
||||
web = True,
|
||||
additional_args = [
|
||||
"--copy-files",
|
||||
"--quiet"
|
||||
],
|
||||
)
|
||||
|
||||
ts_config(
|
||||
name = "tsconfig",
|
||||
src = "tsconfig.json",
|
||||
deps = [
|
||||
"//:tsconfig.base.json",
|
||||
"//:tsconfig.bazel.json",
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "tsc_types",
|
||||
args = ['--pretty'],
|
||||
srcs = SRCS,
|
||||
deps = TYPES_DEPS,
|
||||
declaration = True,
|
||||
emit_declaration_only = True,
|
||||
out_dir = "target_types",
|
||||
root_dir = "src",
|
||||
tsconfig = ":tsconfig",
|
||||
)
|
||||
|
||||
js_library(
|
||||
name = PKG_DIRNAME,
|
||||
srcs = NPM_MODULE_EXTRA_FILES,
|
||||
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
|
||||
package_name = PKG_REQUIRE_NAME,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
pkg_npm(
|
||||
name = "npm_module",
|
||||
deps = [":" + PKG_DIRNAME],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "build",
|
||||
srcs = [":npm_module"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
pkg_npm_types(
|
||||
name = "npm_module_types",
|
||||
srcs = SRCS,
|
||||
deps = [":tsc_types"],
|
||||
package_name = PKG_REQUIRE_NAME,
|
||||
tsconfig = ":tsconfig",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "build_types",
|
||||
srcs = [":npm_module_types"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
3
packages/kbn-coloring/README.md
Normal file
3
packages/kbn-coloring/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/coloring
|
||||
|
||||
This package contains the types, utility methods, and components needed to make Color Palette work.
|
13
packages/kbn-coloring/jest.config.js
Normal file
13
packages/kbn-coloring/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-coloring'],
|
||||
};
|
8
packages/kbn-coloring/package.json
Normal file
8
packages/kbn-coloring/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@kbn/coloring",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "./target_node/index.js",
|
||||
"browser": "./target_web/index.js",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
10
packages/kbn-coloring/src/index.ts
Normal file
10
packages/kbn-coloring/src/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './palettes';
|
||||
export * from './shared_components';
|
|
@ -5,26 +5,12 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { RequiredPaletteParamTypes } from '../common';
|
||||
|
||||
export const DEFAULT_PALETTE_NAME = 'positive';
|
||||
export const FIXED_PROGRESSION = 'fixed' as const;
|
||||
export const CUSTOM_PALETTE = 'custom';
|
||||
export const DEFAULT_CONTINUITY = 'above';
|
||||
|
||||
export const DEFAULT_PALETTE_NAME = 'gray';
|
||||
export const DEFAULT_COLOR_STEPS = 3;
|
||||
export const DEFAULT_RANGE_TYPE = 'percent';
|
||||
export const DEFAULT_MIN_STOP = 0;
|
||||
export const DEFAULT_MAX_STOP = 100;
|
||||
|
||||
export const defaultPaletteParams: RequiredPaletteParamTypes & { maxSteps: number } = {
|
||||
name: DEFAULT_PALETTE_NAME,
|
||||
colorStops: [],
|
||||
continuity: DEFAULT_CONTINUITY,
|
||||
reverse: false,
|
||||
rangeType: 'percent',
|
||||
rangeMin: DEFAULT_MIN_STOP,
|
||||
rangeMax: DEFAULT_MAX_STOP,
|
||||
progression: FIXED_PROGRESSION,
|
||||
stops: [],
|
||||
steps: DEFAULT_COLOR_STEPS,
|
||||
maxSteps: 5,
|
||||
};
|
||||
export const DEFAULT_COLOR_STEPS = 5;
|
11
packages/kbn-coloring/src/palettes/index.ts
Normal file
11
packages/kbn-coloring/src/palettes/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './utils';
|
||||
export * from './types';
|
||||
export * from './constants';
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 type { PaletteDefinition, SeriesLayer } from '../types';
|
||||
|
||||
export const getPaletteRegistry = () => {
|
||||
const mockPalette1: jest.Mocked<PaletteDefinition> = {
|
||||
id: 'default',
|
||||
title: 'My Palette',
|
||||
getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'black'),
|
||||
getCategoricalColors: jest.fn((num: number) => ['red', 'black']),
|
||||
toExpression: jest.fn(() => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['default'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
const mockPalette2: jest.Mocked<PaletteDefinition> = {
|
||||
id: 'mocked',
|
||||
title: 'Mocked Palette',
|
||||
getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'blue'),
|
||||
getCategoricalColors: jest.fn((num: number) => ['blue', 'yellow']),
|
||||
toExpression: jest.fn(() => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['mocked'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
const mockPalette3: jest.Mocked<PaletteDefinition> = {
|
||||
id: 'custom',
|
||||
title: 'Custom Mocked Palette',
|
||||
getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'blue'),
|
||||
getCategoricalColors: jest.fn((num: number) => ['blue', 'yellow']),
|
||||
getColorForValue: jest.fn(
|
||||
(num: number | undefined, state: unknown, minMax: { min: number; max: number }) =>
|
||||
num == null || num < 1 ? undefined : 'blue'
|
||||
),
|
||||
canDynamicColoring: true,
|
||||
toExpression: jest.fn(() => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['mocked'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
return {
|
||||
get: (name: string) =>
|
||||
name === 'custom' ? mockPalette3 : name !== 'default' ? mockPalette2 : mockPalette1,
|
||||
getAll: () => [mockPalette1, mockPalette2, mockPalette3],
|
||||
};
|
||||
};
|
|
@ -6,7 +6,24 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExpressionAstExpression } from '../../../../expressions/common/ast';
|
||||
import { Ast } from '@kbn/interpreter';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
|
||||
/** @public **/
|
||||
export type PaletteContinuity = 'above' | 'below' | 'none' | 'all';
|
||||
|
||||
/** @public **/
|
||||
export interface DataBounds {
|
||||
min: number;
|
||||
max: number;
|
||||
fallback?: boolean;
|
||||
}
|
||||
|
||||
export interface PaletteOutput<T = { [key: string]: unknown }> {
|
||||
type: 'palette' | 'system_palette';
|
||||
name: string;
|
||||
params?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a series in a chart used to determine its color.
|
||||
|
@ -78,7 +95,7 @@ export interface PaletteDefinition<T = unknown> {
|
|||
* This function should be used to pass the palette to the expression function applying color and other styles
|
||||
* @param state The internal state of the palette
|
||||
*/
|
||||
toExpression: (state?: T) => ExpressionAstExpression;
|
||||
toExpression: (state?: T) => Ast;
|
||||
/**
|
||||
* Color a series according to the internal rules of the palette.
|
||||
* @param series The current series along with its ancestors.
|
||||
|
@ -110,6 +127,30 @@ export interface PaletteDefinition<T = unknown> {
|
|||
}
|
||||
|
||||
export interface PaletteRegistry {
|
||||
get: (name: string) => PaletteDefinition<unknown>;
|
||||
getAll: () => Array<PaletteDefinition<unknown>>;
|
||||
get: (name: string) => PaletteDefinition;
|
||||
getAll: () => PaletteDefinition[];
|
||||
}
|
||||
|
||||
export interface CustomPaletteParams {
|
||||
name?: string;
|
||||
reverse?: boolean;
|
||||
rangeType?: 'number' | 'percent';
|
||||
continuity?: PaletteContinuity;
|
||||
progression?: 'fixed';
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
stops?: ColorStop[];
|
||||
colorStops?: ColorStop[];
|
||||
steps?: number;
|
||||
maxSteps?: number | undefined;
|
||||
}
|
||||
|
||||
export type RequiredPaletteParamTypes = Assign<
|
||||
Required<CustomPaletteParams>,
|
||||
{ maxSteps?: number }
|
||||
>;
|
||||
|
||||
export interface ColorStop {
|
||||
color: string;
|
||||
stop: number;
|
||||
}
|
313
packages/kbn-coloring/src/palettes/utils.test.ts
Normal file
313
packages/kbn-coloring/src/palettes/utils.test.ts
Normal file
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
* 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 { getPaletteRegistry } from './mocks/palettes_registry';
|
||||
import {
|
||||
getDataMinMax,
|
||||
getPaletteStops,
|
||||
getStepValue,
|
||||
remapStopsByNewInterval,
|
||||
reversePalette,
|
||||
} from './utils';
|
||||
|
||||
describe('remapStopsByNewInterval', () => {
|
||||
it('should correctly remap the current palette from 0..1 to 0...100', () => {
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'red', stop: 0.9 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 1, newMin: 0, oldMin: 0 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 90 },
|
||||
]);
|
||||
|
||||
// now test the other way around
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 90 },
|
||||
],
|
||||
{ newInterval: 1, oldInterval: 100, newMin: 0, oldMin: 0 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'red', stop: 0.9 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly handle negative numbers to/from', () => {
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: -100 },
|
||||
{ color: 'green', stop: -50 },
|
||||
{ color: 'red', stop: -1 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 100, newMin: 0, oldMin: -100 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 99 },
|
||||
]);
|
||||
|
||||
// now map the other way around
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 99 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 100, newMin: -100, oldMin: 0 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: -100 },
|
||||
{ color: 'green', stop: -50 },
|
||||
{ color: 'red', stop: -1 },
|
||||
]);
|
||||
|
||||
// and test also palettes that also contains negative values
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: -50 },
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 100, newMin: 0, oldMin: -50 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 100 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDataMinMax', () => {
|
||||
it('should pick the correct min/max based on the current range type', () => {
|
||||
expect(getDataMinMax('percent', { min: -100, max: 0 })).toEqual({ min: 0, max: 100 });
|
||||
});
|
||||
|
||||
it('should pick the correct min/max apply percent by default', () => {
|
||||
expect(getDataMinMax(undefined, { min: -100, max: 0 })).toEqual({ min: 0, max: 100 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPaletteStops', () => {
|
||||
const paletteRegistry = getPaletteRegistry();
|
||||
it('should correctly compute a predefined palette stops definition from only the name', () => {
|
||||
expect(
|
||||
getPaletteStops(paletteRegistry, { name: 'mock' }, { dataBounds: { min: 0, max: 100 } })
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 20 },
|
||||
{ color: 'yellow', stop: 70 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly compute a predefined palette stops definition from explicit prevPalette (override)', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{ name: 'default' },
|
||||
{ dataBounds: { min: 0, max: 100 }, prevPalette: 'mock' }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 20 },
|
||||
{ color: 'yellow', stop: 70 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should infer the domain from dataBounds but start from 0', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{ name: 'default', rangeType: 'number' },
|
||||
{ dataBounds: { min: 1, max: 11 }, prevPalette: 'mock' }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 2 },
|
||||
{ color: 'yellow', stop: 7 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should override the minStop when requested', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{ name: 'default', rangeType: 'number' },
|
||||
{ dataBounds: { min: 1, max: 11 }, mapFromMinValue: true }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'red', stop: 1 },
|
||||
{ color: 'black', stop: 6 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
rangeType: 'number',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 100 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 40 },
|
||||
{ color: 'blue', stop: 80 },
|
||||
{ color: 'red', stop: 100 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user - handle stop at the end', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
rangeType: 'number',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 100 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 100 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 40 },
|
||||
{ color: 'blue', stop: 100 },
|
||||
{ color: 'red', stop: 101 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user - handle stop at the end (fractional)', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
rangeType: 'number',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 0.4 },
|
||||
{ color: 'red', stop: 1 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 1 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 0.4 },
|
||||
{ color: 'blue', stop: 1 },
|
||||
{ color: 'red', stop: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user - stretch the stops to 100% percent', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 0.4 },
|
||||
{ color: 'red', stop: 1 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 1 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 0.4 },
|
||||
{ color: 'blue', stop: 1 },
|
||||
{ color: 'red', stop: 100 }, // default rangeType is percent, hence stretch to 100%
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reversePalette', () => {
|
||||
it('should correctly reverse color and stops', () => {
|
||||
expect(
|
||||
reversePalette([
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'blue', stop: 0.9 },
|
||||
])
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'red', stop: 0.9 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStepValue', () => {
|
||||
it('should compute the next step based on the last 2 stops', () => {
|
||||
expect(
|
||||
getStepValue(
|
||||
// first arg is taken as max reference
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
100
|
||||
)
|
||||
).toBe(50);
|
||||
|
||||
expect(
|
||||
getStepValue(
|
||||
// first arg is taken as max reference
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
90
|
||||
)
|
||||
).toBe(10); // 90 - 80
|
||||
|
||||
expect(
|
||||
getStepValue(
|
||||
// first arg is taken as max reference
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 100 },
|
||||
],
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
100
|
||||
)
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
191
packages/kbn-coloring/src/palettes/utils.ts
Normal file
191
packages/kbn-coloring/src/palettes/utils.ts
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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 {
|
||||
PaletteContinuity,
|
||||
PaletteRegistry,
|
||||
CustomPaletteParams,
|
||||
DataBounds,
|
||||
ColorStop,
|
||||
} from '../palettes/types';
|
||||
|
||||
import {
|
||||
DEFAULT_COLOR_STEPS,
|
||||
DEFAULT_PALETTE_NAME,
|
||||
DEFAULT_MAX_STOP,
|
||||
DEFAULT_MIN_STOP,
|
||||
} from './constants';
|
||||
|
||||
/** @internal **/
|
||||
export function calculateStop(
|
||||
stopValue: number,
|
||||
newMin: number,
|
||||
oldMin: number,
|
||||
oldInterval: number,
|
||||
newInterval: number
|
||||
) {
|
||||
if (oldInterval === 0) {
|
||||
return newInterval + newMin;
|
||||
}
|
||||
return roundValue(newMin + ((stopValue - oldMin) * newInterval) / oldInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a generic function to compute stops from the current parameters.
|
||||
*/
|
||||
export function getPaletteStops(
|
||||
palettes: PaletteRegistry,
|
||||
activePaletteParams: CustomPaletteParams,
|
||||
// used to customize color resolution
|
||||
{
|
||||
prevPalette,
|
||||
dataBounds,
|
||||
mapFromMinValue,
|
||||
defaultPaletteName,
|
||||
}: {
|
||||
prevPalette?: string;
|
||||
dataBounds: DataBounds;
|
||||
mapFromMinValue?: boolean;
|
||||
defaultPaletteName?: string;
|
||||
}
|
||||
) {
|
||||
const { min: minValue, max: maxValue } = getOverallMinMax(activePaletteParams, dataBounds);
|
||||
const interval = maxValue - minValue;
|
||||
const { stops: currentStops, ...otherParams } = activePaletteParams || {};
|
||||
|
||||
if (activePaletteParams.name === 'custom' && activePaletteParams?.colorStops) {
|
||||
// need to generate the palette from the existing controlStops
|
||||
return shiftPalette(activePaletteParams.colorStops, maxValue);
|
||||
}
|
||||
|
||||
const steps = activePaletteParams?.steps || DEFAULT_COLOR_STEPS;
|
||||
// generate a palette from predefined ones and customize the domain
|
||||
const colorStopsFromPredefined = palettes
|
||||
.get(prevPalette || activePaletteParams?.name || defaultPaletteName || DEFAULT_PALETTE_NAME)
|
||||
.getCategoricalColors(steps, otherParams);
|
||||
|
||||
const newStopsMin = mapFromMinValue || interval === 0 ? minValue : interval / steps;
|
||||
|
||||
return remapStopsByNewInterval(
|
||||
colorStopsFromPredefined.map((color, index) => ({ color, stop: index })),
|
||||
{
|
||||
newInterval: interval,
|
||||
oldInterval: colorStopsFromPredefined.length,
|
||||
newMin: newStopsMin,
|
||||
oldMin: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Utility to remap color stops within new domain
|
||||
export function remapStopsByNewInterval(
|
||||
controlStops: ColorStop[],
|
||||
{
|
||||
newInterval,
|
||||
oldInterval,
|
||||
newMin,
|
||||
oldMin,
|
||||
}: { newInterval: number; oldInterval: number; newMin: number; oldMin: number }
|
||||
) {
|
||||
return (controlStops || []).map(({ color, stop }) => {
|
||||
return {
|
||||
color,
|
||||
stop: calculateStop(stop, newMin, oldMin, oldInterval, newInterval),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Need to shift the Custom palette in order to correctly visualize it when in display mode
|
||||
export function shiftPalette(stops: ColorStop[], max: number) {
|
||||
// shift everything right and add an additional stop at the end
|
||||
const result = stops.map((entry, i, array) => ({
|
||||
...entry,
|
||||
stop: i + 1 < array.length ? array[i + 1].stop : max,
|
||||
}));
|
||||
|
||||
if (stops[stops.length - 1].stop === max) {
|
||||
// extends the range by a fair amount to make it work the extra case for the last stop === max
|
||||
const computedStep = getStepValue(stops, result, max) || 1;
|
||||
// do not go beyond the unit step in this case
|
||||
const step = Math.min(1, computedStep);
|
||||
result[stops.length - 1].stop = max + step;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getOverallMinMax(params: CustomPaletteParams | undefined, dataBounds: DataBounds) {
|
||||
const { min: dataMin, max: dataMax } = getDataMinMax(params?.rangeType, dataBounds);
|
||||
const minStopValue = params?.colorStops?.[0]?.stop ?? Number.POSITIVE_INFINITY;
|
||||
const maxStopValue =
|
||||
params?.colorStops?.[params.colorStops.length - 1]?.stop ?? Number.NEGATIVE_INFINITY;
|
||||
const overallMin = Math.min(dataMin, minStopValue);
|
||||
const overallMax = Math.max(dataMax, maxStopValue);
|
||||
return { min: overallMin, max: overallMax };
|
||||
}
|
||||
|
||||
export function roundValue(value: number, fractionDigits: number = 2) {
|
||||
return Number((Math.floor(value * 100) / 100).toFixed(fractionDigits));
|
||||
}
|
||||
|
||||
// very simple heuristic: pick last two stops and compute a new stop based on the same distance
|
||||
// if the new stop is above max, then reduce the step to reach max, or if zero then just 1.
|
||||
//
|
||||
// it accepts two series of stops as the function is used also when computing stops from colorStops
|
||||
export function getStepValue(colorStops: ColorStop[], newColorStops: ColorStop[], max: number) {
|
||||
const length = newColorStops.length;
|
||||
// workout the steps from the last 2 items
|
||||
const dataStep =
|
||||
length > 1 ? newColorStops[length - 1].stop - newColorStops[length - 2].stop || 1 : 1;
|
||||
let step = Number(dataStep.toFixed(2));
|
||||
if (max < colorStops[length - 1].stop + step) {
|
||||
const diffToMax = max - colorStops[length - 1].stop;
|
||||
// if the computed step goes way out of bound, fallback to 1, otherwise reach max
|
||||
step = diffToMax > 0 ? diffToMax : 1;
|
||||
}
|
||||
return step;
|
||||
}
|
||||
|
||||
export function getDataMinMax(
|
||||
rangeType: CustomPaletteParams['rangeType'] | undefined,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
const dataMin = rangeType === 'number' ? dataBounds.min : DEFAULT_MIN_STOP;
|
||||
const dataMax = rangeType === 'number' ? dataBounds.max : DEFAULT_MAX_STOP;
|
||||
return { min: dataMin, max: dataMax };
|
||||
}
|
||||
|
||||
export const checkIsMinContinuity = (continuity: PaletteContinuity | undefined) =>
|
||||
Boolean(continuity && ['below', 'all'].includes(continuity));
|
||||
|
||||
export const checkIsMaxContinuity = (continuity: PaletteContinuity | undefined) =>
|
||||
Boolean(continuity && ['above', 'all'].includes(continuity));
|
||||
|
||||
export const getFallbackDataBounds = (
|
||||
rangeType: CustomPaletteParams['rangeType'] = 'percent'
|
||||
): DataBounds =>
|
||||
rangeType === 'percent'
|
||||
? {
|
||||
min: 0,
|
||||
max: 100,
|
||||
fallback: true,
|
||||
}
|
||||
: {
|
||||
min: 1,
|
||||
max: 1,
|
||||
fallback: true,
|
||||
};
|
||||
|
||||
export function reversePalette(paletteColorRepresentation: ColorStop[] = []) {
|
||||
const stops = paletteColorRepresentation.map(({ stop }) => stop);
|
||||
return paletteColorRepresentation
|
||||
.map(({ color }, i) => ({
|
||||
color,
|
||||
stop: stops[paletteColorRepresentation.length - i - 1],
|
||||
}))
|
||||
.reverse();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { EuiForm } from '@elastic/eui';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { CustomizablePalette, CustomizablePaletteProps } from '../palette_configuration';
|
||||
import { getPaletteRegistry } from './palettes';
|
||||
|
||||
export default {
|
||||
title: 'CustomizablePalette',
|
||||
component: CustomizablePalette,
|
||||
decorators: [(story: Function) => <EuiForm>{story()}</EuiForm>],
|
||||
};
|
||||
|
||||
const Template: ComponentStory<FC<CustomizablePaletteProps>> = (args) => (
|
||||
<CustomizablePalette {...args} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
palettes: getPaletteRegistry(),
|
||||
activePalette: {
|
||||
type: 'palette',
|
||||
name: 'default',
|
||||
params: {
|
||||
steps: 1,
|
||||
maxSteps: 10,
|
||||
continuity: 'none',
|
||||
},
|
||||
},
|
||||
dataBounds: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
showExtraActions: true,
|
||||
showRangeTypeSelector: true,
|
||||
disableSwitchingContinuity: false,
|
||||
setPalette: () => {},
|
||||
};
|
|
@ -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 type { PaletteDefinition, SeriesLayer } from '../../../palettes/types';
|
||||
|
||||
const getMockedPalette = (
|
||||
id: string,
|
||||
title: string,
|
||||
colors: string[],
|
||||
canDynamicColoring: boolean = true
|
||||
): PaletteDefinition => {
|
||||
let counter = 0;
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
getCategoricalColor: (_: SeriesLayer[]) => {
|
||||
counter++;
|
||||
if (counter > colors.length - 1) counter = 0;
|
||||
return colors[counter];
|
||||
},
|
||||
canDynamicColoring,
|
||||
internal: true,
|
||||
getCategoricalColors: (num: number) => colors,
|
||||
toExpression: () => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['default'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const getPaletteRegistry = () => {
|
||||
const getMockedPalettes = (): Record<string, PaletteDefinition> => ({
|
||||
default: getMockedPalette('default', 'Default Palette', [
|
||||
'#54B399',
|
||||
'#6092C0',
|
||||
'#D36086',
|
||||
'#9170B8',
|
||||
'#CA8EAE',
|
||||
'#D6BF57',
|
||||
'#B9A888',
|
||||
'#DA8B45',
|
||||
'#AA6556',
|
||||
'#E7664C',
|
||||
]),
|
||||
positive: getMockedPalette('positive', 'Grey Palette', [
|
||||
'#222',
|
||||
'#333',
|
||||
'#444',
|
||||
'#555',
|
||||
'#666',
|
||||
'#777',
|
||||
'#888',
|
||||
'#999',
|
||||
'#AAA',
|
||||
'#BBB',
|
||||
]),
|
||||
foo: getMockedPalette('foo', 'Foo Palette', [
|
||||
'#7E7',
|
||||
'#7D7',
|
||||
'#7A7',
|
||||
'#797',
|
||||
'#787',
|
||||
'#777',
|
||||
'#767',
|
||||
'#757',
|
||||
'#747',
|
||||
'#737',
|
||||
]),
|
||||
custom: getMockedPalette('custom', 'Custom Palette', [], false),
|
||||
});
|
||||
|
||||
return {
|
||||
get: (name: string) => getMockedPalettes()[name],
|
||||
getAll: () => Object.values(getMockedPalettes()),
|
||||
};
|
||||
};
|
||||
|
||||
export const palettes = {
|
||||
getPalettes: async () => {
|
||||
return getPaletteRegistry();
|
||||
},
|
||||
};
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
@ -10,8 +11,8 @@ import React from 'react';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { ColorRanges, ColorRangesProps } from './color_ranges';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { ColorRangesContext } from './color_ranges_context';
|
||||
import type { PaletteRegistry } from '../../../palettes';
|
||||
|
||||
const extraActionSelectors = {
|
||||
addColor: '[data-test-subj^="lnsPalettePanel_dynamicColoring_addColor"]',
|
|
@ -1,12 +1,15 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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, { useState, useEffect, Dispatch } from 'react';
|
||||
import { camelCase } from 'lodash';
|
||||
import { EuiFlexGroup, EuiTextColor, EuiFlexItem } from '@elastic/eui';
|
||||
import { CustomPaletteParams, DEFAULT_CONTINUITY, DEFAULT_RANGE_TYPE } from '../../../palettes';
|
||||
|
||||
import { ColorRangesExtraActions } from './color_ranges_extra_actions';
|
||||
import { ColorRangeItem } from './color_ranges_item';
|
||||
|
@ -16,23 +19,20 @@ import {
|
|||
ColorRangeValidation,
|
||||
} from './color_ranges_validation';
|
||||
|
||||
import type { CustomPaletteParamsConfig } from '../../../../common';
|
||||
import type { ColorRange } from './types';
|
||||
import type { PaletteConfigurationActions } from '../types';
|
||||
|
||||
import { defaultPaletteParams } from '../constants';
|
||||
|
||||
export interface ColorRangesProps {
|
||||
colorRanges: ColorRange[];
|
||||
paletteConfiguration: CustomPaletteParamsConfig | undefined;
|
||||
showExtraActions?: boolean;
|
||||
paletteConfiguration: CustomPaletteParams | undefined;
|
||||
showExtraActions: boolean;
|
||||
dispatch: Dispatch<PaletteConfigurationActions>;
|
||||
}
|
||||
|
||||
export function ColorRanges({
|
||||
colorRanges,
|
||||
paletteConfiguration,
|
||||
showExtraActions = true,
|
||||
showExtraActions,
|
||||
dispatch,
|
||||
}: ColorRangesProps) {
|
||||
const [colorRangesValidity, setColorRangesValidity] = useState<
|
||||
|
@ -41,8 +41,8 @@ export function ColorRanges({
|
|||
|
||||
const lastColorRange = colorRanges[colorRanges.length - 1];
|
||||
const errors = getErrorMessages(colorRangesValidity);
|
||||
const continuity = paletteConfiguration?.continuity ?? defaultPaletteParams.continuity;
|
||||
const rangeType = paletteConfiguration?.rangeType ?? defaultPaletteParams.rangeType;
|
||||
const continuity = paletteConfiguration?.continuity ?? DEFAULT_CONTINUITY;
|
||||
const rangeType = paletteConfiguration?.rangeType ?? DEFAULT_RANGE_TYPE;
|
||||
|
||||
useEffect(() => {
|
||||
setColorRangesValidity(validateColorRanges(colorRanges));
|
|
@ -1,13 +1,13 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { DataBounds } from '../types';
|
||||
import type { PaletteRegistry, DataBounds } from '../../../palettes';
|
||||
|
||||
interface ColorRangesContextType {
|
||||
dataBounds: DataBounds;
|
|
@ -1,16 +1,18 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, Dispatch, useContext } from 'react';
|
||||
import { EuiFlexGroup, EuiButtonEmpty, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { DistributeEquallyIcon } from '../../../assets/distribute_equally';
|
||||
import { TooltipWrapper } from '../../index';
|
||||
import { DistributeEquallyIcon } from '../assets/distribute_equally';
|
||||
import { TooltipWrapper } from '../tooltip_wrapper';
|
||||
|
||||
import type { ColorRangesActions } from './types';
|
||||
import { ColorRangesContext } from './color_ranges_context';
|
||||
|
@ -44,19 +46,16 @@ export function ColorRangesExtraActions({
|
|||
dispatch({ type: 'distributeEqually', payload: { dataBounds, palettes } });
|
||||
}, [dataBounds, dispatch, palettes]);
|
||||
|
||||
const oneColorRangeWarn = i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.oneColorRange',
|
||||
{
|
||||
defaultMessage: `Requires more than one color`,
|
||||
}
|
||||
);
|
||||
const oneColorRangeWarn = i18n.translate('coloring.dynamicColoring.customPalette.oneColorRange', {
|
||||
defaultMessage: `Requires more than one color`,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexStart" gutterSize="none" wrap={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TooltipWrapper
|
||||
tooltipContent={i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.maximumStepsApplied',
|
||||
'coloring.dynamicColoring.customPalette.maximumStepsApplied',
|
||||
{
|
||||
defaultMessage: `You've applied the maximum number of steps`,
|
||||
}
|
||||
|
@ -69,19 +68,16 @@ export function ColorRangesExtraActions({
|
|||
data-test-subj={`lnsPalettePanel_dynamicColoring_addColor`}
|
||||
iconType="plusInCircle"
|
||||
color="primary"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.addColorAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Add color',
|
||||
}
|
||||
)}
|
||||
aria-label={i18n.translate('coloring.dynamicColoring.customPalette.addColorAriaLabel', {
|
||||
defaultMessage: 'Add color',
|
||||
})}
|
||||
size="xs"
|
||||
flush="left"
|
||||
disabled={shouldDisableAdd}
|
||||
onClick={onAddColorRange}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.dynamicColoring.customPalette.addColor"
|
||||
id="coloring.dynamicColoring.customPalette.addColor"
|
||||
defaultMessage="Add color"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -98,7 +94,7 @@ export function ColorRangesExtraActions({
|
|||
data-test-subj={`lnsPalettePanel_dynamicColoring_reverseColors`}
|
||||
iconType="sortable"
|
||||
color="primary"
|
||||
aria-label={i18n.translate('xpack.lens.dynamicColoring.customPaletteAriaLabel', {
|
||||
aria-label={i18n.translate('coloring.dynamicColoring.customPaletteAriaLabel', {
|
||||
defaultMessage: 'Reverse colors',
|
||||
})}
|
||||
size="xs"
|
||||
|
@ -107,7 +103,7 @@ export function ColorRangesExtraActions({
|
|||
disabled={shouldDisableReverse}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.dynamicColoring.customPalette.reverseColors"
|
||||
id="coloring.dynamicColoring.customPalette.reverseColors"
|
||||
defaultMessage="Reverse colors"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -125,7 +121,7 @@ export function ColorRangesExtraActions({
|
|||
iconType={DistributeEquallyIcon}
|
||||
color="primary"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.distributeValuesAriaLabel',
|
||||
'coloring.dynamicColoring.customPalette.distributeValuesAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Distribute values',
|
||||
}
|
||||
|
@ -136,7 +132,7 @@ export function ColorRangesExtraActions({
|
|||
onClick={onDistributeValues}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.dynamicColoring.customPalette.distributeValues"
|
||||
id="coloring.dynamicColoring.customPalette.distributeValues"
|
||||
defaultMessage="Distribute values"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
@ -20,7 +21,14 @@ import {
|
|||
EuiFieldNumberProps,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { RelatedIcon } from '../../../assets/related';
|
||||
import {
|
||||
PaletteContinuity,
|
||||
checkIsMaxContinuity,
|
||||
checkIsMinContinuity,
|
||||
CustomPaletteParams,
|
||||
} from '../../../palettes';
|
||||
|
||||
import { RelatedIcon } from '../assets/related';
|
||||
import { isLastItem } from './utils';
|
||||
import { isValidColor } from '../utils';
|
||||
import {
|
||||
|
@ -32,12 +40,6 @@ import {
|
|||
import type { ColorRange, ColorRangeAccessor, ColorRangesActions } from './types';
|
||||
import { ColorRangesContext } from './color_ranges_context';
|
||||
import type { ColorRangeValidation } from './color_ranges_validation';
|
||||
import type { CustomPaletteParams } from '../../../../common';
|
||||
import {
|
||||
PaletteContinuity,
|
||||
checkIsMaxContinuity,
|
||||
checkIsMinContinuity,
|
||||
} from '../../../../../../../src/plugins/charts/common';
|
||||
|
||||
export interface ColorRangesItemProps {
|
||||
colorRange: ColorRange;
|
||||
|
@ -65,10 +67,10 @@ const getMode = (
|
|||
|
||||
const getPlaceholderForAutoMode = (isLast: boolean) =>
|
||||
isLast
|
||||
? i18n.translate('xpack.lens.dynamicColoring.customPalette.maxValuePlaceholder', {
|
||||
? i18n.translate('coloring.dynamicColoring.customPalette.maxValuePlaceholder', {
|
||||
defaultMessage: 'Max. value',
|
||||
})
|
||||
: i18n.translate('xpack.lens.dynamicColoring.customPalette.minValuePlaceholder', {
|
||||
: i18n.translate('coloring.dynamicColoring.customPalette.minValuePlaceholder', {
|
||||
defaultMessage: 'Min. value',
|
||||
});
|
||||
|
||||
|
@ -154,14 +156,14 @@ export function ColorRangeItem({
|
|||
}, [localValue, colorRange, accessor]);
|
||||
|
||||
const selectNewColorText = i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.selectNewColor',
|
||||
'coloring.dynamicColoring.customPalette.selectNewColor',
|
||||
{
|
||||
defaultMessage: 'Select a new color',
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" wrap={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" wrap={false} responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!isLast ? (
|
||||
<EuiColorPicker
|
||||
|
@ -206,7 +208,7 @@ export function ColorRangeItem({
|
|||
onBlur={onLeaveFocus}
|
||||
data-test-subj={`lnsPalettePanel_dynamicColoring_range_value_${index}`}
|
||||
prepend={<span className="euiFormLabel">{isLast ? '\u2264' : '\u2265'}</span>}
|
||||
aria-label={i18n.translate('xpack.lens.dynamicColoring.customPalette.rangeAriaLabel', {
|
||||
aria-label={i18n.translate('coloring.dynamicColoring.customPalette.rangeAriaLabel', {
|
||||
defaultMessage: 'Range {index}',
|
||||
values: {
|
||||
index: index + 1,
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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, { Dispatch, useCallback, useContext } from 'react';
|
||||
|
@ -10,15 +11,15 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { EuiButtonIcon } from '@elastic/eui';
|
||||
|
||||
import { ValueMaxIcon } from '../../../assets/value_max';
|
||||
import { ValueMinIcon } from '../../../assets/value_min';
|
||||
import type { PaletteContinuity, CustomPaletteParams } from '../../../palettes';
|
||||
|
||||
import { ValueMaxIcon } from '../assets/value_max';
|
||||
import { ValueMinIcon } from '../assets/value_min';
|
||||
import { isLastItem } from './utils';
|
||||
import { TooltipWrapper } from '../../index';
|
||||
import { TooltipWrapper } from '../tooltip_wrapper';
|
||||
|
||||
import type { ColorRangesActions, ColorRange, ColorRangeAccessor } from './types';
|
||||
import { ColorRangesContext } from './color_ranges_context';
|
||||
import type { CustomPaletteParams } from '../../../../common';
|
||||
import type { PaletteContinuity } from '../../../../../../../src/plugins/charts/common';
|
||||
|
||||
export interface ColorRangesItemButtonProps {
|
||||
index: number;
|
||||
|
@ -48,7 +49,7 @@ export function ColorRangeDeleteButton({ index, dispatch }: ColorRangesItemButto
|
|||
dispatch({ type: 'deleteColorRange', payload: { index, dataBounds, palettes } });
|
||||
}, [dispatch, index, dataBounds, palettes]);
|
||||
|
||||
const title = i18n.translate('xpack.lens.dynamicColoring.customPalette.deleteButtonAriaLabel', {
|
||||
const title = i18n.translate('coloring.dynamicColoring.customPalette.deleteButtonAriaLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
});
|
||||
|
||||
|
@ -83,16 +84,16 @@ export function ColorRangeEditButton({
|
|||
}, [isLast, dispatch, continuity, dataBounds, palettes]);
|
||||
|
||||
let tooltipContent = isLast
|
||||
? i18n.translate('xpack.lens.dynamicColoring.customPalette.setCustomMinValue', {
|
||||
? i18n.translate('coloring.dynamicColoring.customPalette.setCustomMinValue', {
|
||||
defaultMessage: `Set custom maximum value`,
|
||||
})
|
||||
: i18n.translate('xpack.lens.dynamicColoring.customPalette.setCustomMaxValue', {
|
||||
: i18n.translate('coloring.dynamicColoring.customPalette.setCustomMaxValue', {
|
||||
defaultMessage: `Set custom minimum value`,
|
||||
});
|
||||
|
||||
if (disableSwitchingContinuity) {
|
||||
tooltipContent = i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.disallowedEditMinMaxValues',
|
||||
'coloring.dynamicColoring.customPalette.disallowedEditMinMaxValues',
|
||||
{
|
||||
defaultMessage: `You cannot set custom value for current configuration`,
|
||||
}
|
||||
|
@ -130,10 +131,10 @@ export function ColorRangeAutoDetectButton({
|
|||
}, [continuity, dataBounds, dispatch, isLast, palettes]);
|
||||
|
||||
const tooltipContent = isLast
|
||||
? i18n.translate('xpack.lens.dynamicColoring.customPalette.useAutoMaxValue', {
|
||||
? i18n.translate('coloring.dynamicColoring.customPalette.useAutoMaxValue', {
|
||||
defaultMessage: `Use maximum data value`,
|
||||
})
|
||||
: i18n.translate('xpack.lens.dynamicColoring.customPalette.useAutoMinValue', {
|
||||
: i18n.translate('coloring.dynamicColoring.customPalette.useAutoMinValue', {
|
||||
defaultMessage: `Use minimum data value`,
|
||||
});
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { validateColorRanges, isAllColorRangesValid } from './color_ranges_validation';
|
|
@ -1,9 +1,11 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { isValidColor } from '../utils';
|
||||
|
||||
|
@ -20,8 +22,8 @@ export interface ColorRangeValidation {
|
|||
|
||||
/** @internal **/
|
||||
export const getErrorMessages = (colorRangesValidity: Record<string, ColorRangeValidation>) => {
|
||||
return [
|
||||
...new Set(
|
||||
return Array.from(
|
||||
new Set(
|
||||
Object.values(colorRangesValidity)
|
||||
.map((item) => item.errors)
|
||||
.flat()
|
||||
|
@ -29,22 +31,19 @@ export const getErrorMessages = (colorRangesValidity: Record<string, ColorRangeV
|
|||
switch (item) {
|
||||
case 'invalidColor':
|
||||
case 'invalidValue':
|
||||
return i18n.translate(
|
||||
'xpack.lens.dynamicColoring.customPalette.invalidValueOrColor',
|
||||
{
|
||||
defaultMessage: 'At least one color range contains the wrong value or color',
|
||||
}
|
||||
);
|
||||
return i18n.translate('coloring.dynamicColoring.customPalette.invalidValueOrColor', {
|
||||
defaultMessage: 'At least one color range contains the wrong value or color',
|
||||
});
|
||||
case 'greaterThanMaxValue':
|
||||
return i18n.translate('xpack.lens.dynamicColoring.customPalette.invalidMaxValue', {
|
||||
return i18n.translate('coloring.dynamicColoring.customPalette.invalidMaxValue', {
|
||||
defaultMessage: 'Maximum value must be greater than preceding values',
|
||||
});
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
})
|
||||
),
|
||||
];
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/** @internal **/
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ColorRanges } from './color_ranges';
|
|
@ -1,13 +1,17 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { CustomPaletteParams } from '../../../../common';
|
||||
import type { PaletteContinuity } from '../../../../../../../src/plugins/charts/common';
|
||||
import type { DataBounds } from '../types';
|
||||
|
||||
import type {
|
||||
PaletteContinuity,
|
||||
PaletteRegistry,
|
||||
DataBounds,
|
||||
CustomPaletteParams,
|
||||
} from '../../../palettes';
|
||||
|
||||
export interface ColorRange {
|
||||
color: string;
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 {
|
|
@ -1,14 +1,13 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { getDataMinMax, roundValue } from '../../utils';
|
||||
|
||||
import { DataBounds, roundValue, getDataMinMax, CustomPaletteParams } from '../../../../palettes';
|
||||
import type { ColorRange, ColorRangeAccessor } from '../types';
|
||||
import type { DataBounds } from '../../types';
|
||||
import type { CustomPaletteParamsConfig } from '../../../../../common';
|
||||
|
||||
/**
|
||||
* Allows to update a ColorRange
|
||||
|
@ -30,7 +29,7 @@ const updateColorRangeItem = (
|
|||
*/
|
||||
export const addColorRange = (
|
||||
colorRanges: ColorRange[],
|
||||
rangeType: CustomPaletteParamsConfig['rangeType'],
|
||||
rangeType: CustomPaletteParams['rangeType'],
|
||||
dataBounds: DataBounds
|
||||
) => {
|
||||
let newColorRanges = [...colorRanges];
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { distributeEqually, reversePalette } from './color_ranges_extra_actions';
|
|
@ -1,20 +1,22 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { getDataMinMax, roundValue } from '../../utils';
|
||||
import {
|
||||
checkIsMaxContinuity,
|
||||
checkIsMinContinuity,
|
||||
PaletteContinuity,
|
||||
DataBounds,
|
||||
roundValue,
|
||||
getDataMinMax,
|
||||
CustomPaletteParams,
|
||||
} from '../../../../palettes';
|
||||
|
||||
import type { ColorRange } from '../types';
|
||||
import type { DataBounds } from '../../types';
|
||||
import type { CustomPaletteParamsConfig } from '../../../../../common';
|
||||
import {
|
||||
PaletteContinuity,
|
||||
checkIsMinContinuity,
|
||||
checkIsMaxContinuity,
|
||||
} from '../../../../../../../../src/plugins/charts/common';
|
||||
|
||||
/**
|
||||
* Distribute equally
|
||||
|
@ -22,7 +24,7 @@ import {
|
|||
*/
|
||||
export const distributeEqually = (
|
||||
colorRanges: ColorRange[],
|
||||
rangeType: CustomPaletteParamsConfig['rangeType'],
|
||||
rangeType: CustomPaletteParams['rangeType'],
|
||||
continuity: PaletteContinuity,
|
||||
dataBounds: DataBounds
|
||||
) => {
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './utils';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { sortColorRanges, calculateMaxStep, toColorStops, getValueForContinuity } from './utils';
|
|
@ -1,18 +1,22 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { roundValue, getDataMinMax } from '../../utils';
|
||||
|
||||
import {
|
||||
PaletteContinuity,
|
||||
checkIsMaxContinuity,
|
||||
checkIsMinContinuity,
|
||||
} from '../../../../../../../../src/plugins/charts/common';
|
||||
import type { CustomPaletteParams } from '../../../../../common';
|
||||
DataBounds,
|
||||
roundValue,
|
||||
getDataMinMax,
|
||||
CustomPaletteParams,
|
||||
} from '../../../../palettes';
|
||||
|
||||
import type { ColorRange, ColorRangeAccessor } from '../types';
|
||||
import type { DataBounds } from '../../types';
|
||||
|
||||
/**
|
||||
* Check if item is last
|
|
@ -6,10 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PaletteContinuity } from '../../types';
|
||||
import { CustomizablePalette } from './palette_configuration';
|
||||
|
||||
export const checkIsMinContinuity = (continuity: PaletteContinuity | undefined) =>
|
||||
Boolean(continuity && ['below', 'all'].includes(continuity));
|
||||
|
||||
export const checkIsMaxContinuity = (continuity: PaletteContinuity | undefined) =>
|
||||
Boolean(continuity && ['above', 'all'].includes(continuity));
|
||||
// React.lazy support
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default CustomizablePalette;
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 type { PaletteDefinition, SeriesLayer } from '../../../palettes/types';
|
||||
|
||||
export const getPaletteRegistry = () => {
|
||||
const mockPalette1: jest.Mocked<PaletteDefinition> = {
|
||||
id: 'default',
|
||||
title: 'My Palette',
|
||||
getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'black'),
|
||||
getCategoricalColors: jest.fn((num: number) => ['red', 'black']),
|
||||
toExpression: jest.fn(() => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['default'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
const mockPalette2: jest.Mocked<PaletteDefinition> = {
|
||||
id: 'mocked',
|
||||
title: 'Mocked Palette',
|
||||
getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'blue'),
|
||||
getCategoricalColors: jest.fn((num: number) => ['blue', 'yellow']),
|
||||
toExpression: jest.fn(() => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['mocked'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
const mockPalette3: jest.Mocked<PaletteDefinition> = {
|
||||
id: 'custom',
|
||||
title: 'Custom Mocked Palette',
|
||||
getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'blue'),
|
||||
getCategoricalColors: jest.fn((num: number) => ['blue', 'yellow']),
|
||||
getColorForValue: jest.fn(
|
||||
(num: number | undefined, state: unknown, minMax: { min: number; max: number }) =>
|
||||
num == null || num < 1 ? undefined : 'blue'
|
||||
),
|
||||
canDynamicColoring: true,
|
||||
toExpression: jest.fn(() => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'system_palette',
|
||||
arguments: {
|
||||
name: ['mocked'],
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
|
||||
return {
|
||||
get: (name: string) =>
|
||||
name === 'custom' ? mockPalette3 : name !== 'default' ? mockPalette2 : mockPalette1,
|
||||
getAll: () => [mockPalette1, mockPalette2, mockPalette3],
|
||||
};
|
||||
};
|
|
@ -1,20 +1,24 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { EuiButtonGroup, EuiColorPalettePickerPaletteProps } from '@elastic/eui';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
|
||||
import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import type { CustomPaletteParams } from '../../../common';
|
||||
import type {
|
||||
PaletteOutput,
|
||||
PaletteRegistry,
|
||||
DataBounds,
|
||||
CustomPaletteParams,
|
||||
} from '../../palettes';
|
||||
import { CustomizablePalette } from './palette_configuration';
|
||||
import { getPaletteRegistry } from './mocks/palettes_registry';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import type { DataBounds } from './types';
|
||||
|
||||
// mocking random id generator function
|
||||
jest.mock('@elastic/eui', () => {
|
||||
|
@ -40,7 +44,7 @@ jest.mock('./color_ranges/color_ranges_validation', () => {
|
|||
});
|
||||
|
||||
describe('palette panel', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
const paletteRegistry = getPaletteRegistry();
|
||||
let props: {
|
||||
palettes: PaletteRegistry;
|
||||
activePalette: PaletteOutput<CustomPaletteParams>;
|
|
@ -1,41 +1,50 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import React, { useReducer, useMemo } from 'react';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { EuiFormRow, htmlIdGenerator, EuiButtonGroup, EuiIconTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PalettePicker } from './palette_picker';
|
||||
import type { DataBounds } from './types';
|
||||
import {
|
||||
PaletteOutput,
|
||||
PaletteRegistry,
|
||||
DataBounds,
|
||||
getFallbackDataBounds,
|
||||
CustomPaletteParams,
|
||||
RequiredPaletteParamTypes,
|
||||
} from '../../palettes';
|
||||
|
||||
import './palette_configuration.scss';
|
||||
|
||||
import type { CustomPaletteParams, RequiredPaletteParamTypes } from '../../../common';
|
||||
import { toColorRanges, getFallbackDataBounds } from './utils';
|
||||
import { toColorRanges } from './utils';
|
||||
import { ColorRanges, ColorRangesContext } from './color_ranges';
|
||||
import { isAllColorRangesValid } from './color_ranges/color_ranges_validation';
|
||||
import { paletteConfigurationReducer } from './palette_configuration_reducer';
|
||||
|
||||
export function CustomizablePalette({
|
||||
palettes,
|
||||
activePalette,
|
||||
setPalette,
|
||||
dataBounds = getFallbackDataBounds(activePalette.params?.rangeType),
|
||||
showRangeTypeSelector = true,
|
||||
disableSwitchingContinuity = false,
|
||||
}: {
|
||||
export interface CustomizablePaletteProps {
|
||||
palettes: PaletteRegistry;
|
||||
activePalette: PaletteOutput<CustomPaletteParams>;
|
||||
setPalette: (palette: PaletteOutput<CustomPaletteParams>) => void;
|
||||
dataBounds?: DataBounds;
|
||||
showRangeTypeSelector?: boolean;
|
||||
disableSwitchingContinuity?: boolean;
|
||||
}) {
|
||||
showExtraActions?: boolean;
|
||||
}
|
||||
|
||||
export const CustomizablePalette = ({
|
||||
palettes,
|
||||
activePalette,
|
||||
setPalette,
|
||||
dataBounds = getFallbackDataBounds(activePalette.params?.rangeType),
|
||||
showExtraActions = true,
|
||||
showRangeTypeSelector = true,
|
||||
disableSwitchingContinuity = false,
|
||||
}: CustomizablePaletteProps) => {
|
||||
const idPrefix = useMemo(() => htmlIdGenerator()(), []);
|
||||
const colorRangesToShow = toColorRanges(
|
||||
palettes,
|
||||
|
@ -67,7 +76,7 @@ export function CustomizablePalette({
|
|||
<div className="lnsPalettePanel__section lnsPalettePanel__section--shaded">
|
||||
<EuiFormRow
|
||||
display="rowCompressed"
|
||||
label={i18n.translate('xpack.lens.palettePicker.label', {
|
||||
label={i18n.translate('coloring.dynamicColoring.palettePicker.label', {
|
||||
defaultMessage: 'Color palette',
|
||||
})}
|
||||
fullWidth
|
||||
|
@ -93,12 +102,12 @@ export function CustomizablePalette({
|
|||
<EuiFormRow
|
||||
label={
|
||||
<>
|
||||
{i18n.translate('xpack.lens.table.dynamicColoring.rangeType.label', {
|
||||
{i18n.translate('coloring.dynamicColoring.rangeType.label', {
|
||||
defaultMessage: 'Value type',
|
||||
})}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.lens.table.dynamicColoring.customPalette.colorStopsHelpPercentage',
|
||||
'coloring.dynamicColoring.customPalette.colorStopsHelpPercentage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Percent value types are relative to the full range of available data values.',
|
||||
|
@ -112,7 +121,7 @@ export function CustomizablePalette({
|
|||
display="rowCompressed"
|
||||
>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('xpack.lens.table.dynamicColoring.rangeType.label', {
|
||||
legend={i18n.translate('coloring.dynamicColoring.rangeType.label', {
|
||||
defaultMessage: 'Value type',
|
||||
})}
|
||||
data-test-subj="lnsPalettePanel_dynamicColoring_custom_range_groups"
|
||||
|
@ -121,14 +130,14 @@ export function CustomizablePalette({
|
|||
options={[
|
||||
{
|
||||
id: `${idPrefix}percent`,
|
||||
label: i18n.translate('xpack.lens.table.dynamicColoring.rangeType.percent', {
|
||||
label: i18n.translate('coloring.dynamicColoring.rangeType.percent', {
|
||||
defaultMessage: 'Percent',
|
||||
}),
|
||||
'data-test-subj': 'lnsPalettePanel_dynamicColoring_rangeType_groups_percent',
|
||||
},
|
||||
{
|
||||
id: `${idPrefix}number`,
|
||||
label: i18n.translate('xpack.lens.table.dynamicColoring.rangeType.number', {
|
||||
label: i18n.translate('coloring.dynamicColoring.rangeType.number', {
|
||||
defaultMessage: 'Number',
|
||||
}),
|
||||
'data-test-subj': 'lnsPalettePanel_dynamicColoring_rangeType_groups_number',
|
||||
|
@ -155,7 +164,7 @@ export function CustomizablePalette({
|
|||
</EuiFormRow>
|
||||
)}
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.lens.palettePicker.colorRangesLabel', {
|
||||
label={i18n.translate('coloring.dynamicColoring.palettePicker.colorRangesLabel', {
|
||||
defaultMessage: 'Color Ranges',
|
||||
})}
|
||||
display="rowCompressed"
|
||||
|
@ -169,6 +178,7 @@ export function CustomizablePalette({
|
|||
}}
|
||||
>
|
||||
<ColorRanges
|
||||
showExtraActions={showExtraActions}
|
||||
paletteConfiguration={localState.activePalette?.params}
|
||||
colorRanges={localState.colorRanges}
|
||||
dispatch={dispatch}
|
||||
|
@ -177,4 +187,4 @@ export function CustomizablePalette({
|
|||
</EuiFormRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -1,11 +1,13 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 type { Reducer } from 'react';
|
||||
import { DEFAULT_CONTINUITY, DEFAULT_RANGE_TYPE } from '../../palettes';
|
||||
import {
|
||||
addColorRange,
|
||||
deleteColorRange,
|
||||
|
@ -16,7 +18,6 @@ import {
|
|||
updateColorRangeValue,
|
||||
getValueForContinuity,
|
||||
} from './color_ranges/utils';
|
||||
import { DEFAULT_CONTINUITY, DEFAULT_RANGE_TYPE } from './constants';
|
||||
|
||||
import {
|
||||
mergePaletteParams,
|
|
@ -1,20 +1,22 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui';
|
||||
import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import {
|
||||
PaletteOutput,
|
||||
PaletteRegistry,
|
||||
CustomPaletteParams,
|
||||
CUSTOM_PALETTE,
|
||||
DEFAULT_COLOR_STEPS,
|
||||
FIXED_PROGRESSION,
|
||||
defaultPaletteParams,
|
||||
} from './constants';
|
||||
import type { CustomPaletteParams } from '../../../common';
|
||||
DEFAULT_PALETTE_NAME,
|
||||
} from '../../palettes';
|
||||
|
||||
function getCustomPaletteConfig(
|
||||
palettes: PaletteRegistry,
|
||||
|
@ -105,7 +107,7 @@ export function PalettePicker({
|
|||
name: newPalette,
|
||||
});
|
||||
}}
|
||||
valueOfSelected={activePalette?.name || defaultPaletteParams.name}
|
||||
valueOfSelected={activePalette?.name || DEFAULT_PALETTE_NAME}
|
||||
selectionDisplay="palette"
|
||||
{...rest}
|
||||
/>
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { EuiToolTip, EuiToolTipProps } from '@elastic/eui';
|
||||
|
||||
export type TooltipWrapperProps = Partial<Omit<EuiToolTipProps, 'content'>> & {
|
||||
tooltipContent: string;
|
||||
/** When the condition is truthy, the tooltip will be shown */
|
||||
condition: boolean;
|
||||
};
|
||||
|
||||
export const TooltipWrapper: React.FunctionComponent<TooltipWrapperProps> = ({
|
||||
children,
|
||||
condition,
|
||||
tooltipContent,
|
||||
...tooltipProps
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{condition ? (
|
||||
<EuiToolTip content={tooltipContent} delay="long" {...tooltipProps}>
|
||||
<>{children}</>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,26 +1,25 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { CustomPaletteParams } from '../../../common';
|
||||
import type {
|
||||
PaletteOutput,
|
||||
PaletteRegistry,
|
||||
DataBounds,
|
||||
CustomPaletteParams,
|
||||
} from '../../palettes';
|
||||
import type { ColorRange, ColorRangesActions } from './color_ranges';
|
||||
|
||||
/** @internal **/
|
||||
export interface PaletteConfigurationState {
|
||||
activePalette: PaletteOutput<CustomPaletteParams>;
|
||||
colorRanges: ColorRange[];
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
export interface DataBounds {
|
||||
min: number;
|
||||
max: number;
|
||||
fallback?: boolean;
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
export interface UpdateRangeTypePayload {
|
||||
rangeType: CustomPaletteParams['rangeType'];
|
||||
|
@ -36,6 +35,7 @@ export interface ChangeColorPalettePayload {
|
|||
disableSwitchingContinuity: boolean;
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
export type PaletteConfigurationActions =
|
||||
| ColorRangesActions
|
||||
| { type: 'updateRangeType'; payload: UpdateRangeTypePayload }
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* 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 {
|
||||
getColorStops,
|
||||
isValidColor,
|
||||
mergePaletteParams,
|
||||
updateRangeType,
|
||||
changeColorPalette,
|
||||
} from './utils';
|
||||
|
||||
import { getPaletteRegistry } from './mocks/palettes_registry';
|
||||
|
||||
describe('getColorStops', () => {
|
||||
const paletteRegistry = getPaletteRegistry();
|
||||
it('should return the same colorStops if a custom palette is passed, avoiding recomputation', () => {
|
||||
const colorStops = [
|
||||
{ stop: 0, color: 'red' },
|
||||
{ stop: 100, color: 'blue' },
|
||||
];
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
colorStops,
|
||||
{ name: 'custom', type: 'palette' },
|
||||
{ min: 0, max: 100 }
|
||||
)
|
||||
).toBe(colorStops);
|
||||
});
|
||||
|
||||
it('should get a fresh list of colors', () => {
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
[
|
||||
{ stop: 0, color: 'red' },
|
||||
{ stop: 100, color: 'blue' },
|
||||
],
|
||||
{ name: 'mocked', type: 'palette' },
|
||||
{ min: 0, max: 100 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 0 },
|
||||
{ color: 'yellow', stop: 50 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should get a fresh list of colors even if custom palette but empty colorStops', () => {
|
||||
expect(
|
||||
getColorStops(paletteRegistry, [], { name: 'mocked', type: 'palette' }, { min: 0, max: 100 })
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 0 },
|
||||
{ color: 'yellow', stop: 50 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly map the new colorStop to the current data bound and minValue', () => {
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
[],
|
||||
{ name: 'mocked', type: 'palette', params: { rangeType: 'number' } },
|
||||
{ min: 100, max: 1000 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 100 },
|
||||
{ color: 'yellow', stop: 550 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reverse the colors', () => {
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
[],
|
||||
{ name: 'mocked', type: 'palette', params: { reverse: true } },
|
||||
{ min: 100, max: 1000 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'yellow', stop: 0 },
|
||||
{ color: 'blue', stop: 50 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergePaletteParams', () => {
|
||||
it('should return a full palette', () => {
|
||||
expect(mergePaletteParams({ type: 'palette', name: 'myPalette' }, { reverse: true })).toEqual({
|
||||
type: 'palette',
|
||||
name: 'myPalette',
|
||||
params: { reverse: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidColor', () => {
|
||||
it('should return ok for valid hex color notation', () => {
|
||||
expect(isValidColor('#fff')).toBe(true);
|
||||
expect(isValidColor('#ffffff')).toBe(true);
|
||||
expect(isValidColor('#ffffffaa')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non valid strings', () => {
|
||||
expect(isValidColor('')).toBe(false);
|
||||
expect(isValidColor('#')).toBe(false);
|
||||
expect(isValidColor('#ff')).toBe(false);
|
||||
expect(isValidColor('123')).toBe(false);
|
||||
expect(isValidColor('rgb(1, 1, 1)')).toBe(false);
|
||||
expect(isValidColor('rgba(1, 1, 1, 0)')).toBe(false);
|
||||
expect(isValidColor('#ffffffgg')).toBe(false);
|
||||
expect(isValidColor('#fff00')).toBe(false);
|
||||
// this version of chroma does not support hex4 format
|
||||
expect(isValidColor('#fffa')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRangeType', () => {
|
||||
const paletteRegistry = getPaletteRegistry();
|
||||
const colorRanges = [
|
||||
{
|
||||
start: 0,
|
||||
end: 40,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
start: 40,
|
||||
end: 80,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
start: 80,
|
||||
end: 100,
|
||||
color: 'red',
|
||||
},
|
||||
];
|
||||
it('should correctly update palette params with new range type if continuity is none', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'none',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: 0,
|
||||
rangeMax: 200,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly update palette params with new range type if continuity is all', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'all',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: Number.NEGATIVE_INFINITY,
|
||||
rangeMax: Number.POSITIVE_INFINITY,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly update palette params with new range type if continuity is below', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'below',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: Number.NEGATIVE_INFINITY,
|
||||
rangeMax: 200,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly update palette params with new range type if continuity is above', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'above',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: 0,
|
||||
rangeMax: Number.POSITIVE_INFINITY,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeColorPalette', () => {
|
||||
const paletteRegistry = getPaletteRegistry();
|
||||
|
||||
it('should correct update params for new palette', () => {
|
||||
const newPaletteParams = changeColorPalette(
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'default',
|
||||
},
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'above',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
paletteRegistry,
|
||||
{ min: 0, max: 200 },
|
||||
false
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
name: 'default',
|
||||
type: 'palette',
|
||||
params: {
|
||||
rangeType: 'percent',
|
||||
name: 'default',
|
||||
continuity: 'above',
|
||||
rangeMin: 0,
|
||||
rangeMax: Number.POSITIVE_INFINITY,
|
||||
reverse: false,
|
||||
colorStops: undefined,
|
||||
stops: [
|
||||
{
|
||||
color: 'red',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'black',
|
||||
stop: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
371
packages/kbn-coloring/src/shared_components/coloring/utils.ts
Normal file
371
packages/kbn-coloring/src/shared_components/coloring/utils.ts
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* 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 chroma from 'chroma-js';
|
||||
|
||||
import {
|
||||
DataBounds,
|
||||
PaletteOutput,
|
||||
PaletteRegistry,
|
||||
checkIsMinContinuity,
|
||||
reversePalette,
|
||||
checkIsMaxContinuity,
|
||||
calculateStop,
|
||||
roundValue,
|
||||
getPaletteStops,
|
||||
getDataMinMax,
|
||||
CustomPaletteParams,
|
||||
CUSTOM_PALETTE,
|
||||
DEFAULT_RANGE_TYPE,
|
||||
DEFAULT_COLOR_STEPS,
|
||||
DEFAULT_CONTINUITY,
|
||||
} from '../../palettes';
|
||||
|
||||
import type { ColorRange } from './color_ranges';
|
||||
import { toColorStops, sortColorRanges } from './color_ranges/utils';
|
||||
import type { PaletteConfigurationState } from './types';
|
||||
|
||||
/**
|
||||
* Some name conventions here:
|
||||
* * `displayStops` => It's an additional transformation of `stops` into a [0, N] domain for the EUIPaletteDisplay component.
|
||||
* * `stops` => final steps used to table coloring. It is a rightShift of the colorStops
|
||||
* * `colorStops` => user's color stop inputs. Used to compute range min.
|
||||
*
|
||||
* When the user inputs the colorStops, they are designed to be the initial part of the color segment,
|
||||
* so the next stops indicate where the previous stop ends.
|
||||
* Both table coloring logic and EuiPaletteDisplay format implementation works differently than our current `colorStops`,
|
||||
* by having the stop values at the end of each color segment rather than at the beginning: `stops` values are computed by a rightShift of `colorStops`.
|
||||
* EuiPaletteDisplay has an additional requirement as it is always mapped against a domain [0, N]: from `stops` the `displayStops` are computed with
|
||||
* some continuity enrichment and a remap against a [0, 100] domain to make the palette component work ok.
|
||||
*
|
||||
* These naming conventions would be useful to track the code flow in this feature as multiple transformations are happening
|
||||
* for a single change.
|
||||
*/
|
||||
|
||||
export function updateRangeType(
|
||||
newRangeType: CustomPaletteParams['rangeType'],
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
dataBounds: DataBounds,
|
||||
palettes: PaletteRegistry,
|
||||
colorRanges: PaletteConfigurationState['colorRanges']
|
||||
) {
|
||||
const continuity = activePalette.params?.continuity ?? DEFAULT_CONTINUITY;
|
||||
const params: CustomPaletteParams = { rangeType: newRangeType };
|
||||
const { min: newMin, max: newMax } = getDataMinMax(newRangeType, dataBounds);
|
||||
const { min: oldMin, max: oldMax } = getDataMinMax(activePalette.params?.rangeType, dataBounds);
|
||||
const newColorStops = getStopsFromColorRangesByNewInterval(colorRanges, {
|
||||
oldInterval: oldMax - oldMin,
|
||||
newInterval: newMax - newMin,
|
||||
newMin,
|
||||
oldMin,
|
||||
});
|
||||
|
||||
if (activePalette.name === CUSTOM_PALETTE) {
|
||||
const stops = getPaletteStops(
|
||||
palettes,
|
||||
{ ...activePalette.params, colorStops: newColorStops, ...params },
|
||||
{ dataBounds }
|
||||
);
|
||||
params.colorStops = newColorStops;
|
||||
params.stops = stops;
|
||||
} else {
|
||||
params.stops = getPaletteStops(
|
||||
palettes,
|
||||
{ ...activePalette.params, ...params },
|
||||
{ prevPalette: activePalette.name, dataBounds }
|
||||
);
|
||||
}
|
||||
|
||||
const lastStop =
|
||||
activePalette.name === CUSTOM_PALETTE
|
||||
? newColorStops[newColorStops.length - 1].stop
|
||||
: params.stops[params.stops.length - 1].stop;
|
||||
|
||||
params.rangeMin = checkIsMinContinuity(continuity)
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: activePalette.name === CUSTOM_PALETTE
|
||||
? newColorStops[0].stop
|
||||
: params.stops[0].stop;
|
||||
|
||||
params.rangeMax = checkIsMaxContinuity(continuity)
|
||||
? Number.POSITIVE_INFINITY
|
||||
: activePalette.params?.rangeMax
|
||||
? calculateStop(activePalette.params.rangeMax, newMin, oldMin, oldMax - oldMin, newMax - newMin)
|
||||
: lastStop > newMax
|
||||
? lastStop + 1
|
||||
: newMax;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export function changeColorPalette(
|
||||
newPalette: PaletteConfigurationState['activePalette'],
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
palettes: PaletteRegistry,
|
||||
dataBounds: DataBounds,
|
||||
disableSwitchingContinuity: boolean
|
||||
) {
|
||||
const isNewPaletteCustom = newPalette.name === CUSTOM_PALETTE;
|
||||
const newParams: CustomPaletteParams = {
|
||||
...activePalette.params,
|
||||
name: newPalette.name,
|
||||
colorStops: undefined,
|
||||
continuity: disableSwitchingContinuity
|
||||
? activePalette.params?.continuity ?? DEFAULT_CONTINUITY
|
||||
: DEFAULT_CONTINUITY,
|
||||
reverse: false, // restore the reverse flag
|
||||
};
|
||||
|
||||
// we should pass colorStops so that correct calculate new color stops (if there was before) for custom palette
|
||||
const newColorStops = getColorStops(
|
||||
palettes,
|
||||
activePalette.params?.colorStops || [],
|
||||
activePalette,
|
||||
dataBounds
|
||||
);
|
||||
|
||||
if (isNewPaletteCustom) {
|
||||
newParams.colorStops = newColorStops;
|
||||
}
|
||||
|
||||
return {
|
||||
...newPalette,
|
||||
params: {
|
||||
...newParams,
|
||||
stops: getPaletteStops(palettes, newParams, {
|
||||
prevPalette:
|
||||
isNewPaletteCustom || activePalette.name === CUSTOM_PALETTE ? undefined : newPalette.name,
|
||||
dataBounds,
|
||||
mapFromMinValue: true,
|
||||
}),
|
||||
rangeMin: checkIsMinContinuity(newParams.continuity)
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: Math.min(dataBounds.min, newColorStops[0].stop),
|
||||
rangeMax: checkIsMaxContinuity(newParams.continuity)
|
||||
? Number.POSITIVE_INFINITY
|
||||
: Math.min(dataBounds.max, newColorStops[newColorStops.length - 1].stop),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function withUpdatingPalette(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
colorRanges: ColorRange[],
|
||||
dataBounds: DataBounds,
|
||||
continuity?: CustomPaletteParams['continuity']
|
||||
) {
|
||||
const currentContinuity = continuity ?? activePalette.params?.continuity ?? DEFAULT_CONTINUITY;
|
||||
let sortedColorRanges = colorRanges;
|
||||
if (
|
||||
colorRanges.some((value, index) =>
|
||||
index !== colorRanges.length - 1 ? value.start > colorRanges[index + 1].start : false
|
||||
)
|
||||
) {
|
||||
sortedColorRanges = sortColorRanges(colorRanges);
|
||||
}
|
||||
|
||||
const { max, colorStops } = toColorStops(sortedColorRanges, currentContinuity);
|
||||
|
||||
const newPallete = getSwitchToCustomParams(
|
||||
palettes,
|
||||
activePalette!,
|
||||
{
|
||||
continuity: currentContinuity,
|
||||
colorStops,
|
||||
steps: activePalette!.params?.steps || DEFAULT_COLOR_STEPS,
|
||||
reverse: activePalette!.params?.reverse,
|
||||
rangeMin: colorStops[0]?.stop,
|
||||
rangeMax: max,
|
||||
},
|
||||
dataBounds!
|
||||
);
|
||||
|
||||
return {
|
||||
activePalette: newPallete,
|
||||
colorRanges,
|
||||
};
|
||||
}
|
||||
|
||||
export function withUpdatingColorRanges(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
return {
|
||||
colorRanges: toColorRanges(
|
||||
palettes,
|
||||
activePalette.params?.colorStops || [],
|
||||
activePalette,
|
||||
dataBounds
|
||||
),
|
||||
activePalette,
|
||||
};
|
||||
}
|
||||
|
||||
// Utility to remap color stops within new domain
|
||||
export function getStopsFromColorRangesByNewInterval(
|
||||
colorRanges: ColorRange[],
|
||||
{
|
||||
newInterval,
|
||||
oldInterval,
|
||||
newMin,
|
||||
oldMin,
|
||||
}: { newInterval: number; oldInterval: number; newMin: number; oldMin: number }
|
||||
) {
|
||||
return (colorRanges || []).map(({ color, start }) => {
|
||||
let stop = calculateStop(start, newMin, oldMin, oldInterval, newInterval);
|
||||
|
||||
if (oldInterval === 0) {
|
||||
stop = newInterval + newMin;
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
stop: roundValue(stop),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function mergePaletteParams(
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
newParams: CustomPaletteParams
|
||||
): PaletteOutput<CustomPaletteParams> {
|
||||
return {
|
||||
...activePalette,
|
||||
params: {
|
||||
...activePalette.params,
|
||||
...newParams,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function isValidPonyfill(colorString: string) {
|
||||
// we're using an old version of chroma without the valid function
|
||||
try {
|
||||
chroma(colorString);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidColor(colorString: string) {
|
||||
// chroma can handle also hex values with alpha channel/transparency
|
||||
// chroma accepts also hex without #, so test for it
|
||||
return colorString !== '' && /^#/.test(colorString) && isValidPonyfill(colorString);
|
||||
}
|
||||
|
||||
function getSwitchToCustomParams(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
newParams: CustomPaletteParams,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
// if it's already a custom palette just return the params
|
||||
if (activePalette?.params?.name === CUSTOM_PALETTE) {
|
||||
const stops = getPaletteStops(
|
||||
palettes,
|
||||
{
|
||||
steps: DEFAULT_COLOR_STEPS,
|
||||
...activePalette.params,
|
||||
...newParams,
|
||||
},
|
||||
{
|
||||
dataBounds,
|
||||
}
|
||||
);
|
||||
return mergePaletteParams(activePalette, {
|
||||
...newParams,
|
||||
stops,
|
||||
});
|
||||
}
|
||||
// prepare everything to switch to custom palette
|
||||
const newPaletteParams = {
|
||||
steps: DEFAULT_COLOR_STEPS,
|
||||
...activePalette.params,
|
||||
...newParams,
|
||||
name: CUSTOM_PALETTE,
|
||||
};
|
||||
|
||||
const stops = getPaletteStops(palettes, newPaletteParams, {
|
||||
prevPalette: newPaletteParams.colorStops ? undefined : activePalette.name,
|
||||
dataBounds,
|
||||
});
|
||||
return mergePaletteParams(
|
||||
{ name: CUSTOM_PALETTE, type: 'palette' },
|
||||
{
|
||||
...newPaletteParams,
|
||||
stops,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getColorStops(
|
||||
palettes: PaletteRegistry,
|
||||
colorStops: Required<CustomPaletteParams>['stops'],
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
// just forward the current stops if custom
|
||||
if (activePalette?.name === CUSTOM_PALETTE && colorStops?.length) {
|
||||
return colorStops;
|
||||
}
|
||||
// for predefined palettes create some stops, then drop the last one.
|
||||
// we're using these as starting point for the user
|
||||
let freshColorStops = getPaletteStops(
|
||||
palettes,
|
||||
{ ...activePalette?.params },
|
||||
// mapFromMinValue is a special flag to offset the stops values
|
||||
// used here to avoid a new remap/left shift
|
||||
{ dataBounds, mapFromMinValue: true, defaultPaletteName: activePalette.name }
|
||||
);
|
||||
if (activePalette?.params?.reverse) {
|
||||
freshColorStops = reversePalette(freshColorStops);
|
||||
}
|
||||
return freshColorStops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Both table coloring logic and EuiPaletteDisplay format implementation works differently than our current `colorStops`,
|
||||
* by having the stop values at the end of each color segment rather than at the beginning: `stops` values are computed by a rightShift of `colorStops`.
|
||||
* EuiPaletteDisplay has an additional requirement as it is always mapped against a domain [0, N]: from `stops` the `displayStops` are computed with
|
||||
* some continuity enrichment and a remap against a [0, 100] domain to make the palette component work ok.
|
||||
*
|
||||
* These naming conventions would be useful to track the code flow in this feature as multiple transformations are happening
|
||||
* for a single change.
|
||||
*/
|
||||
export function toColorRanges(
|
||||
palettes: PaletteRegistry,
|
||||
colorStops: CustomPaletteParams['colorStops'],
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
const { continuity = DEFAULT_CONTINUITY, rangeType = DEFAULT_RANGE_TYPE } =
|
||||
activePalette.params ?? {};
|
||||
const { min: dataMin, max: dataMax } = getDataMinMax(rangeType, dataBounds);
|
||||
|
||||
return getColorStops(palettes, colorStops || [], activePalette, dataBounds).map(
|
||||
(colorStop, index, array) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === array.length - 1;
|
||||
|
||||
return {
|
||||
color: colorStop.color,
|
||||
start:
|
||||
isFirst && checkIsMinContinuity(continuity)
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: colorStop.stop ?? activePalette.params?.rangeMin ?? dataMin,
|
||||
end:
|
||||
isLast && checkIsMaxContinuity(continuity)
|
||||
? Number.POSITIVE_INFINITY
|
||||
: array[index + 1]?.stop ?? activePalette.params?.rangeMax ?? dataMax,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
23
packages/kbn-coloring/src/shared_components/index.ts
Normal file
23
packages/kbn-coloring/src/shared_components/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { withSuspense } from '@kbn/shared-ux-utility';
|
||||
|
||||
/**
|
||||
* The Lazily-loaded `CustomizablePalette` component. Consumers should use `React.Suspense` or
|
||||
* the withSuspense` HOC to load this component.
|
||||
*/
|
||||
export const CustomizablePaletteLazy = React.lazy(() => import('./coloring'));
|
||||
|
||||
/**
|
||||
* A `CustomizablePalette` component that is wrapped by the `withSuspense` HOC. This component can
|
||||
* be used directly by consumers and will load the `KibanaPageTemplateSolutionNavAvatarLazy` component lazily with
|
||||
* a predefined fallback and error boundary.
|
||||
*/
|
||||
export const CustomizablePalette = withSuspense(CustomizablePaletteLazy);
|
19
packages/kbn-coloring/tsconfig.json
Normal file
19
packages/kbn-coloring/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.bazel.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "target_types",
|
||||
"rootDir": "src",
|
||||
"stripInternal": false,
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react",
|
||||
"@emotion/react/types/css-prop"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
// Please also add new aliases to test/scripts/jenkins_storybook.sh
|
||||
export const storybookAliases = {
|
||||
coloring: 'packages/kbn-coloring/.storybook',
|
||||
apm: 'x-pack/plugins/apm/.storybook',
|
||||
canvas: 'x-pack/plugins/canvas/storybook',
|
||||
ci_composite: '.ci/.storybook',
|
||||
|
|
|
@ -9,12 +9,8 @@
|
|||
export const COLOR_MAPPING_SETTING = 'visualization:colorMapping';
|
||||
export const LEGACY_TIME_AXIS = 'visualization:useLegacyTimeAxis';
|
||||
|
||||
export type {
|
||||
CustomPaletteArguments,
|
||||
CustomPaletteState,
|
||||
SystemPaletteArguments,
|
||||
PaletteOutput,
|
||||
} from './palette';
|
||||
export type { CustomPaletteArguments, CustomPaletteState, SystemPaletteArguments } from './palette';
|
||||
|
||||
export { defaultCustomColors, palette, systemPalette } from './palette';
|
||||
|
||||
export { paletteIds } from './constants';
|
||||
|
@ -30,8 +26,14 @@ export {
|
|||
LabelRotation,
|
||||
defaultCountLabel,
|
||||
MULTILAYER_TIME_AXIS_STYLE,
|
||||
checkIsMinContinuity,
|
||||
checkIsMaxContinuity,
|
||||
} from './static';
|
||||
|
||||
export type { ColorSchemaParams, Labels, Style, PaletteContinuity } from './types';
|
||||
export type { ColorSchemaParams, Labels, Style } from './types';
|
||||
|
||||
/** @deprecated **/
|
||||
/** Please import directly from @kbn/coloring **/
|
||||
export { checkIsMinContinuity, checkIsMaxContinuity } from '@kbn/coloring';
|
||||
|
||||
/** @deprecated **/
|
||||
/** Please import directly from @kbn/coloring **/
|
||||
export type { PaletteOutput, PaletteContinuity } from '@kbn/coloring';
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import {
|
||||
palette,
|
||||
defaultCustomColors,
|
||||
systemPalette,
|
||||
PaletteOutput,
|
||||
CustomPaletteState,
|
||||
CustomPaletteArguments,
|
||||
} from './palette';
|
||||
|
|
|
@ -7,12 +7,11 @@
|
|||
*/
|
||||
|
||||
import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
|
||||
import type { PaletteContinuity } from '@kbn/coloring';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { checkIsMaxContinuity, checkIsMinContinuity } from '@kbn/coloring';
|
||||
import { last } from 'lodash';
|
||||
import { paletteIds } from './constants';
|
||||
import { checkIsMaxContinuity, checkIsMinContinuity } from './static';
|
||||
|
||||
import type { PaletteContinuity } from './types';
|
||||
|
||||
export interface CustomPaletteArguments {
|
||||
color?: string[];
|
||||
|
|
|
@ -17,5 +17,4 @@ export {
|
|||
} from './color_maps';
|
||||
|
||||
export { ColorMode, LabelRotation, defaultCountLabel } from './components';
|
||||
export { checkIsMaxContinuity, checkIsMinContinuity } from './palette';
|
||||
export * from './styles';
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
import { ColorSchemas, LabelRotation } from './static';
|
||||
|
||||
export type PaletteContinuity = 'above' | 'below' | 'none' | 'all';
|
||||
|
||||
export interface ColorSchemaParams {
|
||||
colorSchema: ColorSchemas;
|
||||
invertColors: boolean;
|
||||
|
|
|
@ -16,12 +16,10 @@ export const plugin = () => new ChartsPlugin();
|
|||
export type { ChartsPluginSetup, ChartsPluginStart } from './plugin';
|
||||
|
||||
export * from './static';
|
||||
export * from './services/palettes/types';
|
||||
export { lightenColor } from './services/palettes/lighten_color';
|
||||
export { useActiveCursor } from './services/active_cursor';
|
||||
|
||||
export type {
|
||||
PaletteOutput,
|
||||
CustomPaletteArguments,
|
||||
CustomPaletteState,
|
||||
SystemPaletteArguments,
|
||||
|
@ -44,3 +42,7 @@ export {
|
|||
LabelRotation,
|
||||
defaultCountLabel,
|
||||
} from '../common';
|
||||
|
||||
/** @deprecated **/
|
||||
/** Please import directly from @kbn/coloring **/
|
||||
export type { SeriesLayer, PaletteRegistry, PaletteOutput, PaletteDefinition } from '@kbn/coloring';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { checkIsMinContinuity, checkIsMaxContinuity } from '../../../common';
|
||||
import { checkIsMaxContinuity, checkIsMinContinuity } from '@kbn/coloring';
|
||||
import type { CustomPaletteState } from '../..';
|
||||
|
||||
function findColorSegment(
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { PaletteService } from './service';
|
||||
import { PaletteDefinition, SeriesLayer } from './types';
|
||||
import type { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import type { PaletteDefinition, SeriesLayer } from '@kbn/coloring';
|
||||
import type { PaletteService } from './service';
|
||||
|
||||
export const getPaletteRegistry = () => {
|
||||
const mockPalette1: jest.Mocked<PaletteDefinition> = {
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { PaletteDefinition } from '@kbn/coloring';
|
||||
import { createColorPalette as createLegacyColorPalette } from '../../../../../../src/plugins/charts/public';
|
||||
import { PaletteDefinition } from './types';
|
||||
import { buildPalettes } from './palettes';
|
||||
import { colorsServiceMock } from '../legacy_colors/mock';
|
||||
import { euiPaletteColorBlind, euiPaletteColorBlindBehindText } from '@elastic/eui';
|
||||
|
|
|
@ -21,13 +21,13 @@ import {
|
|||
euiPaletteComplimentary,
|
||||
euiPaletteColorBlindBehindText,
|
||||
} from '@elastic/eui';
|
||||
import type { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from '@kbn/coloring';
|
||||
import { flatten, zip } from 'lodash';
|
||||
import {
|
||||
ChartsPluginSetup,
|
||||
createColorPalette as createLegacyColorPalette,
|
||||
} from '../../../../../../src/plugins/charts/public';
|
||||
import { lightenColor } from './lighten_color';
|
||||
import { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from './types';
|
||||
import { LegacyColorsService } from '../legacy_colors';
|
||||
import { MappedColors } from '../mapped_colors';
|
||||
import { workoutColorForValue } from './helpers';
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public';
|
||||
import {
|
||||
ChartsPluginSetup,
|
||||
PaletteDefinition,
|
||||
PaletteRegistry,
|
||||
} from '../../../../../../src/plugins/charts/public';
|
||||
import { LegacyColorsService } from '../legacy_colors';
|
||||
import type { PaletteRegistry, PaletteDefinition } from '@kbn/coloring';
|
||||
import type { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public';
|
||||
import type { ChartsPluginSetup } from '../../../../../../src/plugins/charts/public';
|
||||
import type { LegacyColorsService } from '../legacy_colors';
|
||||
|
||||
export interface PaletteSetupPlugins {
|
||||
expressions: ExpressionsSetup;
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
*/
|
||||
|
||||
import { ChartsServerPlugin } from './plugin';
|
||||
export type {
|
||||
PaletteOutput,
|
||||
CustomPaletteArguments,
|
||||
CustomPaletteState,
|
||||
SystemPaletteArguments,
|
||||
} from '../common';
|
||||
export type { CustomPaletteArguments, CustomPaletteState, SystemPaletteArguments } from '../common';
|
||||
export { paletteIds } from '../common';
|
||||
|
||||
export const plugin = () => new ChartsServerPlugin();
|
||||
|
|
|
@ -6,16 +6,13 @@
|
|||
*/
|
||||
|
||||
import type { Direction } from '@elastic/eui';
|
||||
import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring';
|
||||
import { SortingHint } from '../..';
|
||||
import type {
|
||||
CustomPaletteState,
|
||||
PaletteOutput,
|
||||
} from '../../../../../../src/plugins/charts/common';
|
||||
import type { CustomPaletteState } from '../../../../../../src/plugins/charts/common';
|
||||
import type {
|
||||
ExpressionFunctionDefinition,
|
||||
DatatableColumn,
|
||||
} from '../../../../../../src/plugins/expressions/common';
|
||||
import type { CustomPaletteParams } from '../../types';
|
||||
|
||||
export type LensGridDirection = 'none' | Direction;
|
||||
|
||||
|
|
|
@ -8,16 +8,13 @@
|
|||
import type { Filter, FilterMeta } from '@kbn/es-query';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring';
|
||||
import type {
|
||||
IFieldFormat,
|
||||
SerializedFieldFormat,
|
||||
} from '../../../../src/plugins/field_formats/common';
|
||||
import type { Datatable } from '../../../../src/plugins/expressions/common';
|
||||
import type {
|
||||
PaletteContinuity,
|
||||
PaletteOutput,
|
||||
ColorMode,
|
||||
} from '../../../../src/plugins/charts/common';
|
||||
import type { ColorMode } from '../../../../src/plugins/charts/common';
|
||||
import {
|
||||
CategoryDisplay,
|
||||
layerTypes,
|
||||
|
@ -55,33 +52,12 @@ export interface LensMultiTable {
|
|||
};
|
||||
}
|
||||
|
||||
export interface ColorStop {
|
||||
color: string;
|
||||
stop: number;
|
||||
}
|
||||
|
||||
export type SortingHint = 'version';
|
||||
|
||||
export interface CustomPaletteParams {
|
||||
name?: string;
|
||||
reverse?: boolean;
|
||||
rangeType?: 'number' | 'percent';
|
||||
continuity?: PaletteContinuity;
|
||||
progression?: 'fixed';
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
stops?: ColorStop[];
|
||||
colorStops?: ColorStop[];
|
||||
steps?: number;
|
||||
}
|
||||
export type CustomPaletteParamsConfig = CustomPaletteParams & {
|
||||
maxSteps?: number;
|
||||
};
|
||||
|
||||
export type RequiredPaletteParamTypes = Required<CustomPaletteParams> & {
|
||||
maxSteps?: number;
|
||||
};
|
||||
|
||||
export type LayerType = typeof layerTypes[keyof typeof layerTypes];
|
||||
|
||||
// Shared by XY Chart and Heatmap as for now
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiButtonGroup, EuiComboBox, EuiFieldText } from '@elastic/eui';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import {
|
||||
FramePublicAPI,
|
||||
OperationDescriptor,
|
||||
|
@ -17,7 +18,6 @@ import { createMockDatasource, createMockFramePublicAPI } from '../../mocks';
|
|||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { TableDimensionEditor } from './dimension_editor';
|
||||
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
|
||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { PalettePanelContainer } from '../../shared_components';
|
||||
import { layerTypes } from '../../../common';
|
||||
|
|
|
@ -19,14 +19,13 @@ import {
|
|||
EuiFieldText,
|
||||
EuiComboBox,
|
||||
} from '@elastic/eui';
|
||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { CustomizablePalette, PaletteRegistry, FIXED_PROGRESSION } from '@kbn/coloring';
|
||||
import { VisualizationDimensionEditorProps } from '../../types';
|
||||
import { DatatableVisualizationState } from '../visualization';
|
||||
|
||||
import {
|
||||
CustomizablePalette,
|
||||
applyPaletteParams,
|
||||
defaultPaletteParams,
|
||||
FIXED_PROGRESSION,
|
||||
useDebouncedValue,
|
||||
PalettePanelContainer,
|
||||
findMinMaxByColumnId,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import './table_basic.scss';
|
||||
|
||||
import { CUSTOM_PALETTE } from '@kbn/coloring';
|
||||
import React, { useCallback, useMemo, useRef, useState, useContext, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
|
||||
|
@ -42,7 +42,6 @@ import {
|
|||
createGridSortingConfig,
|
||||
createTransposeColumnFilterHandler,
|
||||
} from './table_actions';
|
||||
import { CUSTOM_PALETTE } from '../../shared_components/coloring/constants';
|
||||
import { getOriginalId, getFinalSummaryConfiguration } from '../../../common/expressions';
|
||||
|
||||
export const DataContext = React.createContext<DataContextType>({});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import { IUiSettingsClient } from 'kibana/public';
|
||||
import { CustomPaletteState, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import { CustomPaletteState } from 'src/plugins/charts/public';
|
||||
import type { IAggType } from 'src/plugins/data/public';
|
||||
import type { Datatable, RenderMode } from 'src/plugins/expressions';
|
||||
import type { ILensInterpreterRenderHandlers, LensEditEvent } from '../../types';
|
||||
|
|
|
@ -9,9 +9,8 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import type { IAggType } from 'src/plugins/data/public';
|
||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { IUiSettingsClient, ThemeServiceStart } from 'kibana/public';
|
||||
import { ExpressionRenderDefinition } from 'src/plugins/expressions';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
|
|
|
@ -10,7 +10,7 @@ import { render } from 'react-dom';
|
|||
import { Ast } from '@kbn/interpreter';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { PaletteRegistry, CUSTOM_PALETTE } from '@kbn/coloring';
|
||||
import { ThemeServiceStart } from 'kibana/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import type {
|
||||
|
@ -21,12 +21,12 @@ import type {
|
|||
} from '../types';
|
||||
import { LensIconChartDatatable } from '../assets/chart_datatable';
|
||||
import { TableDimensionEditor } from './components/dimension_editor';
|
||||
import { CUSTOM_PALETTE } from '../shared_components/coloring/constants';
|
||||
import { LayerType, layerTypes } from '../../common';
|
||||
import { getDefaultSummaryLabel, PagingState } from '../../common/expressions';
|
||||
import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public';
|
||||
import type { ColumnState, SortingState } from '../../common/expressions';
|
||||
import { DataTableToolbar } from './components/toolbar';
|
||||
|
||||
export interface DatatableVisualizationState {
|
||||
columns: ColumnState[];
|
||||
layerId: string;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { getSuggestions, getTopSuggestionForField } from './suggestion_helpers';
|
||||
import { createMockVisualization, createMockDatasource, DatasourceMock } from '../../mocks';
|
||||
import {
|
||||
|
@ -13,7 +14,6 @@ import {
|
|||
Visualization,
|
||||
VisualizeEditorContext,
|
||||
} from '../../types';
|
||||
import { PaletteOutput } from 'src/plugins/charts/public';
|
||||
import { DatasourceStates } from '../../state_management';
|
||||
|
||||
const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSuggestion => ({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { Datatable } from 'src/plugins/expressions';
|
||||
import { PaletteOutput } from 'src/plugins/charts/public';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import {
|
||||
Visualization,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import {
|
||||
createMockVisualization,
|
||||
mockStoreDeps,
|
||||
|
@ -30,7 +31,6 @@ jest.mock('react-virtualized-auto-sizer', () => {
|
|||
|
||||
import { Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../../types';
|
||||
import { ChartSwitch } from './chart_switch';
|
||||
import { PaletteOutput } from 'src/plugins/charts/public';
|
||||
import { applyChanges } from '../../../state_management';
|
||||
|
||||
describe('chart_switch', () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PaletteDefinition } from 'src/plugins/charts/public';
|
||||
import type { PaletteDefinition } from '@kbn/coloring';
|
||||
import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public';
|
||||
import { embeddablePluginMock } from '../../../../../src/plugins/embeddable/public/mocks';
|
||||
import { expressionsPluginMock } from '../../../../../src/plugins/expressions/public/mocks';
|
||||
|
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { DataViewBase, Filter } from '@kbn/es-query';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import {
|
||||
ExecutionContextSearch,
|
||||
Query,
|
||||
|
@ -17,7 +18,6 @@ import {
|
|||
TimeRange,
|
||||
FilterManager,
|
||||
} from 'src/plugins/data/public';
|
||||
import type { PaletteOutput } from 'src/plugins/charts/public';
|
||||
import type { Start as InspectorStart } from 'src/plugins/inspector/public';
|
||||
|
||||
import { Subscription } from 'rxjs';
|
||||
|
|
|
@ -14,13 +14,9 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { CustomizablePalette, FIXED_PROGRESSION, PaletteRegistry } from '@kbn/coloring';
|
||||
import type { VisualizationDimensionEditorProps } from '../types';
|
||||
import {
|
||||
CustomizablePalette,
|
||||
FIXED_PROGRESSION,
|
||||
PalettePanelContainer,
|
||||
} from '../shared_components/';
|
||||
import { PalettePanelContainer } from '../shared_components/';
|
||||
import './dimension_editor.scss';
|
||||
import type { HeatmapVisualizationState } from './types';
|
||||
import { getSafePaletteParams } from './utils';
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PaletteOutput } from '../../../../../src/plugins/charts/common';
|
||||
import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring';
|
||||
import type { HeatmapArguments } from '../../../../../src/plugins/chart_expressions/expression_heatmap/common';
|
||||
import type { CustomPaletteParams, LayerType } from '../../common';
|
||||
import type { LayerType } from '../../common';
|
||||
export type ChartShapes = 'heatmap';
|
||||
|
||||
export type HeatmapLayerState = HeatmapArguments & {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import type { Datatable } from 'src/plugins/expressions';
|
||||
import { applyPaletteParams, findMinMaxByColumnId } from '../shared_components';
|
||||
import { DEFAULT_PALETTE_NAME } from './constants';
|
||||
|
|
|
@ -11,9 +11,9 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||
import { Ast } from '@kbn/interpreter';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { CUSTOM_PALETTE, PaletteRegistry, CustomPaletteParams } from '@kbn/coloring';
|
||||
import { ThemeServiceStart } from 'kibana/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { PaletteRegistry } from '../../../../../src/plugins/charts/public';
|
||||
import { HeatmapIcon } from '../../../../../src/plugins/chart_expressions/expression_heatmap/public';
|
||||
import type { OperationMetadata, Visualization } from '../types';
|
||||
import type { HeatmapVisualizationState } from './types';
|
||||
|
@ -29,10 +29,8 @@ import {
|
|||
LENS_HEATMAP_ID,
|
||||
} from './constants';
|
||||
import { HeatmapToolbar } from './toolbar_component';
|
||||
import { CUSTOM_PALETTE } from '../shared_components';
|
||||
import { HeatmapDimensionEditor } from './dimension_editor';
|
||||
import { getSafePaletteParams } from './utils';
|
||||
import type { CustomPaletteParams } from '../../common';
|
||||
import { layerTypes } from '../../common';
|
||||
|
||||
const groupLabelForHeatmap = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', {
|
||||
|
|
|
@ -12,10 +12,17 @@ import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
|
|||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { MetricDimensionEditor } from './dimension_editor';
|
||||
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
|
||||
import { ColorMode, PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { ColorMode } from 'src/plugins/charts/public';
|
||||
import {
|
||||
CustomizablePalette,
|
||||
PaletteOutput,
|
||||
PaletteRegistry,
|
||||
CustomPaletteParams,
|
||||
} from '@kbn/coloring';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { CustomizablePalette, PalettePanelContainer } from '../shared_components';
|
||||
import { CustomPaletteParams, layerTypes } from '../../common';
|
||||
|
||||
import { PalettePanelContainer } from '../shared_components';
|
||||
import { layerTypes } from '../../common';
|
||||
import type { MetricState } from '../../common/types';
|
||||
|
||||
// mocking random id generator function
|
||||
|
|
|
@ -13,19 +13,18 @@ import {
|
|||
EuiFormRow,
|
||||
htmlIdGenerator,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ColorMode } from '../../../../../src/plugins/charts/common';
|
||||
import type { PaletteRegistry } from '../../../../../src/plugins/charts/public';
|
||||
import type { MetricState } from '../../common/types';
|
||||
import { isNumericFieldForDatatable } from '../../common/expressions';
|
||||
import {
|
||||
applyPaletteParams,
|
||||
PaletteRegistry,
|
||||
CustomizablePalette,
|
||||
CUSTOM_PALETTE,
|
||||
FIXED_PROGRESSION,
|
||||
PalettePanelContainer,
|
||||
} from '../shared_components';
|
||||
} from '@kbn/coloring';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ColorMode } from '../../../../../src/plugins/charts/common';
|
||||
import type { MetricState } from '../../common/types';
|
||||
import { isNumericFieldForDatatable } from '../../common/expressions';
|
||||
import { applyPaletteParams, PalettePanelContainer } from '../shared_components';
|
||||
import type { VisualizationDimensionEditorProps } from '../types';
|
||||
import { defaultPaletteParams } from './palette_config';
|
||||
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RequiredPaletteParamTypes } from '../../common';
|
||||
import { RequiredPaletteParamTypes } from '@kbn/coloring';
|
||||
import { defaultPaletteParams as sharedDefaultParams } from '../shared_components/';
|
||||
|
||||
export const DEFAULT_PALETTE_NAME = 'status';
|
||||
export const DEFAULT_COLOR_STEPS = 3;
|
||||
|
||||
export const defaultPaletteParams: RequiredPaletteParamTypes = {
|
||||
...sharedDefaultParams,
|
||||
maxSteps: 5,
|
||||
|
|
|
@ -11,20 +11,15 @@ import { I18nProvider } from '@kbn/i18n-react';
|
|||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { render } from 'react-dom';
|
||||
import { Ast } from '@kbn/interpreter';
|
||||
import { PaletteOutput, PaletteRegistry, CUSTOM_PALETTE, shiftPalette } from '@kbn/coloring';
|
||||
import { ThemeServiceStart } from 'kibana/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import {
|
||||
ColorMode,
|
||||
CustomPaletteState,
|
||||
PaletteOutput,
|
||||
} from '../../../../../src/plugins/charts/common';
|
||||
import { PaletteRegistry } from '../../../../../src/plugins/charts/public';
|
||||
import { ColorMode, CustomPaletteState } from '../../../../../src/plugins/charts/common';
|
||||
import { getSuggestions } from './metric_suggestions';
|
||||
import { LensIconChartMetric } from '../assets/chart_metric';
|
||||
import { Visualization, OperationMetadata, DatasourcePublicAPI } from '../types';
|
||||
import type { MetricState } from '../../common/types';
|
||||
import { layerTypes } from '../../common';
|
||||
import { CUSTOM_PALETTE, shiftPalette } from '../shared_components';
|
||||
import { MetricDimensionEditor } from './dimension_editor';
|
||||
import { MetricToolbar } from './metric_config_panel';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PaletteOutput } from 'src/plugins/charts/public';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { suggestions } from './suggestions';
|
||||
import type { DataType, SuggestionRequest } from '../types';
|
||||
import {
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import type { Ast } from '@kbn/interpreter';
|
||||
import { Position } from '@elastic/charts';
|
||||
import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring';
|
||||
|
||||
import type { PaletteOutput, PaletteRegistry } from '../../../../../src/plugins/charts/public';
|
||||
import {
|
||||
buildExpression,
|
||||
buildExpressionFunction,
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
EuiButtonGroup,
|
||||
} from '@elastic/eui';
|
||||
import type { Position } from '@elastic/charts';
|
||||
import type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import { DEFAULT_PERCENT_DECIMALS } from './constants';
|
||||
import { PartitionChartsMeta } from './partition_charts_meta';
|
||||
import { LegendDisplay, PieVisualizationState, SharedPieLayerState } from '../../common';
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
import { render } from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||
import type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import { ThemeServiceStart } from 'kibana/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public';
|
||||
|
|
|
@ -5,20 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RequiredPaletteParamTypes } from '../../../common';
|
||||
|
||||
export const DEFAULT_PALETTE_NAME = 'positive';
|
||||
export const FIXED_PROGRESSION = 'fixed' as const;
|
||||
export const CUSTOM_PALETTE = 'custom';
|
||||
export const DEFAULT_CONTINUITY = 'above';
|
||||
export const DEFAULT_RANGE_TYPE = 'percent';
|
||||
export const DEFAULT_MIN_STOP = 0;
|
||||
export const DEFAULT_MAX_STOP = 100;
|
||||
export const DEFAULT_COLOR_STEPS = 5;
|
||||
export const DEFAULT_COLOR = '#6092C0'; // Same as EUI ColorStops default for new stops
|
||||
import {
|
||||
DEFAULT_PALETTE_NAME,
|
||||
FIXED_PROGRESSION,
|
||||
DEFAULT_CONTINUITY,
|
||||
DEFAULT_RANGE_TYPE,
|
||||
DEFAULT_MIN_STOP,
|
||||
DEFAULT_MAX_STOP,
|
||||
DEFAULT_COLOR_STEPS,
|
||||
RequiredPaletteParamTypes,
|
||||
} from '@kbn/coloring';
|
||||
|
||||
export const defaultPaletteParams: RequiredPaletteParamTypes = {
|
||||
maxSteps: undefined,
|
||||
name: DEFAULT_PALETTE_NAME,
|
||||
reverse: false,
|
||||
rangeType: DEFAULT_RANGE_TYPE,
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { CustomizablePalette } from './palette_configuration';
|
||||
export { PalettePanelContainer } from './palette_panel_container';
|
||||
export { ColorRanges } from './color_ranges';
|
||||
|
||||
export * from './utils';
|
||||
export * from './constants';
|
||||
export { defaultPaletteParams } from './constants';
|
||||
|
|
|
@ -6,20 +6,7 @@
|
|||
*/
|
||||
|
||||
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
|
||||
import {
|
||||
applyPaletteParams,
|
||||
getColorStops,
|
||||
getContrastColor,
|
||||
getDataMinMax,
|
||||
getPaletteStops,
|
||||
getStepValue,
|
||||
isValidColor,
|
||||
mergePaletteParams,
|
||||
remapStopsByNewInterval,
|
||||
reversePalette,
|
||||
updateRangeType,
|
||||
changeColorPalette,
|
||||
} from './utils';
|
||||
import { applyPaletteParams, getContrastColor } from './utils';
|
||||
|
||||
describe('applyPaletteParams', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
|
@ -61,406 +48,6 @@ describe('applyPaletteParams', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getColorStops', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
it('should return the same colorStops if a custom palette is passed, avoiding recomputation', () => {
|
||||
const colorStops = [
|
||||
{ stop: 0, color: 'red' },
|
||||
{ stop: 100, color: 'blue' },
|
||||
];
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
colorStops,
|
||||
{ name: 'custom', type: 'palette' },
|
||||
{ min: 0, max: 100 }
|
||||
)
|
||||
).toBe(colorStops);
|
||||
});
|
||||
|
||||
it('should get a fresh list of colors', () => {
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
[
|
||||
{ stop: 0, color: 'red' },
|
||||
{ stop: 100, color: 'blue' },
|
||||
],
|
||||
{ name: 'mocked', type: 'palette' },
|
||||
{ min: 0, max: 100 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 0 },
|
||||
{ color: 'yellow', stop: 50 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should get a fresh list of colors even if custom palette but empty colorStops', () => {
|
||||
expect(
|
||||
getColorStops(paletteRegistry, [], { name: 'mocked', type: 'palette' }, { min: 0, max: 100 })
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 0 },
|
||||
{ color: 'yellow', stop: 50 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly map the new colorStop to the current data bound and minValue', () => {
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
[],
|
||||
{ name: 'mocked', type: 'palette', params: { rangeType: 'number' } },
|
||||
{ min: 100, max: 1000 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 100 },
|
||||
{ color: 'yellow', stop: 550 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reverse the colors', () => {
|
||||
expect(
|
||||
getColorStops(
|
||||
paletteRegistry,
|
||||
[],
|
||||
{ name: 'mocked', type: 'palette', params: { reverse: true } },
|
||||
{ min: 100, max: 1000 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'yellow', stop: 0 },
|
||||
{ color: 'blue', stop: 50 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remapStopsByNewInterval', () => {
|
||||
it('should correctly remap the current palette from 0..1 to 0...100', () => {
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'red', stop: 0.9 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 1, newMin: 0, oldMin: 0 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 90 },
|
||||
]);
|
||||
|
||||
// now test the other way around
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 90 },
|
||||
],
|
||||
{ newInterval: 1, oldInterval: 100, newMin: 0, oldMin: 0 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'red', stop: 0.9 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly handle negative numbers to/from', () => {
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: -100 },
|
||||
{ color: 'green', stop: -50 },
|
||||
{ color: 'red', stop: -1 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 100, newMin: 0, oldMin: -100 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 99 },
|
||||
]);
|
||||
|
||||
// now map the other way around
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 99 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 100, newMin: -100, oldMin: 0 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: -100 },
|
||||
{ color: 'green', stop: -50 },
|
||||
{ color: 'red', stop: -1 },
|
||||
]);
|
||||
|
||||
// and test also palettes that also contains negative values
|
||||
expect(
|
||||
remapStopsByNewInterval(
|
||||
[
|
||||
{ color: 'black', stop: -50 },
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
{ newInterval: 100, oldInterval: 100, newMin: 0, oldMin: -50 }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'black', stop: 0 },
|
||||
{ color: 'green', stop: 50 },
|
||||
{ color: 'red', stop: 100 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDataMinMax', () => {
|
||||
it('should pick the correct min/max based on the current range type', () => {
|
||||
expect(getDataMinMax('percent', { min: -100, max: 0 })).toEqual({ min: 0, max: 100 });
|
||||
});
|
||||
|
||||
it('should pick the correct min/max apply percent by default', () => {
|
||||
expect(getDataMinMax(undefined, { min: -100, max: 0 })).toEqual({ min: 0, max: 100 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPaletteStops', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
it('should correctly compute a predefined palette stops definition from only the name', () => {
|
||||
expect(
|
||||
getPaletteStops(paletteRegistry, { name: 'mock' }, { dataBounds: { min: 0, max: 100 } })
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 20 },
|
||||
{ color: 'yellow', stop: 70 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly compute a predefined palette stops definition from explicit prevPalette (override)', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{ name: 'default' },
|
||||
{ dataBounds: { min: 0, max: 100 }, prevPalette: 'mock' }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 20 },
|
||||
{ color: 'yellow', stop: 70 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should infer the domain from dataBounds but start from 0', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{ name: 'default', rangeType: 'number' },
|
||||
{ dataBounds: { min: 1, max: 11 }, prevPalette: 'mock' }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 2 },
|
||||
{ color: 'yellow', stop: 7 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should override the minStop when requested', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{ name: 'default', rangeType: 'number' },
|
||||
{ dataBounds: { min: 1, max: 11 }, mapFromMinValue: true }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'red', stop: 1 },
|
||||
{ color: 'black', stop: 6 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
rangeType: 'number',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 100 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 40 },
|
||||
{ color: 'blue', stop: 80 },
|
||||
{ color: 'red', stop: 100 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user - handle stop at the end', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
rangeType: 'number',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 100 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 100 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 40 },
|
||||
{ color: 'blue', stop: 100 },
|
||||
{ color: 'red', stop: 101 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user - handle stop at the end (fractional)', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
rangeType: 'number',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 0.4 },
|
||||
{ color: 'red', stop: 1 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 1 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 0.4 },
|
||||
{ color: 'blue', stop: 1 },
|
||||
{ color: 'red', stop: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should compute a display stop palette from custom colorStops defined by the user - stretch the stops to 100% percent', () => {
|
||||
expect(
|
||||
getPaletteStops(
|
||||
paletteRegistry,
|
||||
{
|
||||
name: 'custom',
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 0.4 },
|
||||
{ color: 'red', stop: 1 },
|
||||
],
|
||||
},
|
||||
{ dataBounds: { min: 0, max: 1 } }
|
||||
)
|
||||
).toEqual([
|
||||
{ color: 'green', stop: 0.4 },
|
||||
{ color: 'blue', stop: 1 },
|
||||
{ color: 'red', stop: 100 }, // default rangeType is percent, hence stretch to 100%
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reversePalette', () => {
|
||||
it('should correctly reverse color and stops', () => {
|
||||
expect(
|
||||
reversePalette([
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'blue', stop: 0.9 },
|
||||
])
|
||||
).toEqual([
|
||||
{ color: 'blue', stop: 0 },
|
||||
{ color: 'green', stop: 0.5 },
|
||||
{ color: 'red', stop: 0.9 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergePaletteParams', () => {
|
||||
it('should return a full palette', () => {
|
||||
expect(mergePaletteParams({ type: 'palette', name: 'myPalette' }, { reverse: true })).toEqual({
|
||||
type: 'palette',
|
||||
name: 'myPalette',
|
||||
params: { reverse: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidColor', () => {
|
||||
it('should return ok for valid hex color notation', () => {
|
||||
expect(isValidColor('#fff')).toBe(true);
|
||||
expect(isValidColor('#ffffff')).toBe(true);
|
||||
expect(isValidColor('#ffffffaa')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non valid strings', () => {
|
||||
expect(isValidColor('')).toBe(false);
|
||||
expect(isValidColor('#')).toBe(false);
|
||||
expect(isValidColor('#ff')).toBe(false);
|
||||
expect(isValidColor('123')).toBe(false);
|
||||
expect(isValidColor('rgb(1, 1, 1)')).toBe(false);
|
||||
expect(isValidColor('rgba(1, 1, 1, 0)')).toBe(false);
|
||||
expect(isValidColor('#ffffffgg')).toBe(false);
|
||||
expect(isValidColor('#fff00')).toBe(false);
|
||||
// this version of chroma does not support hex4 format
|
||||
expect(isValidColor('#fffa')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStepValue', () => {
|
||||
it('should compute the next step based on the last 2 stops', () => {
|
||||
expect(
|
||||
getStepValue(
|
||||
// first arg is taken as max reference
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
100
|
||||
)
|
||||
).toBe(50);
|
||||
|
||||
expect(
|
||||
getStepValue(
|
||||
// first arg is taken as max reference
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
90
|
||||
)
|
||||
).toBe(10); // 90 - 80
|
||||
|
||||
expect(
|
||||
getStepValue(
|
||||
// first arg is taken as max reference
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 100 },
|
||||
],
|
||||
[
|
||||
{ color: 'red', stop: 0 },
|
||||
{ color: 'red', stop: 50 },
|
||||
],
|
||||
100
|
||||
)
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContrastColor', () => {
|
||||
it('should pick the light color when the passed one is dark', () => {
|
||||
expect(getContrastColor('#000', true)).toBe('#ffffff');
|
||||
|
@ -483,310 +70,3 @@ describe('getContrastColor', () => {
|
|||
expect(getContrastColor('rgba(255,255,255,0)', false)).toBe('#000000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRangeType', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
const colorRanges = [
|
||||
{
|
||||
start: 0,
|
||||
end: 40,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
start: 40,
|
||||
end: 80,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
start: 80,
|
||||
end: 100,
|
||||
color: 'red',
|
||||
},
|
||||
];
|
||||
it('should correctly update palette params with new range type if continuity is none', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'none',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: 0,
|
||||
rangeMax: 200,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly update palette params with new range type if continuity is all', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'all',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: Number.NEGATIVE_INFINITY,
|
||||
rangeMax: Number.POSITIVE_INFINITY,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly update palette params with new range type if continuity is below', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'below',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: Number.NEGATIVE_INFINITY,
|
||||
rangeMax: 200,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly update palette params with new range type if continuity is above', () => {
|
||||
const newPaletteParams = updateRangeType(
|
||||
'number',
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'above',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ min: 0, max: 200 },
|
||||
paletteRegistry,
|
||||
colorRanges
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
rangeType: 'number',
|
||||
rangeMin: 0,
|
||||
rangeMax: Number.POSITIVE_INFINITY,
|
||||
colorStops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 160,
|
||||
},
|
||||
],
|
||||
stops: [
|
||||
{
|
||||
color: 'green',
|
||||
stop: 80,
|
||||
},
|
||||
{
|
||||
color: 'blue',
|
||||
stop: 160,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
stop: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeColorPalette', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
|
||||
it('should correct update params for new palette', () => {
|
||||
const newPaletteParams = changeColorPalette(
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'default',
|
||||
},
|
||||
{
|
||||
type: 'palette',
|
||||
name: 'custom',
|
||||
params: {
|
||||
continuity: 'above',
|
||||
name: 'custom',
|
||||
rangeType: 'percent',
|
||||
rangeMax: 100,
|
||||
rangeMin: 0,
|
||||
colorStops: [
|
||||
{ color: 'green', stop: 0 },
|
||||
{ color: 'blue', stop: 40 },
|
||||
{ color: 'red', stop: 80 },
|
||||
],
|
||||
},
|
||||
},
|
||||
paletteRegistry,
|
||||
{ min: 0, max: 200 },
|
||||
false
|
||||
);
|
||||
expect(newPaletteParams).toEqual({
|
||||
name: 'default',
|
||||
type: 'palette',
|
||||
params: {
|
||||
rangeType: 'percent',
|
||||
name: 'default',
|
||||
continuity: 'above',
|
||||
rangeMin: 0,
|
||||
rangeMax: Number.POSITIVE_INFINITY,
|
||||
reverse: false,
|
||||
colorStops: undefined,
|
||||
stops: [
|
||||
{
|
||||
color: 'red',
|
||||
stop: 0,
|
||||
},
|
||||
{
|
||||
color: 'black',
|
||||
stop: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,535 +6,20 @@
|
|||
*/
|
||||
|
||||
import chroma from 'chroma-js';
|
||||
import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import { euiLightVars, euiDarkVars } from '@kbn/ui-theme';
|
||||
import { isColorDark } from '@elastic/eui';
|
||||
import type { Datatable } from 'src/plugins/expressions/public';
|
||||
|
||||
import {
|
||||
DataBounds,
|
||||
PaletteRegistry,
|
||||
PaletteOutput,
|
||||
CustomPaletteParams,
|
||||
getFallbackDataBounds,
|
||||
reversePalette,
|
||||
getPaletteStops,
|
||||
CUSTOM_PALETTE,
|
||||
defaultPaletteParams,
|
||||
DEFAULT_COLOR_STEPS,
|
||||
DEFAULT_MAX_STOP,
|
||||
DEFAULT_MIN_STOP,
|
||||
DEFAULT_CONTINUITY,
|
||||
} from './constants';
|
||||
import type { ColorRange } from './color_ranges';
|
||||
import { toColorStops, sortColorRanges } from './color_ranges/utils';
|
||||
import type { PaletteConfigurationState, DataBounds } from './types';
|
||||
import type { CustomPaletteParams, ColorStop } from '../../../common';
|
||||
import {
|
||||
checkIsMinContinuity,
|
||||
checkIsMaxContinuity,
|
||||
} from '../../../../../../src/plugins/charts/common';
|
||||
|
||||
/**
|
||||
* Some name conventions here:
|
||||
* * `displayStops` => It's an additional transformation of `stops` into a [0, N] domain for the EUIPaletteDisplay component.
|
||||
* * `stops` => final steps used to table coloring. It is a rightShift of the colorStops
|
||||
* * `colorStops` => user's color stop inputs. Used to compute range min.
|
||||
*
|
||||
* When the user inputs the colorStops, they are designed to be the initial part of the color segment,
|
||||
* so the next stops indicate where the previous stop ends.
|
||||
* Both table coloring logic and EuiPaletteDisplay format implementation works differently than our current `colorStops`,
|
||||
* by having the stop values at the end of each color segment rather than at the beginning: `stops` values are computed by a rightShift of `colorStops`.
|
||||
* EuiPaletteDisplay has an additional requirement as it is always mapped against a domain [0, N]: from `stops` the `displayStops` are computed with
|
||||
* some continuity enrichment and a remap against a [0, 100] domain to make the palette component work ok.
|
||||
*
|
||||
* These naming conventions would be useful to track the code flow in this feature as multiple transformations are happening
|
||||
* for a single change.
|
||||
*/
|
||||
|
||||
export function updateRangeType(
|
||||
newRangeType: CustomPaletteParams['rangeType'],
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
dataBounds: DataBounds,
|
||||
palettes: PaletteRegistry,
|
||||
colorRanges: PaletteConfigurationState['colorRanges']
|
||||
) {
|
||||
const continuity = activePalette.params?.continuity ?? DEFAULT_CONTINUITY;
|
||||
const params: CustomPaletteParams = { rangeType: newRangeType };
|
||||
const { min: newMin, max: newMax } = getDataMinMax(newRangeType, dataBounds);
|
||||
const { min: oldMin, max: oldMax } = getDataMinMax(activePalette.params?.rangeType, dataBounds);
|
||||
const newColorStops = getStopsFromColorRangesByNewInterval(colorRanges, {
|
||||
oldInterval: oldMax - oldMin,
|
||||
newInterval: newMax - newMin,
|
||||
newMin,
|
||||
oldMin,
|
||||
});
|
||||
|
||||
if (activePalette.name === CUSTOM_PALETTE) {
|
||||
const stops = getPaletteStops(
|
||||
palettes,
|
||||
{ ...activePalette.params, colorStops: newColorStops, ...params },
|
||||
{ dataBounds }
|
||||
);
|
||||
params.colorStops = newColorStops;
|
||||
params.stops = stops;
|
||||
} else {
|
||||
params.stops = getPaletteStops(
|
||||
palettes,
|
||||
{ ...activePalette.params, ...params },
|
||||
{ prevPalette: activePalette.name, dataBounds }
|
||||
);
|
||||
}
|
||||
|
||||
const lastStop =
|
||||
activePalette.name === CUSTOM_PALETTE
|
||||
? newColorStops[newColorStops.length - 1].stop
|
||||
: params.stops[params.stops.length - 1].stop;
|
||||
|
||||
params.rangeMin = checkIsMinContinuity(continuity)
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: activePalette.name === CUSTOM_PALETTE
|
||||
? newColorStops[0].stop
|
||||
: params.stops[0].stop;
|
||||
|
||||
params.rangeMax = checkIsMaxContinuity(continuity)
|
||||
? Number.POSITIVE_INFINITY
|
||||
: activePalette.params?.rangeMax
|
||||
? calculateStop(activePalette.params.rangeMax, newMin, oldMin, oldMax - oldMin, newMax - newMin)
|
||||
: lastStop > newMax
|
||||
? lastStop + 1
|
||||
: newMax;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export function changeColorPalette(
|
||||
newPalette: PaletteConfigurationState['activePalette'],
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
palettes: PaletteRegistry,
|
||||
dataBounds: DataBounds,
|
||||
disableSwitchingContinuity: boolean
|
||||
) {
|
||||
const isNewPaletteCustom = newPalette.name === CUSTOM_PALETTE;
|
||||
const newParams: CustomPaletteParams = {
|
||||
...activePalette.params,
|
||||
name: newPalette.name,
|
||||
colorStops: undefined,
|
||||
continuity: disableSwitchingContinuity
|
||||
? activePalette.params?.continuity ?? DEFAULT_CONTINUITY
|
||||
: DEFAULT_CONTINUITY,
|
||||
reverse: false, // restore the reverse flag
|
||||
};
|
||||
|
||||
// we should pass colorStops so that correct calculate new color stops (if there was before) for custom palette
|
||||
const newColorStops = getColorStops(
|
||||
palettes,
|
||||
activePalette.params?.colorStops || [],
|
||||
activePalette,
|
||||
dataBounds
|
||||
);
|
||||
|
||||
if (isNewPaletteCustom) {
|
||||
newParams.colorStops = newColorStops;
|
||||
}
|
||||
|
||||
return {
|
||||
...newPalette,
|
||||
params: {
|
||||
...newParams,
|
||||
stops: getPaletteStops(palettes, newParams, {
|
||||
prevPalette:
|
||||
isNewPaletteCustom || activePalette.name === CUSTOM_PALETTE ? undefined : newPalette.name,
|
||||
dataBounds,
|
||||
mapFromMinValue: true,
|
||||
}),
|
||||
rangeMin: checkIsMinContinuity(newParams.continuity)
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: Math.min(dataBounds.min, newColorStops[0].stop),
|
||||
rangeMax: checkIsMaxContinuity(newParams.continuity)
|
||||
? Number.POSITIVE_INFINITY
|
||||
: Math.min(dataBounds.max, newColorStops[newColorStops.length - 1].stop),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function withUpdatingPalette(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
colorRanges: ColorRange[],
|
||||
dataBounds: DataBounds,
|
||||
continuity?: CustomPaletteParams['continuity']
|
||||
) {
|
||||
const currentContinuity = continuity ?? activePalette.params?.continuity ?? DEFAULT_CONTINUITY;
|
||||
let sortedColorRanges = colorRanges;
|
||||
if (
|
||||
colorRanges.some((value, index) =>
|
||||
index !== colorRanges.length - 1 ? value.start > colorRanges[index + 1].start : false
|
||||
)
|
||||
) {
|
||||
sortedColorRanges = sortColorRanges(colorRanges);
|
||||
}
|
||||
|
||||
const { max, colorStops } = toColorStops(sortedColorRanges, currentContinuity);
|
||||
|
||||
const newPallete = getSwitchToCustomParams(
|
||||
palettes,
|
||||
activePalette!,
|
||||
{
|
||||
continuity: currentContinuity,
|
||||
colorStops,
|
||||
steps: activePalette!.params?.steps || DEFAULT_COLOR_STEPS,
|
||||
reverse: activePalette!.params?.reverse,
|
||||
rangeMin: colorStops[0]?.stop,
|
||||
rangeMax: max,
|
||||
},
|
||||
dataBounds!
|
||||
);
|
||||
|
||||
return {
|
||||
activePalette: newPallete,
|
||||
colorRanges,
|
||||
};
|
||||
}
|
||||
|
||||
export function withUpdatingColorRanges(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: PaletteConfigurationState['activePalette'],
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
return {
|
||||
colorRanges: toColorRanges(
|
||||
palettes,
|
||||
activePalette.params?.colorStops || [],
|
||||
activePalette,
|
||||
dataBounds
|
||||
),
|
||||
activePalette,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyPaletteParams<T extends PaletteOutput<CustomPaletteParams>>(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: T,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
// make a copy of it as they have to be manipulated later on
|
||||
const displayStops = getPaletteStops(palettes, activePalette?.params || {}, {
|
||||
dataBounds,
|
||||
defaultPaletteName: activePalette?.name,
|
||||
});
|
||||
|
||||
if (activePalette?.params?.reverse && activePalette?.params?.name !== CUSTOM_PALETTE) {
|
||||
return reversePalette(displayStops);
|
||||
}
|
||||
return displayStops;
|
||||
}
|
||||
|
||||
// Need to shift the Custom palette in order to correctly visualize it when in display mode
|
||||
export function shiftPalette(stops: ColorStop[], max: number) {
|
||||
// shift everything right and add an additional stop at the end
|
||||
const result = stops.map((entry, i, array) => ({
|
||||
...entry,
|
||||
stop: i + 1 < array.length ? array[i + 1].stop : max,
|
||||
}));
|
||||
|
||||
if (stops[stops.length - 1].stop === max) {
|
||||
// extends the range by a fair amount to make it work the extra case for the last stop === max
|
||||
const computedStep = getStepValue(stops, result, max) || 1;
|
||||
// do not go beyond the unit step in this case
|
||||
const step = Math.min(1, computedStep);
|
||||
result[stops.length - 1].stop = max + step;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
export function calculateStop(
|
||||
stopValue: number,
|
||||
newMin: number,
|
||||
oldMin: number,
|
||||
oldInterval: number,
|
||||
newInterval: number
|
||||
) {
|
||||
if (oldInterval === 0) {
|
||||
return newInterval + newMin;
|
||||
}
|
||||
return roundValue(newMin + ((stopValue - oldMin) * newInterval) / oldInterval);
|
||||
}
|
||||
|
||||
// Utility to remap color stops within new domain
|
||||
export function remapStopsByNewInterval(
|
||||
controlStops: ColorStop[],
|
||||
{
|
||||
newInterval,
|
||||
oldInterval,
|
||||
newMin,
|
||||
oldMin,
|
||||
}: { newInterval: number; oldInterval: number; newMin: number; oldMin: number }
|
||||
) {
|
||||
return (controlStops || []).map(({ color, stop }) => {
|
||||
return {
|
||||
color,
|
||||
stop: calculateStop(stop, newMin, oldMin, oldInterval, newInterval),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Utility to remap color stops within new domain
|
||||
export function getStopsFromColorRangesByNewInterval(
|
||||
colorRanges: ColorRange[],
|
||||
{
|
||||
newInterval,
|
||||
oldInterval,
|
||||
newMin,
|
||||
oldMin,
|
||||
}: { newInterval: number; oldInterval: number; newMin: number; oldMin: number }
|
||||
) {
|
||||
return (colorRanges || []).map(({ color, start }) => {
|
||||
let stop = calculateStop(start, newMin, oldMin, oldInterval, newInterval);
|
||||
|
||||
if (oldInterval === 0) {
|
||||
stop = newInterval + newMin;
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
stop: roundValue(stop),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getOverallMinMax(params: CustomPaletteParams | undefined, dataBounds: DataBounds) {
|
||||
const { min: dataMin, max: dataMax } = getDataMinMax(params?.rangeType, dataBounds);
|
||||
const minStopValue = params?.colorStops?.[0]?.stop ?? Number.POSITIVE_INFINITY;
|
||||
const maxStopValue =
|
||||
params?.colorStops?.[params.colorStops.length - 1]?.stop ?? Number.NEGATIVE_INFINITY;
|
||||
const overallMin = Math.min(dataMin, minStopValue);
|
||||
const overallMax = Math.max(dataMax, maxStopValue);
|
||||
return { min: overallMin, max: overallMax };
|
||||
}
|
||||
|
||||
export function getDataMinMax(
|
||||
rangeType: CustomPaletteParams['rangeType'] | undefined,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
const dataMin = rangeType === 'number' ? dataBounds.min : DEFAULT_MIN_STOP;
|
||||
const dataMax = rangeType === 'number' ? dataBounds.max : DEFAULT_MAX_STOP;
|
||||
return { min: dataMin, max: dataMax };
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a generic function to compute stops from the current parameters.
|
||||
*/
|
||||
export function getPaletteStops(
|
||||
palettes: PaletteRegistry,
|
||||
activePaletteParams: CustomPaletteParams,
|
||||
// used to customize color resolution
|
||||
{
|
||||
prevPalette,
|
||||
dataBounds,
|
||||
mapFromMinValue,
|
||||
defaultPaletteName,
|
||||
}: {
|
||||
prevPalette?: string;
|
||||
dataBounds: DataBounds;
|
||||
mapFromMinValue?: boolean;
|
||||
defaultPaletteName?: string;
|
||||
}
|
||||
) {
|
||||
const { min: minValue, max: maxValue } = getOverallMinMax(activePaletteParams, dataBounds);
|
||||
const interval = maxValue - minValue;
|
||||
const { stops: currentStops, ...otherParams } = activePaletteParams || {};
|
||||
|
||||
if (activePaletteParams.name === 'custom' && activePaletteParams?.colorStops) {
|
||||
// need to generate the palette from the existing controlStops
|
||||
return shiftPalette(activePaletteParams.colorStops, maxValue);
|
||||
}
|
||||
|
||||
const steps = activePaletteParams?.steps || defaultPaletteParams.steps;
|
||||
// generate a palette from predefined ones and customize the domain
|
||||
const colorStopsFromPredefined = palettes
|
||||
.get(
|
||||
prevPalette || activePaletteParams?.name || defaultPaletteName || defaultPaletteParams.name
|
||||
)
|
||||
.getCategoricalColors(steps, otherParams);
|
||||
|
||||
const newStopsMin = mapFromMinValue || interval === 0 ? minValue : interval / steps;
|
||||
|
||||
return remapStopsByNewInterval(
|
||||
colorStopsFromPredefined.map((color, index) => ({ color, stop: index })),
|
||||
{
|
||||
newInterval: interval,
|
||||
oldInterval: colorStopsFromPredefined.length,
|
||||
newMin: newStopsMin,
|
||||
oldMin: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function reversePalette(paletteColorRepresentation: ColorStop[] = []) {
|
||||
const stops = paletteColorRepresentation.map(({ stop }) => stop);
|
||||
return paletteColorRepresentation
|
||||
.map(({ color }, i) => ({
|
||||
color,
|
||||
stop: stops[paletteColorRepresentation.length - i - 1],
|
||||
}))
|
||||
.reverse();
|
||||
}
|
||||
|
||||
export function mergePaletteParams(
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
newParams: CustomPaletteParams
|
||||
): PaletteOutput<CustomPaletteParams> {
|
||||
return {
|
||||
...activePalette,
|
||||
params: {
|
||||
...activePalette.params,
|
||||
...newParams,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function isValidPonyfill(colorString: string) {
|
||||
// we're using an old version of chroma without the valid function
|
||||
try {
|
||||
chroma(colorString);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidColor(colorString: string) {
|
||||
// chroma can handle also hex values with alpha channel/transparency
|
||||
// chroma accepts also hex without #, so test for it
|
||||
return colorString !== '' && /^#/.test(colorString) && isValidPonyfill(colorString);
|
||||
}
|
||||
|
||||
export function roundValue(value: number, fractionDigits: number = 2) {
|
||||
return Number((Math.floor(value * 100) / 100).toFixed(fractionDigits));
|
||||
}
|
||||
|
||||
// very simple heuristic: pick last two stops and compute a new stop based on the same distance
|
||||
// if the new stop is above max, then reduce the step to reach max, or if zero then just 1.
|
||||
//
|
||||
// it accepts two series of stops as the function is used also when computing stops from colorStops
|
||||
export function getStepValue(colorStops: ColorStop[], newColorStops: ColorStop[], max: number) {
|
||||
const length = newColorStops.length;
|
||||
// workout the steps from the last 2 items
|
||||
const dataStep =
|
||||
length > 1 ? newColorStops[length - 1].stop - newColorStops[length - 2].stop || 1 : 1;
|
||||
let step = Number(dataStep.toFixed(2));
|
||||
if (max < colorStops[length - 1].stop + step) {
|
||||
const diffToMax = max - colorStops[length - 1].stop;
|
||||
// if the computed step goes way out of bound, fallback to 1, otherwise reach max
|
||||
step = diffToMax > 0 ? diffToMax : 1;
|
||||
}
|
||||
return step;
|
||||
}
|
||||
|
||||
export function getSwitchToCustomParams(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
newParams: CustomPaletteParams,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
// if it's already a custom palette just return the params
|
||||
if (activePalette?.params?.name === CUSTOM_PALETTE) {
|
||||
const stops = getPaletteStops(
|
||||
palettes,
|
||||
{
|
||||
steps: DEFAULT_COLOR_STEPS,
|
||||
...activePalette.params,
|
||||
...newParams,
|
||||
},
|
||||
{
|
||||
dataBounds,
|
||||
}
|
||||
);
|
||||
return mergePaletteParams(activePalette, {
|
||||
...newParams,
|
||||
stops,
|
||||
});
|
||||
}
|
||||
// prepare everything to switch to custom palette
|
||||
const newPaletteParams = {
|
||||
steps: DEFAULT_COLOR_STEPS,
|
||||
...activePalette.params,
|
||||
...newParams,
|
||||
name: CUSTOM_PALETTE,
|
||||
};
|
||||
|
||||
const stops = getPaletteStops(palettes, newPaletteParams, {
|
||||
prevPalette: newPaletteParams.colorStops ? undefined : activePalette.name,
|
||||
dataBounds,
|
||||
});
|
||||
return mergePaletteParams(
|
||||
{ name: CUSTOM_PALETTE, type: 'palette' },
|
||||
{
|
||||
...newPaletteParams,
|
||||
stops,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getColorStops(
|
||||
palettes: PaletteRegistry,
|
||||
colorStops: Required<CustomPaletteParams>['stops'],
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
// just forward the current stops if custom
|
||||
if (activePalette?.name === CUSTOM_PALETTE && colorStops?.length) {
|
||||
return colorStops;
|
||||
}
|
||||
// for predefined palettes create some stops, then drop the last one.
|
||||
// we're using these as starting point for the user
|
||||
let freshColorStops = getPaletteStops(
|
||||
palettes,
|
||||
{ ...activePalette?.params },
|
||||
// mapFromMinValue is a special flag to offset the stops values
|
||||
// used here to avoid a new remap/left shift
|
||||
{ dataBounds, mapFromMinValue: true, defaultPaletteName: activePalette.name }
|
||||
);
|
||||
if (activePalette?.params?.reverse) {
|
||||
freshColorStops = reversePalette(freshColorStops);
|
||||
}
|
||||
return freshColorStops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Both table coloring logic and EuiPaletteDisplay format implementation works differently than our current `colorStops`,
|
||||
* by having the stop values at the end of each color segment rather than at the beginning: `stops` values are computed by a rightShift of `colorStops`.
|
||||
* EuiPaletteDisplay has an additional requirement as it is always mapped against a domain [0, N]: from `stops` the `displayStops` are computed with
|
||||
* some continuity enrichment and a remap against a [0, 100] domain to make the palette component work ok.
|
||||
*
|
||||
* These naming conventions would be useful to track the code flow in this feature as multiple transformations are happening
|
||||
* for a single change.
|
||||
*/
|
||||
export function toColorRanges(
|
||||
palettes: PaletteRegistry,
|
||||
colorStops: CustomPaletteParams['colorStops'],
|
||||
activePalette: PaletteOutput<CustomPaletteParams>,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
const {
|
||||
continuity = defaultPaletteParams.continuity,
|
||||
rangeType = defaultPaletteParams.rangeType,
|
||||
} = activePalette.params ?? {};
|
||||
const { min: dataMin, max: dataMax } = getDataMinMax(rangeType, dataBounds);
|
||||
|
||||
return getColorStops(palettes, colorStops || [], activePalette, dataBounds).map(
|
||||
(colorStop, index, array) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === array.length - 1;
|
||||
|
||||
return {
|
||||
color: colorStop.color,
|
||||
start:
|
||||
isFirst && checkIsMinContinuity(continuity)
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: colorStop.stop ?? activePalette.params?.rangeMin ?? dataMin,
|
||||
end:
|
||||
isLast && checkIsMaxContinuity(continuity)
|
||||
? Number.POSITIVE_INFINITY
|
||||
: array[index + 1]?.stop ?? activePalette.params?.rangeMax ?? dataMax,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
} from '@kbn/coloring';
|
||||
import { Datatable } from '../../../../../../src/plugins/expressions';
|
||||
|
||||
export function getContrastColor(
|
||||
color: string,
|
||||
|
@ -555,10 +40,6 @@ export function getContrastColor(
|
|||
return isColorDark(...finalColor.rgb()) ? lightColor : darkColor;
|
||||
}
|
||||
|
||||
function getId(id: string) {
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getNumericValue(rowValue: number | number[] | undefined) {
|
||||
if (rowValue == null || Array.isArray(rowValue)) {
|
||||
return;
|
||||
|
@ -566,25 +47,27 @@ export function getNumericValue(rowValue: number | number[] | undefined) {
|
|||
return rowValue;
|
||||
}
|
||||
|
||||
export const getFallbackDataBounds = (
|
||||
rangeType: CustomPaletteParams['rangeType'] = 'percent'
|
||||
): DataBounds =>
|
||||
rangeType === 'percent'
|
||||
? {
|
||||
min: 0,
|
||||
max: 100,
|
||||
fallback: true,
|
||||
}
|
||||
: {
|
||||
min: 1,
|
||||
max: 1,
|
||||
fallback: true,
|
||||
};
|
||||
export function applyPaletteParams<T extends PaletteOutput<CustomPaletteParams>>(
|
||||
palettes: PaletteRegistry,
|
||||
activePalette: T,
|
||||
dataBounds: DataBounds
|
||||
) {
|
||||
// make a copy of it as they have to be manipulated later on
|
||||
const displayStops = getPaletteStops(palettes, activePalette?.params || {}, {
|
||||
dataBounds,
|
||||
defaultPaletteName: activePalette?.name,
|
||||
});
|
||||
|
||||
if (activePalette?.params?.reverse && activePalette?.params?.name !== CUSTOM_PALETTE) {
|
||||
return reversePalette(displayStops);
|
||||
}
|
||||
return displayStops;
|
||||
}
|
||||
|
||||
export const findMinMaxByColumnId = (
|
||||
columnIds: string[],
|
||||
table: Datatable | undefined,
|
||||
getOriginalId: (id: string) => string = getId
|
||||
getOriginalId: (id: string) => string = (id: string) => id
|
||||
) => {
|
||||
const minMax: Record<string, DataBounds> = {};
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring';
|
||||
import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui';
|
||||
import { EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { Ast } from '@kbn/interpreter';
|
||||
import type { IconType } from '@elastic/eui/src/components/icon/icon';
|
||||
import type { CoreSetup, SavedObjectReference, SavedObjectsResolveResponse } from 'kibana/public';
|
||||
import type { PaletteOutput } from 'src/plugins/charts/public';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import type { TopNavMenuData } from 'src/plugins/navigation/public';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
|
|
@ -17,7 +17,12 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import {
|
||||
PaletteRegistry,
|
||||
CustomizablePalette,
|
||||
CUSTOM_PALETTE,
|
||||
FIXED_PROGRESSION,
|
||||
} from '@kbn/coloring';
|
||||
import {
|
||||
GaugeTicksPositions,
|
||||
GaugeColorModes,
|
||||
|
@ -29,9 +34,6 @@ import {
|
|||
import { isNumericFieldForDatatable } from '../../../common/expressions';
|
||||
import {
|
||||
applyPaletteParams,
|
||||
CustomizablePalette,
|
||||
CUSTOM_PALETTE,
|
||||
FIXED_PROGRESSION,
|
||||
PalettePanelContainer,
|
||||
TooltipWrapper,
|
||||
} from '../../shared_components/';
|
||||
|
|
|
@ -10,9 +10,10 @@ import {
|
|||
PaletteDefinition,
|
||||
PaletteRegistry,
|
||||
SeriesLayer,
|
||||
} from 'src/plugins/charts/public';
|
||||
RequiredPaletteParamTypes,
|
||||
} from '@kbn/coloring';
|
||||
|
||||
import Color from 'color';
|
||||
import { RequiredPaletteParamTypes } from '../../../common';
|
||||
import { defaultPaletteParams as sharedDefaultParams } from '../../shared_components/';
|
||||
|
||||
export const DEFAULT_PALETTE_NAME = 'gray';
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PaletteOutput, CustomPaletteParams } from '@kbn/coloring';
|
||||
import { getGaugeVisualization, isNumericDynamicMetric, isNumericMetric } from './visualization';
|
||||
import { createMockDatasource, createMockFramePublicAPI } from '../../mocks';
|
||||
import { GROUP_ID } from './constants';
|
||||
import type { DatasourcePublicAPI, OperationDescriptor } from '../../types';
|
||||
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
|
||||
import { CustomPaletteParams, layerTypes } from '../../../common';
|
||||
import { layerTypes } from '../../../common';
|
||||
import type { GaugeVisualizationState } from './constants';
|
||||
import { PaletteOutput } from 'src/plugins/charts/common';
|
||||
|
||||
function exampleState(): GaugeVisualizationState {
|
||||
return {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||
import { Ast } from '@kbn/interpreter';
|
||||
import { DatatableRow } from 'src/plugins/expressions';
|
||||
import { PaletteRegistry, CustomPaletteParams, CUSTOM_PALETTE } from '@kbn/coloring';
|
||||
import type { GaugeArguments } from '../../../../../../src/plugins/chart_expressions/expression_gauge/common';
|
||||
import {
|
||||
GaugeShapes,
|
||||
|
@ -24,7 +25,6 @@ import {
|
|||
VerticalBulletIcon,
|
||||
HorizontalBulletIcon,
|
||||
} from '../../../../../../src/plugins/chart_expressions/expression_gauge/public';
|
||||
import { PaletteRegistry } from '../../../../../../src/plugins/charts/public';
|
||||
import type { DatasourcePublicAPI, OperationMetadata, Visualization } from '../../types';
|
||||
import { getSuggestions } from './suggestions';
|
||||
import {
|
||||
|
@ -34,9 +34,9 @@ import {
|
|||
GaugeExpressionState,
|
||||
} from './constants';
|
||||
import { GaugeToolbar } from './toolbar_component';
|
||||
import { applyPaletteParams, CUSTOM_PALETTE } from '../../shared_components';
|
||||
import { applyPaletteParams } from '../../shared_components';
|
||||
import { GaugeDimensionEditor } from './dimension_editor';
|
||||
import { CustomPaletteParams, layerTypes } from '../../../common';
|
||||
import { layerTypes } from '../../../common';
|
||||
import { generateId } from '../../id_generator';
|
||||
import { getAccessorsFromState } from './utils';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { uniq, mapValues } from 'lodash';
|
||||
import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public';
|
||||
import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring';
|
||||
import type { Datatable } from 'src/plugins/expressions';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import type { AccessorConfig, FramePublicAPI } from '../types';
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue