@antv/s2
Version:
effective spreadsheet render core lib
470 lines • 21.4 kB
JavaScript
import { __awaiter } from "tslib";
import { Group, Path, } from '@antv/g';
import { clone, isEmpty, throttle } from 'lodash';
import { InterceptType, RESIZE_END_GUIDE_LINE_ID, RESIZE_MASK_ID, RESIZE_MIN_CELL_HEIGHT, RESIZE_MIN_CELL_WIDTH, RESIZE_START_GUIDE_LINE_ID, ResizeAreaEffect, ResizeDirectionType, ResizeType, S2Event, } from '../common/constant';
import { CustomRect } from '../engine';
import { Node } from '../facet/layout/node';
import { round } from '../utils/math';
import { BaseEvent } from './base-interaction';
export class RowColumnResize extends BaseEvent {
constructor() {
super(...arguments);
this.resizeStartPosition = {};
}
bindEvents() {
this.bindMouseDown();
this.bindMouseMove();
this.bindMouseUp();
}
initResizeGroup() {
if (this.resizeReferenceGroup) {
return;
}
this.resizeReferenceGroup =
this.spreadsheet.facet.foregroundGroup.appendChild(new Group());
const { width, height } = this.spreadsheet.options;
const { guideLineColor, guideLineDash, size } = this.getResizeAreaTheme();
const style = {
d: '',
lineDash: guideLineDash,
stroke: guideLineColor,
lineWidth: size,
};
// 起始参考线
this.resizeReferenceGroup.appendChild(new Path({
id: RESIZE_START_GUIDE_LINE_ID,
style,
}));
// 结束参考线
this.resizeReferenceGroup.appendChild(new Path({
id: RESIZE_END_GUIDE_LINE_ID,
style,
}));
// Resize 蒙层
this.resizeReferenceGroup.appendChild(new CustomRect({
id: RESIZE_MASK_ID,
style: {
x: 0,
y: 0,
width: width,
height: height,
fill: 'transparent',
},
}, {
isResizeArea: true,
isResizeMask: true,
}));
}
getResizeAreaTheme() {
return this.spreadsheet.theme.resizeArea;
}
setResizeTarget(target) {
this.resizeTarget = target;
}
getGuideLineWidthAndHeight() {
const { width: canvasWidth, height: canvasHeight } = this.spreadsheet.options;
const { maxY, maxX } = this.spreadsheet.facet.panelBBox;
const width = Math.min(maxX, canvasWidth);
const height = Math.min(maxY, canvasHeight);
return {
width,
height,
};
}
getResizeShapes() {
var _a;
return (((_a = this.resizeReferenceGroup) === null || _a === void 0 ? void 0 : _a.children) || []);
}
setResizeMaskCursor(cursor) {
const [, , resizeMask] = this.getResizeShapes();
resizeMask === null || resizeMask === void 0 ? void 0 : resizeMask.attr('cursor', cursor);
}
updateResizeGuideLinePosition(event, resizeInfo) {
const resizeShapes = this.getResizeShapes();
if (isEmpty(resizeShapes)) {
return;
}
const [startResizeGuideLineShape, endResizeGuideLineShape] = resizeShapes;
const { type, offsetX, offsetY, width, height, size } = resizeInfo;
const { width: guideLineMaxWidth, height: guideLineMaxHeight } = this.getGuideLineWidthAndHeight();
this.cursorType = `${type}-resize`;
this.setResizeMaskCursor(this.cursorType);
/*
* resize guide line 向内收缩 halfSize,保证都绘制在单元格内,防止在开始和末尾的格子中有一半线段被 clip
* 后续计算 resized 尺寸时,需要把收缩的部分加回来
*/
const halfSize = size / 2;
if (type === ResizeDirectionType.Horizontal) {
startResizeGuideLineShape.attr('d', [
['M', offsetX + halfSize, offsetY],
['L', offsetX + halfSize, guideLineMaxHeight],
]);
endResizeGuideLineShape.attr('d', [
['M', offsetX + width - halfSize, offsetY],
['L', offsetX + width - halfSize, guideLineMaxHeight],
]);
this.resizeStartPosition.offsetX = event.offsetX;
this.resizeStartPosition.clientX = event.clientX;
return;
}
startResizeGuideLineShape.attr('d', [
['M', offsetX, offsetY + halfSize],
['L', guideLineMaxWidth, offsetY + halfSize],
]);
endResizeGuideLineShape.attr('d', [
['M', offsetX, offsetY + height - halfSize],
['L', guideLineMaxWidth, offsetY + height - halfSize],
]);
this.resizeStartPosition.offsetY = event.offsetY;
this.resizeStartPosition.clientY = event.clientY;
}
bindMouseDown() {
this.spreadsheet.on(S2Event.LAYOUT_RESIZE_MOUSE_DOWN, (event) => {
var _a;
(_a = event === null || event === void 0 ? void 0 : event.preventDefault) === null || _a === void 0 ? void 0 : _a.call(event);
const shape = event.target;
const resizeInfo = this.getCellAppendInfo(event.target);
this.spreadsheet.store.set('resized', false);
if (!(resizeInfo === null || resizeInfo === void 0 ? void 0 : resizeInfo.isResizeArea)) {
return;
}
// 鼠标在 resize 热区 按下时, 保留交互态, 但是把 tooltip 关闭, 避免造成干扰
this.spreadsheet.hideTooltip();
this.spreadsheet.interaction.addIntercepts([InterceptType.RESIZE]);
this.setResizeTarget(shape);
this.showResizeGroup();
this.updateResizeGuideLinePosition(event, resizeInfo);
});
}
bindMouseMove() {
this.spreadsheet.on(S2Event.LAYOUT_RESIZE_MOUSE_MOVE, throttle(this.resizeMouseMove.bind(this), 33));
}
// 将 SVG 的 path 转成更可读的坐标对象
getResizeGuideLinePosition() {
var _a;
const [startGuideLineShape, endGuideLineShape] = (((_a = this.resizeReferenceGroup) === null || _a === void 0 ? void 0 : _a.children) || []);
const startGuideLinePath = (startGuideLineShape === null || startGuideLineShape === void 0 ? void 0 : startGuideLineShape.attr('d')) || [];
const endGuideLinePath = (endGuideLineShape === null || endGuideLineShape === void 0 ? void 0 : endGuideLineShape.attr('d')) || [];
const [, startX = 0, startY = 0] = startGuideLinePath[0] || [];
const [, endX = 0, endY = 0] = endGuideLinePath[0] || [];
return {
start: {
x: +startX,
y: +startY,
},
end: {
x: +endX,
y: +endY,
},
};
}
getDisAllowResizeInfo() {
var _a;
const resizeInfo = this.getResizeInfo();
const { resize } = this.spreadsheet.options.interaction;
const { width: originalWidth, height: originalHeight, resizedWidth = 0, resizedHeight = 0, } = resizeInfo;
const isDisabled = (_a = resize === null || resize === void 0 ? void 0 : resize.disable) === null || _a === void 0 ? void 0 : _a.call(resize, resizeInfo);
const displayWidth = isDisabled ? originalWidth : resizedWidth;
const displayHeight = isDisabled ? originalHeight : resizedHeight;
return {
displayWidth,
displayHeight,
isDisabled,
};
}
getResizeCellField(resizeInfo) {
var _a, _b, _c, _d, _e;
const isVertical = resizeInfo.type === ResizeDirectionType.Vertical;
const isOnlyEffectPartial = isVertical
? !this.isEffectRowOf(ResizeType.ALL)
: !this.isEffectColOf(ResizeType.ALL);
if (this.spreadsheet.isTableMode()) {
return isVertical
? ((_a = resizeInfo === null || resizeInfo === void 0 ? void 0 : resizeInfo.meta) === null || _a === void 0 ? void 0 : _a.rowId) || String((_b = resizeInfo === null || resizeInfo === void 0 ? void 0 : resizeInfo.meta) === null || _b === void 0 ? void 0 : _b.rowIndex)
: (_c = resizeInfo === null || resizeInfo === void 0 ? void 0 : resizeInfo.meta) === null || _c === void 0 ? void 0 : _c.field;
}
return isOnlyEffectPartial ? (_d = resizeInfo === null || resizeInfo === void 0 ? void 0 : resizeInfo.meta) === null || _d === void 0 ? void 0 : _d.id : (_e = resizeInfo === null || resizeInfo === void 0 ? void 0 : resizeInfo.meta) === null || _e === void 0 ? void 0 : _e.field;
}
isEffectRowOf(resizeType) {
var _a, _b;
return (((_b = (_a = this.spreadsheet.options.interaction) === null || _a === void 0 ? void 0 : _a.resize) === null || _b === void 0 ? void 0 : _b.rowResizeType) === resizeType);
}
isEffectColOf(resizeType) {
var _a, _b;
return (((_b = (_a = this.spreadsheet.options.interaction) === null || _a === void 0 ? void 0 : _a.resize) === null || _b === void 0 ? void 0 : _b.colResizeType) === resizeType);
}
getCellStyleByField(resizeValue) {
const { interaction } = this.spreadsheet;
const resizeInfo = this.getResizeInfo();
const isVertical = resizeInfo.type === ResizeDirectionType.Vertical;
const activeCells = isVertical
? interaction.getActiveRowCells()
: interaction.getActiveColCells();
const isMultiSelected = interaction.isSelectedState() && activeCells.length > 1;
// 非多选: 正常设置即可
if ((!this.isEffectRowOf(ResizeType.SELECTED) &&
!this.isEffectColOf(ResizeType.SELECTED)) ||
!isMultiSelected) {
return {
[this.getResizeCellField(resizeInfo)]: resizeValue,
};
}
// 多选: 将当前选中的行列单元格对应的叶子节点统一设置宽高
return activeCells.reduce((result, cell) => {
// 热区是绘制在叶子节点的, 如果选中的父节点, 那么叶子节点也算是多选, 需要给每一个叶子节点批量设置
Node.getAllLeaveNodes(cell.getMeta()).forEach((node) => {
const newResizeInfo = Object.assign(Object.assign({}, resizeInfo), { meta: node });
result[this.getResizeCellField(newResizeInfo)] = resizeValue;
});
return result;
}, {});
}
getResizeWidthDetail() {
const resizeInfo = this.getResizeInfo();
const { displayWidth } = this.getDisAllowResizeInfo();
switch (resizeInfo.effect) {
case ResizeAreaEffect.Field:
return {
eventType: S2Event.LAYOUT_RESIZE_ROW_WIDTH,
style: {
rowCell: {
widthByField: {
[resizeInfo.meta.field]: displayWidth,
},
},
},
};
case ResizeAreaEffect.Tree:
return {
eventType: S2Event.LAYOUT_RESIZE_TREE_WIDTH,
style: {
rowCell: {
treeWidth: displayWidth,
},
},
};
case ResizeAreaEffect.Cell:
return {
eventType: S2Event.LAYOUT_RESIZE_COL_WIDTH,
style: {
colCell: {
width: !this.isEffectColOf(ResizeType.ALL)
? undefined
: displayWidth,
widthByField: this.getCellStyleByField(displayWidth),
},
},
};
case ResizeAreaEffect.Series:
return {
eventType: S2Event.LAYOUT_RESIZE_SERIES_WIDTH,
seriesNumberWidth: displayWidth,
};
default:
return null;
}
}
getResizeHeightDetail() {
const { style } = this.spreadsheet.options;
const resizeInfo = this.getResizeInfo();
const { displayHeight } = this.getDisAllowResizeInfo();
switch (resizeInfo.effect) {
case ResizeAreaEffect.Field:
return {
eventType: S2Event.LAYOUT_RESIZE_COL_HEIGHT,
style: {
colCell: this.getResizedCellStyleByField(this.getColCellHeightByField(resizeInfo, displayHeight), style === null || style === void 0 ? void 0 : style.colCell, displayHeight),
},
};
case ResizeAreaEffect.Cell:
return {
eventType: S2Event.LAYOUT_RESIZE_ROW_HEIGHT,
style: {
rowCell: Object.assign(Object.assign({}, this.getResizedCellStyleByField(this.getCellStyleByField(displayHeight), style === null || style === void 0 ? void 0 : style.rowCell, displayHeight)), { height: !this.isEffectRowOf(ResizeType.ALL)
? undefined
: displayHeight }),
},
};
default:
return null;
}
}
getResizedCellStyleByField(heightByField, cellStyle, displayHeight) {
const isEnableHeightAdaptive = (cellStyle === null || cellStyle === void 0 ? void 0 : cellStyle.maxLines) > 1 && (cellStyle === null || cellStyle === void 0 ? void 0 : cellStyle.wordWrap);
if (!isEnableHeightAdaptive) {
return {
heightByField,
};
}
// 如果开启了换行, 高度拖拽后动态计算 maxLines 的值, 已保证展示合理性.
const { cell } = this.getResizeInfo();
const maxLines = cell.getMaxLinesByCustomHeight({
targetCell: cell,
displayHeight,
isCustomHeight: true,
});
const maxLinesByField = Object.keys(heightByField || {}).reduce((result, field) => {
result[field] = maxLines;
return result;
}, {});
return {
heightByField,
maxLinesByField,
};
}
getColCellHeightByField(resizeInfo, displayHeight) {
// 1. 自定义列头: 给同一层级且同高度的单元格设置高度. 2. 明细表: 列高一致
if (this.spreadsheet.isCustomColumnFields() ||
this.spreadsheet.isTableMode()) {
return this.spreadsheet.facet
.getColNodes()
.filter((node) => {
var _a, _b;
return node.level === ((_a = resizeInfo.meta) === null || _a === void 0 ? void 0 : _a.level) &&
node.height === ((_b = resizeInfo.meta) === null || _b === void 0 ? void 0 : _b.height);
})
.reduce((result, node) => {
result[node.field] = displayHeight;
return result;
}, {});
}
return {
[resizeInfo.meta.field]: displayHeight,
};
}
getResizeDetail() {
const resizeInfo = this.getResizeInfo();
return resizeInfo.type === ResizeDirectionType.Horizontal
? this.getResizeWidthDetail()
: this.getResizeHeightDetail();
}
showResizeGroup() {
var _a;
this.initResizeGroup();
(_a = this.resizeReferenceGroup) === null || _a === void 0 ? void 0 : _a.setAttribute('visibility', 'visible');
}
hideResizeGroup() {
var _a;
(_a = this.resizeReferenceGroup) === null || _a === void 0 ? void 0 : _a.setAttribute('visibility', 'hidden');
}
bindMouseUp() {
this.spreadsheet.on(S2Event.GLOBAL_MOUSE_UP, () => {
var _a;
this.cursorType = 'default';
this.setResizeMaskCursor(this.cursorType);
if (!this.resizeReferenceGroup ||
isEmpty((_a = this.resizeReferenceGroup) === null || _a === void 0 ? void 0 : _a.children)) {
return;
}
this.hideResizeGroup();
this.renderResizedResult();
});
}
resizeMouseMove(event) {
var _a, _b;
if (((_a = this.resizeReferenceGroup) === null || _a === void 0 ? void 0 : _a.parsedStyle.visibility) !== 'visible') {
return;
}
const resizeInfo = this.getResizeInfo();
const resizeShapes = ((_b = this.resizeReferenceGroup) === null || _b === void 0 ? void 0 : _b.children) || [];
if (isEmpty(resizeShapes)) {
return;
}
const [, endGuideLineShape] = resizeShapes;
const [guideLineStart, guideLineEnd] = clone(endGuideLineShape.attr('d'));
if (resizeInfo.type === ResizeDirectionType.Horizontal) {
this.updateHorizontalResizingEndGuideLinePosition(event.offsetX, resizeInfo, { start: guideLineStart, end: guideLineEnd });
}
else {
this.updateVerticalResizingEndGuideLinePosition(event.offsetY, resizeInfo, { start: guideLineStart, end: guideLineEnd });
}
this.updateResizeGuideLineTheme(endGuideLineShape);
endGuideLineShape.attr('d', [guideLineStart, guideLineEnd]);
}
updateResizeGuideLineTheme(endGuideLineShape) {
const { guideLineColor, guideLineDisableColor } = this.getResizeAreaTheme();
const { isDisabled } = this.getDisAllowResizeInfo();
endGuideLineShape.attr('stroke', isDisabled ? guideLineDisableColor : guideLineColor);
this.setResizeMaskCursor(isDisabled ? 'no-drop' : this.cursorType);
}
updateHorizontalResizingEndGuideLinePosition(offsetX, resizeInfo, guideLine) {
var _a, _b;
const { minCellWidth = RESIZE_MIN_CELL_WIDTH } = ((_a = this.spreadsheet.options.interaction) === null || _a === void 0 ? void 0 : _a.resize) || {};
let nextOffsetX = offsetX - this.resizeStartPosition.offsetX;
if (resizeInfo.width + nextOffsetX < minCellWidth) {
// 禁止拖到最小宽度
nextOffsetX = -(resizeInfo.width - minCellWidth);
}
const resizedOffsetX = resizeInfo.offsetX + resizeInfo.width + nextOffsetX;
const halfSize = resizeInfo.size / 2;
guideLine.start[1] = resizedOffsetX - halfSize;
guideLine.end[1] = resizedOffsetX - halfSize;
(_b = this.resizeTarget) === null || _b === void 0 ? void 0 : _b.attr({
x: resizedOffsetX - resizeInfo.size,
});
}
updateVerticalResizingEndGuideLinePosition(offsetY, resizeInfo, guideLine) {
var _a, _b;
const { minCellHeight = RESIZE_MIN_CELL_HEIGHT } = ((_a = this.spreadsheet.options.interaction) === null || _a === void 0 ? void 0 : _a.resize) || {};
let nextOffsetY = offsetY - this.resizeStartPosition.offsetY;
if (resizeInfo.height + nextOffsetY < minCellHeight) {
nextOffsetY = -(resizeInfo.height - minCellHeight);
}
const resizedOffsetY = resizeInfo.offsetY + resizeInfo.height + nextOffsetY;
const halfSize = resizeInfo.size / 2;
guideLine.start[2] = resizedOffsetY - halfSize;
guideLine.end[2] = resizedOffsetY - halfSize;
(_b = this.resizeTarget) === null || _b === void 0 ? void 0 : _b.attr({
y: resizedOffsetY - resizeInfo.size,
});
}
renderResizedResult() {
return __awaiter(this, void 0, void 0, function* () {
const resizeInfo = this.getResizeInfo();
const { style, seriesNumberWidth, eventType: resizeEventType, } = this.getResizeDetail() || {};
const resizeDetail = {
info: resizeInfo,
style,
};
this.spreadsheet.emit(S2Event.LAYOUT_RESIZE, resizeDetail);
this.spreadsheet.emit(resizeEventType, resizeDetail);
if (style) {
this.spreadsheet.setOptions({ style });
}
if (seriesNumberWidth) {
this.spreadsheet.setTheme({
rowCell: {
seriesNumberWidth,
},
});
}
this.spreadsheet.store.set('resized', true);
yield this.render();
});
}
getResizeInfo() {
const defaultResizeInfo = this.getCellAppendInfo(this.resizeTarget);
const { start, end } = this.getResizeGuideLinePosition();
const resizedWidth = round(end.x -
start.x +
(defaultResizeInfo.type === ResizeDirectionType.Horizontal
? defaultResizeInfo.size
: 0));
const resizedHeight = round(end.y -
start.y +
(defaultResizeInfo.type === ResizeDirectionType.Vertical
? defaultResizeInfo.size
: 0));
return Object.assign(Object.assign({}, defaultResizeInfo), { resizedWidth,
resizedHeight });
}
render() {
return __awaiter(this, void 0, void 0, function* () {
this.resizeStartPosition = {};
this.resizeTarget = null;
this.resizeReferenceGroup = null;
yield this.spreadsheet.render(false);
});
}
}
//# sourceMappingURL=row-column-resize.js.map