@antv/s2
Version:
effective spreadsheet render core lib
523 lines • 25.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TableFacet = void 0;
const tslib_1 = require("tslib");
const g_1 = require("@antv/g");
const lodash_1 = require("lodash");
const cell_1 = require("../cell");
const common_1 = require("../common");
const constant_1 = require("../common/constant");
const debug_1 = require("../common/debug");
const theme_1 = require("../theme");
const utils_1 = require("../utils");
const data_cell_1 = require("../utils/cell/data-cell");
const table_col_cell_1 = require("../utils/cell/table-col-cell");
const facet_1 = require("../utils/facet");
const get_all_child_cells_1 = require("../utils/get-all-child-cells");
const math_1 = require("../utils/math");
const corner_bbox_1 = require("./bbox/corner-bbox");
const frozen_facet_1 = require("./frozen-facet");
const header_1 = require("./header");
const table_col_1 = require("./header/table-col");
const build_header_hierarchy_1 = require("./layout/build-header-hierarchy");
const hierarchy_1 = require("./layout/hierarchy");
const layout_hooks_1 = require("./layout/layout-hooks");
const node_1 = require("./layout/node");
class TableFacet extends frozen_facet_1.FrozenFacet {
constructor(spreadsheet) {
super(spreadsheet);
this.onSortHandler = (sortParams) => tslib_1.__awaiter(this, void 0, void 0, function* () {
const s2 = this.spreadsheet;
let params = sortParams;
// 兼容之前 sortParams 为对象的用法
if (!Array.isArray(sortParams)) {
params = [sortParams];
}
const currentParams = s2.dataCfg.sortParams || [];
params = params.map((item) => {
var _a, _b;
const newItem = Object.assign(Object.assign({}, item), {
// 兼容之前 sortKey 的用法
sortFieldId: (_a = item.sortKey) !== null && _a !== void 0 ? _a : item.sortFieldId });
const oldItem = (_b = currentParams.find((p) => p.sortFieldId === newItem.sortFieldId)) !== null && _b !== void 0 ? _b : {};
return Object.assign(Object.assign({}, oldItem), newItem);
});
const oldConfigs = currentParams.filter((config) => {
const newItem = params.find((p) => p.sortFieldId === config.sortFieldId);
if (newItem) {
return false;
}
return true;
});
(0, lodash_1.set)(s2.dataCfg, 'sortParams', [...oldConfigs, ...params]);
s2.setDataCfg(s2.dataCfg);
yield s2.render(true);
s2.emit(constant_1.S2Event.RANGE_SORTED, s2.dataSet.getDisplayDataSet());
});
this.onFilterHandler = (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
const s2 = this.spreadsheet;
const unFilter = !params.filteredValues || params.filteredValues.length === 0;
const oldConfig = s2.dataCfg.filterParams || [];
// check whether filter condition already exists on column, if so, modify it instead.
const oldIndex = oldConfig.findIndex((item) => item.filterKey === params.filterKey);
if (oldIndex !== -1) {
if (unFilter) {
// remove filter params on current key if passed an empty filterValues field
oldConfig.splice(oldIndex, 1);
}
else {
// if filter with same key already exists, replace it
oldConfig[oldIndex] = params;
}
}
else {
oldConfig.push(params);
}
(0, lodash_1.set)(s2.dataCfg, 'filterParams', oldConfig);
yield s2.render(true);
s2.emit(constant_1.S2Event.RANGE_FILTERED, s2.dataSet.getDisplayDataSet());
});
this.spreadsheet.on(constant_1.S2Event.RANGE_SORT, this.onSortHandler);
this.spreadsheet.on(constant_1.S2Event.RANGE_FILTER, this.onFilterHandler);
}
getCornerCellInstance() {
return null;
}
getRowCellInstance(node) {
const { dataCell } = this.spreadsheet.options;
return ((dataCell === null || dataCell === void 0 ? void 0 : dataCell(node, this.spreadsheet)) ||
new cell_1.TableDataCell(node, this.spreadsheet));
}
getColCellInstance(...args) {
const { colCell } = this.spreadsheet.options;
return (colCell === null || colCell === void 0 ? void 0 : colCell(...args)) || new cell_1.TableColCell(...args);
}
initGroups() {
super.initGroups();
this.initEmptyPlaceholderGroup();
}
shouldRender() {
var _a;
const { fields } = this.spreadsheet.dataSet;
const isOnlyContainedSeriesNumber = (_a = fields === null || fields === void 0 ? void 0 : fields.columns) === null || _a === void 0 ? void 0 : _a.every((field) => field === constant_1.SERIES_NUMBER_FIELD);
return super.shouldRender() && !isOnlyContainedSeriesNumber;
}
render() {
if (!this.shouldRender()) {
return;
}
super.render();
this.renderEmptyPlaceholder();
}
clearAllGroup() {
super.clearAllGroup();
this.emptyPlaceholderGroup.remove();
}
initEmptyPlaceholderGroup() {
this.emptyPlaceholderGroup = this.spreadsheet.container.appendChild(new g_1.Group({
name: constant_1.KEY_GROUP_EMPTY_PLACEHOLDER,
style: { zIndex: constant_1.EMPTY_PLACEHOLDER_GROUP_CONTAINER_Z_INDEX },
}));
}
renderEmptyPlaceholder() {
var _a, _b, _c;
if (!((_a = this.spreadsheet.dataSet) === null || _a === void 0 ? void 0 : _a.isEmpty())) {
return;
}
const { empty } = this.spreadsheet.options.placeholder;
const { background } = this.spreadsheet.theme;
const { icon, description } = this.spreadsheet.theme.empty;
const { horizontalBorderWidth, horizontalBorderColor, horizontalBorderColorOpacity, } = this.spreadsheet.theme.dataCell.cell;
const { maxY, viewportWidth, height } = this.panelBBox;
const iconX = viewportWidth / 2 - icon.width / 2;
const iconY = height / 2 + maxY - icon.height / 2 + icon.margin.top;
const text = (_b = empty === null || empty === void 0 ? void 0 : empty.description) !== null && _b !== void 0 ? _b : (0, common_1.i18n)('暂无数据');
const descWidth = this.measureTextWidth(text, description);
const descX = viewportWidth / 2 - descWidth / 2;
const descY = iconY + icon.height + icon.margin.bottom;
// 边框
const border = new g_1.Rect({
style: {
x: 0,
y: maxY,
width: viewportWidth,
height,
stroke: horizontalBorderColor,
strokeWidth: horizontalBorderWidth,
strokeOpacity: horizontalBorderColorOpacity,
fill: background === null || background === void 0 ? void 0 : background.color,
fillOpacity: (_c = background === null || background === void 0 ? void 0 : background.opacity) !== null && _c !== void 0 ? _c : 1,
},
});
this.emptyPlaceholderGroup.appendChild(border);
// 空状态 Icon
(0, utils_1.renderIcon)(this.emptyPlaceholderGroup, Object.assign(Object.assign({}, icon), { name: empty === null || empty === void 0 ? void 0 : empty.icon, x: iconX, y: iconY, width: icon.width, height: icon.height }));
// 空状态描述文本
(0, utils_1.renderText)({
group: this.emptyPlaceholderGroup,
style: Object.assign(Object.assign({}, description), { text, x: descX, y: descY }),
});
this.emptyPlaceholderGroup.toFront();
}
getDataCellAdaptiveHeight(viewMeta) {
const node = { id: String(viewMeta === null || viewMeta === void 0 ? void 0 : viewMeta.rowIndex) };
const rowHeight = this.getRowCellHeight(node);
if (this.isCustomRowCellHeight(node)) {
// 标记当前行是否为自定义高度
this.customRowHeightStatusMap[viewMeta === null || viewMeta === void 0 ? void 0 : viewMeta.rowIndex] = true;
return rowHeight || 0;
}
const defaultHeight = this.getCellHeightByRowIndex(viewMeta === null || viewMeta === void 0 ? void 0 : viewMeta.rowIndex);
return this.getNodeAdaptiveHeight({
meta: viewMeta,
cell: this.textWrapTempRowCell,
defaultHeight,
});
}
getCellHeightByRowIndex(rowIndex) {
var _a;
if (this.rowOffsets) {
return (_a = this.getRowCellHeight({ id: String(rowIndex) })) !== null && _a !== void 0 ? _a : 0;
}
return this.getDefaultCellHeight();
}
/**
* 开启换行后, 需要自适应调整高度, 明细表通过 rowCell.heightByField 调整, 同时还有一个 rowOffsets 控制行高, 所以要提前设置好, 保证渲染正确.
*/
presetRowCellHeightIfNeeded(rowIndex) {
var _a;
const { style } = this.spreadsheet.options;
const colLeafNodes = this.getColLeafNodes();
// 不超过一行或者用户已经配置过当前行高则无需预设
if (((0, lodash_1.isEmpty)(colLeafNodes) || ((_a = style === null || style === void 0 ? void 0 : style.dataCell) === null || _a === void 0 ? void 0 : _a.maxLines) <= 1) &&
this.spreadsheet.theme.dataCell.text.fontSize <= theme_1.DEFAULT_FONTSIZE &&
this.spreadsheet.theme.dataCell.bolderText.fontSize <= theme_1.DEFAULT_FONTSIZE) {
return;
}
// 当前行高取整行 dataCell 高度最大的值
const maxDataCellHeight = (0, lodash_1.max)(colLeafNodes.map((colNode) => {
const viewMeta = this.getCellMeta(rowIndex, colNode.colIndex);
return this.getDataCellAdaptiveHeight(viewMeta);
}));
// getCellHeightByRowIndex 会优先读取 heightByField, 保持逻辑统一
const height = maxDataCellHeight || this.getDefaultCellHeight();
(0, lodash_1.set)(this.spreadsheet.options, `style.rowCell.heightByField.${rowIndex}`, height);
}
calculateRowOffsets() {
var _a, _b, _c;
const { style } = this.spreadsheet.options;
const heightByField = (_a = style === null || style === void 0 ? void 0 : style.rowCell) === null || _a === void 0 ? void 0 : _a.heightByField;
const isEnableHeightAdaptive = (((_b = style === null || style === void 0 ? void 0 : style.dataCell) === null || _b === void 0 ? void 0 : _b.maxLines) > 1 && ((_c = style === null || style === void 0 ? void 0 : style.dataCell) === null || _c === void 0 ? void 0 : _c.wordWrap)) ||
this.spreadsheet.theme.dataCell.text.fontSize > theme_1.DEFAULT_FONTSIZE ||
this.spreadsheet.theme.dataCell.bolderText.fontSize > theme_1.DEFAULT_FONTSIZE;
if ((0, lodash_1.keys)(heightByField).length || isEnableHeightAdaptive) {
const data = this.spreadsheet.dataSet.getDisplayDataSet();
this.textWrapNodeHeightCache.clear(false);
this.customRowHeightStatusMap = {};
this.rowOffsets = [0];
this.lastRowOffset = 0;
data.forEach((_, rowIndex) => {
this.presetRowCellHeightIfNeeded(rowIndex);
const currentHeight = this.getCellHeightByRowIndex(rowIndex);
const currentOffset = this.lastRowOffset + currentHeight;
this.rowOffsets.push(currentOffset);
this.lastRowOffset = currentOffset;
});
}
}
destroy() {
super.destroy();
this.spreadsheet.off(constant_1.S2Event.RANGE_SORT, this.onSortHandler);
this.spreadsheet.off(constant_1.S2Event.RANGE_FILTER, this.onFilterHandler);
}
calculateCornerBBox() {
const { colsHierarchy } = this.getLayoutResult();
const height = (0, math_1.floor)(colsHierarchy.height);
this.cornerBBox = new corner_bbox_1.CornerBBox(this);
this.cornerBBox.height = height;
this.cornerBBox.maxY = height;
}
doLayout() {
const rowsHierarchy = new hierarchy_1.Hierarchy();
const { colLeafNodes, colsHierarchy } = this.buildColHeaderHierarchy();
this.calculateColNodesCoordinate(colLeafNodes, colsHierarchy);
return {
colNodes: colsHierarchy.getNodes(),
colLeafNodes,
colsHierarchy,
rowNodes: [],
rowsHierarchy,
rowLeafNodes: [],
cornerNodes: [],
};
}
buildColHeaderHierarchy() {
const colHierarchy = (0, build_header_hierarchy_1.buildHeaderHierarchy)({
isRowHeader: false,
spreadsheet: this.spreadsheet,
});
return {
colLeafNodes: colHierarchy.leafNodes,
colsHierarchy: colHierarchy.hierarchy,
};
}
getCellMeta(rowIndex = 0, colIndex = 0) {
var _a, _b, _c;
const { options, dataSet } = this.spreadsheet;
const colLeafNodes = this.getColLeafNodes();
const colNode = colLeafNodes[colIndex];
if (!colNode) {
return null;
}
let data;
const x = colNode.x;
const y = this.viewCellHeights.getCellOffsetY(rowIndex);
const cellHeight = this.getCellHeightByRowIndex(rowIndex);
if (((_a = options.seriesNumber) === null || _a === void 0 ? void 0 : _a.enable) && colNode.field === constant_1.SERIES_NUMBER_FIELD) {
data = rowIndex + 1;
}
else {
data = dataSet.getCellData({
query: {
field: colNode.field,
rowIndex,
},
});
}
const valueField = colNode.field;
const fieldValue = data;
const rowQuery = { rowIndex };
const colQuery = { colIndex };
const cellMeta = {
spreadsheet: this.spreadsheet,
x,
y,
width: colNode.width,
height: cellHeight,
data: {
[colNode.field]: data,
},
rowIndex,
colIndex,
isTotals: false,
colId: colNode.id,
rowId: String(rowIndex),
valueField,
fieldValue,
id: (0, data_cell_1.getDataCellId)(String(rowIndex), colNode.id),
rowQuery,
colQuery,
query: Object.assign(Object.assign({}, rowQuery), colQuery),
};
return (_c = (_b = options.layoutCellMeta) === null || _b === void 0 ? void 0 : _b.call(options, cellMeta)) !== null && _c !== void 0 ? _c : cellMeta;
}
getAdaptiveColWidth(colLeafNodes) {
var _a;
const { dataCell } = this.spreadsheet.options.style;
const { seriesNumber } = this.spreadsheet.options;
if (this.spreadsheet.getLayoutWidthType() !== constant_1.LayoutWidthType.Compact) {
const seriesNumberWidth = this.getSeriesNumberWidth();
const colHeaderColSize = colLeafNodes.length - ((seriesNumber === null || seriesNumber === void 0 ? void 0 : seriesNumber.enable) ? 1 : 0);
const canvasW = this.getCanvasSize().width -
seriesNumberWidth -
header_1.Frame.getVerticalBorderWidth(this.spreadsheet);
return Math.max(dataCell === null || dataCell === void 0 ? void 0 : dataCell.width, (0, math_1.floor)(canvasW / Math.max(1, colHeaderColSize)));
}
return (0, math_1.round)((_a = dataCell === null || dataCell === void 0 ? void 0 : dataCell.width) !== null && _a !== void 0 ? _a : 0);
}
calculateColLeafNodesWidth(colLeafNodes, colsHierarchy) {
let preLeafNode = node_1.Node.blankNode();
let currentCollIndex = 0;
const adaptiveColWidth = this.getAdaptiveColWidth(colLeafNodes);
colsHierarchy.getLeaves().forEach((currentNode) => {
currentNode.colIndex = currentCollIndex;
currentCollIndex += 1;
currentNode.x = preLeafNode.x + preLeafNode.width;
currentNode.width = this.getColLeafNodesWidth(currentNode, adaptiveColWidth);
(0, layout_hooks_1.layoutCoordinate)(this.spreadsheet, null, currentNode);
colsHierarchy.width += currentNode.width;
preLeafNode = currentNode;
});
}
calculateColNodesHeight(colsHierarchy) {
const colNodes = colsHierarchy.getNodes();
colNodes.forEach((colNode) => {
var _a, _b;
if (colNode.level === 0) {
colNode.y = 0;
}
else {
colNode.y = ((_a = colNode === null || colNode === void 0 ? void 0 : colNode.parent) === null || _a === void 0 ? void 0 : _a.y) + ((_b = colNode === null || colNode === void 0 ? void 0 : colNode.parent) === null || _b === void 0 ? void 0 : _b.height) || 0;
}
colNode.height = this.getColNodeHeight({
colNode,
colsHierarchy,
useCache: false,
});
});
}
calculateColNodesCoordinate(colLeafNodes, colsHierarchy) {
// 先计算宽度, 再计算高度, 确保计算多行文本时能获取到正确的最大文本宽度
this.calculateColLeafNodesWidth(colLeafNodes, colsHierarchy);
this.calculateColParentNodeWidthAndX(colLeafNodes);
this.updateColsHierarchySampleMaxHeightNodes(colsHierarchy);
this.calculateColNodesHeight(colsHierarchy);
this.updateCustomFieldsSampleNodes(colsHierarchy);
this.adjustCustomColLeafNodesHeight({
leafNodes: colLeafNodes,
hierarchy: colsHierarchy,
});
}
getCompactColNodeWidth(colNode) {
const { theme, dataSet } = this.spreadsheet;
const { bolderText: colCellTextStyle } = theme.colCell;
const { text: dataCellTextStyle, cell: cellStyle } = theme.dataCell;
const displayData = dataSet.getDisplayDataSet();
const formatter = dataSet.getFieldFormatter(colNode.field);
// 采样前 50,找出表身最长的数据
const maxLabel = (0, lodash_1.maxBy)(displayData === null || displayData === void 0 ? void 0 : displayData.slice(0, common_1.LAYOUT_SAMPLE_COUNT).map((data) => { var _a; return `${(_a = formatter === null || formatter === void 0 ? void 0 : formatter(data[colNode.field])) !== null && _a !== void 0 ? _a : data[colNode.field]}`; }), (label) => this.measureTextWidth(label, dataCellTextStyle));
debug_1.DebuggerUtil.getInstance().logger('Max Label In Col:', colNode.field, maxLabel);
const maxLabelWidth = this.measureTextWidth(maxLabel, dataCellTextStyle) +
cellStyle.padding.left +
cellStyle.padding.right;
// 计算表头 label+icon 占用的空间
const colHeaderNodeWidth = this.measureTextWidth(colNode.value, colCellTextStyle) +
(0, table_col_cell_1.getOccupiedWidthForTableCol)(this.spreadsheet, colNode, theme.colCell);
const { compactExtraWidth = 0, compactMinWidth } = this.spreadsheet.options.style || {};
const calculatedWidth = (0, math_1.round)(Math.max(colHeaderNodeWidth, maxLabelWidth) + compactExtraWidth);
return (0, lodash_1.isNumber)(compactMinWidth)
? Math.max(calculatedWidth, compactMinWidth)
: calculatedWidth;
}
getColLeafNodesWidth(colNode, adaptiveColWidth) {
const { colCell } = this.spreadsheet.options.style;
const layoutWidthType = this.spreadsheet.getLayoutWidthType();
const cellDraggedWidth = this.getColCellDraggedWidth(colNode);
// 1. 拖拽后的宽度优先级最高
if ((0, lodash_1.isNumber)(cellDraggedWidth)) {
return (0, math_1.round)(cellDraggedWidth);
}
// 2. 其次是自定义, 返回 null 则使用默认宽度
const cellCustomWidth = this.getCellCustomSize(colNode, colCell === null || colCell === void 0 ? void 0 : colCell.width);
if ((0, lodash_1.isNumber)(cellCustomWidth)) {
return (0, math_1.round)(cellCustomWidth);
}
// 3. 序号列, 使用配置宽度
if (colNode.field === constant_1.SERIES_NUMBER_FIELD) {
return this.getSeriesNumberWidth();
}
// 4. 紧凑模式
if (layoutWidthType === constant_1.LayoutWidthType.Compact) {
return this.getCompactColNodeWidth(colNode);
}
// 5. 默认自适应列宽
return (0, math_1.round)(adaptiveColWidth);
}
getViewCellHeights() {
const defaultCellHeight = this.getDefaultCellHeight();
return {
getTotalHeight: () => {
if (this.rowOffsets) {
return (0, lodash_1.last)(this.rowOffsets) || 0;
}
return (defaultCellHeight *
this.spreadsheet.dataSet.getDisplayDataSet().length);
},
getCellOffsetY: (offset) => {
if (offset <= 0) {
return 0;
}
if (this.rowOffsets) {
return this.rowOffsets[offset];
}
return offset * defaultCellHeight;
},
getTotalLength: () => this.spreadsheet.dataSet.getDisplayDataSet().length,
getIndexRange: (minHeight, maxHeight) => {
if (this.rowOffsets) {
return (0, facet_1.getIndexRangeWithOffsets)(this.rowOffsets, minHeight, maxHeight);
}
const yMin = (0, math_1.floor)(minHeight / defaultCellHeight, 0);
// 防止数组index溢出导致报错
const yMax = maxHeight % defaultCellHeight === 0
? maxHeight / defaultCellHeight - 1
: (0, math_1.floor)(maxHeight / defaultCellHeight, 0);
return {
start: Math.max(0, yMin),
end: Math.max(0, yMax),
};
},
};
}
renderRowResizeArea() {
const { resize } = this.spreadsheet.options.interaction;
const shouldDrawResize = (0, lodash_1.isBoolean)(resize)
? resize
: resize === null || resize === void 0 ? void 0 : resize.rowCellVertical;
if (!shouldDrawResize) {
return;
}
const rowResizeGroup = this.foregroundGroup.getElementById(constant_1.KEY_GROUP_ROW_RESIZE_AREA);
if (rowResizeGroup) {
rowResizeGroup.removeChildren();
}
const cells = (0, get_all_child_cells_1.getAllChildCells)(this.panelGroup.children, cell_1.TableDataCell);
cells.forEach((cell) => {
cell.drawResizeArea();
});
}
getRowHeader() {
return null;
}
getColHeader() {
if (!this.columnHeader) {
const { x, width, viewportHeight, viewportWidth } = this.panelBBox;
return new table_col_1.TableColHeader({
width,
height: this.cornerBBox.height,
viewportWidth,
viewportHeight,
cornerWidth: this.cornerBBox.width,
position: { x, y: 0 },
nodes: this.getColNodes(),
sortParam: this.spreadsheet.store.get('sortParam'),
spreadsheet: this.spreadsheet,
});
}
return this.columnHeader;
}
getSeriesNumberHeader() {
return null;
}
getScrollbarPosition() {
const { height } = this.getCanvasSize();
const position = super.getScrollbarPosition();
// 滚动条有两种模式, 一种是根据实际内容撑开, 一种是根据 Canvas 高度撑开, 现在有空数据占位符, 对于这种, 滚动条需要撑满
const maxY = this.spreadsheet.dataSet.isEmpty()
? height - this.scrollBarSize
: position.maxY;
return Object.assign(Object.assign({}, position), { maxY });
}
/**
* 获取序号单元格
* @description 明细表序号单元格是基于 DataCell 实现
*/
getSeriesNumberCells() {
return this.getDataCells().filter((cell) => {
return cell.getMeta().valueField === constant_1.SERIES_NUMBER_FIELD;
});
}
getContentWidth() {
const { colsHierarchy } = this.layoutResult;
return (0, math_1.round)(colsHierarchy.width);
}
getContentHeight() {
const { getTotalHeight } = this.getViewCellHeights();
const { colsHierarchy } = this.layoutResult;
return (0, math_1.round)(getTotalHeight() +
colsHierarchy.height +
header_1.Frame.getHorizontalBorderWidth(this.spreadsheet));
}
}
exports.TableFacet = TableFacet;
//# sourceMappingURL=table-facet.js.map