From 656c2eda7c97beaa5381adba24515d9d73f3a30c Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Mon, 22 Jan 2024 16:44:23 +0100 Subject: [PATCH] loop color v2 --- .../__stories__/color_mapping.stories.tsx | 251 ++++++--- .../color_mapping/color/color_handling.ts | 133 +++-- .../components/assignment/assignment.tsx | 6 + .../components/assignment/match.tsx | 24 +- .../assignment/special_assignment.tsx | 70 +-- .../components/color_picker/color_swatch.tsx | 4 +- .../components/container/container.tsx | 530 ++++++++++++------ .../components/palette_selector/gradient.tsx | 166 ++---- .../palette_selector/palette_selector.tsx | 204 ++----- .../components/palette_selector/scale.tsx | 145 +++++ .../config/assignment_from_categories.ts | 65 --- .../color_mapping/config/assignments.ts | 70 +-- .../config/default_color_mapping.ts | 49 +- .../color_mapping/config/types.ts | 12 +- .../color_mapping/palettes/elastic_brand.ts | 6 +- .../color_mapping/palettes/eui_amsterdam.ts | 6 +- .../color_mapping/palettes/kibana_legacy.ts | 6 +- .../color_mapping/state/color_mapping.ts | 12 +- .../color_mapping/state/selectors.ts | 3 - 19 files changed, 969 insertions(+), 793 deletions(-) create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale.tsx delete mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx index 95f4ff5623ea3..b1c7c12261206 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/__stories__/color_mapping.stories.tsx @@ -6,122 +6,201 @@ * Side Public License, v 1. */ -import React, { FC } from 'react'; -import { EuiFlyout, EuiForm } from '@elastic/eui'; +import React, { FC, useState } from 'react'; +import { EuiFlyout, EuiForm, EuiPage, isColorDark } from '@elastic/eui'; import { ComponentStory } from '@storybook/react'; +import { css } from '@emotion/react'; import { CategoricalColorMapping, ColorMappingProps } from '../categorical_color_mapping'; -import { AVAILABLE_PALETTES } from '../palettes'; +import { AVAILABLE_PALETTES, getPalette, NeutralPalette } from '../palettes'; import { DEFAULT_COLOR_MAPPING_CONFIG } from '../config/default_color_mapping'; +import { ColorMapping } from '../config'; +import { getColorFactory } from '../color/color_handling'; +import { ruleMatch } from '../color/rule_matching'; +import { getValidColor } from '../color/color_math'; export default { title: 'Color Mapping', component: CategoricalColorMapping, - decorators: [ - (story: Function) => ( - {}} hideCloseButton> - {story()} - - ), - ], + decorators: [(story: Function) => story()], }; -const Template: ComponentStory> = (args) => ( - -); +const Template: ComponentStory> = (args) => { + const [updatedModel, setUpdateModel] = useState( + DEFAULT_COLOR_MAPPING_CONFIG + ); + + const getPaletteFn = getPalette(AVAILABLE_PALETTES, NeutralPalette); + + const colorFactory = getColorFactory(updatedModel, getPaletteFn, false, args.data); + return ( + +
    + {args.data.type === 'categories' && + args.data.categories.map((c, i) => { + const match = updatedModel.assignments.some(({ rule }) => { + return ruleMatch(rule, c); + }); + const color = colorFactory(c); + const isDark = isColorDark(...getValidColor(color).rgb()); + + return ( +
  1. + {c} +
  2. + ); + })} +
+ {}} + hideCloseButton + ownFocus={false} + > + + + + +
+ ); +}; export const Default = Template.bind({}); Default.args = { model: { ...DEFAULT_COLOR_MAPPING_CONFIG, - assignmentMode: 'manual', + // colorMode: { + // type: 'gradient', + // steps: [ + // { + // type: 'categorical', + // colorIndex: 0, + // paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, + // touched: false, + // }, + // { + // type: 'categorical', + // colorIndex: 1, + // paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, + // touched: false, + // }, + // { + // type: 'categorical', + // colorIndex: 2, + // paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, + // touched: false, + // }, + // ], + // sort: 'asc', + // }, colorMode: { - type: 'gradient', - steps: [ - { - type: 'categorical', - colorIndex: 0, - paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, - touched: false, - }, - { - type: 'categorical', - colorIndex: 1, - paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, - touched: false, - }, - { - type: 'categorical', - colorIndex: 2, - paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, - touched: false, - }, - ], - sort: 'asc', + type: 'categorical', + // sort: 'asc', + // steps: [ + // { + // paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, + // type: 'categorical', + // colorIndex: 0, + // touched: false, + // }, + // { + // paletteId: DEFAULT_COLOR_MAPPING_CONFIG.paletteId, + // type: 'categorical', + // colorIndex: 3, + // touched: false, + // }, + // ], }, - assignments: [ - { - rule: { - type: 'matchExactly', - values: ['this is', 'a multi-line combobox that is very long and that will be truncated'], - }, - color: { - type: 'gradient', - }, - touched: false, - }, + specialAssignments: [ { rule: { - type: 'matchExactly', - values: ['b', ['double', 'value']], + type: 'other', }, color: { - type: 'gradient', - }, - touched: false, - }, - { - rule: { - type: 'matchExactly', - values: ['c'], - }, - color: { - type: 'gradient', - }, - touched: false, - }, - { - rule: { - type: 'matchExactly', - values: [ - 'this is', - 'a multi-line wrap', - 'combo box', - 'test combo', - '3 lines', - ['double', 'value'], - ], - }, - color: { - type: 'gradient', + type: 'loop', }, touched: false, }, ], + assignments: [ + // { + // rule: { + // type: 'matchExactly', + // values: ['this is', 'a multi-line combobox that is very long and that will be truncated'], + // }, + // color: { + // type: 'cate', + // }, + // touched: false, + // }, + // { + // rule: { + // type: 'matchExactly', + // values: ['b', ['double', 'value']], + // }, + // color: { + // type: 'gradient', + // }, + // touched: false, + // }, + // { + // rule: { + // type: 'matchExactly', + // values: ['c'], + // }, + // color: { + // type: 'gradient', + // }, + // touched: false, + // }, + // { + // rule: { + // type: 'matchExactly', + // values: [ + // 'this is', + // 'a multi-line wrap', + // 'combo box', + // 'test combo', + // '3 lines', + // ['double', 'value'], + // ], + // }, + // color: { + // type: 'gradient', + // }, + // touched: false, + // }, + ], }, isDarkMode: false, data: { type: 'categories', categories: [ - 'a', - 'b', - 'c', - 'd', - 'this is', - 'a multi-line wrap', - 'combo box', - 'test combo', - '3 lines', + 'US', + 'Mexico', + 'Brasil', + 'Canada', + 'Italy', + 'Germany', + 'France', + 'Spain', + 'UK', + 'Portugal', + 'Greece', + 'Sweden', + 'Finland', ], }, diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts index 795f94b740e9b..ba9bc46d27bce 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.ts @@ -8,7 +8,6 @@ import chroma from 'chroma-js'; import { ColorMapping } from '../config'; import { changeAlpha, combineColors, getValidColor } from './color_math'; -import { generateAutoAssignmentsForCategories } from '../config/assignment_from_categories'; import { getPalette } from '../palettes'; import { ColorMappingInputData } from '../categorical_color_mapping'; import { ruleMatch } from './rule_matching'; @@ -16,15 +15,18 @@ import { GradientColorMode } from '../config/types'; export function getAssignmentColor( colorMode: ColorMapping.Config['colorMode'], - color: ColorMapping.Config['assignments'][number]['color'], + color: + | ColorMapping.Config['assignments'][number]['color'] + | (ColorMapping.LoopColor & { paletteId: string; colorIndex: number }), getPaletteFn: ReturnType, isDarkMode: boolean, index: number, total: number -) { +): string { switch (color.type) { case 'colorCode': case 'categorical': + case 'loop': return getColor(color, getPaletteFn, isDarkMode); case 'gradient': { if (colorMode.type === 'categorical') { @@ -37,31 +39,32 @@ export function getAssignmentColor( } export function getColor( - color: ColorMapping.ColorCode | ColorMapping.CategoricalColor, + color: + | ColorMapping.ColorCode + | ColorMapping.CategoricalColor + | (ColorMapping.LoopColor & { paletteId: string; colorIndex: number }), getPaletteFn: ReturnType, isDarkMode: boolean -) { +): string { return color.type === 'colorCode' ? color.colorCode - : getValidColor(getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode)).hex(); + : color.type === 'loop' + ? getValidColor( + getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode, true) + ).hex() + : getValidColor( + getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode, true) + ).hex(); } export function getColorFactory( - model: ColorMapping.Config, + { assignments, specialAssignments, colorMode, paletteId }: ColorMapping.Config, getPaletteFn: ReturnType, isDarkMode: boolean, data: ColorMappingInputData ): (category: string | string[]) => string { - const palette = getPaletteFn(model.paletteId); - // generate on-the-fly assignments in auto-mode based on current data. - // This simplify the code by always using assignments, even if there is no real static assigmnets - const assignments = - model.assignmentMode === 'auto' - ? generateAutoAssignmentsForCategories(data, palette, model.colorMode) - : model.assignments; - // find auto-assigned colors - const autoAssignedColors = + const autoByOrderAssignments = data.type === 'categories' ? assignments.filter((a) => { return ( @@ -71,7 +74,7 @@ export function getColorFactory( : []; // find all categories that doesn't match with an assignment - const nonAssignedCategories = + const notAssignedCategories = data.type === 'categories' ? data.categories.filter((category) => { return !assignments.some(({ rule }) => ruleMatch(rule, category)); @@ -80,66 +83,80 @@ export function getColorFactory( return (category: string | string[]) => { if (typeof category === 'string' || Array.isArray(category)) { - const nonAssignedCategoryIndex = nonAssignedCategories.indexOf(category); + const nonAssignedCategoryIndex = notAssignedCategories.indexOf(category); - // return color for a non assigned category + // this category is not assigned to a specific color if (nonAssignedCategoryIndex > -1) { - if (nonAssignedCategoryIndex < autoAssignedColors.length) { + // if the category order is within current number of auto-assigned items pick the defined color + if (nonAssignedCategoryIndex < autoByOrderAssignments.length) { const autoAssignmentIndex = assignments.findIndex( - (d) => d === autoAssignedColors[nonAssignedCategoryIndex] + (d) => d === autoByOrderAssignments[nonAssignedCategoryIndex] ); return getAssignmentColor( - model.colorMode, - autoAssignedColors[nonAssignedCategoryIndex].color, + colorMode, + autoByOrderAssignments[nonAssignedCategoryIndex].color, getPaletteFn, isDarkMode, autoAssignmentIndex, assignments.length ); } - // if no auto-assign color rule/color is available then use the other color - // TODO: the specialAssignment[0] position is arbitrary, we should fix it better - return getColor(model.specialAssignments[0].color, getPaletteFn, isDarkMode); - } - - // find the assignment where the category matches the rule - const matchingAssignmentIndex = assignments.findIndex(({ rule }) => { - return ruleMatch(rule, category); - }); + const totalColorsIfGradient = + assignments.length === 0 ? notAssignedCategories.length : assignments.length; + const indexIfGradient = + (nonAssignedCategoryIndex - autoByOrderAssignments.length) % totalColorsIfGradient; - // return the assigned color - if (matchingAssignmentIndex > -1) { - const assignment = assignments[matchingAssignmentIndex]; + // if no auto-assign color rule/color is available then use the color looping palette return getAssignmentColor( - model.colorMode, - assignment.color, + colorMode, + // TODO: the specialAssignment[0] position is arbitrary, we should fix it better + specialAssignments[0].color.type === 'loop' + ? colorMode.type === 'gradient' + ? { type: 'gradient' } + : { + type: 'loop', + // those are applied here and depends on the current non-assigned category - auto-assignment list + colorIndex: nonAssignedCategoryIndex - autoByOrderAssignments.length, + paletteId, + } + : specialAssignments[0].color, getPaletteFn, isDarkMode, - matchingAssignmentIndex, - assignments.length + indexIfGradient, + totalColorsIfGradient ); } - // if no assign color rule/color is available then use the other color - // TODO: the specialAssignment[0] position is arbitrary, we should fix it better - return getColor(model.specialAssignments[0].color, getPaletteFn, isDarkMode); - } else { - const matchingAssignmentIndex = assignments.findIndex(({ rule }) => { - return ruleMatch(rule, category); - }); + } + // find the assignment where the category matches the rule + const matchingAssignmentIndex = assignments.findIndex(({ rule }) => { + return ruleMatch(rule, category); + }); - if (matchingAssignmentIndex > -1) { - const assignment = assignments[matchingAssignmentIndex]; - return getAssignmentColor( - model.colorMode, - assignment.color, - getPaletteFn, - isDarkMode, - matchingAssignmentIndex, - assignments.length - ); - } - return getColor(model.specialAssignments[0].color, getPaletteFn, isDarkMode); + if (matchingAssignmentIndex > -1) { + const assignment = assignments[matchingAssignmentIndex]; + return getAssignmentColor( + colorMode, + assignment.color, + getPaletteFn, + isDarkMode, + matchingAssignmentIndex, + assignments.length + ); } + // if the rule doesn't match anymore let's use the special assignment color + return getColor( + // TODO: the specialAssignment[0] position is arbitrary, we should fix it better + specialAssignments[0].color.type === 'loop' + ? { + type: 'loop', + // this is a catch all and should not be reached + colorIndex: 0, + paletteId, + } + : specialAssignments[0].color, + getPaletteFn, + isDarkMode + ); }; } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx index 896f2ea392884..9e92b484a5704 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/assignment.tsx @@ -39,6 +39,8 @@ export function Assignment({ isDarkMode, specialTokens, assignmentValuesCounter, + focusOnMount, + onBlur, }: { data: ColorMappingInputData; index: number; @@ -53,6 +55,8 @@ export function Assignment({ isDarkMode: boolean; specialTokens: Map; assignmentValuesCounter: Map; + focusOnMount: boolean; + onBlur: () => void; }) { const dispatch = useDispatch(); @@ -85,6 +89,8 @@ export function Assignment({ assignment.rule.type === 'matchExactly' || assignment.rule.type === 'matchExactlyCI' ? ( ; specialTokens: Map; assignmentValuesCounter: Map; -}> = ({ index, rule, updateValue, editable, options, specialTokens, assignmentValuesCounter }) => { + focusOnMount: boolean; + onBlur: () => void; +}> = ({ + index, + rule, + updateValue, + editable, + options, + specialTokens, + assignmentValuesCounter, + focusOnMount, + onBlur, +}) => { const duplicateWarning = i18n.translate( 'coloring.colorMapping.assignments.duplicateCategoryWarning', { @@ -70,10 +82,18 @@ export const Match: React.FC<{ value, }; }); + const comboBoxRef = useRef(); + useEffect(() => { + if (focusOnMount) comboBoxRef.current?.focus(); + }); return ( { + comboBoxRef.current = ref; + }} + onBlur={onBlur} data-test-subj={`lns-colorMapping-assignmentsItem${index}`} isDisabled={!editable} fullWidth={true} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx index 29ede59e37f41..92825509bc942 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/special_assignment.tsx @@ -6,17 +6,16 @@ * Side Public License, v 1. */ -import { EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import React from 'react'; -import { i18n } from '@kbn/i18n'; import { ColorMapping } from '../../config'; import { getPalette } from '../../palettes'; import { ColorSwatch } from '../color_picker/color_swatch'; import { updateSpecialAssignmentColor } from '../../state/color_mapping'; +import { ColorCode, CategoricalColor } from '../../config/types'; export function SpecialAssignment({ - assignment, + assignmentColor, index, palette, getPaletteFn, @@ -25,55 +24,32 @@ export function SpecialAssignment({ }: { isDarkMode: boolean; index: number; - assignment: ColorMapping.Config['specialAssignments'][number]; + assignmentColor: CategoricalColor | ColorCode; palette: ColorMapping.CategoricalPalette; getPaletteFn: ReturnType; total: number; }) { const dispatch = useDispatch(); - const canPickColor = true; return ( - - - { - dispatch( - updateSpecialAssignmentColor({ - assignmentIndex: index, - color, - }) - ); - }} - /> - - - - - + { + dispatch( + updateSpecialAssignmentColor({ + assignmentIndex: index, + color, + }) + ); + }} + /> ); } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx index 8ddc56d2476c7..1eeffa959f5fb 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/color_swatch.tsx @@ -29,9 +29,7 @@ import { getValidColor } from '../../color/color_math'; interface ColorPickerSwatchProps { colorMode: ColorMapping.Config['colorMode']; - assignmentColor: - | ColorMapping.Config['assignments'][number]['color'] - | ColorMapping.Config['specialAssignments'][number]['color']; + assignmentColor: ColorMapping.Config['assignments'][number]['color']; getPaletteFn: ReturnType; canPickColor: boolean; index: number; diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx index 748f17fa45842..cff65dab8a708 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx @@ -6,19 +6,26 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { EuiButtonEmpty, + EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiFormLabel, - EuiHorizontalRule, + EuiFormRow, + EuiIcon, EuiPanel, - EuiSwitch, - EuiText, + EuiSpacer, + EuiToolTip, + EuiHorizontalRule, + EuiEmptyPrompt, + EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + import { css } from '@emotion/react'; import { Assignment } from '../assignment/assignment'; import { SpecialAssignment } from '../assignment/special_assignment'; @@ -27,37 +34,31 @@ import { PaletteSelector } from '../palette_selector/palette_selector'; import { RootState, addNewAssignment, - assignAutomatically, - assignStatically, changeGradientSortOrder, + updateSpecialAssignmentColor, + addNewAssignments, + removeAllAssignments, } from '../../state/color_mapping'; -import { generateAutoAssignmentsForCategories } from '../../config/assignment_from_categories'; import { ColorMapping } from '../../config'; import { getPalette } from '../../palettes'; -import { getUnusedColorForNewAssignment } from '../../config/assignments'; -import { - selectColorMode, - selectPalette, - selectSpecialAssignments, - selectIsAutoAssignmentMode, -} from '../../state/selectors'; +import { selectColorMode, selectPalette, selectSpecialAssignments } from '../../state/selectors'; import { ColorMappingInputData } from '../../categorical_color_mapping'; import { Gradient } from '../palette_selector/gradient'; import { NeutralPalette } from '../../palettes/neutral'; +import { DEFAULT_NEUTRAL_PALETTE_INDEX } from '../../config/default_color_mapping'; +import { ruleMatch } from '../../color/rule_matching'; +import { ScaleMode } from '../palette_selector/scale'; -export const MAX_ASSIGNABLE_COLORS = 10; - -function selectComputedAssignments( - data: ColorMappingInputData, - palette: ColorMapping.CategoricalPalette, - colorMode: ColorMapping.Config['colorMode'] -) { - return (state: RootState) => - state.colorMapping.assignmentMode === 'auto' - ? generateAutoAssignmentsForCategories(data, palette, colorMode) - : state.colorMapping.assignments; -} -export function Container(props: { +export const MAX_ASSIGNABLE_COLORS = Infinity; + +const selectComputedAssignments = (state: RootState) => state.colorMapping.assignments; + +export function Container({ + data, + palettes, + isDarkMode, + specialTokens, +}: { palettes: Map; data: ColorMappingInputData; isDarkMode: boolean; @@ -65,16 +66,16 @@ export function Container(props: { specialTokens: Map; }) { const dispatch = useDispatch(); + const [assignmentOnFocus, setAssignmentOnFocus] = useState(-1); - const getPaletteFn = getPalette(props.palettes, NeutralPalette); + const getPaletteFn = getPalette(palettes, NeutralPalette); const palette = useSelector(selectPalette(getPaletteFn)); const colorMode = useSelector(selectColorMode); - const autoAssignmentMode = useSelector(selectIsAutoAssignmentMode); - const assignments = useSelector(selectComputedAssignments(props.data, palette, colorMode)); + const assignments = useSelector(selectComputedAssignments); const specialAssignments = useSelector(selectSpecialAssignments); - const canAddNewAssignment = !autoAssignmentMode && assignments.length < MAX_ASSIGNABLE_COLORS; + const canAddNewAssignment = assignments.length < MAX_ASSIGNABLE_COLORS; const assignmentValuesCounter = assignments.reduce>( (acc, assignment) => { @@ -87,166 +88,369 @@ export function Container(props: { new Map() ); + const unmatchingCategories = + data.type === 'categories' + ? data.categories.filter((category) => { + return !assignments.some(({ rule }) => ruleMatch(rule, category)); + }) + : []; + + const onClickAddNewAssignment = () => { + setAssignmentOnFocus(assignments.length); + dispatch( + addNewAssignment({ + rule: + data.type === 'categories' + ? { + type: 'matchExactly', + values: [], + } + : { type: 'range', min: 0, max: 0, minInclusive: true, maxInclusive: true }, + color: + colorMode.type === 'categorical' + ? { + type: 'categorical', + paletteId: palette.id, + colorIndex: assignments.length % palette.colorCount, + } + : { type: 'gradient' }, + touched: false, + }) + ); + }; + const onClickAddAllCurrentCategories = () => { + setAssignmentOnFocus(-1); + if (data.type === 'categories') { + const newAssignments: ColorMapping.Config['assignments'] = unmatchingCategories.map( + (c, i) => { + return { + rule: { + type: 'matchExactly', + values: [c], + }, + color: + colorMode.type === 'categorical' + ? { + type: 'categorical', + paletteId: palette.id, + colorIndex: (assignments.length + i) % palette.colorCount, + } + : { type: 'gradient' }, + touched: false, + }; + } + ); + dispatch(addNewAssignments(newAssignments)); + } + }; + + const otherAssignment = specialAssignments[0]; + return ( - + + + + + {colorMode.type === 'gradient' && ( + + +
+ + + + { + dispatch(changeGradientSortOrder(colorMode.sort === 'asc' ? 'desc' : 'asc')); + }} + > + {i18n.translate('coloring.colorMapping.container.invertGradientButtonLabel', { + defaultMessage: 'Invert gradient', + })} + + +
+
+
+ )} + {i18n.translate('coloring.colorMapping.container.mappingAssignmentHeader', { - defaultMessage: 'Mapping assignments', + defaultMessage: 'Mappings', })} - - - {i18n.translate('coloring.colorMapping.container.autoAssignLabel', { - defaultMessage: 'Auto assign', - })} - - } - checked={autoAssignmentMode} - compressed - onChange={() => { - if (autoAssignmentMode) { - dispatch(assignStatically(assignments)); - } else { - dispatch(assignAutomatically()); - } - }} - /> - -
- {colorMode.type !== 'gradient' ? null : ( - - )} - {assignments.map((assignment, i) => { - return ( -
0 && ( + <> + + - -
- ); - })} -
- - - - - {props.data.type === 'categories' && - specialAssignments.map((assignment, i) => { - return ( - + + {data.type === 'categories' && ( + + {i18n.translate('coloring.colorMapping.container.mapCurrentValuesButtonLabel', { + defaultMessage: 'Map current values {termsCount}', + values: { + termsCount: + unmatchingCategories.length > 0 ? `(${unmatchingCategories.length})` : '', + }, + })} + + )} + + { + setAssignmentOnFocus(-1); + dispatch(removeAllAssignments()); + }} + disabled={assignments.length === 0} + css={css` + margin-right: 8px; + `} + > + {i18n.translate('coloring.colorMapping.container.resetButtonLabel', { + defaultMessage: 'Reset', + })} + + + + + )} + + +
+ {assignments.map((assignment, i) => { + return ( +
+ setAssignmentOnFocus(-1)} /> - ); - })} - - - - - - { - dispatch( - addNewAssignment({ - rule: - props.data.type === 'categories' - ? { - type: 'matchExactly', - values: [], - } - : { type: 'range', min: 0, max: 0, minInclusive: true, maxInclusive: true }, - color: getUnusedColorForNewAssignment(palette, colorMode, assignments), - touched: false, - }) +
); - }} - disabled={!canAddNewAssignment} - css={css` - margin-right: 8px; - `} - > - {i18n.translate('coloring.colorMapping.container.addAssignmentButtonLabel', { - defaultMessage: 'Add assignment', })} - - {colorMode.type === 'gradient' && ( - { - dispatch(changeGradientSortOrder(colorMode.sort === 'asc' ? 'desc' : 'asc')); - }} - > - {i18n.translate('coloring.colorMapping.container.invertGradientButtonLabel', { - defaultMessage: 'Invert gradient', + {assignments.length === 0 && ( + + + + + ), + }} + /> +

+ } + actions={[ + + {i18n.translate( + 'coloring.colorMapping.container.mapCurrentValuesButtonLabel', + { + defaultMessage: 'Map current values {termsCount}', + values: { + termsCount: + unmatchingCategories.length > 0 + ? `(${unmatchingCategories.length})` + : '', + }, + } + )} + , + + {i18n.translate('coloring.colorMapping.container.mapValueButtonLabel', { + defaultMessage: 'Map a value', + })} + , + ]} + /> + )} +
+
+
+
+ + + + {i18n.translate('coloring.colorMapping.container.fallbackModeHeader', { + defaultMessage: 'Fallback mode', })} - - )} - -
+ + + + } + > + { + dispatch( + updateSpecialAssignmentColor({ + assignmentIndex: 0, + color: + optionId === 'loop' + ? { + type: 'loop', + } + : { + type: 'categorical', + colorIndex: DEFAULT_NEUTRAL_PALETTE_INDEX, + paletteId: NeutralPalette.id, + }, + }) + ); + }} + buttonSize="compressed" + isFullWidth + /> +
+ {data.type === 'categories' && otherAssignment.color.type !== 'loop' && ( + + + + + + )} ); } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx index c4e22f797deaa..7fd329328d94b 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient.tsx @@ -67,37 +67,38 @@ export function Gradient({ const middleMostColorStopIndex = colorMode.steps.length === 3 ? 1 : NaN; return ( - <> - {assignmentsSize > 1 && ( -
- )} +
+
+
{topMostColorStop ? ( @@ -114,92 +115,35 @@ export function Gradient({ )}
- {assignmentsSize > 1 && ( -
-
- {middleMostColorSep ? ( - - ) : colorMode.steps.length === 2 ? ( - - ) : undefined} -
-
- )} - {assignmentsSize > 1 && ( -
- )} -
+ {middleMostColorSep ? ( + + ) : colorMode.steps.length === 2 ? ( + + ) : undefined} +
+
{bottomMostColorStop ? ( @@ -215,7 +159,7 @@ export function Gradient({ )}
- +
); } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx index c9fab3526a786..3baa3991de7fe 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/palette_selector.tsx @@ -8,14 +8,7 @@ import React, { useCallback, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { - EuiButtonGroup, - EuiColorPalettePicker, - EuiConfirmModal, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, -} from '@elastic/eui'; +import { EuiColorPalettePicker, EuiConfirmModal, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { RootState, updatePalette } from '../../state/color_mapping'; @@ -33,11 +26,8 @@ export function PaletteSelector({ isDarkMode: boolean; }) { const dispatch = useDispatch(); - const colorMode = useSelector((state: RootState) => state.colorMapping.colorMode); const model = useSelector((state: RootState) => state.colorMapping); - const { paletteId } = model; - const switchPaletteFn = useCallback( (selectedPaletteId: string, preserveColorChanges: boolean) => { dispatch( @@ -45,7 +35,6 @@ export function PaletteSelector({ paletteId: selectedPaletteId, assignments: updateAssignmentsPalette( model.assignments, - model.assignmentMode, model.colorMode, selectedPaletteId, getPaletteFn, @@ -62,37 +51,6 @@ export function PaletteSelector({ [getPaletteFn, model, dispatch] ); - const updateColorMode = useCallback( - (type: 'gradient' | 'categorical', preserveColorChanges: boolean) => { - const updatedColorMode: ColorMapping.Config['colorMode'] = - type === 'gradient' - ? { - type: 'gradient', - steps: [ - { - type: 'categorical', - paletteId, - colorIndex: 0, - touched: false, - }, - ], - sort: 'desc', - } - : { type: 'categorical' }; - - const assignments = updateAssignmentsPalette( - model.assignments, - model.assignmentMode, - updatedColorMode, - paletteId, - getPaletteFn, - preserveColorChanges - ); - dispatch(updatePalette({ paletteId, assignments, colorMode: updatedColorMode })); - }, - [getPaletteFn, model, dispatch, paletteId] - ); - const [preserveModalPaletteId, setPreserveModalPaletteId] = useState(null); const preserveChangesModal = @@ -126,136 +84,42 @@ export function PaletteSelector({ ) : null; - const [colorScaleModalId, setColorScaleModalId] = useState<'gradient' | 'categorical' | null>( - null - ); - - const colorScaleModal = - colorScaleModalId !== null ? ( - { - setColorScaleModalId(null); - }} - onConfirm={() => { - if (colorScaleModalId) updateColorMode(colorScaleModalId, false); - setColorScaleModalId(null); - }} - cancelButtonText={i18n.translate( - 'coloring.colorMapping.colorChangesModal.goBackButtonLabel', - { - defaultMessage: 'Go back', - } - )} - confirmButtonText={i18n.translate( - 'coloring.colorMapping.colorChangesModal.discardButtonLabel', - { - defaultMessage: 'Discard changes', - } - )} - defaultFocusedButton="confirm" - buttonColor="danger" - > -

- {colorScaleModalId === 'categorical' - ? i18n.translate('coloring.colorMapping.colorChangesModal.categoricalModeDescription', { - defaultMessage: `Switching to a categorical mode will discard all your custom color changes`, - }) - : i18n.translate('coloring.colorMapping.colorChangesModal.sequentialModeDescription', { - defaultMessage: `Switching to a sequential mode will discard all your custom color changes`, - })} -

-
- ) : null; - return ( <> {preserveChangesModal} - {colorScaleModal} - - - - d.name !== 'Neutral') - .map((palette) => ({ - 'data-test-subj': `kbnColoring_ColorMapping_Palette-${palette.id}`, - value: palette.id, - title: palette.name, - palette: Array.from({ length: palette.colorCount }, (_, i) => { - return palette.getColor(i, isDarkMode); - }), - type: 'fixed', - }))} - onChange={(selectedPaletteId) => { - const hasChanges = model.assignments.some((a) => a.touched); - const hasGradientChanges = - model.colorMode.type === 'gradient' && - model.colorMode.steps.some((a) => a.touched); - if (hasChanges || hasGradientChanges) { - setPreserveModalPaletteId(selectedPaletteId); - } else { - switchPaletteFn(selectedPaletteId, false); - } - }} - valueOfSelected={model.paletteId} - selectionDisplay={'palette'} - compressed={true} - /> - - - - - { - const hasChanges = model.assignments.some((a) => a.touched); - const hasGradientChanges = - model.colorMode.type === 'gradient' && - model.colorMode.steps.some((a) => a.touched); - - if (hasChanges || hasGradientChanges) { - setColorScaleModalId(id as 'gradient' | 'categorical'); - } else { - updateColorMode(id as 'gradient' | 'categorical', false); - } - }} - isIconOnly - /> - - - + + d.name !== 'Neutral') + .map((palette) => ({ + 'data-test-subj': `kbnColoring_ColorMapping_Palette-${palette.id}`, + value: palette.id, + title: palette.name, + palette: Array.from({ length: palette.colorCount }, (_, i) => { + return palette.getColor(i, isDarkMode, false); + }), + type: 'fixed', + }))} + onChange={(selectedPaletteId) => { + const hasChanges = model.assignments.some((a) => a.touched); + const hasGradientChanges = + model.colorMode.type === 'gradient' && model.colorMode.steps.some((a) => a.touched); + if (hasChanges || hasGradientChanges) { + setPreserveModalPaletteId(selectedPaletteId); + } else { + switchPaletteFn(selectedPaletteId, false); + } + }} + valueOfSelected={model.paletteId} + selectionDisplay={'palette'} + compressed={true} + /> + ); } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale.tsx new file mode 100644 index 0000000000000..f55a2cd5e72f2 --- /dev/null +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/scale.tsx @@ -0,0 +1,145 @@ +/* + * 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, { useCallback, useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { EuiButtonGroup, EuiConfirmModal, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { RootState, updatePalette } from '../../state/color_mapping'; +import { ColorMapping } from '../../config'; +import { updateAssignmentsPalette } from '../../config/assignments'; +import { getPalette } from '../../palettes'; + +export function ScaleMode({ getPaletteFn }: { getPaletteFn: ReturnType }) { + const dispatch = useDispatch(); + const colorMode = useSelector((state: RootState) => state.colorMapping.colorMode); + const model = useSelector((state: RootState) => state.colorMapping); + + const { paletteId } = model; + + const updateColorMode = useCallback( + (type: 'gradient' | 'categorical', preserveColorChanges: boolean) => { + const updatedColorMode: ColorMapping.Config['colorMode'] = + type === 'gradient' + ? { + type: 'gradient', + steps: [ + { + type: 'categorical', + paletteId, + colorIndex: 0, + touched: false, + }, + ], + sort: 'desc', + } + : { type: 'categorical' }; + + const assignments = updateAssignmentsPalette( + model.assignments, + updatedColorMode, + paletteId, + getPaletteFn, + preserveColorChanges + ); + dispatch(updatePalette({ paletteId, assignments, colorMode: updatedColorMode })); + }, + [getPaletteFn, model, dispatch, paletteId] + ); + + const [colorScaleModalId, setColorScaleModalId] = useState<'gradient' | 'categorical' | null>( + null + ); + + const colorScaleModal = + colorScaleModalId !== null ? ( + { + setColorScaleModalId(null); + }} + onConfirm={() => { + if (colorScaleModalId) updateColorMode(colorScaleModalId, false); + setColorScaleModalId(null); + }} + cancelButtonText={i18n.translate( + 'coloring.colorMapping.colorChangesModal.goBackButtonLabel', + { + defaultMessage: 'Go back', + } + )} + confirmButtonText={i18n.translate( + 'coloring.colorMapping.colorChangesModal.discardButtonLabel', + { + defaultMessage: 'Discard changes', + } + )} + defaultFocusedButton="confirm" + buttonColor="danger" + > +

+ {colorScaleModalId === 'categorical' + ? i18n.translate('coloring.colorMapping.colorChangesModal.categoricalModeDescription', { + defaultMessage: `Switching to a categorical mode will discard all your custom color changes`, + }) + : i18n.translate('coloring.colorMapping.colorChangesModal.sequentialModeDescription', { + defaultMessage: `Switching to a sequential mode will discard all your custom color changes`, + })} +

+
+ ) : null; + + return ( + <> + {colorScaleModal} + + { + const hasChanges = model.assignments.some((a) => a.touched); + const hasGradientChanges = + model.colorMode.type === 'gradient' && model.colorMode.steps.some((a) => a.touched); + + if (hasChanges || hasGradientChanges) { + setColorScaleModalId(id as 'gradient' | 'categorical'); + } else { + updateColorMode(id as 'gradient' | 'categorical', false); + } + }} + /> + + + ); +} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts deleted file mode 100644 index 97c4d17c35e4d..0000000000000 --- a/packages/kbn-coloring/src/shared_components/color_mapping/config/assignment_from_categories.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 { ColorMapping } from '.'; -import { ColorMappingInputData } from '../categorical_color_mapping'; -import { MAX_ASSIGNABLE_COLORS } from '../components/container/container'; - -export function generateAutoAssignmentsForCategories( - data: ColorMappingInputData, - palette: ColorMapping.CategoricalPalette, - colorMode: ColorMapping.Config['colorMode'] -): ColorMapping.Config['assignments'] { - const isCategorical = colorMode.type === 'categorical'; - - const maxColorAssignable = data.type === 'categories' ? data.categories.length : data.bins; - - const assignableColors = isCategorical - ? Math.min(palette.colorCount, maxColorAssignable) - : Math.min(MAX_ASSIGNABLE_COLORS, maxColorAssignable); - - const autoRules: Array = - data.type === 'categories' - ? data.categories.map((c) => ({ type: 'matchExactly', values: [c] })) - : Array.from({ length: data.bins }, (d, i) => { - const step = (data.max - data.min) / data.bins; - return { - type: 'range', - min: data.max - i * step - step, - max: data.max - i * step, - minInclusive: true, - maxInclusive: false, - }; - }); - - const assignments = autoRules - .slice(0, assignableColors) - .map((rule, colorIndex) => { - if (isCategorical) { - return { - rule, - color: { - type: 'categorical', - paletteId: palette.id, - colorIndex, - }, - touched: false, - }; - } else { - return { - rule, - color: { - type: 'gradient', - }, - touched: false, - }; - } - }); - - return assignments; -} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts index 701baa1b1710b..d10b7e75648a3 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/assignments.ts @@ -9,11 +9,9 @@ import type { ColorMapping } from '.'; import { MAX_ASSIGNABLE_COLORS } from '../components/container/container'; import { getPalette, NeutralPalette } from '../palettes'; -import { DEFAULT_NEUTRAL_PALETTE_INDEX } from './default_color_mapping'; export function updateAssignmentsPalette( assignments: ColorMapping.Config['assignments'], - assignmentMode: ColorMapping.Config['assignmentMode'], colorMode: ColorMapping.Config['colorMode'], paletteId: string, getPaletteFn: ReturnType, @@ -21,27 +19,25 @@ export function updateAssignmentsPalette( ): ColorMapping.Config['assignments'] { const palette = getPaletteFn(paletteId); const maxColors = palette.type === 'categorical' ? palette.colorCount : MAX_ASSIGNABLE_COLORS; - return assignmentMode === 'auto' - ? [] - : assignments.map(({ rule, color, touched }, index) => { - if (preserveColorChanges && touched) { - return { rule, color, touched }; - } else { - const newColor: ColorMapping.Config['assignments'][number]['color'] = - colorMode.type === 'categorical' - ? { - type: 'categorical', - paletteId: index < maxColors ? paletteId : NeutralPalette.id, - colorIndex: index < maxColors ? index : 0, - } - : { type: 'gradient' }; - return { - rule, - color: newColor, - touched: false, - }; - } - }); + return assignments.map(({ rule, color, touched }, index) => { + if (preserveColorChanges && touched) { + return { rule, color, touched }; + } else { + const newColor: ColorMapping.Config['assignments'][number]['color'] = + colorMode.type === 'categorical' + ? { + type: 'categorical', + paletteId: index < maxColors ? paletteId : NeutralPalette.id, + colorIndex: index < maxColors ? index : 0, + } + : { type: 'gradient' }; + return { + rule, + color: newColor, + touched: false, + }; + } + }); } export function updateColorModePalette( @@ -61,31 +57,3 @@ export function updateColorModePalette( sort: colorMode.sort, }; } - -export function getUnusedColorForNewAssignment( - palette: ColorMapping.CategoricalPalette, - colorMode: ColorMapping.Config['colorMode'], - assignments: ColorMapping.Config['assignments'] -): ColorMapping.Config['assignments'][number]['color'] { - if (colorMode.type === 'categorical') { - // TODO: change the type of color assignment depending on palette - // compute the next unused color index in the palette. - const maxColors = palette.type === 'categorical' ? palette.colorCount : MAX_ASSIGNABLE_COLORS; - const colorIndices = new Set(Array.from({ length: maxColors }, (d, i) => i)); - assignments.forEach(({ color }) => { - if (color.type === 'categorical' && color.paletteId === palette.id) { - colorIndices.delete(color.colorIndex); - } - }); - const paletteForNextUnusedColorIndex = colorIndices.size > 0 ? palette.id : NeutralPalette.id; - const nextUnusedColorIndex = - colorIndices.size > 0 ? [...colorIndices][0] : DEFAULT_NEUTRAL_PALETTE_INDEX; - return { - type: 'categorical', - paletteId: paletteForNextUnusedColorIndex, - colorIndex: nextUnusedColorIndex, - }; - } else { - return { type: 'gradient' }; - } -} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts index e4005770b2883..c47680e7b68c9 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/default_color_mapping.ts @@ -18,7 +18,7 @@ export const DEFAULT_NEUTRAL_PALETTE_INDEX = 1; * The default color mapping used in Kibana, starts with the EUI color palette */ export const DEFAULT_COLOR_MAPPING_CONFIG: ColorMapping.Config = { - assignmentMode: 'auto', + // assignmentMode: 'auto', assignments: [], specialAssignments: [ { @@ -26,9 +26,7 @@ export const DEFAULT_COLOR_MAPPING_CONFIG: ColorMapping.Config = { type: 'other', }, color: { - type: 'categorical', - paletteId: NeutralPalette.id, - colorIndex: DEFAULT_NEUTRAL_PALETTE_INDEX, + type: 'loop', }, touched: false, }, @@ -45,17 +43,26 @@ export function getPaletteColors( ): string[] { const colorMappingModel = colorMappings ?? { ...DEFAULT_COLOR_MAPPING_CONFIG }; const palette = getPalette(AVAILABLE_PALETTES, NeutralPalette)(colorMappingModel.paletteId); - return Array.from({ length: palette.colorCount }, (d, i) => palette.getColor(i, isDarkMode)); + return getPaletteColorsFromPaletteId(isDarkMode, palette.id); +} + +export function getPaletteColorsFromPaletteId( + isDarkMode: boolean, + paletteId: ColorMapping.Config['paletteId'] +): string[] { + const palette = getPalette(AVAILABLE_PALETTES, NeutralPalette)(paletteId); + return Array.from({ length: palette.colorCount }, (d, i) => + palette.getColor(i, isDarkMode, true) + ); } export function getColorsFromMapping( isDarkMode: boolean, colorMappings?: ColorMapping.Config ): string[] { - const { colorMode, paletteId, assignmentMode, assignments, specialAssignments } = - colorMappings ?? { - ...DEFAULT_COLOR_MAPPING_CONFIG, - }; + const { colorMode, paletteId, assignments, specialAssignments } = colorMappings ?? { + ...DEFAULT_COLOR_MAPPING_CONFIG, + }; const getPaletteFn = getPalette(AVAILABLE_PALETTES, NeutralPalette); if (colorMode.type === 'gradient') { @@ -63,17 +70,17 @@ export function getColorsFromMapping( return Array.from({ length: 6 }, (d, i) => colorScale(i / 6)); } else { const palette = getPaletteFn(paletteId); - if (assignmentMode === 'auto') { - return Array.from({ length: palette.colorCount }, (d, i) => palette.getColor(i, isDarkMode)); - } else { - return [ - ...assignments.map((a) => { - return a.color.type === 'gradient' ? '' : getColor(a.color, getPaletteFn, isDarkMode); - }), - ...specialAssignments.map((a) => { - return getColor(a.color, getPaletteFn, isDarkMode); - }), - ].filter((color) => color !== ''); - } + const otherColors = + specialAssignments[0].color.type === 'loop' + ? Array.from({ length: palette.colorCount }, (d, i) => + palette.getColor(i, isDarkMode, true) + ) + : getColor(specialAssignments[0].color, getPaletteFn, isDarkMode); + return [ + ...assignments.map((a) => { + return a.color.type === 'gradient' ? '' : getColor(a.color, getPaletteFn, isDarkMode); + }), + ...otherColors, + ].filter((color) => color !== ''); } } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts b/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts index 59cb18435112d..4c62044be9242 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/config/types.ts @@ -30,6 +30,13 @@ export interface GradientColor { type: 'gradient'; } +/** + * An index specified categorical color, coming from paletteId + */ +export interface LoopColor { + type: 'loop'; +} + /** * A special rule that match automatically, in order, all the categories that are not matching a specified rule */ @@ -134,14 +141,13 @@ export interface GradientColorMode { export interface Config { paletteId: string; colorMode: CategoricalColorMode | GradientColorMode; - assignmentMode: 'auto' | 'manual'; assignments: Array< Assignment< RuleAuto | RuleMatchExactly | RuleMatchExactlyCI | RuleRange | RuleRegExp, CategoricalColor | ColorCode | GradientColor > >; - specialAssignments: Array>; + specialAssignments: Array>; } export interface CategoricalPalette { @@ -149,5 +155,5 @@ export interface CategoricalPalette { name: string; type: 'categorical'; colorCount: number; - getColor: (valueInRange: number, isDarkMode: boolean) => string; + getColor: (valueInRange: number, isDarkMode: boolean, loop: boolean) => string; } diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts index d93440c5ac5e4..ce13184ff062a 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/elastic_brand.ts @@ -22,7 +22,9 @@ export const ElasticBrandPalette: ColorMapping.CategoricalPalette = { name: 'Elastic Brand', colorCount: ELASTIC_BRAND_PALETTE_COLORS.length, type: 'categorical', - getColor(valueInRange) { - return ELASTIC_BRAND_PALETTE_COLORS[valueInRange]; + getColor(indexInRange, isDarkMode, loop) { + return ELASTIC_BRAND_PALETTE_COLORS[ + loop ? indexInRange % ELASTIC_BRAND_PALETTE_COLORS.length : indexInRange + ]; }, }; diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts index ec48793e12819..f9836a400b877 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/eui_amsterdam.ts @@ -26,7 +26,9 @@ export const EUIAmsterdamColorBlindPalette: ColorMapping.CategoricalPalette = { name: 'Default', colorCount: EUI_AMSTERDAM_PALETTE_COLORS.length, type: 'categorical', - getColor(valueInRange) { - return EUI_AMSTERDAM_PALETTE_COLORS[valueInRange]; + getColor(indexInRange, isDarkMode, loop) { + return EUI_AMSTERDAM_PALETTE_COLORS[ + loop ? indexInRange % EUI_AMSTERDAM_PALETTE_COLORS.length : indexInRange + ]; }, }; diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts index 9b576e0b05c66..bb90130a817fe 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/palettes/kibana_legacy.ts @@ -23,7 +23,9 @@ export const KibanaV7LegacyPalette: ColorMapping.CategoricalPalette = { name: 'Kibana Legacy', colorCount: KIBANA_V7_LEGACY_PALETTE_COLORS.length, type: 'categorical', - getColor(valueInRange) { - return KIBANA_V7_LEGACY_PALETTE_COLORS[valueInRange]; + getColor(indexInRange, isDarkMode, loop) { + return KIBANA_V7_LEGACY_PALETTE_COLORS[ + loop ? indexInRange % KIBANA_V7_LEGACY_PALETTE_COLORS.length : indexInRange + ]; }, }; diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts b/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts index 27588aff2b389..7fd23c0939508 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/state/color_mapping.ts @@ -22,7 +22,6 @@ export interface RootState { } const initialState: RootState['colorMapping'] = { - assignmentMode: 'auto', assignments: [], specialAssignments: [], paletteId: 'eui', @@ -34,7 +33,6 @@ export const colorMappingSlice = createSlice({ initialState, reducers: { updateModel: (state, action: PayloadAction) => { - state.assignmentMode = action.payload.assignmentMode; state.assignments = [...action.payload.assignments]; state.specialAssignments = [...action.payload.specialAssignments]; state.paletteId = action.payload.paletteId; @@ -53,11 +51,9 @@ export const colorMappingSlice = createSlice({ state.colorMode = { ...action.payload.colorMode }; }, assignStatically: (state, action: PayloadAction) => { - state.assignmentMode = 'manual'; state.assignments = [...action.payload]; }, assignAutomatically: (state) => { - state.assignmentMode = 'auto'; state.assignments = []; }, @@ -67,6 +63,9 @@ export const colorMappingSlice = createSlice({ ) => { state.assignments.push({ ...action.payload }); }, + addNewAssignments: (state, action: PayloadAction) => { + state.assignments.push(...action.payload); + }, updateAssignment: ( state, action: PayloadAction<{ @@ -121,6 +120,9 @@ export const colorMappingSlice = createSlice({ removeAssignment: (state, action: PayloadAction) => { state.assignments.splice(action.payload, 1); }, + removeAllAssignments: (state) => { + state.assignments = []; + }, changeColorMode: (state, action: PayloadAction) => { state.colorMode = { ...action.payload }; }, @@ -209,11 +211,13 @@ export const { assignStatically, assignAutomatically, addNewAssignment, + addNewAssignments, updateAssignment, updateAssignmentColor, updateSpecialAssignmentColor, updateAssignmentRule, removeAssignment, + removeAllAssignments, changeColorMode, updateGradientColorStep, removeGradientColorStep, diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts b/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts index 69bd57d2d852e..80360da642c2b 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/state/selectors.ts @@ -18,9 +18,6 @@ export function selectColorMode(state: RootState) { export function selectSpecialAssignments(state: RootState) { return state.colorMapping.specialAssignments; } -export function selectIsAutoAssignmentMode(state: RootState) { - return state.colorMapping.assignmentMode === 'auto'; -} export function selectColorPickerVisibility(state: RootState) { return state.ui.colorPicker; }