@antv/s2
Version:
effective spreadsheet render core lib
382 lines • 17 kB
JavaScript
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