From 656c2eda7c97beaa5381adba24515d9d73f3a30c Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Mon, 22 Jan 2024 16:44:23 +0100 Subject: [PATCH 01/25] 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; } From 165ba640319eb7aead945e75313da9519853062b Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 24 Jan 2024 15:03:48 +0100 Subject: [PATCH 02/25] Remane reset --- .../color_mapping/components/container/container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cff65dab8a708..ca020e1e1bb0f 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 @@ -262,7 +262,7 @@ export function Container({ `} > {i18n.translate('coloring.colorMapping.container.resetButtonLabel', { - defaultMessage: 'Reset', + defaultMessage: 'Clear all', })} From 69d63b725b8ea5a6db29974edb37e526791dba2b Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Mon, 29 Jan 2024 16:11:32 +0100 Subject: [PATCH 03/25] update designs --- .../components/container/container.tsx | 419 ++++++++++-------- .../components/palette_selector/gradient.tsx | 122 +++-- .../palette_selector/palette_selector.tsx | 2 +- .../components/palette_selector/scale.tsx | 5 +- 4 files changed, 306 insertions(+), 242 deletions(-) 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 ca020e1e1bb0f..49272437fca0d 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 @@ -15,18 +15,24 @@ import { EuiFlexItem, EuiFormLabel, EuiFormRow, - EuiIcon, EuiPanel, - EuiSpacer, - EuiToolTip, EuiHorizontalRule, EuiEmptyPrompt, EuiButton, + EuiText, + EuiPopover, + EuiContextMenuPanel, + EuiButtonIcon, + EuiContextMenuItem, + EuiIcon, + EuiNotificationBadge, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; import { Assignment } from '../assignment/assignment'; import { SpecialAssignment } from '../assignment/special_assignment'; import { PaletteSelector } from '../palette_selector/palette_selector'; @@ -67,6 +73,7 @@ export function Container({ }) { const dispatch = useDispatch(); const [assignmentOnFocus, setAssignmentOnFocus] = useState(-1); + const [showOtherActions, setShowOtherActions] = useState(false); const getPaletteFn = getPalette(palettes, NeutralPalette); @@ -75,8 +82,6 @@ export function Container({ const assignments = useSelector(selectComputedAssignments); const specialAssignments = useSelector(selectSpecialAssignments); - const canAddNewAssignment = assignments.length < MAX_ASSIGNABLE_COLORS; - const assignmentValuesCounter = assignments.reduce>( (acc, assignment) => { const values = assignment.rule.type === 'matchExactly' ? assignment.rule.values : []; @@ -147,45 +152,68 @@ export function Container({ const otherAssignment = specialAssignments[0]; return ( - + - - - - + + + + + + + + {colorMode.type === 'gradient' && ( - + +
+ + { + dispatch(changeGradientSortOrder(colorMode.sort === 'asc' ? 'desc' : 'asc')); + }} + /> + +
-
- - - - { - dispatch(changeGradientSortOrder(colorMode.sort === 'asc' ? 'desc' : 'asc')); - }} - > - {i18n.translate('coloring.colorMapping.container.invertGradientButtonLabel', { - defaultMessage: 'Invert gradient', - })} - - -
+
)} @@ -195,186 +223,199 @@ export function Container({ {i18n.translate('coloring.colorMapping.container.mappingAssignmentHeader', { - defaultMessage: 'Mappings', + defaultMessage: 'Color assignments', })}
- - {assignments.length > 0 && ( - <> - - - {i18n.translate('coloring.colorMapping.container.mapValueButtonLabel', { - defaultMessage: 'Map a value', - })} - - - {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: 'Clear all', - })} - - - - - )} - - -
+ +
+ {assignments.map((assignment, i) => { return ( -
- setAssignmentOnFocus(-1)} - /> -
+ setAssignmentOnFocus(-1)} + /> ); })} {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.AddAssignmentButtonLabel', { + defaultMessage: 'Add assignment', + })} , - + {i18n.translate('coloring.colorMapping.container.mapValueButtonLabel', { - defaultMessage: 'Map a value', + defaultMessage: 'Add all unassigned terms', })} , ]} /> )} -
- + +
+ {assignments.length > 0 && } +
+ {assignments.length > 0 && ( + + + {i18n.translate('coloring.colorMapping.container.AddAssignmentButtonLabel', { + defaultMessage: 'Add assignment', + })} + + {data.type === 'categories' && ( + setShowOtherActions(true)} + /> + } + isOpen={showOtherActions} + closePopover={() => setShowOtherActions(false)} + panelPaddingSize="xs" + anchorPosition="downRight" + ownFocus + > + { + setShowOtherActions(false); + requestAnimationFrame(() => { + onClickAddAllCurrentCategories(); + }); + }} + disabled={unmatchingCategories.length === 0} + > + + + {i18n.translate( + 'coloring.colorMapping.container.mapCurrentValuesButtonLabel', + { + defaultMessage: 'Add all unsassigned terms', + values: { + termsCount: + unmatchingCategories.length > 0 + ? `(${unmatchingCategories.length})` + : '', + }, + } + )} + + {unmatchingCategories.length > 0 && ( + + + {unmatchingCategories.length} + + + )} + + , + } + onClick={() => { + setShowOtherActions(false); + setAssignmentOnFocus(-1); + dispatch(removeAllAssignments()); + }} + color="danger" + > + {i18n.translate( + 'coloring.colorMapping.container.clearAllAssignmentsButtonLabel', + { + defaultMessage: 'Clear all assignments', + values: { + termsCount: + unmatchingCategories.length > 0 + ? `(${unmatchingCategories.length})` + : '', + }, + } + )} + , + ]} + /> + + )} + + )} +
- - {i18n.translate('coloring.colorMapping.container.fallbackModeHeader', { - defaultMessage: 'Fallback mode', - })} - - - - } + label={i18n.translate('coloring.colorMapping.container.fallbackModeHeader', { + defaultMessage: 'Color for unassigned terms', + })} >
{ - dispatch( - addGradientColorStep({ - color: { - type: 'categorical', - // TODO assign the next available color or a better one - colorIndex: colorMode.steps.length, - paletteId: currentPalette.id, - }, - at, - }) - ); - dispatch( - colorPickerVisibility({ - index: at, - type: 'gradient', - visible: true, - }) - ); - }} + -
{ + dispatch( + addGradientColorStep({ + color: { + type: 'categorical', + // TODO assign the next available color or a better one + colorIndex: colorMode.steps.length, + paletteId: currentPalette.id, + }, + at, + }) + ); + dispatch( + colorPickerVisibility({ + index: at, + type: 'gradient', + visible: true, + }) + ); + }} > - -
- + > + +
+ + ); } 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 3baa3991de7fe..71a3c1a8e0bb7 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 @@ -89,7 +89,7 @@ export function PaletteSelector({ {preserveChangesModal} { const hasChanges = model.assignments.some((a) => a.touched); From 7d8e2845ccd82850920c3caa9eefb070bfffbc77 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Mon, 29 Jan 2024 16:29:57 +0100 Subject: [PATCH 04/25] Remove focus on new assignment --- .../components/assignment/assignment.tsx | 6 ----- .../components/assignment/match.tsx | 24 ++----------------- .../components/container/container.tsx | 6 ----- 3 files changed, 2 insertions(+), 34 deletions(-) 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 9e92b484a5704..896f2ea392884 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,8 +39,6 @@ export function Assignment({ isDarkMode, specialTokens, assignmentValuesCounter, - focusOnMount, - onBlur, }: { data: ColorMappingInputData; index: number; @@ -55,8 +53,6 @@ export function Assignment({ isDarkMode: boolean; specialTokens: Map; assignmentValuesCounter: Map; - focusOnMount: boolean; - onBlur: () => void; }) { const dispatch = useDispatch(); @@ -89,8 +85,6 @@ export function Assignment({ assignment.rule.type === 'matchExactly' || assignment.rule.type === 'matchExactlyCI' ? ( ; specialTokens: Map; assignmentValuesCounter: Map; - focusOnMount: boolean; - onBlur: () => void; -}> = ({ - index, - rule, - updateValue, - editable, - options, - specialTokens, - assignmentValuesCounter, - focusOnMount, - onBlur, -}) => { +}> = ({ index, rule, updateValue, editable, options, specialTokens, assignmentValuesCounter }) => { const duplicateWarning = i18n.translate( 'coloring.colorMapping.assignments.duplicateCategoryWarning', { @@ -82,18 +70,10 @@ 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/container/container.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/container.tsx index 49272437fca0d..a9f0ec2f4f940 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 @@ -72,7 +72,6 @@ export function Container({ specialTokens: Map; }) { const dispatch = useDispatch(); - const [assignmentOnFocus, setAssignmentOnFocus] = useState(-1); const [showOtherActions, setShowOtherActions] = useState(false); const getPaletteFn = getPalette(palettes, NeutralPalette); @@ -101,7 +100,6 @@ export function Container({ : []; const onClickAddNewAssignment = () => { - setAssignmentOnFocus(assignments.length); dispatch( addNewAssignment({ rule: @@ -124,7 +122,6 @@ export function Container({ ); }; const onClickAddAllCurrentCategories = () => { - setAssignmentOnFocus(-1); if (data.type === 'categories') { const newAssignments: ColorMapping.Config['assignments'] = unmatchingCategories.map( (c, i) => { @@ -261,8 +258,6 @@ export function Container({ disableDelete={false} specialTokens={specialTokens} assignmentValuesCounter={assignmentValuesCounter} - focusOnMount={assignmentOnFocus === i} - onBlur={() => setAssignmentOnFocus(-1)} /> ); })} @@ -384,7 +379,6 @@ export function Container({ icon={} onClick={() => { setShowOtherActions(false); - setAssignmentOnFocus(-1); dispatch(removeAllAssignments()); }} color="danger" From a46785d1795997bf1270bf7ce040d708ab017273 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 31 Jan 2024 12:43:18 +0100 Subject: [PATCH 05/25] refactored components and tests --- .../color/color_handling.test.ts | 179 ++++++-- .../color_mapping/color/color_handling.ts | 30 +- .../components/assignment/assignment.tsx | 7 - .../components/assignment/match.tsx | 4 +- .../components/assignment/range.tsx | 5 +- .../assignment/special_assignment.tsx | 1 - .../components/color_picker/color_picker.tsx | 2 +- .../components/color_picker/color_swatch.tsx | 8 +- .../components/container/assigments.tsx | 322 ++++++++++++++ .../components/container/container.tsx | 392 +----------------- .../container/unassigned_terms_config.tsx | 146 +++++++ .../components/palette_selector/gradient.tsx | 213 ++-------- .../palette_selector/gradient_add_stop.tsx | 108 +++++ .../components/palette_selector/scale.tsx | 4 +- .../color_mapping/config/assignments.ts | 8 +- .../config/default_color_mapping.ts | 1 - 16 files changed, 815 insertions(+), 615 deletions(-) create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/container/unassigned_terms_config.tsx create mode 100644 packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts index 93896394daf41..f8631ed5768da 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts +++ b/packages/kbn-coloring/src/shared_components/color_mapping/color/color_handling.test.ts @@ -23,6 +23,7 @@ import { ColorMapping } from '../config'; describe('Color mapping - color generation', () => { const getPaletteFn = getPalette(AVAILABLE_PALETTES, NeutralPalette); + it('returns EUI light colors from default config', () => { const colorFactory = getColorFactory(DEFAULT_COLOR_MAPPING_CONFIG, getPaletteFn, false, { type: 'categories', @@ -31,18 +32,36 @@ describe('Color mapping - color generation', () => { expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]); expect(colorFactory('catC')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]); - // if the category is not available in the `categories` list then a default neutral color is used - expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); + // if the category is not available in the `categories` list then a default netural is used + // this is an edge case and ideally never happen + expect(colorFactory('not_available_1')).toBe( + NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX] + ); }); - it('returns max number of colors defined in palette, use other color otherwise', () => { + // currently there is no difference in the two colors, but this could change in the future + // this test will catch the change + it('returns EUI dark colors from default config', () => { + const colorFactory = getColorFactory(DEFAULT_COLOR_MAPPING_CONFIG, getPaletteFn, true, { + type: 'categories', + categories: ['catA', 'catB', 'catC'], + }); + expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); + expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]); + expect(colorFactory('catC')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]); + // if the category is not available in the `categories` list then a default netural is used + // this is an edge case and ideally never happen + expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_DARK[DEFAULT_NEUTRAL_PALETTE_INDEX]); + }); + + it('by default loops colors defined in palette', () => { const twoColorPalette: ColorMapping.CategoricalPalette = { id: 'twoColors', name: 'twoColors', colorCount: 2, type: 'categorical', - getColor(valueInRange, isDarkMode) { - return ['red', 'blue'][valueInRange]; + getColor(indexInRange, isDarkMode, loop) { + return ['red', 'blue'][loop ? indexInRange % 2 : indexInRange]; }, }; @@ -53,6 +72,17 @@ describe('Color mapping - color generation', () => { const colorFactory = getColorFactory( { ...DEFAULT_COLOR_MAPPING_CONFIG, + specialAssignments: [ + { + color: { + type: 'loop', + }, + rule: { + type: 'other', + }, + touched: false, + }, + ], paletteId: twoColorPalette.id, }, simplifiedGetPaletteGn, @@ -64,23 +94,58 @@ describe('Color mapping - color generation', () => { ); expect(colorFactory('cat1')).toBe('#ff0000'); expect(colorFactory('cat2')).toBe('#0000ff'); - // return a palette color only up to the max number of color in the palette - expect(colorFactory('cat3')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); - expect(colorFactory('cat4')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); + // the palette will loop depending on the number of colors available + expect(colorFactory('cat3')).toBe('#ff0000'); + expect(colorFactory('cat4')).toBe('#0000ff'); }); - // currently there is no difference in the two colors, but this could change in the future - // this test will catch the change - it('returns EUI dark colors from default config', () => { - const colorFactory = getColorFactory(DEFAULT_COLOR_MAPPING_CONFIG, getPaletteFn, true, { - type: 'categories', - categories: ['catA', 'catB', 'catC'], - }); - expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); - expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]); - expect(colorFactory('catC')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]); - // if the category is not available in the `categories` list then a default neutral color is used - expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_DARK[DEFAULT_NEUTRAL_PALETTE_INDEX]); + it('returns the unassigned color if configured statically', () => { + const twoColorPalette: ColorMapping.CategoricalPalette = { + id: 'twoColors', + name: 'twoColors', + colorCount: 2, + type: 'categorical', + getColor(indexInRange, darkMode, loop) { + return ['red', 'blue'][loop ? indexInRange % 2 : indexInRange]; + }, + }; + + const simplifiedGetPaletteGn = getPalette( + new Map([[twoColorPalette.id, twoColorPalette]]), + NeutralPalette + ); + const colorFactory = getColorFactory( + { + ...DEFAULT_COLOR_MAPPING_CONFIG, + specialAssignments: [ + { + color: { + type: 'categorical', + paletteId: NeutralPalette.id, + colorIndex: DEFAULT_NEUTRAL_PALETTE_INDEX, + }, + rule: { + type: 'other', + }, + touched: false, + }, + ], + paletteId: twoColorPalette.id, + }, + simplifiedGetPaletteGn, + false, + { + type: 'categories', + categories: ['cat1', 'cat2', 'cat3', 'cat4'], + } + ); + expect(colorFactory('cat1')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); + expect(colorFactory('cat2')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); + expect(colorFactory('cat3')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); + expect(colorFactory('cat4')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); + // if the category is not available in the `categories` list then a default netural is used + // this is an edge case and ideally never happen + expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); }); it('handles special tokens, multi-field categories and non-trimmed whitespaces', () => { @@ -89,19 +154,19 @@ describe('Color mapping - color generation', () => { categories: ['__other__', ['fieldA', 'fieldB'], '__empty__', ' with-whitespaces '], }); expect(colorFactory('__other__')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); - expect(colorFactory(['fieldA', 'fieldB'])).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]); + // expect(colorFactory(['fieldA', 'fieldB'])).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]); expect(colorFactory('__empty__')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]); expect(colorFactory(' with-whitespaces ')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[3]); }); - it('ignores configured assignments in auto mode', () => { + it('ignores configured assignments in loop mode', () => { const colorFactory = getColorFactory( { ...DEFAULT_COLOR_MAPPING_CONFIG, assignments: [ { color: { type: 'colorCode', colorCode: 'red' }, - rule: { type: 'matchExactly', values: ['assignmentToIgnore'] }, + rule: { type: 'matchExactly', values: ['configuredAssignment'] }, touched: false, }, ], @@ -110,19 +175,19 @@ describe('Color mapping - color generation', () => { false, { type: 'categories', - categories: ['catA', 'catB', 'assignmentToIgnore'], + categories: ['catA', 'catB', 'configuredAssignment', 'nextCat'], } ); expect(colorFactory('catA')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); expect(colorFactory('catB')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[1]); - expect(colorFactory('assignmentToIgnore')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]); + expect(colorFactory('configuredAssignment')).toBe('red'); + expect(colorFactory('nextCat')).toBe(EUI_AMSTERDAM_PALETTE_COLORS[2]); }); it('color with auto rule are assigned in order of the configured data input', () => { const colorFactory = getColorFactory( { ...DEFAULT_COLOR_MAPPING_CONFIG, - assignmentMode: 'manual', assignments: [ { color: { type: 'colorCode', colorCode: 'red' }, @@ -154,7 +219,8 @@ describe('Color mapping - color generation', () => { expect(colorFactory('redCat')).toBe('red'); // this matches with the second availabe "auto" rule expect(colorFactory('greenCat')).toBe('green'); - // if the category is not available in the `categories` list then a default neutral color is used + // if the category is not available in the `categories` list then a default netural is used + // this is an edge case and ideally never happen expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); }); @@ -188,7 +254,7 @@ describe('Color mapping - color generation', () => { expect(toHex(colorFactory('cat3'))).toBe('#cce8e0'); }); - it('returns sequential gradient colors from lighter to darker [asc, lightMode]', () => { + it('sequential gradient colors from lighter to darker [asc, lightMode]', () => { const colorFactory = getColorFactory( { ...DEFAULT_COLOR_MAPPING_CONFIG, @@ -212,10 +278,59 @@ describe('Color mapping - color generation', () => { categories: ['cat1', 'cat2', 'cat3'], } ); + // light green expect(toHex(colorFactory('cat1'))).toBe('#cce8e0'); + // mid green point expect(toHex(colorFactory('cat2'))).toBe('#93cebc'); + // initial gradient color + expect(toHex(colorFactory('cat3'))).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); + }); + it('sequential gradients and static color from lighter to darker [asc, lightMode]', () => { + const colorFactory = getColorFactory( + { + ...DEFAULT_COLOR_MAPPING_CONFIG, + assignments: [ + { color: { type: 'gradient' }, rule: { type: 'auto' }, touched: false }, + { color: { type: 'gradient' }, rule: { type: 'auto' }, touched: false }, + ], + + colorMode: { + type: 'gradient', + steps: [ + { + type: 'categorical', + paletteId: EUIAmsterdamColorBlindPalette.id, + colorIndex: 0, + touched: false, + }, + ], + sort: 'asc', + }, + specialAssignments: [ + { + color: { + type: 'categorical', + colorIndex: DEFAULT_NEUTRAL_PALETTE_INDEX, + paletteId: NeutralPalette.id, + }, + rule: { + type: 'other', + }, + touched: false, + }, + ], + }, + getPaletteFn, + false, + { + type: 'categories', + categories: ['cat1', 'cat2', 'cat3'], + } + ); + expect(toHex(colorFactory('cat1'))).toBe('#cce8e0'); + expect(toHex(colorFactory('cat2'))).toBe(EUI_AMSTERDAM_PALETTE_COLORS[0]); // this matches exactly with the initial step selected - expect(toHex(colorFactory('cat3'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[0])); + expect(toHex(colorFactory('cat3'))).toBe(NEUTRAL_COLOR_LIGHT[DEFAULT_NEUTRAL_PALETTE_INDEX]); }); it('returns 2 colors gradient [desc, lightMode]', () => { @@ -287,8 +402,8 @@ describe('Color mapping - color generation', () => { expect(toHex(colorFactory('cat1'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[2])); // EUI pink expect(toHex(colorFactory('cat2'))).toBe(NEUTRAL_COLOR_DARK[0]); // NEUTRAL LIGHT GRAY expect(toHex(colorFactory('cat3'))).toBe(toHex(EUI_AMSTERDAM_PALETTE_COLORS[0])); // EUI green - expect(toHex(colorFactory('not available cat'))).toBe( - toHex(NEUTRAL_COLOR_DARK[DEFAULT_NEUTRAL_PALETTE_INDEX]) - ); // check the other + // if the category is not available in the `categories` list then a default netural is used + // this is an edge case and ideally never happen + expect(colorFactory('not_available')).toBe(NEUTRAL_COLOR_DARK[DEFAULT_NEUTRAL_PALETTE_INDEX]); }); }); 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 ba9bc46d27bce..cc0578c49e2fb 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,10 +8,11 @@ import chroma from 'chroma-js'; import { ColorMapping } from '../config'; import { changeAlpha, combineColors, getValidColor } from './color_math'; -import { getPalette } from '../palettes'; +import { getPalette, NeutralPalette } from '../palettes'; import { ColorMappingInputData } from '../categorical_color_mapping'; import { ruleMatch } from './rule_matching'; import { GradientColorMode } from '../config/types'; +import { DEFAULT_NEUTRAL_PALETTE_INDEX } from '../config/default_color_mapping'; export function getAssignmentColor( colorMode: ColorMapping.Config['colorMode'], @@ -144,16 +145,25 @@ export function getColorFactory( ); } // 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 + // ); 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, + { + type: 'categorical', + paletteId: NeutralPalette.id, + colorIndex: DEFAULT_NEUTRAL_PALETTE_INDEX, + }, 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..05e1fa8372ac7 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 @@ -31,8 +31,6 @@ export function Assignment({ disableDelete, index, total, - canPickColor, - editable, palette, colorMode, getPaletteFn, @@ -48,8 +46,6 @@ export function Assignment({ disableDelete: boolean; palette: ColorMapping.CategoricalPalette; getPaletteFn: ReturnType; - canPickColor: boolean; - editable: boolean; isDarkMode: boolean; specialTokens: Map; assignmentValuesCounter: Map; @@ -68,7 +64,6 @@ export function Assignment({ forType="assignment" isDarkMode={isDarkMode} swatchShape="square" - canPickColor={canPickColor} colorMode={colorMode} assignmentColor={assignment.color} getPaletteFn={getPaletteFn} @@ -85,7 +80,6 @@ export function Assignment({ assignment.rule.type === 'matchExactly' || assignment.rule.type === 'matchExactlyCI' ? ( { const rule: ColorMapping.RuleRange = { type: 'range', diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx index 1f57e731e84c0..c8314b522c6d6 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/assignment/match.tsx @@ -15,7 +15,6 @@ import { ColorMapping } from '../../config'; export const Match: React.FC<{ index: number; - editable: boolean; rule: | ColorMapping.RuleAuto | ColorMapping.RuleMatchExactly @@ -25,7 +24,7 @@ export const Match: React.FC<{ options: Array; specialTokens: Map; assignmentValuesCounter: Map; -}> = ({ index, rule, updateValue, editable, options, specialTokens, assignmentValuesCounter }) => { +}> = ({ index, rule, updateValue, options, specialTokens, assignmentValuesCounter }) => { const duplicateWarning = i18n.translate( 'coloring.colorMapping.assignments.duplicateCategoryWarning', { @@ -75,7 +74,6 @@ export const Match: React.FC<{ void; -}> = ({ rule, updateValue, editable }) => { +}> = ({ rule, updateValue }) => { const minValid = rule.min <= rule.max; const maxValid = rule.max >= rule.min; @@ -34,7 +33,6 @@ export const Range: React.FC<{ placeholder="min" value={rule.min} isInvalid={!minValid} - disabled={!editable} onChange={(e) => updateValue(+e.currentTarget.value, rule.max, rule.minInclusive, rule.maxInclusive) } @@ -54,7 +52,6 @@ export const Range: React.FC<{ } placeholder="max" - disabled={!editable} value={rule.max} onChange={(e) => updateValue(rule.min, +e.currentTarget.value, rule.minInclusive, rule.maxInclusive) 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 92825509bc942..fff892fc9cc7b 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 @@ -33,7 +33,6 @@ export function SpecialAssignment({ return ( {i18n.translate('coloring.colorMapping.colorPicker.removeGradientColorButtonLabel', { - defaultMessage: 'Remove color step', + defaultMessage: 'Remove color stop', })} 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 1eeffa959f5fb..4348efa81797f 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 @@ -31,7 +31,6 @@ interface ColorPickerSwatchProps { colorMode: ColorMapping.Config['colorMode']; assignmentColor: ColorMapping.Config['assignments'][number]['color']; getPaletteFn: ReturnType; - canPickColor: boolean; index: number; total: number; palette: ColorMapping.CategoricalPalette; @@ -44,7 +43,6 @@ export const ColorSwatch = ({ colorMode, assignmentColor, getPaletteFn, - canPickColor, index, total, palette, @@ -69,7 +67,7 @@ export const ColorSwatch = ({ ); const colorIsDark = isColorDark(...getValidColor(colorHex).rgb()); const euiTheme = useEuiTheme(); - return canPickColor && assignmentColor.type !== 'gradient' ? ( + return assignmentColor.type !== 'gradient' ? ( ) : ( @@ -119,7 +117,7 @@ export const ColorSwatch = ({ style={{ // the color swatch can't pickup colors written in rgb/css standard backgroundColor: colorHex, - cursor: canPickColor ? 'pointer' : 'not-allowed', + cursor: 'pointer', width: 32, height: 32, }} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx new file mode 100644 index 0000000000000..b5f0f61932e33 --- /dev/null +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx @@ -0,0 +1,322 @@ +/* + * 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 { + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiNotificationBadge, + EuiPanel, + EuiPopover, + EuiText, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import React, { useCallback, useMemo, useState } from 'react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import { Assignment } from '../assignment/assignment'; +import { + addNewAssignment, + addNewAssignments, + removeAllAssignments, + RootState, +} from '../../state/color_mapping'; +import { selectColorMode, selectPalette } from '../../state/selectors'; +import { ColorMappingInputData } from '../../categorical_color_mapping'; +import { ColorMapping } from '../../config'; +import { getPalette, NeutralPalette } from '../../palettes'; +import { ruleMatch } from '../../color/rule_matching'; + +const selectComputedAssignments = (state: RootState) => state.colorMapping.assignments; + +export function AssignmentsConfig({ + data, + palettes, + isDarkMode, + specialTokens, +}: { + palettes: Map; + data: ColorMappingInputData; + isDarkMode: boolean; + /** map between original and formatted tokens used to handle special cases, like the Other bucket and the empty bucket */ + specialTokens: Map; +}) { + const [showOtherActions, setShowOtherActions] = useState(false); + + const dispatch = useDispatch(); + const getPaletteFn = getPalette(palettes, NeutralPalette); + const palette = useSelector(selectPalette(getPaletteFn)); + const colorMode = useSelector(selectColorMode); + const assignments = useSelector(selectComputedAssignments); + + const unmatchingCategories = useMemo(() => { + return data.type === 'categories' + ? data.categories.filter((category) => { + return !assignments.some(({ rule }) => ruleMatch(rule, category)); + }) + : []; + }, [data, assignments]); + + const assignmentValuesCounter = assignments.reduce>( + (acc, assignment) => { + const values = assignment.rule.type === 'matchExactly' ? assignment.rule.values : []; + values.forEach((value) => { + acc.set(value, (acc.get(value) ?? 0) + 1); + }); + return acc; + }, + new Map() + ); + + const onClickAddNewAssignment = useCallback(() => { + 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, + }) + ); + }, [assignments, colorMode.type, data.type, dispatch, palette.colorCount, palette.id]); + + const onClickAddAllCurrentCategories = useCallback(() => { + 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)); + } + }, [ + dispatch, + assignments, + colorMode.type, + data.type, + palette.colorCount, + palette.id, + unmatchingCategories, + ]); + + return ( + +
+ + {assignments.map((assignment, i) => { + return ( + + ); + })} + {assignments.length === 0 && ( + +

+ {i18n.translate( + 'coloring.colorMapping.container.mapValuesPromptDescription.mapValuesPromptDetail', + { + defaultMessage: + 'Add new assignments to begin associating terms in your data with specified colors.', + } + )} +

+ + } + actions={[ + + {i18n.translate('coloring.colorMapping.container.AddAssignmentButtonLabel', { + defaultMessage: 'Add assignment', + })} + , + + {i18n.translate('coloring.colorMapping.container.mapValueButtonLabel', { + defaultMessage: 'Add all unassigned terms', + })} + , + ]} + /> + )} +
+
+ {assignments.length > 0 && } +
+ {assignments.length > 0 && ( + + + {i18n.translate('coloring.colorMapping.container.AddAssignmentButtonLabel', { + defaultMessage: 'Add assignment', + })} + + {data.type === 'categories' && ( + setShowOtherActions(true)} + /> + } + isOpen={showOtherActions} + closePopover={() => setShowOtherActions(false)} + panelPaddingSize="xs" + anchorPosition="downRight" + ownFocus + > + { + setShowOtherActions(false); + requestAnimationFrame(() => { + onClickAddAllCurrentCategories(); + }); + }} + disabled={unmatchingCategories.length === 0} + > + + + {i18n.translate( + 'coloring.colorMapping.container.mapCurrentValuesButtonLabel', + { + defaultMessage: 'Add all unsassigned terms', + values: { + termsCount: + unmatchingCategories.length > 0 + ? `(${unmatchingCategories.length})` + : '', + }, + } + )} + + {unmatchingCategories.length > 0 && ( + + + {unmatchingCategories.length} + + + )} + + , + } + onClick={() => { + setShowOtherActions(false); + dispatch(removeAllAssignments()); + }} + color="danger" + > + {i18n.translate( + 'coloring.colorMapping.container.clearAllAssignmentsButtonLabel', + { + defaultMessage: 'Clear all assignments', + values: { + termsCount: + unmatchingCategories.length > 0 + ? `(${unmatchingCategories.length})` + : '', + }, + } + )} + , + ]} + /> + + )} + + )} +
+
+ ); +} 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 a9f0ec2f4f940..4b969a2cdc34c 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,58 +6,23 @@ * Side Public License, v 1. */ -import React, { useState } from 'react'; +import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { - EuiButtonEmpty, - EuiButtonGroup, - EuiFlexGroup, - EuiFlexItem, - EuiFormLabel, - EuiFormRow, - EuiPanel, - EuiHorizontalRule, - EuiEmptyPrompt, - EuiButton, - EuiText, - EuiPopover, - EuiContextMenuPanel, - EuiButtonIcon, - EuiContextMenuItem, - EuiIcon, - EuiNotificationBadge, - EuiToolTip, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { Assignment } from '../assignment/assignment'; -import { SpecialAssignment } from '../assignment/special_assignment'; import { PaletteSelector } from '../palette_selector/palette_selector'; -import { - RootState, - addNewAssignment, - changeGradientSortOrder, - updateSpecialAssignmentColor, - addNewAssignments, - removeAllAssignments, -} from '../../state/color_mapping'; +import { changeGradientSortOrder } from '../../state/color_mapping'; import { ColorMapping } from '../../config'; import { getPalette } from '../../palettes'; -import { selectColorMode, selectPalette, selectSpecialAssignments } from '../../state/selectors'; +import { selectColorMode, selectPalette } 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 = Infinity; - -const selectComputedAssignments = (state: RootState) => state.colorMapping.assignments; +import { UnassignedTermsConfig } from './unassigned_terms_config'; +import { AssignmentsConfig } from './assigments'; export function Container({ data, @@ -72,81 +37,11 @@ export function Container({ specialTokens: Map; }) { const dispatch = useDispatch(); - const [showOtherActions, setShowOtherActions] = useState(false); const getPaletteFn = getPalette(palettes, NeutralPalette); const palette = useSelector(selectPalette(getPaletteFn)); const colorMode = useSelector(selectColorMode); - const assignments = useSelector(selectComputedAssignments); - const specialAssignments = useSelector(selectSpecialAssignments); - - const assignmentValuesCounter = assignments.reduce>( - (acc, assignment) => { - const values = assignment.rule.type === 'matchExactly' ? assignment.rule.values : []; - values.forEach((value) => { - acc.set(value, (acc.get(value) ?? 0) + 1); - }); - return acc; - }, - new Map() - ); - - const unmatchingCategories = - data.type === 'categories' - ? data.categories.filter((category) => { - return !assignments.some(({ rule }) => ruleMatch(rule, category)); - }) - : []; - - const onClickAddNewAssignment = () => { - 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 = () => { - 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 ( @@ -209,283 +104,28 @@ export function Container({ getPaletteFn={getPaletteFn} isDarkMode={isDarkMode} paletteId={palette.id} - assignmentsSize={assignments.length} />
)} - - - - - {i18n.translate('coloring.colorMapping.container.mappingAssignmentHeader', { - defaultMessage: 'Color assignments', - })} - - - - - - -
- - {assignments.map((assignment, i) => { - return ( - - ); - })} - {assignments.length === 0 && ( - -

- -

- - } - actions={[ - - {i18n.translate('coloring.colorMapping.container.AddAssignmentButtonLabel', { - defaultMessage: 'Add assignment', - })} - , - - {i18n.translate('coloring.colorMapping.container.mapValueButtonLabel', { - defaultMessage: 'Add all unassigned terms', - })} - , - ]} - /> - )} -
-
- {assignments.length > 0 && } -
- {assignments.length > 0 && ( - - - {i18n.translate('coloring.colorMapping.container.AddAssignmentButtonLabel', { - defaultMessage: 'Add assignment', - })} - - {data.type === 'categories' && ( - setShowOtherActions(true)} - /> - } - isOpen={showOtherActions} - closePopover={() => setShowOtherActions(false)} - panelPaddingSize="xs" - anchorPosition="downRight" - ownFocus - > - { - setShowOtherActions(false); - requestAnimationFrame(() => { - onClickAddAllCurrentCategories(); - }); - }} - disabled={unmatchingCategories.length === 0} - > - - - {i18n.translate( - 'coloring.colorMapping.container.mapCurrentValuesButtonLabel', - { - defaultMessage: 'Add all unsassigned terms', - values: { - termsCount: - unmatchingCategories.length > 0 - ? `(${unmatchingCategories.length})` - : '', - }, - } - )} - - {unmatchingCategories.length > 0 && ( - - - {unmatchingCategories.length} - - - )} - - , - } - onClick={() => { - setShowOtherActions(false); - dispatch(removeAllAssignments()); - }} - color="danger" - > - {i18n.translate( - 'coloring.colorMapping.container.clearAllAssignmentsButtonLabel', - { - defaultMessage: 'Clear all assignments', - values: { - termsCount: - unmatchingCategories.length > 0 - ? `(${unmatchingCategories.length})` - : '', - }, - } - )} - , - ]} - /> - - )} - - )} -
-
-
- { - 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/container/unassigned_terms_config.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/unassigned_terms_config.tsx new file mode 100644 index 0000000000000..9ab685dcb75f2 --- /dev/null +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/unassigned_terms_config.tsx @@ -0,0 +1,146 @@ +/* + * 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 { + EuiButtonGroup, + EuiButtonGroupOptionProps, + EuiColorPickerSwatch, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import { css } from '@emotion/react'; +import { updateSpecialAssignmentColor } from '../../state/color_mapping'; +import { getPalette, NeutralPalette } from '../../palettes'; +import { DEFAULT_NEUTRAL_PALETTE_INDEX } from '../../config/default_color_mapping'; +import { SpecialAssignment } from '../assignment/special_assignment'; +import { ColorMapping } from '../../config'; +import { selectColorMode, selectPalette, selectSpecialAssignments } from '../../state/selectors'; +import { ColorMappingInputData } from '../../categorical_color_mapping'; + +export function UnassignedTermsConfig({ + palettes, + data, + isDarkMode, +}: { + palettes: Map; + data: ColorMappingInputData; + isDarkMode: boolean; +}) { + const dispatch = useDispatch(); + + const getPaletteFn = getPalette(palettes, NeutralPalette); + + const palette = useSelector(selectPalette(getPaletteFn)); + const colorMode = useSelector(selectColorMode); + const specialAssignments = useSelector(selectSpecialAssignments); + const otherAssignment = specialAssignments[0]; + + const colorModes: EuiButtonGroupOptionProps[] = [ + { + id: 'loop', + label: + colorMode.type === 'gradient' + ? i18n.translate( + 'coloring.colorMapping.container.unassignedTermsMode.ReuseGradientLabel', + { + defaultMessage: 'Gradient', + } + ) + : i18n.translate('coloring.colorMapping.container.unassignedTermsMode.ReuseColorsLabel', { + defaultMessage: 'Color palette', + }), + }, + { + id: 'static', + label: i18n.translate( + 'coloring.colorMapping.container.unassignedTermsMode.SingleColorLabel', + { + defaultMessage: 'Single color', + } + ), + }, + ]; + + return ( + + + + { + 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 b691cbea8ef18..de20ba573cb70 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 @@ -6,73 +6,61 @@ * Side Public License, v 1. */ -import { - euiCanAnimate, - euiFocusRing, - EuiIcon, - euiShadowSmall, - EuiToolTip, - useEuiTheme, -} from '@elastic/eui'; import React from 'react'; -import { useDispatch } from 'react-redux'; - import { euiThemeVars } from '@kbn/ui-theme'; import { css } from '@emotion/react'; -import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; import { changeAlpha } from '../../color/color_math'; - import { ColorMapping } from '../../config'; -import { ColorSwatch } from '../color_picker/color_swatch'; import { getPalette } from '../../palettes'; - -import { addGradientColorStep, updateGradientColorStep } from '../../state/color_mapping'; -import { colorPickerVisibility } from '../../state/ui'; import { getGradientColorScale } from '../../color/color_handling'; +import { AddStop } from './gradient_add_stop'; +import { ColorSwatch } from '../color_picker/color_swatch'; +import { updateGradientColorStep } from '../../state/color_mapping'; export function Gradient({ paletteId, colorMode, getPaletteFn, isDarkMode, - assignmentsSize, }: { paletteId: string; isDarkMode: boolean; colorMode: ColorMapping.Config['colorMode']; getPaletteFn: ReturnType; - assignmentsSize: number; }) { + const dispatch = useDispatch(); if (colorMode.type === 'categorical') { return null; } + const currentPalette = getPaletteFn(paletteId); const gradientColorScale = getGradientColorScale(colorMode, getPaletteFn, isDarkMode); - const topMostColorStop = + const startStepColor = colorMode.sort === 'asc' ? colorMode.steps.length === 1 ? undefined : colorMode.steps.at(-1) : colorMode.steps.at(0); - const topMostColorStopIndex = + const startStepIndex = colorMode.sort === 'asc' ? colorMode.steps.length === 1 ? NaN : colorMode.steps.length - 1 : 0; - const bottomMostColorStop = + const endStepColor = colorMode.sort === 'asc' ? colorMode.steps.at(0) : colorMode.steps.length === 1 ? undefined : colorMode.steps.at(-1); - const bottomMostColorStopIndex = + const endStepIndex = colorMode.sort === 'asc' ? 0 : colorMode.steps.length === 1 ? NaN : colorMode.steps.length - 1; - const middleMostColorSep = colorMode.steps.length === 3 ? colorMode.steps[1] : undefined; - const middleMostColorStopIndex = colorMode.steps.length === 3 ? 1 : NaN; + const middleStepColor = colorMode.steps.length === 3 ? colorMode.steps[1] : undefined; + const middleStepIndex = colorMode.steps.length === 3 ? 1 : NaN; return (
- {topMostColorStop ? ( - { + dispatch(updateGradientColorStep({ index: startStepIndex, color })); + }} /> ) : ( @@ -137,14 +129,20 @@ export function Gradient({ top: 6px; `} > - {middleMostColorSep ? ( - { + dispatch(updateGradientColorStep({ index: middleStepIndex, color })); + }} /> ) : colorMode.steps.length === 2 ? ( @@ -159,14 +157,20 @@ export function Gradient({ right: 0; `} > - {bottomMostColorStop ? ( - { + dispatch(updateGradientColorStep({ index: endStepIndex, color })); + }} /> ) : ( @@ -175,130 +179,3 @@ export function Gradient({
); } - -function AddStop({ - colorMode, - currentPalette, - at, -}: { - colorMode: { - type: 'gradient'; - steps: Array<(ColorMapping.CategoricalColor | ColorMapping.ColorCode) & { touched: boolean }>; - }; - currentPalette: ColorMapping.CategoricalPalette; - at: number; -}) { - const euiTheme = useEuiTheme(); - const dispatch = useDispatch(); - return ( - - - - ); -} - -function ColorStop({ - colorMode, - step, - index, - currentPalette, - getPaletteFn, - isDarkMode, -}: { - colorMode: ColorMapping.GradientColorMode; - step: ColorMapping.CategoricalColor | ColorMapping.ColorCode; - index: number; - currentPalette: ColorMapping.CategoricalPalette; - getPaletteFn: ReturnType; - isDarkMode: boolean; -}) { - const dispatch = useDispatch(); - return ( - { - dispatch( - updateGradientColorStep({ - index, - color, - }) - ); - }} - forType="gradient" - /> - ); -} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx new file mode 100644 index 0000000000000..6ecc125908f8f --- /dev/null +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx @@ -0,0 +1,108 @@ +/* + * 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 { + euiCanAnimate, + euiFocusRing, + EuiIcon, + euiShadowSmall, + EuiToolTip, + useEuiTheme, +} from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import { ColorMapping } from '../../config'; +import { addGradientColorStep } from '../../state/color_mapping'; +import { colorPickerVisibility } from '../../state/ui'; + +export function AddStop({ + colorMode, + currentPalette, + at, +}: { + colorMode: ColorMapping.GradientColorMode; + currentPalette: ColorMapping.CategoricalPalette; + at: number; +}) { + const euiTheme = useEuiTheme(); + const dispatch = useDispatch(); + return ( + <> + + + + + ); +} 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 index 5604cb27ffdc9..056db47157c60 100644 --- 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 @@ -91,7 +91,7 @@ export function ScaleMode({ getPaletteFn }: { getPaletteFn: ReturnType @@ -118,7 +118,7 @@ export function ScaleMode({ getPaletteFn }: { getPaletteFn: ReturnType { if (preserveColorChanges && touched) { return { rule, color, touched }; @@ -27,8 +25,8 @@ export function updateAssignmentsPalette( colorMode.type === 'categorical' ? { type: 'categorical', - paletteId: index < maxColors ? paletteId : NeutralPalette.id, - colorIndex: index < maxColors ? index : 0, + paletteId, + colorIndex: index % palette.colorCount, } : { type: 'gradient' }; return { 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 c47680e7b68c9..56990d5fb8aeb 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,6 @@ 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', assignments: [], specialAssignments: [ { From 324914f8015657a7e3e3f528b2ac9a76ab314da9 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 31 Jan 2024 15:54:53 +0100 Subject: [PATCH 06/25] fix i18n unused values --- .../components/container/assigments.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx index b5f0f61932e33..2bafed84d3041 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx @@ -269,12 +269,6 @@ export function AssignmentsConfig({ 'coloring.colorMapping.container.mapCurrentValuesButtonLabel', { defaultMessage: 'Add all unsassigned terms', - values: { - termsCount: - unmatchingCategories.length > 0 - ? `(${unmatchingCategories.length})` - : '', - }, } )} @@ -301,12 +295,6 @@ export function AssignmentsConfig({ 'coloring.colorMapping.container.clearAllAssignmentsButtonLabel', { defaultMessage: 'Clear all assignments', - values: { - termsCount: - unmatchingCategories.length > 0 - ? `(${unmatchingCategories.length})` - : '', - }, } )} , From d960db2e7c5e0841339208cf2a86f48552417d10 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 31 Jan 2024 16:08:20 +0100 Subject: [PATCH 07/25] cleanup storybook example --- .../__stories__/color_mapping.stories.tsx | 90 +------------------ 1 file changed, 2 insertions(+), 88 deletions(-) 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 b1c7c12261206..f1d9add2c0f09 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 @@ -81,47 +81,9 @@ export const Default = Template.bind({}); Default.args = { model: { ...DEFAULT_COLOR_MAPPING_CONFIG, - // 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: '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, - // }, - // ], }, specialAssignments: [ { @@ -134,55 +96,7 @@ Default.args = { 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, - // }, - ], + assignments: [], }, isDarkMode: false, data: { From d1dbbc3c9877b978b29547ce69ff7f673e8c88e8 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 31 Jan 2024 16:15:31 +0100 Subject: [PATCH 08/25] cleanup unnecessary comment --- .../color_mapping/color/color_handling.ts | 14 -------------- 1 file changed, 14 deletions(-) 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 cc0578c49e2fb..3ba138ba4b6e4 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 @@ -144,20 +144,6 @@ export function getColorFactory( 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 - // ); return getColor( { type: 'categorical', From 1479cd5ae28172496c70e57061228e4f38efd281 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Thu, 1 Feb 2024 10:53:07 +0100 Subject: [PATCH 09/25] fix i18n labels --- x-pack/plugins/translations/translations/fr-FR.json | 7 +------ x-pack/plugins/translations/translations/ja-JP.json | 7 +------ x-pack/plugins/translations/translations/zh-CN.json | 7 +------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 63b76a5cb04ac..26af0c357304f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -140,8 +140,6 @@ "coloring.colorMapping.assignments.autoAssignedTermAriaLabel": "Cette couleur sera automatiquement affectée au premier terme qui ne correspond pas à toutes les autres affectations", "coloring.colorMapping.assignments.autoAssignedTermPlaceholder": "Affecté automatiquement", "coloring.colorMapping.assignments.deleteAssignmentButtonLabel": "Supprimer cette affectation", - "coloring.colorMapping.assignments.unassignedAriaLabel": "Affecter cette couleur à tout terme non affecté qui n'est pas décrit dans la liste d'affectation", - "coloring.colorMapping.assignments.unassignedPlaceholder": "Termes non affectés", "coloring.colorMapping.colorChangesModal.categoricalModeDescription": "Basculer en mode de catégorie conduira à l'abandon de toutes vos modifications de couleurs personnalisées", "coloring.colorMapping.colorChangesModal.discardButton": "Abandonner les modifications", "coloring.colorMapping.colorChangesModal.discardButtonLabel": "Abandonner les modifications", @@ -159,14 +157,11 @@ "coloring.colorMapping.colorPicker.removeGradientColorButtonLabel": "Supprimer l'étape couleur", "coloring.colorMapping.colorPicker.themeAwareColorsLabel": "Couleurs neutres", "coloring.colorMapping.colorPicker.themeAwareColorsTooltip": "Les couleurs neutres fournies se conforment au thème et s'adapteront en fonction du basculement entre les thèmes clair et sombre", - "coloring.colorMapping.container.addAssignmentButtonLabel": "Ajouter une affectation", - "coloring.colorMapping.container.autoAssignLabel": "Affectation automatique", "coloring.colorMapping.container.invertGradientButtonLabel": "Inverser le gradient", "coloring.colorMapping.container.mappingAssignmentHeader": "Mapping des affectations", "coloring.colorMapping.paletteSelector.categoricalLabel": "De catégorie", "coloring.colorMapping.paletteSelector.paletteLabel": "Palette de couleurs", "coloring.colorMapping.paletteSelector.scaleLabel": "Scaling", - "coloring.colorMapping.paletteSelector.sequentialLabel": "Séquentiel", "coloring.dynamicColoring.customPalette.addColor": "Ajouter une couleur", "coloring.dynamicColoring.customPalette.addColorAriaLabel": "Ajouter une couleur", "coloring.dynamicColoring.customPalette.colorStopsHelpPercentage": "Les types de valeurs en pourcentage sont relatifs à la plage complète des valeurs de données disponibles.", @@ -42935,4 +42930,4 @@ "xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet", "xpack.serverlessObservability.nav.visualizations": "Visualisations" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5ac0b945b1173..2c43ef606557a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -140,8 +140,6 @@ "coloring.colorMapping.assignments.autoAssignedTermAriaLabel": "この色は、他のすべての割り当てと一致しない最初の用語に自動的に割り当てられます。", "coloring.colorMapping.assignments.autoAssignedTermPlaceholder": "自動割り当て済み", "coloring.colorMapping.assignments.deleteAssignmentButtonLabel": "この割り当てを削除", - "coloring.colorMapping.assignments.unassignedAriaLabel": "割り当てリストに記載されていない未割り当てのすべての色にこの色を割り当てます。", - "coloring.colorMapping.assignments.unassignedPlaceholder": "割り当てられていない用語", "coloring.colorMapping.colorChangesModal.categoricalModeDescription": "分類モードに切り替えると、カスタム色の変更はすべて破棄されます。", "coloring.colorMapping.colorChangesModal.discardButton": "変更を破棄", "coloring.colorMapping.colorChangesModal.discardButtonLabel": "変更を破棄", @@ -159,14 +157,11 @@ "coloring.colorMapping.colorPicker.removeGradientColorButtonLabel": "色ステップを削除", "coloring.colorMapping.colorPicker.themeAwareColorsLabel": "中間色", "coloring.colorMapping.colorPicker.themeAwareColorsTooltip": "提供されている中間色はテーマを意識しており、明るいテーマと暗いテーマを切り替えると適切に変化します。", - "coloring.colorMapping.container.addAssignmentButtonLabel": "割り当てを追加", - "coloring.colorMapping.container.autoAssignLabel": "自動割り当て", "coloring.colorMapping.container.invertGradientButtonLabel": "グラデーションを反転", "coloring.colorMapping.container.mappingAssignmentHeader": "マッピング割り当て", "coloring.colorMapping.paletteSelector.categoricalLabel": "分類", "coloring.colorMapping.paletteSelector.paletteLabel": "カラーパレット", "coloring.colorMapping.paletteSelector.scaleLabel": "スケール", - "coloring.colorMapping.paletteSelector.sequentialLabel": "連続", "coloring.dynamicColoring.customPalette.addColor": "色を追加", "coloring.dynamicColoring.customPalette.addColorAriaLabel": "色を追加", "coloring.dynamicColoring.customPalette.colorStopsHelpPercentage": "割合値は使用可能なデータ値の全範囲に対して相対的です。", @@ -42927,4 +42922,4 @@ "xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定", "xpack.serverlessObservability.nav.visualizations": "ビジュアライゼーション" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index eeb98e4c894a1..306cddbc2f3d8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -140,8 +140,6 @@ "coloring.colorMapping.assignments.autoAssignedTermAriaLabel": "会将此颜色自动分配给第一个与所有其他分配均不匹配的词", "coloring.colorMapping.assignments.autoAssignedTermPlaceholder": "已自动分配", "coloring.colorMapping.assignments.deleteAssignmentButtonLabel": "删除此分配", - "coloring.colorMapping.assignments.unassignedAriaLabel": "将此颜色分配给分配列表中未描述的每个未分配项", - "coloring.colorMapping.assignments.unassignedPlaceholder": "未分配的词", "coloring.colorMapping.colorChangesModal.categoricalModeDescription": "切换到分类模式将丢弃您的所有定制颜色更改", "coloring.colorMapping.colorChangesModal.discardButton": "放弃更改", "coloring.colorMapping.colorChangesModal.discardButtonLabel": "放弃更改", @@ -159,14 +157,11 @@ "coloring.colorMapping.colorPicker.removeGradientColorButtonLabel": "移除色阶", "coloring.colorMapping.colorPicker.themeAwareColorsLabel": "中性色", "coloring.colorMapping.colorPicker.themeAwareColorsTooltip": "提供的中性色能够感知主题,在浅色主题与深色主题之间切换时会做出相应更改", - "coloring.colorMapping.container.addAssignmentButtonLabel": "添加分配", - "coloring.colorMapping.container.autoAssignLabel": "自动分配", "coloring.colorMapping.container.invertGradientButtonLabel": "反向渐变", "coloring.colorMapping.container.mappingAssignmentHeader": "映射分配", "coloring.colorMapping.paletteSelector.categoricalLabel": "分类", "coloring.colorMapping.paletteSelector.paletteLabel": "调色板", "coloring.colorMapping.paletteSelector.scaleLabel": "比例", - "coloring.colorMapping.paletteSelector.sequentialLabel": "顺序", "coloring.dynamicColoring.customPalette.addColor": "添加颜色", "coloring.dynamicColoring.customPalette.addColorAriaLabel": "添加颜色", "coloring.dynamicColoring.customPalette.colorStopsHelpPercentage": "百分比值是相对于全范围可用数据值的类型。", @@ -42907,4 +42902,4 @@ "xpack.serverlessObservability.nav.projectSettings": "项目设置", "xpack.serverlessObservability.nav.visualizations": "可视化" } -} +} \ No newline at end of file From 44426a06162ef1db0a65f5923a5f269e9e8dfd8a Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 2 Feb 2024 11:39:49 +0100 Subject: [PATCH 10/25] fix tests --- .../categorical_color_mapping.test.tsx | 20 +++++++------------ .../components/container/assigments.tsx | 14 +++++++++++-- .../test/functional/page_objects/lens_page.ts | 5 +++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx index fe8374d7dcdcd..ccc955d2b8947 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/categorical_color_mapping.test.tsx @@ -13,8 +13,9 @@ import { AVAILABLE_PALETTES } from './palettes'; import { DEFAULT_COLOR_MAPPING_CONFIG } from './config/default_color_mapping'; import { MULTI_FIELD_KEY_SEPARATOR } from '@kbn/data-plugin/common'; -const AUTO_ASSIGN_SWITCH = '[data-test-subj="lns-colorMapping-autoAssignSwitch"]'; const ASSIGNMENTS_LIST = '[data-test-subj="lns-colorMapping-assignmentsList"]'; +const ASSIGNMENTS_PROMPT = '[data-test-subj="lns-colorMapping-assignmentsPrompt"]'; +const ASSIGNMENTS_PROMPT_ADD_ALL = '[data-test-subj="lns-colorMapping-assignmentsPromptAddAll"]'; const ASSIGNMENT_ITEM = (i: number) => `[data-test-subj="lns-colorMapping-assignmentsItem${i}"]`; describe('color mapping', () => { @@ -35,19 +36,12 @@ describe('color mapping', () => { /> ); - expect(component.find(AUTO_ASSIGN_SWITCH).hostNodes().prop('aria-checked')).toEqual(true); - expect(component.find(ASSIGNMENTS_LIST).hostNodes().children().length).toEqual( - dataInput.categories.length - ); - dataInput.categories.forEach((category, index) => { - const assignment = component.find(ASSIGNMENT_ITEM(index)).hostNodes(); - expect(assignment.text()).toEqual(category); - expect(assignment.hasClass('euiComboBox-isDisabled')).toEqual(true); - }); + // empty list prompt visible + expect(component.find(ASSIGNMENTS_PROMPT)).toBeTruthy(); expect(onModelUpdateFn).not.toBeCalled(); }); - it('switch to manual assignments', () => { + it('Add all terms to assignments', () => { const dataInput: ColorMappingInputData = { type: 'categories', categories: ['categoryA', 'categoryB'], @@ -63,9 +57,8 @@ describe('color mapping', () => { specialTokens={new Map()} /> ); - component.find(AUTO_ASSIGN_SWITCH).hostNodes().simulate('click'); + component.find(ASSIGNMENTS_PROMPT_ADD_ALL).hostNodes().simulate('click'); expect(onModelUpdateFn).toBeCalledTimes(1); - expect(component.find(AUTO_ASSIGN_SWITCH).hostNodes().prop('aria-checked')).toEqual(false); expect(component.find(ASSIGNMENTS_LIST).hostNodes().children().length).toEqual( dataInput.categories.length ); @@ -97,6 +90,7 @@ describe('color mapping', () => { } /> ); + component.find(ASSIGNMENTS_PROMPT_ADD_ALL).hostNodes().simulate('click'); expect(component.find(ASSIGNMENTS_LIST).hostNodes().children().length).toEqual( dataInput.categories.length ); diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx index 2bafed84d3041..91c9c6f93ba24 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/container/assigments.tsx @@ -152,7 +152,11 @@ export function AssignmentsConfig({ padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingSmall}; `} > - + {assignments.map((assignment, i) => { return (

@@ -198,7 +203,12 @@ export function AssignmentsConfig({ defaultMessage: 'Add assignment', })} , - + {i18n.translate('coloring.colorMapping.container.mapValueButtonLabel', { defaultMessage: 'Add all unassigned terms', })} diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index c159fb17b4db7..c5018bda39ffa 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -1934,8 +1934,9 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.existOrFail('lns-indexPattern-dimensionContainerClose'); }); await testSubjects.click('lns_colorEditing_trigger'); - // disable autoAssign - await testSubjects.setEuiSwitch('lns-colorMapping-autoAssignSwitch', 'uncheck'); + + // assign all + await testSubjects.click('lns-colorMapping-assignmentsPromptAddAll'); await testSubjects.click(`lns-colorMapping-colorSwatch-${colorSwatchIndex}`); From 5d301043f8d556e005a586c16079a74e0ffa6cd3 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 2 Feb 2024 15:38:31 +0100 Subject: [PATCH 11/25] cleanup style and fix getColorFunction --- .../components/color_picker/color_swatch.tsx | 2 +- .../components/color_picker/palette_colors.tsx | 8 ++++---- .../components/color_picker/rgb_picker.tsx | 3 ++- .../components/palette_selector/gradient.tsx | 9 ++++----- .../palette_selector/gradient_add_stop.tsx | 12 ++++++------ 5 files changed, 17 insertions(+), 17 deletions(-) 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 4348efa81797f..34ffbefeca30f 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 @@ -94,7 +94,7 @@ export const ColorSwatch = ({ height: 16px; border-radius: 50%; top: 8px; - border: 3px solid white; + border: 3px solid ${euiTheme.euiTheme.colors.emptyShade}; ${euiShadowSmall(euiTheme)}; backgroundcolor: ${colorHex}; cursor: pointer; diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx index 21aa18a49f9dc..77a8273654b89 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/palette_colors.tsx @@ -35,16 +35,16 @@ export function PaletteColors({ selectColor: (color: ColorMapping.CategoricalColor | ColorMapping.ColorCode) => void; }) { const colors = Array.from({ length: palette.colorCount }, (d, i) => { - return palette.getColor(i, isDarkMode); + return palette.getColor(i, isDarkMode, false); }); const neutralColors = Array.from({ length: NeutralPalette.colorCount }, (d, i) => { - return NeutralPalette.getColor(i, isDarkMode); + return NeutralPalette.getColor(i, isDarkMode, false); }); const originalColor = color.type === 'categorical' ? color.paletteId === NeutralPalette.id - ? NeutralPalette.getColor(color.colorIndex, isDarkMode) - : getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode) + ? NeutralPalette.getColor(color.colorIndex, isDarkMode, false) + : getPaletteFn(color.paletteId).getColor(color.colorIndex, isDarkMode, false) : color.colorCode; return ( <> diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx index 84f6786922f44..a489fd1940e67 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/color_picker/rgb_picker.tsx @@ -48,7 +48,8 @@ export function RGBPicker({ customColorMappingColor.type === 'categorical' ? getPaletteFn(customColorMappingColor.paletteId).getColor( customColorMappingColor.colorIndex, - isDarkMode + isDarkMode, + false ) : customColorMappingColor.colorCode; 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 de20ba573cb70..54760b6e58732 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 @@ -79,12 +79,11 @@ export function Gradient({ position: absolute; left: 6px; right: 6px; - height: 6px; + height: 8px; top: 12px; - border-top-left-radius: 6px; - border-top-right-radius: 6px; - border-bottom-left-radius: 6px; - border-bottom-right-radius: 6px; + border-start-end-radius: 6px; + border-end-end-radius: 6px; + background-origin: border-box; background-image: linear-gradient( to right, ${[gradientColorScale(0), gradientColorScale(0.5), gradientColorScale(1)].join(',')} diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx index 6ecc125908f8f..6e33754efa90e 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx @@ -79,10 +79,10 @@ export function AddStop({ width: 15px; height: 15px; border-radius: 50%; - transition: 200ms background-color; - background-color: lightgrey; + transition: background-color ${euiTheme.euiTheme.animation.fast} ease-in; + background-color: ${euiTheme.euiTheme.colors.emptyShade}; &:hover { - background-color: #696f7d; + background-color: ${euiTheme.euiTheme.colors.text}; } ${euiShadowSmall(euiTheme)} `} @@ -93,12 +93,12 @@ export function AddStop({ position: absolute; top: 0.5px; left: 0; - transition: 200ms fill; + transition: fill ${euiTheme.euiTheme.animation.fast} ease-in; &:hover { - fill: white; + fill: ${euiTheme.euiTheme.colors.emptyShade}; } `} - color={'#696f7d'} + color={euiTheme.euiTheme.colors.text} />

From 609fd667ee78b47cf41ecc960df25db2925f81f4 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 2 Feb 2024 15:56:18 +0100 Subject: [PATCH 12/25] remove color change on hover --- .../components/palette_selector/gradient_add_stop.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx index 6e33754efa90e..7997056a3fc5e 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx @@ -79,11 +79,7 @@ export function AddStop({ width: 15px; height: 15px; border-radius: 50%; - transition: background-color ${euiTheme.euiTheme.animation.fast} ease-in; background-color: ${euiTheme.euiTheme.colors.emptyShade}; - &:hover { - background-color: ${euiTheme.euiTheme.colors.text}; - } ${euiShadowSmall(euiTheme)} `} > @@ -93,10 +89,6 @@ export function AddStop({ position: absolute; top: 0.5px; left: 0; - transition: fill ${euiTheme.euiTheme.animation.fast} ease-in; - &:hover { - fill: ${euiTheme.euiTheme.colors.emptyShade}; - } `} color={euiTheme.euiTheme.colors.text} /> From f83bcbcd0b532788c9cfd4c86c448597d5d818ed Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Fri, 2 Feb 2024 17:15:55 +0100 Subject: [PATCH 13/25] change gradient add hover style --- .../components/palette_selector/gradient.tsx | 6 ++--- .../palette_selector/gradient_add_stop.tsx | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) 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 54760b6e58732..bee4161dd91a2 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 @@ -125,7 +125,7 @@ export function Gradient({ width: 16px; height: 16px; left: 50%; - top: 6px; + top: 8px; `} > {middleStepColor ? ( @@ -151,8 +151,8 @@ export function Gradient({ css={css` position: absolute; width: 16px; - height: 50%; - top: 6px; + height: 16px; + top: 8px; right: 0; `} > diff --git a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx index 7997056a3fc5e..713e780bc32da 100644 --- a/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx +++ b/packages/kbn-coloring/src/shared_components/color_mapping/components/palette_selector/gradient_add_stop.tsx @@ -42,12 +42,13 @@ export function AddStop({ })} >