UNPKG

@antv/s2

Version:

effective spreadsheet render core lib

470 lines 21.4 kB
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