Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 列头文字&icon无法正确对齐 #1515

Merged
merged 7 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/s2-core/__tests__/spreadsheet/theme-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ describe('SpreadSheet Theme Tests', () => {

expectTextAlign({
textAlign,
fontWight: 520,
fontWight: 500,
customNodes: isRowCell ? rowTotalNodes : colTotalNodes,
});
},
Expand Down
108 changes: 108 additions & 0 deletions packages/s2-core/__tests__/unit/cell/col-cell-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import _ from 'lodash';
import type { Node } from '@/facet/layout/node';
import { PivotDataSet } from '@/data-set';
import { SpreadSheet, PivotSheet } from '@/sheet-type';
import { ColCell } from '@/cell';
import type { TextAlign } from '@/common';

const MockPivotSheet = PivotSheet as unknown as jest.Mock<PivotSheet>;
const MockPivotDataSet = PivotDataSet as unknown as jest.Mock<PivotDataSet>;

describe('Col Cell Tests', () => {
let s2: SpreadSheet;

beforeEach(() => {
const container = document.createElement('div');

s2 = new MockPivotSheet(container);
const dataSet: PivotDataSet = new MockPivotDataSet(s2);
s2.dataSet = dataSet;
});

describe('None-leaf Nodes Tests', () => {
const node = {
isLeaf: false,
x: 0,
y: 0,
height: 30,
width: 200,
} as unknown as Node;

const headerConfig = {
width: 500, // col header width
scrollContainsRowHeader: true,
cornerWidth: 100,
scrollX: 30, // 模拟滚动了 30px
};

const actualTextWidth = 40; // 文字长度

test.each([
['left', 8], // col.padding.left
['center', 100], // col.width / 2
['right', 192], // col.width - col.padding.right
])(
'should calc node text position in %s align mode',
(textAlign: TextAlign, textX: number) => {
s2.setThemeCfg({
theme: {
colCell: {
bolderText: {
textAlign,
},
},
},
});

const colCell = new ColCell(node, s2, { ...headerConfig });
_.set(colCell, 'actualTextWidth', actualTextWidth); // 文字总长度

const getTextPosition = _.get(colCell, 'getTextPosition').bind(colCell);
expect(getTextPosition()).toEqual({
x: textX,
y: 15,
});
},
);

test.each([
['left', 52], // col.padding.left + actualTextWidth + icon.margin.left
['center', 115], // col.width / 2 + (actualTextWidth + icon.margin.left + icon.width + icon.margin.right) / 2 - (icon.width + icon.margin.right)
['right', 178], // col.width - col.padding.right - icon.margin.right - icon.width
])(
'should calc icon position in %s align mode',
(textAlign: TextAlign, iconX: number) => {
s2.setThemeCfg({
theme: {
colCell: {
bolderText: {
textAlign,
},
},
},
});
s2.setOptions({
headerActionIcons: [
{
iconNames: ['SortUp'],
belongsCell: 'colCell',
displayCondition: (meta) => {
return !meta.isLeaf;
},
action: () => true,
},
],
});

const colCell = new ColCell(node, s2, { ...headerConfig });
_.set(colCell, 'actualTextWidth', actualTextWidth); // 文字总长度

const getIconPosition = _.get(colCell, 'getIconPosition').bind(colCell);
expect(getIconPosition()).toEqual({
x: iconX,
y: 10,
});
},
);
});
});
8 changes: 5 additions & 3 deletions packages/s2-core/__tests__/unit/utils/text-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ describe('Text Utils Tests', () => {
placeholder: '--',
});

expect(text).toEqual('12...');
expect(text).toEndWith('...');
expect(text.length).toBeLessThanOrEqual(5);
});

test('should get correct placeholder text with ""', () => {
Expand Down Expand Up @@ -73,10 +74,11 @@ describe('Text Utils Tests', () => {
test('should get correct ellipsis text', () => {
const text = getEllipsisText({
text: '长度测试',
maxWidth: 20,
maxWidth: 24,
});

expect(text).toEqual('长...');
expect(text).toEndWith('...');
expect(text.length).toBeLessThanOrEqual(4);
});

test('should get correct text width', () => {
Expand Down
95 changes: 64 additions & 31 deletions packages/s2-core/src/cell/col-cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import { HeaderCell } from './header-cell';
export class ColCell extends HeaderCell {
protected declare headerConfig: ColHeaderConfig;

/** 文字区域(含icon)绘制起始坐标 */
protected textAreaPosition: Point;
/** 文字绘制起始坐标 */
protected textPosition: Point;

public get cellType() {
return CellTypes.COL_CELL;
Expand Down Expand Up @@ -107,24 +107,60 @@ export class ColCell extends HeaderCell {
}

protected getIconPosition(): Point {
const { isLeaf } = this.meta;
const iconStyle = this.getIconStyle();

if (isLeaf) {
if (this.meta.isLeaf) {
return super.getIconPosition(this.getActionIconsCount());
}

const position = this.textAreaPosition;
// 非叶子节点,因 label 滚动展示,需要适配不同 align情况
const iconStyle = this.getIconStyle();
const iconMarginLeft = iconStyle.margin.left;

const totalSpace =
this.actualTextWidth +
this.getActionIconsWidth() -
iconStyle.margin.right;
const startX = position.x - totalSpace / 2;
const textStyle = this.getTextStyle();
const position = this.textPosition;
const textX = position.x;

const y = position.y - iconStyle.size / 2;

if (textStyle.textAlign === 'left') {
/**
* textX x
* | |
* v v
* +---------+ +----+
* | text |--|icon|
* +---------+ +----+
*/
return {
x: textX + this.actualTextWidth + iconMarginLeft,
y,
};
}
if (textStyle.textAlign === 'right') {
/**
* textX x
* | |
* v v
* +---------+ +----+
* | text |--|icon|
* +---------+ +----+
*/
return {
x: textX + iconMarginLeft,
y,
};
}

/**
* textX x
* | |
* v v
* +---------+ +----+
* | text |--|icon|
* +---------+ +----+
*/
return {
x: startX + this.actualTextWidth + iconStyle.margin.left,
y: position.y - iconStyle.size / 2,
x: textX + this.actualTextWidth / 2 + iconMarginLeft,
y,
};
}

Expand Down Expand Up @@ -173,38 +209,35 @@ export class ColCell extends HeaderCell {
this.getStyle().cell?.padding,
);

const iconCount = this.getActionIconsCount();
const textAndIconSpace =
this.actualTextWidth +
this.getActionIconsWidth() -
(iconCount ? iconStyle.margin.right : 0);
const actionIconSpace = this.getActionIconsWidth();
const textAndIconSpace = this.actualTextWidth + actionIconSpace;

const textAreaRange = getTextAreaRange(
adjustedViewport,
{ start: contentBox.x, width: contentBox.width },
textAndIconSpace, // icon position 默认为 right
);

// textAreaRange.start 是以文字样式为 center 计算出的文字绘制点
// 此处按实际样式(left or right)调整
const startX = adjustColHeaderScrollingTextPosition(
textAreaRange.start,
textAreaRange.width - textAndIconSpace,
// textAreaRange.start 是 text&icon 整个区域的 center
// 此处按实际样式(left or right)调整计算出的文字绘制点
const textX = adjustColHeaderScrollingTextPosition(
textAreaRange,
this.actualTextWidth,
actionIconSpace,
textAlign,
);

const textY = contentBox.y + contentBox.height / 2;
this.textAreaPosition = { x: startX, y: textY };
return {
x: startX - textAndIconSpace / 2 + this.actualTextWidth / 2,
y: textY,
};

this.textPosition = { x: textX, y: textY };
return this.textPosition;
}

protected getActionIconsWidth() {
const { size, margin } = this.getStyle().icon;
const iconCount = this.getActionIconsCount();
return (size + margin.left) * iconCount + iconCount > 0 ? margin.right : 0;
return (
(size + margin.left) * iconCount + (iconCount > 0 ? margin.right : 0)
);
}

protected getColResizeAreaKey() {
Expand Down
3 changes: 3 additions & 0 deletions packages/s2-core/src/sheet-type/spread-sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
getSafetyOptions,
} from '../utils/merge';
import { getTooltipData, getTooltipOptions } from '../utils/tooltip';
import { removeOffscreenCanvas } from '../utils/canvas';

export abstract class SpreadSheet extends EE {
// theme config
Expand Down Expand Up @@ -402,6 +403,8 @@ export abstract class SpreadSheet extends EE {
this.destroyTooltip();
this.clearCanvasEvent();
this.container?.destroy();

removeOffscreenCanvas();
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/s2-core/src/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const getTheme = (
bolderText: {
fontFamily: FONT_FAMILY,
fontSize: 12,
fontWeight: isWindows() ? 'bold' : 520,
fontWeight: isWindows() ? 'bold' : 500,
fill: basicColors[14],
linkTextFill: basicColors[6],
opacity: 1,
Expand Down Expand Up @@ -183,7 +183,7 @@ export const getTheme = (
bolderText: {
fontFamily: FONT_FAMILY,
fontSize: 12,
fontWeight: isWindows() ? 'bold' : 520,
fontWeight: isWindows() ? 'bold' : 500,
fill: basicColors[0],
opacity: 1,
textAlign: 'center',
Expand Down Expand Up @@ -263,7 +263,7 @@ export const getTheme = (
bolderText: {
fontFamily: FONT_FAMILY,
fontSize: 12,
fontWeight: isWindows() ? 'bold' : 520,
fontWeight: isWindows() ? 'bold' : 500,
fill: basicColors[13],
opacity: 1,
textAlign: 'right',
Expand Down
30 changes: 30 additions & 0 deletions packages/s2-core/src/utils/canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const OFFSCREEN_CANVAS_DOM_ID = 's2-offscreen-canvas';

/**
* 获取工具 canvas
* 需要把 canvas 插入到 body 下,继承全局的 css 样式(如 letter-spacing)
* 否则后续的 measureText 与实际渲染会有较大差异
*/
export const getOffscreenCanvas = () => {
let canvas = document.getElementById(
OFFSCREEN_CANVAS_DOM_ID,
) as HTMLCanvasElement;
if (canvas) {
return canvas;
}

canvas = document.createElement('canvas');
canvas.id = OFFSCREEN_CANVAS_DOM_ID;
canvas.style.display = 'none';

document.body.appendChild(canvas);

return canvas;
};

/**
* 移除工具 canvas
*/
export const removeOffscreenCanvas = () => {
document.getElementById(OFFSCREEN_CANVAS_DOM_ID)?.remove();
};
Loading