diff --git a/package-lock.json b/package-lock.json index 1da208a3fa..6b417bc3f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,7 @@ "@gravity-ui/stylelint-config": "^4.0.1", "@gravity-ui/tsconfig": "^1.0.0", "@gravity-ui/ui-logger": "^1.1.0", - "@gravity-ui/uikit": "^6.37.0", + "@gravity-ui/uikit": "^6.39.0", "@jest/types": "^29.6.3", "@microsoft/fetch-event-source": "^2.0.1", "@playwright/test": "^1.48.2", @@ -5309,9 +5309,9 @@ "license": "MIT" }, "node_modules/@gravity-ui/uikit": { - "version": "6.37.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.37.0.tgz", - "integrity": "sha512-XwtD0+HFYaJv5CiRceuHATD97hZJxigF/8swwYlJx7SZauiR5GMOqB/3+yY/nYcJkWte5H/cC1JnVp3nHv5ELw==", + "version": "6.39.0", + "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.39.0.tgz", + "integrity": "sha512-ORb5OHBNxE7J/NwhwSjs7V2pnEwdItnGyOg6eoL9SVM/IUiG++CpsrApWaVMAsbhWChCTKCdAQK9lnJsGBnfZQ==", "dependencies": { "@bem-react/classname": "^1.6.0", "@gravity-ui/i18n": "^1.6.0", diff --git a/package.json b/package.json index cfcb6ca64e..4843d0a6f7 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "@gravity-ui/stylelint-config": "^4.0.1", "@gravity-ui/tsconfig": "^1.0.0", "@gravity-ui/ui-logger": "^1.1.0", - "@gravity-ui/uikit": "^6.37.0", + "@gravity-ui/uikit": "^6.39.0", "@jest/types": "^29.6.3", "@microsoft/fetch-event-source": "^2.0.1", "@playwright/test": "^1.48.2", diff --git a/src/i18n-keysets/wizard/en.json b/src/i18n-keysets/wizard/en.json index 95dc2cc31f..1968619125 100644 --- a/src/i18n-keysets/wizard/en.json +++ b/src/i18n-keysets/wizard/en.json @@ -226,6 +226,8 @@ "label_overlap": "Labels overlap", "label_overriden-from-dashboard": "overriden from dashboard", "label_pagination": "Pagination", + "label_painting-as-0": "Paint as 0", + "label_painting-ignore": "Do not paint", "label_palette": "Palette", "label_part": "Date part", "label_percent": "Percentage", diff --git a/src/i18n-keysets/wizard/ru.json b/src/i18n-keysets/wizard/ru.json index 26c36dc16d..d16eba7119 100644 --- a/src/i18n-keysets/wizard/ru.json +++ b/src/i18n-keysets/wizard/ru.json @@ -226,6 +226,8 @@ "label_overlap": "Перекрытие подписей", "label_overriden-from-dashboard": "перезаписан с дашборда", "label_pagination": "Пагинация", + "label_painting-as-0": "Окрашивать как 0", + "label_painting-ignore": "Не окрашивать", "label_palette": "Палитра", "label_part": "Часть даты", "label_percent": "В процентах", diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts index d55cbd5433..493d21bbac 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts @@ -7,6 +7,7 @@ import type { } from '../../../../../../../../shared'; import { ApiV2Annotations, + GradientNullModes, PseudoFieldTitle, getFakeTitleOrTitle, } from '../../../../../../../../shared'; @@ -231,8 +232,19 @@ export const prepareBackgroundColorSettings = (args: PrepareBackgroundColorSetti const fieldColorValues = Array.from(colorValues); continuousColorsByField[guid] = {}; + const nilValue = + backgroundSettings.settings.gradientState.nullMode === GradientNullModes.AsZero + ? 0 + : null; + + const colorValuesWithoutNull = fieldColorValues.reduce((acc, cv) => { + const colorValue = cv === null ? nilValue : cv; + if (colorValue !== null) { + acc.push(Number(colorValue)); + } - const colorValuesWithoutNull = fieldColorValues.filter((cv): cv is number => cv !== null); + return acc; + }, []); const min = Math.min(...colorValuesWithoutNull); const max = Math.max(...colorValuesWithoutNull); @@ -250,12 +262,15 @@ export const prepareBackgroundColorSettings = (args: PrepareBackgroundColorSetti fieldColorValues.forEach((value) => { const colorValue = getContinuousColorValue(value); - if (colorValue === null) { + if ( + colorValue === null && + backgroundSettings.settings.gradientState.nullMode !== GradientNullModes.AsZero + ) { return; } const color = colorizePivotTableCell(colorValue, chartColorsConfig, [min, max]); - continuousColorsByField[guid][String(value)] = color?.backgroundColor || null; + continuousColorsByField[guid][String(colorValue)] = color?.backgroundColor || null; }); }); @@ -317,7 +332,10 @@ export const colorizePivotTableByFieldBackgroundSettings = ( const {settings, colorFieldGuid} = backgroundColorSettings; const colorKey = cell.colorKey; - if (!colorKey) { + if ( + !colorKey && + backgroundColorSettings.settings.gradientState.nullMode !== GradientNullModes.AsZero + ) { continue; } diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts index 5efdbee2b8..38d632e514 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts @@ -1,5 +1,5 @@ import type {ServerColor} from '../../../../../../../../shared'; -import {ApiV2Annotations, isMeasureName} from '../../../../../../../../shared'; +import {ApiV2Annotations, GradientNullModes, isMeasureName} from '../../../../../../../../shared'; import type {ChartColorsConfig} from '../../../types'; import {colorizePivotTableCell} from '../../../utils/color-helpers'; import type {AnnotationsMap, PivotDataCellValue, PivotDataRows} from '../types'; @@ -110,11 +110,13 @@ export const colorizePivotTableByColorField = (args: ColorizeByColorFieldArgs) = } const {colorValues, min, max} = colorSettings; + const nilValue = colorsConfig.nullMode === GradientNullModes.AsZero ? 0 : null; rows.forEach((row, rowIndex) => { for (let i = rowHeaderLength; i < row.cells.length; i++) { const cell = row.cells[i]; - const colorValue = colorValues[rowIndex][i - rowHeaderLength]; + const rawColorValue = colorValues[rowIndex][i - rowHeaderLength]; + const colorValue = rawColorValue === null ? nilValue : rawColorValue; const isInvalidColorValue = colorValue === null; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts index ff36a0c25e..bc5f603f64 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts @@ -1,10 +1,11 @@ import isNil from 'lodash/isNil'; import isNumber from 'lodash/isNumber'; -import type { - RGBColor, - ServerField, - TableFieldBackgroundSettings, +import { + GradientNullModes, + type RGBColor, + type ServerField, + type TableFieldBackgroundSettings, } from '../../../../../../../../../shared'; import type {ChartColorsConfig} from '../../../../types'; import { @@ -46,15 +47,13 @@ export function colorizeFlatTableColumn({ index: number; colorsConfig: ChartColorsConfig; }) { - const colorValues = data.reduce( - (acc, row) => { - const rowValue = row[index]; - const parsedRowValue = isNil(rowValue) ? null : parseFloat(rowValue); - - return [...acc, parsedRowValue]; - }, - [] as (number | null)[], - ); + const nilValue = colorsConfig.nullMode === GradientNullModes.AsZero ? 0 : null; + const colorValues = data.reduce<(number | null)[]>((acc, row) => { + const rowValue = row[index]; + const parsedRowValue = isNil(rowValue) ? nilValue : parseFloat(rowValue); + acc.push(parsedRowValue); + return acc; + }, []); const {min, mid, max} = getThresholdValues(colorsConfig, colorValues.filter(isNumber)); const currentGradient = getCurrentGradient(colorsConfig); diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts index eec53d17d4..326eca2b3e 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts @@ -7,6 +7,7 @@ import type { } from '../../../../../../../shared'; import { DATASET_FIELD_TYPES, + GradientNullModes, IS_NULL_FILTER_TEMPLATE, MINIMUM_FRACTION_DIGITS, isDateField, @@ -277,7 +278,7 @@ function prepareFlatTable({ if (colors.length) { const valueColor = values[iColor]; - if (valueColor !== null) { + if (valueColor !== null || colorsConfig.nullMode === GradientNullModes.AsZero) { cell.color = Number(valueColor); } } diff --git a/src/shared/constants/gradients/index.ts b/src/shared/constants/gradients/index.ts index 8b223cf25f..9ec8d6e65c 100644 --- a/src/shared/constants/gradients/index.ts +++ b/src/shared/constants/gradients/index.ts @@ -1,4 +1,5 @@ import type {ColorPalette} from '../../types/color-palettes'; +import type {ValueOf} from '../../types/utility-types'; import { THREE_POINT_DEFAULT_GRADIENT, @@ -27,6 +28,13 @@ export interface Gradient { colors: string[]; } +export const GradientNullModes = { + Ignore: 'ignore', + AsZero: 'as-0', +}; + +export type GradientNullMode = ValueOf; + export interface RGBColor { red: number; green: number; diff --git a/src/shared/types/config/wizard/v12.ts b/src/shared/types/config/wizard/v12.ts index 905a357f26..d70e5e8097 100644 --- a/src/shared/types/config/wizard/v12.ts +++ b/src/shared/types/config/wizard/v12.ts @@ -1,4 +1,4 @@ -import type {MarkupType, WidgetSizeType} from '../../..'; +import type {GradientNullMode, MarkupType, WidgetSizeType} from '../../..'; import type {ColorMode} from '../../../constants'; import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; import type { @@ -294,6 +294,7 @@ export type V12ColorsConfig = { coloredByMeasure?: boolean; palette?: string; colorMode?: ColorMode; + nullMode?: GradientNullMode; }; export type V12ShapesConfig = { diff --git a/src/shared/types/wizard/background-settings.ts b/src/shared/types/wizard/background-settings.ts index 1d6901002a..50ca20cfde 100644 --- a/src/shared/types/wizard/background-settings.ts +++ b/src/shared/types/wizard/background-settings.ts @@ -7,7 +7,8 @@ type GradientFields = | 'rightThreshold' | 'gradientPalette' | 'gradientMode' - | 'reversed'; + | 'reversed' + | 'nullMode'; export interface TableFieldBackgroundSettings { enabled: boolean; diff --git a/src/shared/types/wizard/index.ts b/src/shared/types/wizard/index.ts index 17be61dc03..11a9cfc930 100644 --- a/src/shared/types/wizard/index.ts +++ b/src/shared/types/wizard/index.ts @@ -13,6 +13,7 @@ import type { } from '../'; import type { ColorMode, + GradientNullMode, GradientType, NavigatorLinesMode, NavigatorPeriod, @@ -75,6 +76,7 @@ export interface ColorsConfig { coloredByMeasure?: boolean; palette?: string; colorMode?: ColorMode; + nullMode?: GradientNullMode; } export enum LabelsPositions { diff --git a/src/ui/units/wizard/actions/dialog.ts b/src/ui/units/wizard/actions/dialog.ts index af67863c82..2e6f8ee37e 100644 --- a/src/ui/units/wizard/actions/dialog.ts +++ b/src/ui/units/wizard/actions/dialog.ts @@ -279,6 +279,7 @@ export function openDialogColors({item, onApply, colorSectionFields}: OpenDialog } }, colorsConfig, + canSetNullMode: true, }), ); } diff --git a/src/ui/units/wizard/actions/dialogColor.ts b/src/ui/units/wizard/actions/dialogColor.ts index 290ef56f0f..1f40c9d5f1 100644 --- a/src/ui/units/wizard/actions/dialogColor.ts +++ b/src/ui/units/wizard/actions/dialogColor.ts @@ -1,6 +1,6 @@ import {selectAvailableClientGradients} from 'constants/common'; -import type {ColorsConfig, Field, GradientType, PartialBy} from 'shared'; +import type {ColorsConfig, Field, GradientNullMode, GradientType, PartialBy} from 'shared'; import {closeDialog, openDialog} from 'store/actions/dialog'; import type {DatalensGlobalState} from 'ui'; @@ -42,6 +42,7 @@ export interface GradientState { rightThreshold?: string; gradientPalette: string; validationStatus?: ValidationStatus; + nullMode?: GradientNullMode; } export const RESET_DIALOG_COLOR_STATE = Symbol('wizard/dialogColor/RESET_DIALOG_COLOR_STATE'); @@ -141,6 +142,7 @@ export function prepareDialogColorState(props: { middleThreshold: colorsConfig?.middleThreshold || defaultGradientState.middleThreshold, rightThreshold: colorsConfig?.rightThreshold || defaultGradientState.rightThreshold, gradientPalette: colorsConfig?.gradientPalette || defaultGradientState.gradientPalette, + nullMode: colorsConfig?.nullMode || defaultGradientState.nullMode, }; const gradients = selectAvailableClientGradients(getState(), gradientState.gradientMode); diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx b/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx index a468781bca..5323e38cd9 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx @@ -48,6 +48,7 @@ type OwnProps = { onColorModeChange: (value: ColorMode) => void; isColorModeChangeAvailable: boolean; colorSectionFields?: Field[]; + canSetNullMode?: boolean; }; type StateProps = ReturnType; diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss index a255a9839a..33fde7af43 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss @@ -11,7 +11,7 @@ } &_gradient-mode { - height: 370px; + height: 380px; .color-settings-container { &__row { diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx index f9a047ed7a..7d974d64a8 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx @@ -11,10 +11,15 @@ import {bindActionCreators} from 'redux'; import type {ColorsConfig, Field} from 'shared'; import {ColorMode, isMeasureValue} from 'shared'; import type {DatalensGlobalState} from 'ui'; +import {ALLOWED_FOR_NULL_MODE_VISUALIZATIONS} from 'ui/units/wizard/constants/dialogColor'; import {setDialogColorPaletteState} from 'units/wizard/actions/dialogColor'; import {selectDataset, selectParameters} from 'units/wizard/selectors/dataset'; import {selectUpdates} from 'units/wizard/selectors/preview'; -import {selectDashboardParameters, selectFilters} from 'units/wizard/selectors/visualization'; +import { + selectDashboardParameters, + selectFilters, + selectVisualization, +} from 'units/wizard/selectors/visualization'; import { isGradientDialog, @@ -46,6 +51,7 @@ interface OwnProps { colorsConfig: ColorsConfig; extra?: ExtraSettings; isColorModeChangeAvailable: boolean; + canSetNullMode?: boolean; } type StateProps = ReturnType; @@ -87,15 +93,26 @@ class DialogColorComponent extends React.Component { } render() { - const {item, items, dataset, isColorModeChangeAvailable, colorSectionFields} = this.props; + const { + item, + items, + dataset, + isColorModeChangeAvailable, + colorSectionFields, + visualization, + } = this.props; const {mountedColors = {}} = this.props.paletteState; const {validationStatus} = this.props.gradientState; const {colorMode} = this.state; - if (!item || !dataset) { + if (!item || !dataset || !visualization) { return null; } + const canSetNullMode = + this.props.canSetNullMode && + (ALLOWED_FOR_NULL_MODE_VISUALIZATIONS as string[]).includes(visualization.id); + return (
@@ -117,6 +134,7 @@ class DialogColorComponent extends React.Component { colorMode={this.state.colorMode} isColorModeChangeAvailable={isColorModeChangeAvailable} onColorModeChange={this.onColorModeChange} + canSetNullMode={canSetNullMode} /> { leftThreshold, middleThreshold, rightThreshold, + nullMode, } = this.props.gradientState; config = { @@ -195,6 +214,7 @@ class DialogColorComponent extends React.Component { middleThreshold, rightThreshold, colorMode, + nullMode, }; return config; @@ -228,6 +248,7 @@ const mapStateToProps = (state: DatalensGlobalState) => { dataset: selectDataset(state), gradientState: selectDialogColorGradientState(state), paletteState: selectDialogColorPaletteState(state), + visualization: selectVisualization(state), }; }; diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss index 39bc2eb049..f90f3bcd7b 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss @@ -2,48 +2,36 @@ .dialog-color-gradient { &__row { - margin-bottom: 20px; - vertical-align: middle; - line-height: 20px; - display: flex; - align-items: center; - } + --gc-form-row-label-width: 220px; + margin-bottom: var(--g-spacing-4); - &__label { - display: inline-block; - margin-right: 20px; + :last-of-type { + margin-bottom: 0; + } } &__palette-select { - width: 204px; + padding-inline-end: var(--g-spacing-2); + width: var(--gc-form-row-label-width); display: inline-block; } &__reverse-button { - margin-left: 6px; + margin-left: var(--g-spacing-2); line-height: 24px; vertical-align: middle; } - &__thresholds-checkbox { - margin-right: 10px; - } - &__threshold-inputs-wrapper { display: flex; width: 395px; - padding-right: 36px; - height: 28px; + padding-right: 44px; justify-content: space-between; } &__threshold-input { display: inline-block; - width: 80px; - } - - &__error-label { - margin: 20px 32px; + width: 100px; } &__preview { @@ -51,7 +39,6 @@ height: 28px; background: #000000; vertical-align: middle; - margin-left: 12px; } &__select { @@ -63,6 +50,6 @@ } &__hint-icon { - margin-left: 5px; + margin-left: var(--g-spacing-1); } } diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx index 481fea1f4d..7c1ab98061 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import {HelpPopover} from '@gravity-ui/components'; +import {FormRow, HelpPopover} from '@gravity-ui/components'; import {ArrowRightArrowLeft} from '@gravity-ui/icons'; -import {Button, Checkbox, Icon, RadioButton, Select, TextInput} from '@gravity-ui/uikit'; +import {Button, Checkbox, Flex, Icon, RadioButton, Select, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {FieldWrapper} from 'components/FieldWrapper/FieldWrapper'; import {i18n} from 'i18n'; import type {DatalensGlobalState} from 'index'; import {connect} from 'react-redux'; @@ -13,6 +12,7 @@ import {bindActionCreators} from 'redux'; import type {Field, GradientPalettes} from 'shared'; import {DialogColorQa, GradientType, isMeasureValue} from 'shared'; import {selectAvailableClientGradients, selectDefaultClientGradient} from 'ui'; +import {NULLS_OPTIONS} from 'ui/units/wizard/constants/dialogColor'; import type {GradientState} from 'units/wizard/actions/dialogColor'; import {setDialogColorGradientState} from 'units/wizard/actions/dialogColor'; import {GradientPalettePreview} from 'units/wizard/components/GradientPalettePreview/GradientPalettePreview'; @@ -40,6 +40,7 @@ interface Props extends StateProps, DispatchProps { }; setGradientState: (newGradientState: Partial) => void; item: Field; + canSetNullMode?: boolean; } class DialogColorGradientBody extends React.Component { @@ -75,7 +76,7 @@ class DialogColorGradientBody extends React.Component { rightThreshold, validationStatus, } = this.props.gradientState; - const {extra = {}, item, gradients} = this.props; + const {extra = {}, item, gradients, canSetNullMode} = this.props; const currentPalette = this.getCurrentGradientPalette(gradients); const options = getGradientSelectorItems(gradients); @@ -90,15 +91,17 @@ class DialogColorGradientBody extends React.Component { colors.reverse(); } + const leftThresholdError = validationStatus?.left?.text; + const middleThresholdError = validationStatus?.middle?.text; + const rightThresholdError = validationStatus?.right?.text; + return ( -
- {i18n('wizard', 'label_gradient-type')} + { - const gradientType = event.target.value as GradientType; + onUpdate={(gradientType) => { this.props.setGradientState({ gradientMode: gradientType, gradientPalette: selectDefaultClientGradient(gradientType), @@ -113,10 +116,9 @@ class DialogColorGradientBody extends React.Component { {i18n('wizard', 'label_3-point')} -
+ {extra.polygonBorders && ( -
- {i18n('wizard', 'label_borders')} + { {i18n('wizard', 'label_hide')} -
+ )} -
+