Skip to content

Commit

Permalink
feat: 在背景字段标记中添加 文本智能反色 (#1842)
Browse files Browse the repository at this point in the history
* feat: 根据字段标记背景颜色,设置字体颜色

* feat: 添加 intelligentReverseTextColor 字段到 background condition

* test: 添加背景智能反色相关单测

* docs: 添加开启文字智能反色文档
  • Loading branch information
stone-lyl authored Oct 24, 2022
1 parent 6e61b0c commit 80cbf20
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 18 deletions.
92 changes: 87 additions & 5 deletions packages/s2-core/__tests__/unit/cell/data-cell-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PivotSheet>;
const MockPivotDataSet = PivotDataSet as unknown as jest.Mock<PivotDataSet>;
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -118,7 +125,7 @@ describe('Data Cell Tests', () => {
field: 'cost',
mapping() {
return {
fill: '#F7B46F',
fill: '#fffae6',
};
},
},
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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');
});
});
});
45 changes: 43 additions & 2 deletions packages/s2-core/src/cell/data-cell.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -173,13 +179,42 @@ export class DataCell extends BaseCell<ViewMeta> {
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 };
}
Expand Down Expand Up @@ -258,13 +293,19 @@ export class DataCell extends BaseCell<ViewMeta> {

// 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,
};
}

/**
Expand Down
7 changes: 7 additions & 0 deletions packages/s2-core/src/common/constant/condition.ts
Original file line number Diff line number Diff line change
@@ -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';
5 changes: 5 additions & 0 deletions packages/s2-core/src/common/interface/condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
19 changes: 10 additions & 9 deletions packages/s2-core/src/utils/color.ts
Original file line number Diff line number Diff line change
@@ -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];

Expand Down Expand Up @@ -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);

Expand All @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions s2-site/docs/common/conditions.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type MappingFunction = (
isCompare?: boolean;
minValue?: number;
maxValue?: number;

// 仅用于背景字段标记,可选。(当背景颜色较暗,将文本颜色设置为白色。优先级低于 文本字段标记)
intelligentReverseTextColor?: boolean;
} | null | undefined // 返回值为空时,表示当前字段不显示字段标记样式

```
Expand Down
14 changes: 12 additions & 2 deletions s2-site/docs/manual/basic/conditions.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const s2Options = {

<playground path="analysis/conditions/demo/bidirectional-interval.ts" rid='bidirectional'></playground>

​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#text)
​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#bidirectional-interval)

### 渐变柱状图

Expand All @@ -141,4 +141,14 @@ const s2Options = {
`price` 字段使用渐变色:
<playground path="analysis/conditions/demo/gradient-interval.ts" rid='gradient'></playground>

​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#text)
​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#gradient-interval)

### 开启文字智能反色

通过显示指定 `background` 字段标记中的 `mapping` 函数返回值 `intelligentReverseTextColor` 属性值为 `true`
当标记背景颜色较暗时,文本颜色将变为白色。当标记背景颜色明亮时,文本颜色默认为黑色。
优先级: `background condition``intelligentReverseTextColor` < `text condition``fill`

<playground path="analysis/conditions/demo/intelligent-background.ts" rid='intelligentReverseTextColor'></playground>

​📊 查看更多 [字段标记示例](/zh/examples/analysis/conditions#intelligent-background)
Original file line number Diff line number Diff line change
@@ -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();
});
8 changes: 8 additions & 0 deletions s2-site/examples/analysis/conditions/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}

1 comment on commit 80cbf20

@vercel
Copy link

@vercel vercel bot commented on 80cbf20 Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.