@antv/s2
Version:
effective spreadsheet render core lib
419 lines • 18.8 kB
JavaScript
import { isEmpty } from 'lodash';
import { CellType, FrozenGroupArea, HORIZONTAL_RESIZE_AREA_KEY_PRE, KEY_GROUP_COL_RESIZE_AREA, ResizeAreaEffect, ResizeDirectionType, S2Event, SPLIT_LINE_WIDTH, } from '../common/constant';
import { CellBorderPosition, CellClipBox, } from '../common/interface';
import { CustomRect } from '../engine';
import { Frame } from '../facet/header/frame';
import { getHorizontalTextIconPosition, getVerticalIconPosition, getVerticalTextPosition, } from '../utils/cell/cell';
import { adjustTextIconPositionWhileScrolling } from '../utils/cell/text-scrolling';
import { renderIcon, renderLine } from '../utils/g-renders';
import { getHiddenColumnContinuousSiblingNodes, isEqualDisplaySiblingNodeId, isLastColumnAfterHidden, } from '../utils/hide-columns';
import { getOrCreateResizeAreaGroupById, getResizeAreaAttrs, shouldAddResizeArea, } from '../utils/interaction/resize';
import { normalizeTextAlign } from '../utils/normalize';
import { HeaderCell } from './header-cell';
export class ColCell extends HeaderCell {
get cellType() {
return CellType.COL_CELL;
}
getBorderPositions() {
return [CellBorderPosition.TOP, CellBorderPosition.RIGHT];
}
initCell() {
super.initCell();
// 1、draw rect background
this.drawBackgroundShape();
// interactive background shape
this.drawInteractiveBgShape();
// interactive cell border shape
this.drawInteractiveBorderShape();
// draw text
this.drawTextOrCustomRenderer();
}
afterDrawText() {
// 绘制字段标记 -- icon
this.drawActionAndConditionIcons();
// draw borders
this.drawBorders();
// draw resize ares
this.drawResizeArea();
this.addExpandColumnIconShapes();
this.update();
}
getFormattedFieldValue() {
var _a;
const { extra, value, field } = this.meta;
const { fields } = this.spreadsheet.dataSet;
// 列头对应的数值标题不应该格式化
const isCustomValueFieldNode = (extra === null || extra === void 0 ? void 0 : extra.isCustomNode) && ((_a = fields === null || fields === void 0 ? void 0 : fields.values) === null || _a === void 0 ? void 0 : _a.includes(field));
if (isCustomValueFieldNode) {
return {
formattedValue: value,
value,
};
}
return super.getFormattedFieldValue();
}
getMaxTextWidth() {
const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX);
return width - this.getActionAndConditionIconWidth();
}
isBolderText() {
// 非叶子节点、小计总计,均为粗体
const { isLeaf, isTotals } = this.meta;
if (isTotals || !isLeaf) {
return true;
}
return false;
}
/**
* 计算文本位置时候需要,留给后代根据情况(固定列)覆盖
* @param viewport
* @returns viewport
*/
handleViewport() {
if (this.meta.isFrozen) {
return {
start: 0,
size: Number.POSITIVE_INFINITY,
};
}
const { viewportWidth, cornerWidth = 0, scrollX = 0, } = this.getHeaderConfig();
const frozenGroupAreas = this.spreadsheet.facet
.frozenGroupAreas;
const frozenColGroupWidth = frozenGroupAreas[FrozenGroupArea.Col].width;
const frozenTrailingColGroupWidth = frozenGroupAreas[FrozenGroupArea.TrailingCol].width;
if (this.spreadsheet.isFrozenRowHeader()) {
return {
start: scrollX + frozenColGroupWidth,
size: viewportWidth - frozenColGroupWidth - frozenTrailingColGroupWidth,
};
}
return {
start: frozenColGroupWidth + Math.max(0, scrollX - cornerWidth),
size: viewportWidth -
frozenColGroupWidth -
frozenTrailingColGroupWidth +
Math.min(scrollX, cornerWidth),
};
}
getContentPosition({ contentWidth = this.getActualTextWidth(), } = {}) {
var _a, _b, _c;
const { isLeaf } = this.meta;
const textStyle = this.getTextStyle();
const contentBox = this.getBBoxByType(CellClipBox.CONTENT_BOX);
const iconStyle = this.getIconStyle();
const textY = getVerticalTextPosition(contentBox, textStyle.textBaseline);
const iconY = getVerticalIconPosition(iconStyle.size, textY, textStyle.fontSize, textStyle.textBaseline);
if (isLeaf) {
const { textX, leftIconX, rightIconX } = getHorizontalTextIconPosition({
bbox: contentBox,
textWidth: contentWidth,
textAlign: textStyle.textAlign,
groupedIcons: this.groupedIcons,
iconStyle,
isCustomRenderer: !!this.getRenderer(),
});
this.leftIconPosition = {
x: leftIconX,
y: iconY,
};
this.rightIconPosition = {
x: rightIconX,
y: iconY,
};
return { x: textX, y: textY };
}
const viewport = this.handleViewport();
const { cell, icon } = this.getStyle();
const { textAlign, textBaseline } = this.getTextStyle();
const { textStart, iconStart, iconEnd } = adjustTextIconPositionWhileScrolling(viewport, { start: contentBox.x, size: contentBox.width }, {
align: normalizeTextAlign(textAlign),
size: {
textSize: contentWidth,
iconStartSize: this.getActionAndConditionIconWidth('left'),
iconEndSize: this.getActionAndConditionIconWidth('right'),
},
padding: {
start: (_a = cell === null || cell === void 0 ? void 0 : cell.padding) === null || _a === void 0 ? void 0 : _a.left,
end: (_b = cell === null || cell === void 0 ? void 0 : cell.padding) === null || _b === void 0 ? void 0 : _b.right,
betweenTextAndEndIcon: (_c = icon === null || icon === void 0 ? void 0 : icon.margin) === null || _c === void 0 ? void 0 : _c.left,
},
}, {
isCustomRenderer: !!this.getRenderer(),
});
const y = getVerticalTextPosition(contentBox, textBaseline);
this.leftIconPosition = {
x: iconStart,
y: iconY,
};
this.rightIconPosition = {
x: iconEnd,
y: iconY,
};
return { x: textStart, y };
}
getTextPosition() {
return this.getContentPosition();
}
getColResizeArea() {
return getOrCreateResizeAreaGroupById(this.spreadsheet, KEY_GROUP_COL_RESIZE_AREA);
}
getHorizontalResizeAreaName() {
return `${HORIZONTAL_RESIZE_AREA_KEY_PRE}${this.meta.field}`;
}
/**
* @description 叶子节点, 但层级不同于其他节点 (如下图 a-1-1), 说明是任意不规则自定义节点, 此时不需要绘制热区
* --------------------------------------------------
* | 自定义节点 a-1 | |
* |------------- |-----------| 自定义节点 a-1-1 |
* | a-1-1 | a-1-2 | a-1-3 | |
* -------------------------------------------------
*/
isCrossColumnLeafNode() {
var _a;
const { colsHierarchy } = this.spreadsheet.facet.getLayoutResult();
const { level, isLeaf } = this.meta;
return ((_a = colsHierarchy === null || colsHierarchy === void 0 ? void 0 : colsHierarchy.sampleNodeForLastLevel) === null || _a === void 0 ? void 0 : _a.level) !== level && isLeaf;
}
drawHorizontalResizeArea() {
var _a, _b;
// 隐藏列头时不绘制水平热区 https://github.com/antvis/S2/issues/1603
const isHiddenCol = ((_b = (_a = this.spreadsheet.options.style) === null || _a === void 0 ? void 0 : _a.colCell) === null || _b === void 0 ? void 0 : _b.height) === 0;
if (isHiddenCol ||
!this.shouldDrawResizeAreaByType('colCellVertical', this)) {
return;
}
const { y, height } = this.meta;
const { position } = this.getHeaderConfig();
const resizeStyle = this.getResizeAreaStyle();
const resizeArea = this.getColResizeArea();
if (!resizeArea || this.isCrossColumnLeafNode()) {
return;
}
const resizeAreaName = this.getHorizontalResizeAreaName();
const existedHorizontalResizeArea = resizeArea === null || resizeArea === void 0 ? void 0 : resizeArea.find((element) => element.name === resizeAreaName);
// 如果已经绘制当前列高调整热区热区,则不再绘制
if (existedHorizontalResizeArea) {
return;
}
const offsetY = position.y + y;
const resizeAreaWidth = this.getResizeAreaWidth();
// 列高调整热区
const attrs = getResizeAreaAttrs({
theme: resizeStyle,
type: ResizeDirectionType.Vertical,
effect: ResizeAreaEffect.Field,
offsetX: 0,
offsetY,
width: resizeAreaWidth,
height,
meta: this.meta,
cell: this,
});
resizeArea.appendChild(new CustomRect({
name: resizeAreaName,
style: Object.assign(Object.assign({}, attrs.style), { x: 0, y: offsetY + height - resizeStyle.size, width: resizeAreaWidth }),
}, attrs.appendInfo));
}
getResizeAreaWidth() {
const { cornerWidth = 0, viewportWidth: headerWidth } = this.getHeaderConfig();
return (Frame.getVerticalBorderWidth(this.spreadsheet) + cornerWidth + headerWidth);
}
shouldAddVerticalResizeArea() {
if (this.getMeta().isFrozen) {
return true;
}
const { x, y, width, height } = this.meta;
const { scrollX = 0, scrollY, cornerWidth = 0, height: headerHeight, width: headerWidth, } = this.getHeaderConfig();
const resizeStyle = this.getResizeAreaStyle();
const resizeAreaBBox = {
x: x + width - resizeStyle.size,
y,
width: resizeStyle.size,
height,
};
const frozenGroupAreas = this.spreadsheet.facet
.frozenGroupAreas;
const colWidth = frozenGroupAreas[FrozenGroupArea.Col].width;
const trailingColWidth = frozenGroupAreas[FrozenGroupArea.TrailingCol].width;
let resizeClipAreaBBox;
if (this.spreadsheet.isFrozenRowHeader()) {
resizeClipAreaBBox = {
x: colWidth,
y: 0,
width: headerWidth - colWidth - trailingColWidth,
height: headerHeight,
};
}
else {
resizeClipAreaBBox = {
x: colWidth - cornerWidth,
y: 0,
width: headerWidth - colWidth - trailingColWidth + cornerWidth,
height: headerHeight,
};
}
return shouldAddResizeArea(resizeAreaBBox, resizeClipAreaBBox, {
scrollX,
scrollY,
});
}
getVerticalResizeAreaOffset() {
const { x, y } = this.meta;
const { scrollX = 0, position, cornerWidth = 0, viewportWidth, } = this.getHeaderConfig();
const isFrozenRowHeader = this.spreadsheet.isFrozenRowHeader();
const frozenGroupAreas = this.spreadsheet.facet
.frozenGroupAreas;
const frozenColGroup = frozenGroupAreas[FrozenGroupArea.Col];
const frozenTrailingColGroup = frozenGroupAreas[FrozenGroupArea.TrailingCol];
let offsetX = position === null || position === void 0 ? void 0 : position.x;
if (this.getMeta().isFrozenHead) {
offsetX +=
x -
frozenColGroup.x -
(isFrozenRowHeader ? 0 : Math.min(scrollX, cornerWidth));
}
else if (this.getMeta().isFrozenTrailing) {
offsetX +=
x -
frozenTrailingColGroup.x +
viewportWidth -
frozenTrailingColGroup.width;
}
else {
offsetX += x - scrollX;
}
return {
x: offsetX,
y: (position === null || position === void 0 ? void 0 : position.y) + y,
};
}
drawVerticalResizeArea() {
if (!this.meta.isLeaf ||
this.meta.hideColCellHorizontalResize ||
!this.shouldDrawResizeAreaByType('colCellHorizontal', this)) {
return;
}
const { width, height } = this.meta;
const resizeStyle = this.getResizeAreaStyle();
const resizeArea = this.getColResizeArea();
if (!resizeArea || !this.shouldAddVerticalResizeArea()) {
return;
}
const { x: offsetX, y: offsetY } = this.getVerticalResizeAreaOffset();
/*
* 列宽调整热区
* 基准线是根据 container 坐标来的,因此把热区画在 container
*/
const attrs = getResizeAreaAttrs({
theme: resizeStyle,
type: ResizeDirectionType.Horizontal,
effect: ResizeAreaEffect.Cell,
offsetX,
offsetY,
width,
height,
meta: this.meta,
cell: this,
});
resizeArea.appendChild(new CustomRect({
style: Object.assign(Object.assign({}, attrs.style), { x: offsetX + width - resizeStyle.size, y: offsetY, height }),
}, attrs.appendInfo));
}
// 绘制热区
drawResizeArea() {
this.drawHorizontalResizeArea();
this.drawVerticalResizeArea();
}
hasHiddenColumnCell() {
const { interaction } = this.spreadsheet.options;
const hiddenColumnsDetail = this.spreadsheet.store.get('hiddenColumnsDetail', []);
if (isEmpty(hiddenColumnsDetail) ||
isEmpty(interaction === null || interaction === void 0 ? void 0 : interaction.hiddenColumnFields)) {
return false;
}
return !!hiddenColumnsDetail.find((column) => isEqualDisplaySiblingNodeId(column === null || column === void 0 ? void 0 : column.displaySiblingNode, this.meta.id, this.isLastColumn() ? 'prev' : 'next'));
}
getExpandIconTheme() {
const themeCfg = this.getStyle();
return themeCfg.icon;
}
addExpandColumnSplitLine() {
const { x, y, width, height } = this.getBBoxByType();
const { horizontalBorderColor, horizontalBorderColorOpacity } = this.theme.splitLine;
const lineX = this.isLastColumn() ? x + width : x;
renderLine(this, {
x1: lineX,
y1: y,
x2: lineX,
y2: y + height,
stroke: horizontalBorderColor,
lineWidth: SPLIT_LINE_WIDTH,
strokeOpacity: horizontalBorderColorOpacity,
});
}
addExpandColumnIconShapes() {
if (!this.hasHiddenColumnCell()) {
return;
}
this.addExpandColumnSplitLine();
this.addExpandColumnIcons();
}
addExpandColumnIcons() {
const isLastColumn = this.isLastColumn();
this.addExpandColumnIcon(isLastColumn);
// 如果当前节点的兄弟节点 (前/后) 都被隐藏了, 隐藏后当前节点变为最后一个节点, 需要渲染两个展开按钮, 一个展开[前], 一个展开[后]
if (this.isAllDisplaySiblingNodeHidden() && isLastColumn) {
this.addExpandColumnIcon(false);
}
}
addExpandColumnIcon(isLastColumn) {
const iconConfig = this.getExpandColumnIconConfig(isLastColumn);
const icon = renderIcon(this, Object.assign(Object.assign({}, iconConfig), { name: 'ExpandColIcon', cursor: 'pointer' }));
icon.addEventListener('click', () => {
this.spreadsheet.emit(S2Event.COL_CELL_EXPANDED, this.meta, isLastColumn ? 'prev' : 'next');
});
icon.addEventListener('mouseenter', (event) => {
this.spreadsheet.emit(S2Event.COL_CELL_EXPAND_ICON_HOVER, {
event,
meta: this.meta,
hiddenColumns: getHiddenColumnContinuousSiblingNodes(this.spreadsheet, this.meta.id, isLastColumn ? 'prev' : 'next'),
});
});
}
// 在隐藏的下一个兄弟节点的起始坐标显示隐藏提示线和展开按钮, 如果是尾元素, 则显示在前一个兄弟节点的结束坐标
getExpandColumnIconConfig(isLastColumn) {
const { size = 0 } = this.getExpandIconTheme();
const { x, y, width, height } = this.getBBoxByType();
const baseIconX = x - size;
const iconX = isLastColumn ? baseIconX + width : baseIconX;
const iconY = y + height / 2 - size / 2;
return {
x: iconX,
y: iconY,
width: size * 2,
height: size,
};
}
isLastColumn() {
return isLastColumnAfterHidden(this.spreadsheet, this.meta.id);
}
isAllDisplaySiblingNodeHidden() {
const { id } = this.meta;
const lastHiddenColumnDetail = this.spreadsheet.store.get('hiddenColumnsDetail', []);
const isPrevSiblingNodeHidden = lastHiddenColumnDetail.find(({ displaySiblingNode }) => { var _a; return ((_a = displaySiblingNode === null || displaySiblingNode === void 0 ? void 0 : displaySiblingNode.next) === null || _a === void 0 ? void 0 : _a.id) === id; });
const isNextSiblingNodeHidden = lastHiddenColumnDetail.find(({ displaySiblingNode }) => { var _a; return ((_a = displaySiblingNode === null || displaySiblingNode === void 0 ? void 0 : displaySiblingNode.prev) === null || _a === void 0 ? void 0 : _a.id) === id; });
return isNextSiblingNodeHidden && isPrevSiblingNodeHidden;
}
/**
* 以下场景根据当前高度动态计算 maxLines, 保证文本展示合理性
* 1.手动拖拽 2.预设高度
*/
getResizedTextMaxLines() {
var _a, _b, _c, _d, _e;
const { colCell } = this.spreadsheet.options.style;
return ((_d = (_b = (_a = colCell === null || colCell === void 0 ? void 0 : colCell.maxLinesByField) === null || _a === void 0 ? void 0 : _a[this.meta.id]) !== null && _b !== void 0 ? _b : (_c = colCell === null || colCell === void 0 ? void 0 : colCell.maxLinesByField) === null || _c === void 0 ? void 0 : _c[this.meta.field]) !== null && _d !== void 0 ? _d : this.getMaxLinesByCustomHeight({
isCustomHeight: (_e = this.meta.extra) === null || _e === void 0 ? void 0 : _e.isCustomHeight,
}));
}
}
//# sourceMappingURL=col-cell.js.map