UNPKG

@antv/s2

Version:

effective spreadsheet render core lib

382 lines 17 kB
import { find, first, get, isEmpty, isEqual, isObject, merge } from 'lodash'; import { BaseCell } from '../cell/base-cell'; import { DEFAULT_STYLE } from '../common'; import { EMPTY_PLACEHOLDER } from '../common/constant/basic'; import { CellType, InteractionStateName, SHAPE_STYLE_MAP, } from '../common/constant/interaction'; import { CellBorderPosition, CellClipBox } from '../common/interface'; import { CellData } from '../data-set/cell-data'; import { getHorizontalTextIconPosition, getVerticalIconPosition, getVerticalTextPosition, } from '../utils/cell/cell'; import { includeCell, shouldUpdateBySelectedCellsHighlight, updateBySelectedCellsHighlight, } from '../utils/cell/data-cell'; import { groupIconsByPosition } from '../utils/cell/header-cell'; import { findFieldCondition, getIconPosition, } from '../utils/condition/condition'; import { drawInterval } from '../utils/g-mini-charts'; import { updateShapeAttr } from '../utils/g-renders'; /** * DataCell for panelGroup area * ---------------------------- * | | | * |interval text| icon | * | | | * ---------------------------- * There are four conditions({@see BaseCell.conditions}) to determine how to render * 1、background color * 2、icon align in right with size {@link ICON_SIZE} * 3、left rect area is interval(in left) and text(in right) */ export class DataCell extends BaseCell { get cellType() { return CellType.DATA_CELL; } isShallowRender() { return super.isShallowRender() || this.meta.shallowRender || false; } isMultiData() { const fieldValue = this.getFieldValue(); return isObject(fieldValue); } getBorderPositions() { return [CellBorderPosition.BOTTOM, CellBorderPosition.RIGHT]; } getValueRange() { return this.spreadsheet.dataSet.getValueRangeByField(this.meta.valueField); } handleByStateName(cells, stateName) { if (includeCell(cells, this)) { this.updateByState(stateName); } } handleSearchResult(cells) { if (!includeCell(cells, this)) { return; } const targetCell = find(cells, (cell) => cell === null || cell === void 0 ? void 0 : cell['isTarget']); if (targetCell.id === this.getMeta().id) { this.updateByState(InteractionStateName.HIGHLIGHT); } else { this.updateByState(InteractionStateName.SEARCH_RESULT); } } handleSelect(cells) { var _a, _b; const currentCellType = (_a = cells === null || cells === void 0 ? void 0 : cells[0]) === null || _a === void 0 ? void 0 : _a.type; switch (currentCellType) { // 列多选 case CellType.COL_CELL: this.changeRowColSelectState('colIndex'); break; // 行多选 case CellType.ROW_CELL: this.changeRowColSelectState('rowIndex'); break; // 单元格单选/多选 case CellType.DATA_CELL: if (shouldUpdateBySelectedCellsHighlight(this.spreadsheet)) { updateBySelectedCellsHighlight(cells, this, this.spreadsheet); } else if (includeCell(cells, this)) { this.updateByState(InteractionStateName.SELECTED); } else if ((_b = this.spreadsheet.options.interaction) === null || _b === void 0 ? void 0 : _b.selectedCellsSpotlight) { this.updateByState(InteractionStateName.UNSELECTED); } break; default: break; } } isDisableHover(cellMeta) { return cellMeta.type !== CellType.DATA_CELL; } handleHover(cells) { const hoverCellMeta = first(cells); if (this.isDisableHover(hoverCellMeta)) { this.hideInteractionShape(); return; } const { currentRow, currentCol } = this.spreadsheet.interaction.getHoverHighlight(); if (currentRow || currentCol) { // 如果当前是hover,要绘制出十字交叉的行列样式 const currentColIndex = this.meta.colIndex; const currentRowIndex = this.meta.rowIndex; // 当视图内的 cell 行列 index 与 hover 的 cell 一致,绘制hover的十字样式 if ((currentCol && currentColIndex === (hoverCellMeta === null || hoverCellMeta === void 0 ? void 0 : hoverCellMeta.colIndex)) || (currentRow && currentRowIndex === (hoverCellMeta === null || hoverCellMeta === void 0 ? void 0 : hoverCellMeta.rowIndex))) { this.updateByState(InteractionStateName.HOVER); } else { // 当视图内的 cell 行列 index 与 hover 的 cell 不一致,隐藏其他样式 this.hideInteractionShape(); } } const { id, rowIndex, colIndex } = this.getMeta(); // fix issue: https://github.com/antvis/S2/issues/1781 if (isEqual(hoverCellMeta.id, id) && isEqual(hoverCellMeta.rowIndex, rowIndex) && isEqual(hoverCellMeta.colIndex, colIndex)) { this.updateByState(InteractionStateName.HOVER_FOCUS); } } update() { const stateName = this.spreadsheet.interaction.getCurrentStateName(); if (stateName === InteractionStateName.ALL_SELECTED) { this.updateByState(InteractionStateName.SELECTED); return; } // 获取当前 interaction 记录的 Cells 元信息列表,不仅仅是数据单元格,也可能是行头或者列头。 const cells = this.spreadsheet.interaction.getCells(); if (isEmpty(cells) || !stateName) { return; } switch (stateName) { case InteractionStateName.SELECTED: case InteractionStateName.DATA_CELL_BRUSH_SELECTED: this.handleSelect(cells); break; case InteractionStateName.HOVER_FOCUS: case InteractionStateName.HOVER: this.handleHover(cells); break; case InteractionStateName.SEARCH_RESULT: this.handleSearchResult(cells); break; default: this.handleByStateName(cells, stateName); break; } } setMeta(viewMeta) { super.setMeta(viewMeta); if (!this.isShallowRender()) { this.initCell(); } } drawTextShape() { super.drawTextShape(); if (!this.isShallowRender()) { this.drawLinkField(this.meta); } } initCell() { this.resetTextAndConditionIconShapes(); this.generateIconConfig(); this.drawBackgroundShape(); this.drawInteractiveBgShape(); if (!this.shouldHideRowSubtotalData()) { this.drawConditionIntervalShape(); } if (!this.shouldHideRowSubtotalData()) { this.drawTextOrCustomRenderer(); } else { this.afterDrawText(); } } afterDrawText() { if (!this.shouldHideRowSubtotalData()) { this.drawConditionIconShapes(); } this.drawBorders(); this.drawInteractiveBorderShape(); this.update(); } generateIconConfig() { // data cell 只包含 condition icon // 并且为了保证格式的统一,只要有 condition icon 配置,就提供 icon 的占位,不过 mapping 结果是否为 null // 比如在 icon position 为 right 时,如果根据实际的 mappingResult 来决定是否提供 icon 占位,文字本身对齐效果就不太好, var _a; const iconCondition = this.findFieldCondition((_a = this.conditions) === null || _a === void 0 ? void 0 : _a.icon); const iconCfg = iconCondition && iconCondition.mapping && { // 此时 name 是什么值都无所谓,因为后面会根据 mappingResult 来决定 name: '', position: getIconPosition(iconCondition), }; this.groupedIcons = groupIconsByPosition([], iconCfg); } getTextStyle() { const textOverflowStyle = this.getCellTextWordWrapStyle(); const { isTotals } = this.meta; const { dataCell } = this.theme; const textStyle = isTotals ? dataCell === null || dataCell === void 0 ? void 0 : dataCell.bolderText : dataCell === null || dataCell === void 0 ? void 0 : dataCell.text; return this.getContainConditionMappingResultTextStyle(Object.assign(Object.assign({}, textOverflowStyle), textStyle)); } drawConditionIntervalShape() { this.conditionIntervalShape = drawInterval(this); } shouldHideRowSubtotalData() { var _a, _b; const { rowId, rowIndex } = this.meta; // 如果该格子是被下钻的格子,下钻格子本身来说是明细格子,因为下钻变成了小计格子,是应该展示的 const drillDownIdPathMap = this.spreadsheet.store.get('drillDownIdPathMap'); if (drillDownIdPathMap === null || drillDownIdPathMap === void 0 ? void 0 : drillDownIdPathMap.has(rowId)) { return false; } const { row = {} } = (_a = this.spreadsheet.options.totals) !== null && _a !== void 0 ? _a : {}; const node = (_b = this.spreadsheet.facet) === null || _b === void 0 ? void 0 : _b.getRowLeafNodeByIndex(rowIndex); const isRowSubTotal = !(node === null || node === void 0 ? void 0 : node.isGrandTotals) && (node === null || node === void 0 ? void 0 : node.isTotals); /** * 在树状结构时,如果单元格本身是行小计,但是行小计配置又未开启时 * 不管能否查到实际的数据,都不应该展示 */ return (this.spreadsheet.isHierarchyTreeType() && !row.showSubTotals && isRowSubTotal); } getFormattedFieldValue() { var _a; if (this.shouldHideRowSubtotalData()) { return { value: null, /** * 这里使用默认的 placeholder,而不是空字符串,是为了防止后续使用用户自定义的 placeholder * 比如用户自定义 placeholder 为 0, 那行小计也会显示0,也很有迷惑性,显示 - 更为合理 */ formattedValue: EMPTY_PLACEHOLDER, }; } const { rowId, valueField, fieldValue, data, id } = this.meta; const displayFormattedValue = (_a = this.spreadsheet.dataSet.displayFormattedValueMap) === null || _a === void 0 ? void 0 : _a.get(id); const rowMeta = this.spreadsheet.dataSet.getFieldMeta(rowId); const fieldId = rowMeta ? rowId : valueField; const formatter = this.spreadsheet.dataSet.getFieldFormatter(fieldId); const formattedValue = displayFormattedValue !== null && displayFormattedValue !== void 0 ? displayFormattedValue : formatter(fieldValue, data, this.meta); return { value: fieldValue, formattedValue, }; } getMaxTextWidth() { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); return width - this.getActionAndConditionIconWidth(); } getContentPosition({ contentWidth = this.getActualTextWidth(), } = {}) { const contentBox = this.getBBoxByType(CellClipBox.CONTENT_BOX); const textStyle = this.getTextStyle(); const iconStyle = this.getIconStyle(); const { textX, leftIconX, rightIconX } = getHorizontalTextIconPosition({ bbox: contentBox, iconStyle, textWidth: contentWidth, textAlign: textStyle.textAlign, groupedIcons: this.groupedIcons, isCustomRenderer: !!this.getRenderer(), }); const y = getVerticalTextPosition(contentBox, textStyle.textBaseline); const iconY = getVerticalIconPosition(iconStyle.size, y, textStyle.fontSize, textStyle.textBaseline); this.iconPosition = { x: !isEmpty(this.groupedIcons.left) ? leftIconX : rightIconX, y: iconY, }; return { x: textX, y, }; } getTextPosition() { return this.getContentPosition(); } getIconPosition() { return this.iconPosition; } getBackgroundColor() { const backgroundColorByCross = this.getCrossBackgroundColor(this.meta.rowIndex); const backgroundColor = backgroundColorByCross.backgroundColor; const backgroundColorOpacity = backgroundColorByCross.backgroundColorOpacity; if (this.shouldHideRowSubtotalData()) { return { backgroundColor, backgroundColorOpacity, intelligentReverseTextColor: false, }; } return merge({ backgroundColor, backgroundColorOpacity }, this.getBackgroundConditionFill()); } // dataCell 根据 state 改变当前样式, changeRowColSelectState(indexType) { var _a, _b, _c; const { interaction } = this.spreadsheet; const currentIndex = get(this.meta, indexType); const { nodes = [], cells = [] } = interaction.getState(); let isEqualIndex = false; // 明细表模式多级表头计算索引换一种策略 if (this.spreadsheet.isTableMode() && nodes.length) { const leafs = ((_b = (_a = nodes[0]) === null || _a === void 0 ? void 0 : _a.hierarchy) === null || _b === void 0 ? void 0 : _b.getLeaves()) || []; isEqualIndex = leafs.some((cell, i) => { if (nodes.some((node) => node === cell)) { return i === currentIndex; } return false; }); } else { isEqualIndex = [...nodes, ...cells].some((cell) => get(cell, indexType) === currentIndex); } if (isEqualIndex) { this.updateByState(InteractionStateName.SELECTED); } else if ((_c = this.spreadsheet.options.interaction) === null || _c === void 0 ? void 0 : _c.selectedCellsSpotlight) { this.updateByState(InteractionStateName.UNSELECTED); } else { this.hideInteractionShape(); } } drawBorders() { if (!this.meta['isFrozenCorner']) { return; } BaseCell.prototype.drawBorders.call(this); } /** * Find current field related condition * @param conditions */ findFieldCondition(conditions = []) { return findFieldCondition(conditions, this.meta.valueField); } /** * Mapping value to get condition related attrs * @param condition */ mappingValue(condition) { var _a; const value = this.meta.fieldValue; const rowDataInfo = this.spreadsheet.isTableMode() ? this.spreadsheet.dataSet.getCellData({ query: { rowIndex: this.meta.rowIndex }, }) : CellData.getFieldValue(this.meta.data); return (_a = condition.mapping) === null || _a === void 0 ? void 0 : _a.call(condition, value, rowDataInfo, this); } updateByState(stateName) { super.updateByState(stateName, this); if (stateName === InteractionStateName.UNSELECTED) { const interactionStateTheme = get(this.theme, `${this.cellType}.cell.interactionState.${stateName}`); if (interactionStateTheme) { this.toggleConditionIntervalShapeOpacity(interactionStateTheme.opacity); } } } clearUnselectedState() { super.clearUnselectedState(); this.toggleConditionIntervalShapeOpacity(1); } toggleConditionIntervalShapeOpacity(opacity) { updateShapeAttr(this.conditionIntervalShape, SHAPE_STYLE_MAP.backgroundOpacity, opacity); updateShapeAttr(this.conditionIconShapes, SHAPE_STYLE_MAP.opacity, opacity); } getResizedTextMaxLines() { var _a, _b, _c, _d, _e; const { rowCell } = this.spreadsheet.options.style; // 数值和行高保持一致, 同时兼容明细表 return ((_d = (_b = (_a = rowCell === null || rowCell === void 0 ? void 0 : rowCell.maxLinesByField) === null || _a === void 0 ? void 0 : _a[this.meta.id]) !== null && _b !== void 0 ? _b : (_c = rowCell === null || rowCell === void 0 ? void 0 : rowCell.maxLinesByField) === null || _c === void 0 ? void 0 : _c[this.meta.rowId]) !== null && _d !== void 0 ? _d : this.getMaxLinesByCustomHeight({ isCustomHeight: this.meta.height !== ((_e = DEFAULT_STYLE.dataCell) === null || _e === void 0 ? void 0 : _e.height), })); } getMetaField() { return this.meta.valueField; } } //# sourceMappingURL=data-cell.js.map