From 80cbf2060ac530e0edeae1f7adef7a56303f8723 Mon Sep 17 00:00:00 2001 From: stone Date: Mon, 24 Oct 2022 15:00:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E8=83=8C=E6=99=AF=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=A0=87=E8=AE=B0=E4=B8=AD=E6=B7=BB=E5=8A=A0=20?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=99=BA=E8=83=BD=E5=8F=8D=E8=89=B2=20(#1842?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 根据字段标记背景颜色,设置字体颜色 * feat: 添加 intelligentReverseTextColor 字段到 background condition * test: 添加背景智能反色相关单测 * docs: 添加开启文字智能反色文档 --- .../__tests__/unit/cell/data-cell-spec.ts | 92 ++++++++++++++++++- packages/s2-core/src/cell/data-cell.ts | 45 ++++++++- .../s2-core/src/common/constant/condition.ts | 7 ++ .../s2-core/src/common/interface/condition.ts | 5 + packages/s2-core/src/utils/color.ts | 19 ++-- s2-site/docs/common/conditions.zh.md | 3 + s2-site/docs/manual/basic/conditions.zh.md | 14 ++- .../conditions/demo/intelligent-background.ts | 34 +++++++ .../analysis/conditions/demo/meta.json | 8 ++ 9 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 s2-site/examples/analysis/conditions/demo/intelligent-background.ts diff --git a/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts index c61d6c6f80..a2d111b409 100644 --- a/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/data-cell-spec.ts @@ -7,6 +7,10 @@ import { PivotDataSet } from '@/data-set'; import { SpreadSheet, PivotSheet } from '@/sheet-type'; import { DataCell } from '@/cell'; import type { PivotFacet } from '@/facet'; +import { + DEFAULT_FONT_COLOR, + REVERSE_FONT_COLOR, +} from '@/common/constant/condition'; const MockPivotSheet = PivotSheet as unknown as jest.Mock; const MockPivotDataSet = PivotDataSet as unknown as jest.Mock; @@ -54,11 +58,14 @@ describe('Data Cell Tests', () => { test('should return correct formatted value', () => { const formatter: Formatter = (_, data) => `${get(data, 'value') * 10}`; jest.spyOn(s2.dataSet, 'getFieldFormatter').mockReturnValue(formatter); - const dataCell = new DataCell(meta, s2); - // @ts-ignore - expect(dataCell.textShape.attr('text')).toEqual('120'); + expect(dataCell.getTextShape().attr('text')).toEqual('120'); + }); + + test('should get correct text fill color', () => { + const dataCell = new DataCell(meta, s2); + expect(dataCell.getTextShape().attr('fill')).toEqual(DEFAULT_FONT_COLOR); }); }); describe('Condition Tests', () => { @@ -118,7 +125,7 @@ describe('Data Cell Tests', () => { field: 'cost', mapping() { return { - fill: '#F7B46F', + fill: '#fffae6', }; }, }, @@ -130,7 +137,8 @@ describe('Data Cell Tests', () => { .getChildByIndex(0) // @ts-ignore .getChildByIndex(2); - expect(get(dataCell, 'backgroundShape.attrs.fill')).toEqual('#F7B46F'); + expect(get(dataCell, 'backgroundShape.attrs.fill')).toEqual('#fffae6'); + expect(get(dataCell, 'textShape.attrs.fill')).toEqual(DEFAULT_FONT_COLOR); }); test('should draw condition interval shape', () => { @@ -169,5 +177,79 @@ describe('Data Cell Tests', () => { cellWidth, ); }); + + test('should draw REVERSE_FONT_COLOR on text when background low brightness and intelligentReverseTextColor is true', () => { + s2.setOptions({ + conditions: { + background: [ + { + field: 'cost', + mapping() { + return { + fill: '#000000', + intelligentReverseTextColor: true, + }; + }, + }, + ], + }, + }); + s2.render(); + const dataCell = s2.facet.panelGroup + .getChildByIndex(0) + // @ts-ignore + .getChildByIndex(2); + expect(get(dataCell, 'textShape.attrs.fill')).toEqual(REVERSE_FONT_COLOR); + expect(get(dataCell, 'backgroundShape.attrs.fill')).toEqual('#000000'); + }); + + test('should draw DEFAULT_FONT_COLOR on text when background low brightness and intelligentReverseTextColor is false', () => { + s2.setOptions({ + conditions: { + background: [ + { + field: 'cost', + mapping() { + return { + fill: '#000000', + }; + }, + }, + ], + }, + }); + s2.render(); + const dataCell = s2.facet.panelGroup + .getChildByIndex(0) + // @ts-ignore + .getChildByIndex(2); + expect(get(dataCell, 'textShape.attrs.fill')).toEqual(DEFAULT_FONT_COLOR); + expect(get(dataCell, 'backgroundShape.attrs.fill')).toEqual('#000000'); + }); + + test('should draw DEFAULT_FONT_COLOR on text when background high brightness is and intelligentReverseTextColor is true', () => { + s2.setOptions({ + conditions: { + background: [ + { + field: 'cost', + mapping() { + return { + fill: '#ffffff', + intelligentReverseTextColor: true, + }; + }, + }, + ], + }, + }); + s2.render(); + const dataCell = s2.facet.panelGroup + .getChildByIndex(0) + // @ts-ignore + .getChildByIndex(2); + expect(get(dataCell, 'textShape.attrs.fill')).toEqual(DEFAULT_FONT_COLOR); + expect(get(dataCell, 'backgroundShape.attrs.fill')).toEqual('#ffffff'); + }); }); }); diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index afb5f35cb1..4329ca6c2e 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -1,5 +1,6 @@ import type { IShape, Point } from '@antv/g-canvas'; import { find, findLast, first, get, isEmpty, isEqual } from 'lodash'; +import tinycolor from 'tinycolor2'; import { BaseCell } from '../cell/base-cell'; import { CellTypes, @@ -23,6 +24,11 @@ import { includeCell } from '../utils/cell/data-cell'; import { getIconPositionCfg } from '../utils/condition/condition'; import { renderLine, renderRect, updateShapeAttr } from '../utils/g-renders'; import { drawInterval } from '../utils/g-mini-charts'; +import { + DEFAULT_FONT_COLOR, + FONT_COLOR_BRIGHTNESS_THRESHOLD, + REVERSE_FONT_COLOR, +} from '../common/constant/condition'; /** * DataCell for panelGroup area @@ -173,13 +179,42 @@ export class DataCell extends BaseCell { this.update(); } + /** + * 获取默认字体颜色:根据字段标记背景颜色,设置字体颜色 + * @param textStyle + * @private + */ + private getDefaultTextFill(textStyle: TextTheme) { + let textFill = textStyle.fill; + const { backgroundColor, intelligentReverseTextColor } = + this.getBackgroundColor(); + + const isMoreThanThreshold = + tinycolor(backgroundColor).getBrightness() <= + FONT_COLOR_BRIGHTNESS_THRESHOLD; + + // text 默认为黑色,当背景颜色亮度过低时,修改 text 为白色 + if ( + isMoreThanThreshold && + textStyle.fill === DEFAULT_FONT_COLOR && + intelligentReverseTextColor + ) { + textFill = REVERSE_FONT_COLOR; + } + return textFill; + } + protected getTextStyle(): TextTheme { const { isTotals } = this.meta; const textStyle = isTotals ? this.theme.dataCell.bolderText : this.theme.dataCell.text; - const fill = this.getTextConditionFill(textStyle); + // 优先级:默认字体颜色(已经根据背景反色后的) < 用户配置字体颜色 + const fill = this.getTextConditionFill({ + ...textStyle, + fill: this.getDefaultTextFill(textStyle), + }); return { ...textStyle, fill }; } @@ -258,13 +293,19 @@ export class DataCell extends BaseCell { // get background condition fill color const bgCondition = this.findFieldCondition(this.conditions?.background); + let intelligentReverseTextColor = false; if (bgCondition && bgCondition.mapping) { const attrs = this.mappingValue(bgCondition); if (attrs) { backgroundColor = attrs.fill; + intelligentReverseTextColor = attrs.intelligentReverseTextColor; } } - return { backgroundColor, backgroundColorOpacity }; + return { + backgroundColor, + backgroundColorOpacity, + intelligentReverseTextColor, + }; } /** diff --git a/packages/s2-core/src/common/constant/condition.ts b/packages/s2-core/src/common/constant/condition.ts index 1e58a928cb..ffbb51c2a5 100644 --- a/packages/s2-core/src/common/constant/condition.ts +++ b/packages/s2-core/src/common/constant/condition.ts @@ -1,2 +1,9 @@ export const VALUE_RANGES_KEY = 'valueRanges'; export const DEFAULT_VALUE_RANGES = {}; +/** + * 亮度范围 0~255 + * @see https://github.com/bgrins/TinyColor#getbrightness + */ +export const FONT_COLOR_BRIGHTNESS_THRESHOLD = 220; +export const DEFAULT_FONT_COLOR = '#000000'; +export const REVERSE_FONT_COLOR = '#FFFFFF'; diff --git a/packages/s2-core/src/common/interface/condition.ts b/packages/s2-core/src/common/interface/condition.ts index e05db9a3a6..42e5daf741 100644 --- a/packages/s2-core/src/common/interface/condition.ts +++ b/packages/s2-core/src/common/interface/condition.ts @@ -14,6 +14,11 @@ export interface MappingResult extends ValueRange { fill: string; // only used in interval condition isCompare?: boolean; + /** + * @description only used in background condition, when the background color is too light, the font color will be white + * @version 1.34.0 + */ + intelligentReverseTextColor?: boolean; } export type MappingFunction = ( diff --git a/packages/s2-core/src/utils/color.ts b/packages/s2-core/src/utils/color.ts index af15027266..15436d6b81 100644 --- a/packages/s2-core/src/utils/color.ts +++ b/packages/s2-core/src/utils/color.ts @@ -1,16 +1,15 @@ import { toUpper } from 'lodash'; import tinycolor from 'tinycolor2'; import type { Palette, PaletteMeta } from '../common/interface/theme'; +import { + DEFAULT_FONT_COLOR, + FONT_COLOR_BRIGHTNESS_THRESHOLD, + REVERSE_FONT_COLOR, +} from '../common/constant/condition'; const WHITE_COLOR = '#FFFFFF'; const BLACK_COLOR = '#000000'; -/** - * 亮度范围 0~255 - * @see https://github.com/bgrins/TinyColor#getbrightness - */ -const FONT_COLOR_BRIGHTNESS_THRESHOLD = 220; - /** S2 标准色板 mix 规则 */ const STANDARD_COLOR_MIX_PERCENT = [95, 85, 75, 30, 15, 0, 15, 30, 45, 60, 80]; @@ -79,7 +78,9 @@ export const generateStandardColors = (brandColor: string): string[] => { export const generatePalette = ( paletteMeta: PaletteMeta = {} as PaletteMeta, ): Palette => { - const basicColors = Array.from(Array(BASIC_COLOR_COUNT)).fill(WHITE_COLOR); + const basicColors = Array.from(Array(BASIC_COLOR_COUNT)).fill( + REVERSE_FONT_COLOR, + ); const { basicColorRelations = [], brandColor } = paletteMeta; const standardColors = generateStandardColors(brandColor); @@ -94,8 +95,8 @@ export const generatePalette = ( basicColors[fontColorIndex] = tinycolor(basicColors[bgColorIndex]).getBrightness() > FONT_COLOR_BRIGHTNESS_THRESHOLD - ? BLACK_COLOR - : WHITE_COLOR; + ? DEFAULT_FONT_COLOR + : REVERSE_FONT_COLOR; }); return { diff --git a/s2-site/docs/common/conditions.zh.md b/s2-site/docs/common/conditions.zh.md index 34431b668e..701bdf203e 100644 --- a/s2-site/docs/common/conditions.zh.md +++ b/s2-site/docs/common/conditions.zh.md @@ -44,6 +44,9 @@ type MappingFunction = ( isCompare?: boolean; minValue?: number; maxValue?: number; + + // 仅用于背景字段标记,可选。(当背景颜色较暗,将文本颜色设置为白色。优先级低于 文本字段标记) + intelligentReverseTextColor?: boolean; } | null | undefined // 返回值为空时,表示当前字段不显示字段标记样式 ``` diff --git a/s2-site/docs/manual/basic/conditions.zh.md b/s2-site/docs/manual/basic/conditions.zh.md index 81fec0f6c8..989457d2f9 100644 --- a/s2-site/docs/manual/basic/conditions.zh.md +++ b/s2-site/docs/manual/basic/conditions.zh.md @@ -132,7 +132,7 @@ const s2Options = { -​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#text)。 +​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#bidirectional-interval)。 ### 渐变柱状图 @@ -141,4 +141,14 @@ const s2Options = { `price` 字段使用渐变色: -​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#text)。 +​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#gradient-interval)。 + +### 开启文字智能反色 + +通过显示指定 `background` 字段标记中的 `mapping` 函数返回值 `intelligentReverseTextColor` 属性值为 `true`。 +当标记背景颜色较暗时,文本颜色将变为白色。当标记背景颜色明亮时,文本颜色默认为黑色。 +优先级: `background condition` 的 `intelligentReverseTextColor` < `text condition` 的 `fill` + + + +​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#intelligent-background)。 diff --git a/s2-site/examples/analysis/conditions/demo/intelligent-background.ts b/s2-site/examples/analysis/conditions/demo/intelligent-background.ts new file mode 100644 index 0000000000..18e8f53d3c --- /dev/null +++ b/s2-site/examples/analysis/conditions/demo/intelligent-background.ts @@ -0,0 +1,34 @@ +import { PivotSheet } from '@antv/s2'; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then((dataCfg) => { + const container = document.getElementById('container'); + + const s2Options = { + width: 600, + height: 480, + interaction: { + hoverHighlight: false, + }, + conditions: { + background: [ + { + field: 'number', + mapping() { + return { + // fill 是背景字段下唯一必须的字段,用于指定文本颜色 + fill: '#000', + intelligentReverseTextColor: true, + }; + }, + }, + ], + }, + }; + const s2 = new PivotSheet(container, dataCfg, s2Options); + + s2.render(); + }); diff --git a/s2-site/examples/analysis/conditions/demo/meta.json b/s2-site/examples/analysis/conditions/demo/meta.json index ceefae23b2..c2cee16471 100644 --- a/s2-site/examples/analysis/conditions/demo/meta.json +++ b/s2-site/examples/analysis/conditions/demo/meta.json @@ -75,6 +75,14 @@ "en": "Bidirectional interval" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/oUICMHjOA/7fd723cb-fc2a-45a5-a0ab-a2840127a48a.png" + }, + { + "filename": "intelligent-background.ts", + "title": { + "zh": "开启文字智能反色", + "en": "Intelligent background" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/oUICMHjOA/7fd723cb-fc2a-45a5-a0ab-a2840127a48a.png" } ] }