@antv/s2
Version:
effective spreadsheet render core lib
738 lines • 37.1 kB
JavaScript
import { filter, forEach, get, isArray, isEmpty, isNumber, keys, last, map, max, maxBy, merge, reduce, size, sumBy, } from 'lodash';
import { ColCell, CornerCell, RowCell, SeriesNumberCell } from '../cell';
import { DEFAULT_ROW_CELL_TREE_WIDTH, LAYOUT_SAMPLE_COUNT, } from '../common';
import { EXTRA_FIELD, LayoutWidthType, VALUE_FIELD } from '../common/constant';
import { CellType } from '../common/constant/interaction';
import { DebuggerUtil } from '../common/debug';
import { getValidFrozenOptionsForPivot, safeJsonParse } from '../utils';
import { getDataCellId } from '../utils/cell/data-cell';
import { getActionIconConfig } from '../utils/cell/header-cell';
import { findFieldCondition } from '../utils/condition/condition';
import { getHeaderTotalStatus } from '../utils/dataset/pivot-data-set';
import { getIndexRangeWithOffsets } from '../utils/facet';
import { getAllChildCells } from '../utils/get-all-child-cells';
import { floor, round } from '../utils/math';
import { getCellWidth } from '../utils/text';
import { FrozenFacet } from './frozen-facet';
import { CornerHeader, Frame } from './header';
import { buildHeaderHierarchy } from './layout/build-header-hierarchy';
import { layoutCoordinate } from './layout/layout-hooks';
import { Node } from './layout/node';
export class PivotFacet extends FrozenFacet {
get rowCellTheme() {
return this.spreadsheet.theme.rowCell.cell;
}
getCornerCellInstance(...args) {
var _a, _b;
return (((_b = (_a = this.spreadsheet.options).cornerCell) === null || _b === void 0 ? void 0 : _b.call(_a, ...args)) || new CornerCell(...args));
}
getRowCellInstance(...args) {
var _a, _b;
return ((_b = (_a = this.spreadsheet.options).rowCell) === null || _b === void 0 ? void 0 : _b.call(_a, ...args)) || new RowCell(...args);
}
getColCellInstance(...args) {
var _a, _b;
return ((_b = (_a = this.spreadsheet.options).colCell) === null || _b === void 0 ? void 0 : _b.call(_a, ...args)) || new ColCell(...args);
}
doLayout() {
const { rowLeafNodes, colLeafNodes, rowsHierarchy, colsHierarchy } = this.buildAllHeaderHierarchy();
this.calculateHeaderNodesCoordinate({
rowLeafNodes,
rowsHierarchy,
colLeafNodes,
colsHierarchy,
});
return {
colNodes: colsHierarchy.getNodes(),
colsHierarchy,
rowNodes: rowsHierarchy.getNodes(),
rowsHierarchy,
rowLeafNodes,
colLeafNodes,
};
}
buildAllHeaderHierarchy() {
const { leafNodes: rowLeafNodes, hierarchy: rowsHierarchy } = buildHeaderHierarchy({
isRowHeader: true,
spreadsheet: this.spreadsheet,
});
const { leafNodes: colLeafNodes, hierarchy: colsHierarchy } = buildHeaderHierarchy({
isRowHeader: false,
spreadsheet: this.spreadsheet,
});
return {
rowLeafNodes,
colLeafNodes,
rowsHierarchy,
colsHierarchy,
};
}
getDataQueryInfo(rowQuery, colQuery) {
var _a, _b, _c, _d;
const { options, dataSet } = this.spreadsheet;
const hideMeasure = (_c = (_b = (_a = options.style) === null || _a === void 0 ? void 0 : _a.colCell) === null || _b === void 0 ? void 0 : _b.hideValue) !== null && _c !== void 0 ? _c : false;
/**
* 如果在非自定义目录情况下hide measure query中是没有度量信息的,所以需要自动补上
* 存在一个场景的冲突,如果是多个度量,定位数据数据是无法知道哪一列代表什么
* 因此默认只会去 第一个度量拼接query
*/
const measureInfo = hideMeasure
? {
[EXTRA_FIELD]: (_d = dataSet.fields.values) === null || _d === void 0 ? void 0 : _d[0],
}
: {};
const dataQuery = merge({}, rowQuery, colQuery, measureInfo);
const valueField = dataQuery[EXTRA_FIELD];
return {
dataQuery,
valueField,
};
}
/**
* 根据行列索引获取单元格元数据
*/
getCellMeta(rowIndex = 0, colIndex = 0) {
var _a, _b;
const { options, dataSet } = this.spreadsheet;
const { rowLeafNodes, colLeafNodes } = this.getLayoutResult();
const row = rowLeafNodes[rowIndex];
const col = colLeafNodes[colIndex];
if (!row || !col) {
return null;
}
const rowQuery = row.query;
const colQuery = col.query;
const isTotals = row.isTotals || row.isTotalMeasure || col.isTotals || col.isTotalMeasure;
const totalStatus = getHeaderTotalStatus(row, col);
const { dataQuery, valueField } = this.getDataQueryInfo(rowQuery, colQuery);
const data = dataSet.getCellData({
query: dataQuery,
rowNode: row,
isTotals,
totalStatus,
});
const fieldValue = get(data, VALUE_FIELD, null);
const cellMeta = {
spreadsheet: this.spreadsheet,
x: col.x,
y: row.y,
width: col.width,
height: row.height,
data,
rowIndex,
colIndex,
isTotals,
valueField,
fieldValue,
rowQuery,
colQuery,
query: Object.assign(Object.assign({}, rowQuery), colQuery),
rowId: row.id,
colId: col.id,
id: getDataCellId(row.id, col.id),
};
return (_b = (_a = options.layoutCellMeta) === null || _a === void 0 ? void 0 : _a.call(options, cellMeta)) !== null && _b !== void 0 ? _b : cellMeta;
}
getPreLevelSampleNode(colNode, colsHierarchy) {
// 之前是采样每一级第一个节点, 现在 sampleNodesForAllLevels 是采样每一级高度最大的节点
// 但是初始化布局时只有第一个节点有值, 所以这里需要适配下
return colsHierarchy
.getNodes(colNode.level - 1)
.find((node) => !node.isTotals);
}
calculateHeaderNodesCoordinate(layoutResult) {
this.calculateRowNodesCoordinate(layoutResult);
this.calculateColNodesCoordinate(layoutResult);
}
calculateColNodesCoordinate(layoutResult) {
const { colLeafNodes, colsHierarchy, rowsHierarchy } = layoutResult;
// 1. 计算叶子节点宽度
this.calculateColLeafNodesWidth(layoutResult);
// 2. 根据叶子节点宽度计算所有父级节点宽度和 x 坐标, 便于计算自动换行后节点的真实高度
this.calculateColParentNodeWidthAndX(colLeafNodes);
// 3. 计算每一层级的采样节点
this.updateColsHierarchySampleMaxHeightNodes(colsHierarchy, rowsHierarchy);
// 4. 计算所有节点的高度
this.calculateColNodesHeight(colsHierarchy);
// 5. 如果存在自定义多级列头, 还需要更新某一层级的采样
this.updateCustomFieldsSampleNodes(colsHierarchy);
// 6. 补齐自定义列头节点缺失的高度
this.adjustCustomColLeafNodesHeight({
leafNodes: colLeafNodes,
hierarchy: colsHierarchy,
});
// 7. 更新汇总节点坐标
this.adjustColTotalNodesCoordinate(colsHierarchy);
}
calculateRowOffsets() { }
adjustColTotalNodesCoordinate(colsHierarchy) {
var _a;
if (!isEmpty((_a = this.spreadsheet.options.totals) === null || _a === void 0 ? void 0 : _a.col)) {
this.adjustTotalNodesCoordinate({
hierarchy: colsHierarchy,
isRowHeader: false,
isSubTotal: true,
});
this.adjustTotalNodesCoordinate({
hierarchy: colsHierarchy,
isRowHeader: false,
isSubTotal: false,
});
}
}
calculateColLeafNodesWidth(layoutResult) {
const { rowLeafNodes, colLeafNodes, rowsHierarchy, colsHierarchy } = layoutResult;
let preLeafNode = Node.blankNode();
let currentColIndex = 0;
colsHierarchy.getLeaves().forEach((currentNode) => {
currentNode.colIndex = currentColIndex;
currentColIndex++;
currentNode.x = preLeafNode.x + preLeafNode.width;
currentNode.width = this.getColLeafNodesWidth(currentNode, colLeafNodes, rowLeafNodes, rowsHierarchy.width);
colsHierarchy.width += currentNode.width;
preLeafNode = currentNode;
});
}
calculateColNodesHeight(colsHierarchy) {
const colNodes = colsHierarchy.getNodes();
colNodes.forEach((currentNode) => {
if (currentNode.level === 0) {
currentNode.y = 0;
}
else {
const preLevelSample = this.getPreLevelSampleNode(currentNode, colsHierarchy);
currentNode.y = (preLevelSample === null || preLevelSample === void 0 ? void 0 : preLevelSample.y) + (preLevelSample === null || preLevelSample === void 0 ? void 0 : preLevelSample.height) || 0;
}
// 数值置于行头时, 列头的总计即叶子节点, 此时应该用列高: https://github.com/antvis/S2/issues/1715
const colNodeHeight = this.getColNodeHeight({
colNode: currentNode,
colsHierarchy,
useCache: false,
});
currentNode.height =
currentNode.isGrandTotals &&
!currentNode.isTotalMeasure &&
currentNode.isLeaf
? colsHierarchy.height
: colNodeHeight;
layoutCoordinate(this.spreadsheet, null, currentNode);
});
}
// please read README-adjustTotalNodesCoordinate.md to understand this function
getMultipleMap(hierarchy, isRowHeader, isSubTotal) {
const { maxLevel } = hierarchy;
const dataSet = this.spreadsheet.dataSet;
const { totals } = this.spreadsheet.options;
const moreThanOneValue = dataSet.moreThanOneValue();
const { rows, columns } = dataSet.fields;
const fields = isRowHeader ? rows : columns;
const totalConfig = isRowHeader ? totals.row : totals.col;
const defaultDimensionGroup = isSubTotal
? (totalConfig === null || totalConfig === void 0 ? void 0 : totalConfig.subTotalsGroupDimensions) || []
: (totalConfig === null || totalConfig === void 0 ? void 0 : totalConfig.grandTotalsGroupDimensions) || [];
const dimensionGroup = !dataSet.isEmpty() ? defaultDimensionGroup : [];
const multipleMap = Array.from({ length: maxLevel + 1 }, () => 1);
for (let level = maxLevel; level > 0; level--) {
const currentField = fields[level];
// 若不符合【分组维度包含此维度】或者【指标维度下多指标维度】,此表头单元格为空,将宽高合并到上级单元格
const existValueField = currentField === EXTRA_FIELD && moreThanOneValue;
if (!(dimensionGroup.includes(currentField) || existValueField)) {
multipleMap[level - 1] += multipleMap[level];
multipleMap[level] = 0;
}
}
return multipleMap;
}
// please read README-adjustTotalNodesCoordinate.md to understand this function
adjustTotalNodesCoordinate(params) {
const { hierarchy, isRowHeader, isSubTotal } = params;
const multipleMap = this.getMultipleMap(hierarchy, isRowHeader, isSubTotal);
const totalNodes = filter(hierarchy.getNodes(), (node) => isSubTotal ? node.isSubTotals : node.isGrandTotals);
const key = isRowHeader ? 'width' : 'height';
forEach(totalNodes, (node) => {
var _a;
let multiple = multipleMap[node.level];
// 小计根节点若为 0,则改为最近上级倍数 - level 差
if (!multiple && isSubTotal) {
let lowerLevelIndex = 1;
while (multiple < 1) {
multiple =
multipleMap[node.level - lowerLevelIndex] - lowerLevelIndex;
lowerLevelIndex++;
}
}
let res = 0;
for (let i = 0; i < multiple; i++) {
res += get((_a = hierarchy.sampleNodesForAllLevels) === null || _a === void 0 ? void 0 : _a.find((sampleNode) => sampleNode.level === node.level + i), [key], 0);
}
node[key] = res;
});
}
getColLeafNodesWidth(colNode, colLeafNodes, rowLeafNodes, rowHeaderWidth) {
const { colCell } = this.spreadsheet.options.style;
const cellDraggedWidth = this.getColCellDraggedWidth(colNode);
// 1. 拖拽后的宽度优先级最高
if (isNumber(cellDraggedWidth)) {
return round(cellDraggedWidth);
}
// 2. 其次是自定义, 返回 null 则使用默认宽度
const cellCustomWidth = this.getCellCustomSize(colNode, colCell === null || colCell === void 0 ? void 0 : colCell.width);
if (isNumber(cellCustomWidth)) {
return round(cellCustomWidth);
}
// 3. 紧凑布局
if (this.spreadsheet.getLayoutWidthType() === LayoutWidthType.Compact) {
return this.getCompactGridColNodeWidth(colNode, rowLeafNodes);
}
/**
* 4. 自适应 adaptive
* 4.1 树状自定义
*/
if (this.spreadsheet.isHierarchyTreeType()) {
return this.getAdaptTreeColWidth(colNode, colLeafNodes, rowLeafNodes);
}
// 4.2 网格自定义
return this.getAdaptGridColWidth(colLeafNodes, colNode, rowHeaderWidth);
}
getRowNodeHeight(rowNode) {
if (!rowNode) {
return 0;
}
const { rowCell: rowCellStyle } = this.spreadsheet.options.style;
const defaultHeight = this.getRowCellHeight(rowNode);
// 文本超过 1 行时再自适应单元格高度, 不然会频繁触发 GC, 导致性能降低: https://github.com/antvis/S2/issues/2693
const isEnableHeightAdaptive = (rowCellStyle === null || rowCellStyle === void 0 ? void 0 : rowCellStyle.maxLines) > 1 && (rowCellStyle === null || rowCellStyle === void 0 ? void 0 : rowCellStyle.wordWrap);
if (this.isCustomRowCellHeight(rowNode) || !isEnableHeightAdaptive) {
rowNode.extra.isCustomHeight = true;
return defaultHeight || 0;
}
return this.getNodeAdaptiveHeight({
meta: rowNode,
cell: this.textWrapTempRowCell,
defaultHeight,
});
}
/**
* 获得图标区域预估宽度
* 不考虑用户自定义的 displayCondition 条件
* @param iconStyle 图标样式
* @returns 宽度
*/
getExpectedCellIconWidth(cellType, useDefaultIcon, iconStyle) {
var _a, _b;
// count icons
let iconCount = 0;
if (useDefaultIcon) {
iconCount = 1;
}
else {
const actionIcons = map(this.spreadsheet.options.headerActionIcons, (iconCfg) => {
return Object.assign(Object.assign({}, iconCfg), {
// ignore condition func when layout calc
displayCondition: () => true });
});
const customIcons = getActionIconConfig(actionIcons, null, cellType);
iconCount = (customIcons === null || customIcons === void 0 ? void 0 : customIcons.icons.length) || 0;
}
// calc width
return iconCount
? iconCount * (iconStyle.size + ((_a = iconStyle.margin) === null || _a === void 0 ? void 0 : _a.left)) +
((_b = iconStyle.margin) === null || _b === void 0 ? void 0 : _b.right)
: 0;
}
calculateRowNodesAllLevelSampleNodes(layoutResult) {
const { rowsHierarchy, colLeafNodes } = layoutResult;
const isTree = this.spreadsheet.isHierarchyTreeType();
const sampleNodeByLevel = rowsHierarchy.sampleNodesForAllLevels || [];
if (isTree) {
rowsHierarchy.width = this.getTreeRowHeaderWidth();
}
else {
sampleNodeByLevel.forEach((levelSample) => {
var _a;
levelSample.width = this.getGridRowNodesWidth(levelSample, colLeafNodes);
rowsHierarchy.width += levelSample.width;
const preLevelSample = (_a = sampleNodeByLevel[levelSample.level - 1]) !== null && _a !== void 0 ? _a : {
x: 0,
width: 0,
};
levelSample.x = (preLevelSample === null || preLevelSample === void 0 ? void 0 : preLevelSample.x) + (preLevelSample === null || preLevelSample === void 0 ? void 0 : preLevelSample.width);
});
}
rowsHierarchy.rootNode.width = rowsHierarchy.width;
}
getRowLeafNodeHeight(rowLeafNode) {
var _a;
const { rowCell: rowCellStyle } = this.spreadsheet.options.style;
const isEnableHeightAdaptive = (rowCellStyle === null || rowCellStyle === void 0 ? void 0 : rowCellStyle.maxLines) > 1 && (rowCellStyle === null || rowCellStyle === void 0 ? void 0 : rowCellStyle.wordWrap);
const currentBranchNodeHeights = isEnableHeightAdaptive
? Node.getBranchNodes(rowLeafNode).map((rowNode) => this.getRowNodeHeight(rowNode))
: [];
const defaultHeight = this.getRowNodeHeight(rowLeafNode);
// 父节点的高度是叶子节点的高度之和, 由于存在多行文本, 叶子节点的高度以当前路径下节点高度最大的为准: https://github.com/antvis/S2/issues/2678
// 自定义高度除外: https://github.com/antvis/S2/issues/2594
const nodeHeight = this.isCustomRowCellHeight(rowLeafNode)
? defaultHeight
: (_a = max(currentBranchNodeHeights)) !== null && _a !== void 0 ? _a : defaultHeight;
return round(nodeHeight);
}
calculateRowNodesBBox(rowsHierarchy) {
const isTree = this.spreadsheet.isHierarchyTreeType();
const sampleNodeByLevel = rowsHierarchy.sampleNodesForAllLevels || [];
let preLeafNode = Node.blankNode();
const rowNodes = rowsHierarchy.getNodes();
rowNodes.forEach((currentNode) => {
var _a, _b;
// 树状模式都按叶子处理节点
const isLeaf = isTree || (!isTree && currentNode.isLeaf);
// 1. 获取宽度, 便于多行文本计算高度
if (isTree) {
currentNode.width = this.getTreeRowHeaderWidth();
}
else {
const levelSampleNode = sampleNodeByLevel[currentNode.level];
currentNode.width = levelSampleNode === null || levelSampleNode === void 0 ? void 0 : levelSampleNode.width;
}
// 2. 计算叶子节点的高度和坐标
if (isLeaf) {
// 2.1. 普通树状结构, 叶子节点各占一行, 2.2. 自定义树状结构 (平铺模式)
const rowIndex = ((_a = preLeafNode === null || preLeafNode === void 0 ? void 0 : preLeafNode.rowIndex) !== null && _a !== void 0 ? _a : -1) + 1;
// 文本超过 1 行时再自适应单元格高度, 不然会频繁触发 GC, 导致性能降低: https://github.com/antvis/S2/issues/2693
(_b = currentNode.rowIndex) !== null && _b !== void 0 ? _b : (currentNode.rowIndex = rowIndex);
currentNode.y = preLeafNode.y + preLeafNode.height;
currentNode.height = this.getRowLeafNodeHeight(currentNode);
preLeafNode = currentNode;
// mark row hierarchy's height
rowsHierarchy.height += currentNode.height;
}
if (isTree || currentNode.level === 0) {
currentNode.x = 0;
}
else {
const preLevelSample = sampleNodeByLevel[currentNode.level - 1];
currentNode.x = (preLevelSample === null || preLevelSample === void 0 ? void 0 : preLevelSample.x) + (preLevelSample === null || preLevelSample === void 0 ? void 0 : preLevelSample.width);
}
layoutCoordinate(this.spreadsheet, currentNode, null);
});
}
calculateRowNodesCoordinate(layoutResult) {
const { rowsHierarchy, rowLeafNodes } = layoutResult;
const isTree = this.spreadsheet.isHierarchyTreeType();
// 1、计算每一层级的采样节点
this.calculateRowNodesAllLevelSampleNodes(layoutResult);
// 2、计算节点的高度和 y (叶子节点),x坐标和宽度 (所有节点)
this.calculateRowNodesBBox(rowsHierarchy);
if (!isTree) {
// 3. 补齐自定义行头节点缺失的宽度;
this.adjustCustomRowLeafNodesWidth({
leafNodes: rowLeafNodes,
hierarchy: rowsHierarchy,
});
// 4.根据叶子节点高度计算所有父级节点高度和 Y 坐标, 便于计算自动换行后节点的真实高度
this.calculateRowNodeHeightAndY(rowLeafNodes);
// 5. 更新汇总节点坐标
this.adjustRowTotalNodesCoordinate(rowsHierarchy);
}
}
adjustRowTotalNodesCoordinate(rowsHierarchy) {
var _a;
if (!isEmpty((_a = this.spreadsheet.options.totals) === null || _a === void 0 ? void 0 : _a.row)) {
this.adjustTotalNodesCoordinate({
hierarchy: rowsHierarchy,
isRowHeader: true,
isSubTotal: false,
});
this.adjustTotalNodesCoordinate({
hierarchy: rowsHierarchy,
isRowHeader: true,
isSubTotal: true,
});
}
}
/**
* @description Auto calculate row no-leaf node's height and y coordinate
* @param rowLeafNodes
*/
calculateRowNodeHeightAndY(rowLeafNodes) {
// 3、in grid type, all no-leaf node's height, y are auto calculated
let prevRowParent = null;
let i = 0;
const leafNodes = rowLeafNodes.slice(0);
while (i < leafNodes.length) {
const node = leafNodes[i++];
const parent = node === null || node === void 0 ? void 0 : node.parent;
if (prevRowParent !== parent && parent) {
leafNodes.push(parent);
// 父节点 y 坐标 = 第一个未隐藏的子节点的 y 坐标
parent.y = parent.children[0].y;
// 父节点高度 = 所有子节点高度之和
parent.height = sumBy(parent.children, 'height');
prevRowParent = parent;
}
}
}
/**
* 计算 grid 模式下 node 宽度
*/
getGridRowNodesWidth(node, colLeafNodes) {
const { rowCell } = this.spreadsheet.options.style;
const cellDraggedWidth = this.getRowCellDraggedWidth(node);
if (isNumber(cellDraggedWidth)) {
return round(cellDraggedWidth);
}
const cellCustomWidth = this.getCellCustomSize(node, rowCell === null || rowCell === void 0 ? void 0 : rowCell.width);
if (isNumber(cellCustomWidth)) {
return round(cellCustomWidth);
}
if (this.spreadsheet.getLayoutWidthType() !== LayoutWidthType.Adaptive) {
// compact or colAdaptive
return this.getCompactGridRowNodeWidth(node);
}
// adaptive
return this.getAdaptGridColWidth(colLeafNodes);
}
/**
* 计算树状模式等宽条件下的列宽
*/
getAdaptTreeColWidth(col, colLeafNodes, rowLeafNodes) {
// tree row width = [config width, canvas / 2]
const canvasW = this.getCanvasSize().width;
const availableWidth = canvasW -
this.getSeriesNumberWidth() -
Frame.getVerticalBorderWidth(this.spreadsheet);
const rowHeaderWidth = Math.min(availableWidth / 2, this.getTreeRowHeaderWidth());
// calculate col width
const colSize = Math.max(1, colLeafNodes.length);
const { dataCell } = this.spreadsheet.options.style;
return round(Math.max(getCellWidth(dataCell, this.getColLabelLength(col, rowLeafNodes)), floor((availableWidth - rowHeaderWidth) / colSize)));
}
getColLabelLength(col, rowLeafNodes) {
// 如果 label 字段形如 "["xx","xxx"]",直接获取其长度
const labels = safeJsonParse(col === null || col === void 0 ? void 0 : col.value);
if (isArray(labels)) {
return labels.length;
}
// 采样前 50,根据指标个数获取单元格列宽
let maxLength = 1;
for (let index = 0; index < LAYOUT_SAMPLE_COUNT; index++) {
const rowNode = rowLeafNodes[index];
if (!rowNode) {
// 抽样个数大于叶子节点个数
return maxLength;
}
const cellData = this.spreadsheet.dataSet.getCellData({
query: Object.assign(Object.assign({}, col.query), rowNode.query),
rowNode,
isTotals: col.isTotals ||
col.isTotalMeasure ||
rowNode.isTotals ||
rowNode.isTotalMeasure,
});
const cellDataKeys = keys(cellData);
for (let j = 0; j < cellDataKeys.length; j++) {
const dataValue = get(cellData, cellDataKeys[j]);
const valueSize = size(get(dataValue === null || dataValue === void 0 ? void 0 : dataValue.values, '0'));
if (valueSize > maxLength) {
// greater length
maxLength = valueSize;
}
}
}
return maxLength;
}
/**
* 计算平铺模式等宽条件下的列宽
*/
getAdaptGridColWidth(colLeafNodes,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
colNode, rowHeaderWidth) {
const { rows = [] } = this.spreadsheet.dataSet.fields;
const { dataCell } = this.spreadsheet.options.style;
const rowHeaderColSize = rows.length;
const colHeaderColSize = colLeafNodes.length;
const { width } = this.getCanvasSize();
const availableWidth = width -
this.getSeriesNumberWidth() -
Frame.getVerticalBorderWidth(this.spreadsheet);
const colSize = Math.max(1, rowHeaderColSize + colHeaderColSize);
if (!rowHeaderWidth) {
return round(Math.max(getCellWidth(dataCell), floor(availableWidth / colSize)));
}
return round(Math.max(getCellWidth(dataCell), floor((availableWidth - rowHeaderWidth) / colHeaderColSize)));
}
/**
* 计算树状结构行头宽度
*/
getTreeRowHeaderWidth() {
var _a, _b, _c, _d;
const { rowCell } = this.spreadsheet.options.style;
// 1. 用户拖拽或手动指定的行头宽度优先级最高
if (isNumber(rowCell === null || rowCell === void 0 ? void 0 : rowCell.treeWidth)) {
return round(rowCell.treeWidth);
}
const customRowCellWidth = this.getCellCustomSize(null, rowCell === null || rowCell === void 0 ? void 0 : rowCell.width);
if (isNumber(customRowCellWidth)) {
return round(customRowCellWidth);
}
// 2. 然后是计算 (+ icon province/city/level)
const treeHeaderLabel = CornerHeader.getTreeCornerText(this.spreadsheet);
const { bolderText: cornerCellTextStyle, icon: cornerIconStyle } = this.spreadsheet.theme.cornerCell;
/**
* 初始化角头时,保证其在树形模式下不换行, 给与两个icon的宽度空余(tree icon 和 action icon),减少复杂的 action icon 判断
* 额外增加 1,当内容和容器宽度恰好相等时会出现换行
*/
const maxLabelWidth = this.measureTextWidth(treeHeaderLabel, cornerCellTextStyle) +
cornerIconStyle.size * 2 +
((_a = cornerIconStyle.margin) === null || _a === void 0 ? void 0 : _a.left) +
((_b = cornerIconStyle.margin) === null || _b === void 0 ? void 0 : _b.right) +
((_c = this.rowCellTheme.padding) === null || _c === void 0 ? void 0 : _c.left) +
((_d = this.rowCellTheme.padding) === null || _d === void 0 ? void 0 : _d.right);
const width = Math.max(customRowCellWidth !== null && customRowCellWidth !== void 0 ? customRowCellWidth : DEFAULT_ROW_CELL_TREE_WIDTH, maxLabelWidth);
return Number.isNaN(width) ? DEFAULT_ROW_CELL_TREE_WIDTH : round(width);
}
/**
* 计算 compact 模式下 node 宽度
*
* | fieldName |
* _______________
* | label - icon | <- node
* | label - icon |
* | label - icon |
*/
getCompactGridRowNodeWidth(node) {
var _a;
const { bolderText: rowTextStyle, icon: rowIconStyle, cell: rowCellStyle, } = this.spreadsheet.theme.rowCell;
const { bolderText: cornerTextStyle, icon: cornerIconStyle, cell: cornerCellStyle, } = this.spreadsheet.theme.cornerCell;
const { field, isLeaf } = node;
// calc rowNode width
const rowIconWidth = this.getExpectedCellIconWidth(CellType.ROW_CELL, !this.spreadsheet.isValueInCols() &&
isLeaf &&
this.spreadsheet.options.showDefaultHeaderActionIcon, rowIconStyle);
const allLabels = (_a = this.spreadsheet.dataSet
.getDimensionValues(field)) === null || _a === void 0 ? void 0 : _a.slice(0, LAYOUT_SAMPLE_COUNT).map((dimValue) => {
var _a, _b;
return (_b = (_a = this.spreadsheet.dataSet.getFieldFormatter(field)) === null || _a === void 0 ? void 0 : _a(dimValue)) !== null && _b !== void 0 ? _b : dimValue;
});
const maxLabel = maxBy(allLabels, (label) => `${label}`.length);
const rowNodeWidth = this.measureTextWidth(maxLabel, rowTextStyle) +
rowIconWidth +
rowCellStyle.padding.left +
rowCellStyle.padding.right +
rowCellStyle.verticalBorderWidth;
// calc corner fieldNameNodeWidth
const fieldName = this.spreadsheet.dataSet.getFieldName(field);
const cornerIconWidth = this.getExpectedCellIconWidth(CellType.CORNER_CELL, false, cornerIconStyle);
const fieldNameNodeWidth = this.measureTextWidth(fieldName, cornerTextStyle) +
cornerIconWidth +
cornerCellStyle.padding.left +
cornerCellStyle.padding.right;
DebuggerUtil.getInstance().logger('Max Label In Row:', field, rowNodeWidth > fieldNameNodeWidth ? maxLabel : fieldName);
return round(Math.max(rowNodeWidth, fieldNameNodeWidth));
}
getCompactGridColNodeWidth(colNode, rowLeafNodes) {
var _a, _b, _c, _d, _e, _f;
const { bolderText: colCellTextStyle, cell: colCellStyle, icon: colIconStyle, } = this.spreadsheet.theme.colCell;
const { text: dataCellTextStyle, icon: dataCellIconStyle } = this.spreadsheet.theme.dataCell;
// leaf node width
const cellFormatter = this.spreadsheet.dataSet.getFieldFormatter(colNode.field);
const leafNodeLabel = (_a = cellFormatter === null || cellFormatter === void 0 ? void 0 : cellFormatter(colNode.value)) !== null && _a !== void 0 ? _a : colNode.value;
const colIconWidth = this.getExpectedCellIconWidth(CellType.COL_CELL, this.spreadsheet.isValueInCols() &&
this.spreadsheet.options.showDefaultHeaderActionIcon, colIconStyle);
const leafNodeWidth = this.measureTextWidth(leafNodeLabel, colCellTextStyle) + colIconWidth;
// 采样 50 个 label,逐个计算找出最长的 label
let maxDataLabel = '';
let maxDataLabelWidth = 0;
let iconWidthOfMaxDataLabel = 0;
for (let index = 0; index < LAYOUT_SAMPLE_COUNT; index++) {
const rowNode = rowLeafNodes[index];
if (rowNode) {
const cellData = this.spreadsheet.dataSet.getCellData({
query: Object.assign(Object.assign({}, colNode.query), rowNode.query),
rowNode,
isTotals: colNode.isTotals ||
colNode.isTotalMeasure ||
rowNode.isTotals ||
rowNode.isTotalMeasure,
});
if (cellData) {
// 总小计格子不一定有数据
const valueData = cellData === null || cellData === void 0 ? void 0 : cellData[VALUE_FIELD];
const formattedValue = (_c = (_b = this.spreadsheet.dataSet.getFieldFormatter(cellData[EXTRA_FIELD])) === null || _b === void 0 ? void 0 : _b(valueData)) !== null && _c !== void 0 ? _c : valueData;
const cellLabel = formattedValue;
// 考虑字段标记 icon 的宽度: https://github.com/antvis/S2/pull/2673
const { valueField } = this.getDataQueryInfo(rowNode.query, colNode.query);
const hasIcon = findFieldCondition((_d = this.spreadsheet.options.conditions) === null || _d === void 0 ? void 0 : _d.icon, valueField);
const dataCellIconWidth = hasIcon
? (dataCellIconStyle === null || dataCellIconStyle === void 0 ? void 0 : dataCellIconStyle.size) +
((_e = dataCellIconStyle === null || dataCellIconStyle === void 0 ? void 0 : dataCellIconStyle.margin) === null || _e === void 0 ? void 0 : _e.left) +
((_f = dataCellIconStyle === null || dataCellIconStyle === void 0 ? void 0 : dataCellIconStyle.margin) === null || _f === void 0 ? void 0 : _f.right)
: 0;
const cellLabelWidth = this.measureTextWidth(cellLabel, dataCellTextStyle) +
dataCellIconWidth;
if (cellLabelWidth > maxDataLabelWidth) {
maxDataLabel = cellLabel;
maxDataLabelWidth = cellLabelWidth;
iconWidthOfMaxDataLabel = dataCellIconWidth;
}
}
}
}
const isLeafNodeWidthLonger = leafNodeWidth > maxDataLabelWidth;
const maxLabel = isLeafNodeWidthLonger ? leafNodeLabel : maxDataLabel;
const appendedWidth = isLeafNodeWidthLonger
? colIconWidth
: iconWidthOfMaxDataLabel;
DebuggerUtil.getInstance().logger('Max Label In Col:', colNode.field, maxLabel, maxDataLabelWidth, iconWidthOfMaxDataLabel);
// 取列头/数值字体最大的文本宽度: https://github.com/antvis/S2/issues/2385
const maxTextWidth = this.measureTextWidth(maxLabel, Object.assign(Object.assign({}, colCellTextStyle), { fontSize: Math.max(dataCellTextStyle.fontSize, colCellTextStyle.fontSize) }));
return round(maxTextWidth +
colCellStyle.padding.left +
colCellStyle.padding.right +
colCellStyle.verticalBorderWidth * 2 +
appendedWidth);
}
getViewCellHeights() {
const rowLeafNodes = this.getRowLeafNodes();
const heights = reduce(rowLeafNodes, (result, node) => {
const currentNodeHeight = last(result) || 0;
result.push(currentNodeHeight + node.height);
return result;
}, [0]);
return {
getTotalHeight: () => last(heights) || 0,
getCellOffsetY: (index) => heights[index] || 0,
// 多了一个数据 [0]
getTotalLength: () => heights.length - 1,
getIndexRange: (minHeight, maxHeight) => getIndexRangeWithOffsets(heights, minHeight, maxHeight),
};
}
/**
* 获取序号单元格
* @description 对于透视表, 序号属于 RowCell
*/
getSeriesNumberCells() {
var _a;
const headerChildren = (((_a = this.getSeriesNumberHeader()) === null || _a === void 0 ? void 0 : _a.children) ||
[]);
return getAllChildCells(headerChildren, SeriesNumberCell);
}
getFrozenOptions() {
if (!this.validFrozenOptions) {
this.validFrozenOptions = getValidFrozenOptionsForPivot(super.getFrozenOptions(), this.spreadsheet.options);
}
return this.validFrozenOptions;
}
getContentWidth() {
const { rowsHierarchy, colsHierarchy } = this.layoutResult;
return round(rowsHierarchy.width +
colsHierarchy.width +
Frame.getVerticalBorderWidth(this.spreadsheet));
}
getContentHeight() {
const { rowsHierarchy, colsHierarchy } = this.layoutResult;
return round(rowsHierarchy.height +
colsHierarchy.height +
Frame.getHorizontalBorderWidth(this.spreadsheet));
}
}
//# sourceMappingURL=pivot-facet.js.map