UNPKG

@antv/s2

Version:

effective spreadsheet render core lib

573 lines 25.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseCell = void 0; const g_1 = require("@antv/g"); const lodash_1 = require("lodash"); const constant_1 = require("../common/constant"); const interface_1 = require("../common/interface"); const renderer_1 = require("../renderer"); const cell_1 = require("../utils/cell/cell"); const header_cell_1 = require("../utils/cell/header-cell"); const color_1 = require("../utils/color"); const condition_1 = require("../utils/condition/condition"); const g_renders_1 = require("../utils/g-renders"); const g_utils_1 = require("../utils/g-utils"); const link_field_1 = require("../utils/interaction/link-field"); const is_mobile_1 = require("../utils/is-mobile"); const text_1 = require("../utils/text"); class BaseCell extends g_1.Group { get actualText() { return this.getMultiLineActualTexts().join(''); } constructor(meta, spreadsheet, ...restOptions) { super({}); this.textShapes = []; this.conditionIconShapes = []; // interactive control shapes, unify read and manipulate operations this.stateShapes = new Map(); this.borders = new Map(); this.meta = meta; this.spreadsheet = spreadsheet; this.theme = spreadsheet.theme; this.conditions = this.spreadsheet.options.conditions; this.groupedIcons = { left: [], right: [] }; this.handleRestOptions(...restOptions); if (this.shouldInit()) { this.initCell(); } } /** * in case there are more params to be handled * @param options any type's rest params */ // eslint-disable-next-line @typescript-eslint/no-unused-vars handleRestOptions(...options) { } getResizedTextMaxLines() { } /* -------------------------------------------------------------------------- */ /* common functions that will be used in subtype */ /* -------------------------------------------------------------------------- */ getMeta() { return this.meta; } setMeta(viewMeta) { this.meta = viewMeta; } getIconStyle() { var _a; return (_a = this.theme[this.cellType]) === null || _a === void 0 ? void 0 : _a.icon; } isShallowRender() { return false; } getCellTextWordWrapStyle(cellType) { var _a, _b; const { wordWrap, maxLines, textOverflow } = (((_b = (_a = this.spreadsheet.options) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b[cellType || this.cellType]) || {}); return { wordWrap, maxLines, textOverflow, }; } /** * 获取实际渲染的文本 (含省略号) */ getActualText() { return this.getMultiLineActualTexts().join(''); } /** * 实际渲染的文本宽度, 如果是多行文本, 取最大的一行宽度 */ getActualTextWidth() { var _a; return ((_a = this.textShape) === null || _a === void 0 ? void 0 : _a.getComputedTextLength()) || 0; } /** * 实际渲染的文本宽度, 如果是多行文本, 取每一行文本高度的总和 * @alias getMultiLineActualTextHeight */ getActualTextHeight() { return this.getMultiLineActualTextHeight(); } /** * 获取实际渲染的多行文本 (含省略号) */ getMultiLineActualTexts() { var _a, _b, _c; // G6.0 优化延迟了包围盒计算的逻辑,先调用一下 getGeometryBounds 触发包围盒计算(内部有 cache 的不用担心多次调用) (_a = this.textShape) === null || _a === void 0 ? void 0 : _a.getGeometryBounds(); return ((_c = (_b = this.textShape) === null || _b === void 0 ? void 0 : _b.parsedStyle.metrics) === null || _c === void 0 ? void 0 : _c.lines) || []; } /** * 实际渲染的多行文本宽度 (每一行文本宽度的总和) */ getMultiLineActualTextWidth() { return (0, lodash_1.sumBy)(this.getTextLineBoundingRects(), 'width') || 0; } /** * 实际渲染的多行文本高度 (每一行文本高度的总和) * @alias getActualTextHeight */ getMultiLineActualTextHeight() { return (0, lodash_1.sumBy)(this.getTextLineBoundingRects(), 'height') || 0; } /** * 获取原始的文本 (不含省略号) */ getOriginalText() { return this.originalText; } /** * 文本是否溢出 (有省略号) */ isTextOverflowing() { var _a; return (_a = this.textShape) === null || _a === void 0 ? void 0 : _a.isOverflowing(); } /** * 是否是多行文本 */ isMultiLineText() { const { parsedStyle } = this.getTextShape(); return ((parsedStyle === null || parsedStyle === void 0 ? void 0 : parsedStyle.maxLines) > 1 && this.getTextLineBoundingRects().length > 1); } /** * 获取文本包围盒 */ getTextLineBoundingRects() { var _a; return ((_a = this.textShape) === null || _a === void 0 ? void 0 : _a.getLineBoundingRects()) || []; } /** * 获取文本行高 */ getTextLineHeight() { var _a, _b, _c; return (((_c = (_b = (_a = this.textShape) === null || _a === void 0 ? void 0 : _a.parsedStyle) === null || _b === void 0 ? void 0 : _b.metrics) === null || _c === void 0 ? void 0 : _c.lineHeight) || constant_1.DEFAULT_TEXT_LINE_HEIGHT); } /** * 获取单元格空值占位符 */ getEmptyPlaceholder() { const { options: { placeholder }, } = this.spreadsheet; return (0, text_1.getEmptyPlaceholder)(this, placeholder); } /** * 获取单元格展示的数值 */ getFieldValue() { return this.getFormattedFieldValue().formattedValue; } shouldInit() { const { width, height } = this.meta; return width > 0 && height > 0; } getStyle(name) { return (0, lodash_1.get)(this.theme, name || this.cellType); } getLinkFieldShape() { return this.linkFieldShape; } getBackgroundShape() { return this.backgroundShape; } getStateShapes() { return this.stateShapes; } getResizeAreaStyle() { return this.getStyle('resizeArea'); } shouldDrawResizeAreaByType(type, cell) { const { resize } = this.spreadsheet.options.interaction; if ((0, lodash_1.isBoolean)(resize)) { return resize; } if ((0, lodash_1.isFunction)(resize === null || resize === void 0 ? void 0 : resize.visible)) { return resize === null || resize === void 0 ? void 0 : resize.visible(cell); } return resize === null || resize === void 0 ? void 0 : resize[type]; } getBBoxByType(type = interface_1.CellClipBox.BORDER_BOX) { const bbox = { x: this.meta.x, y: this.meta.y, height: this.meta.height, width: this.meta.width, }; const cellStyle = (this.getStyle() || this.theme.dataCell); return (0, cell_1.getCellBoxByType)(bbox, this.getBorderPositions(), cellStyle === null || cellStyle === void 0 ? void 0 : cellStyle.cell, type); } drawBorders() { this.getBorderPositions().forEach((type) => { var _a; const { position, style } = (0, cell_1.getBorderPositionAndStyle)(type, this.getBBoxByType(), (_a = this.getStyle()) === null || _a === void 0 ? void 0 : _a.cell); const borderStyle = Object.assign(Object.assign({}, position), style); if (this.borders.has(type)) { (0, g_utils_1.batchSetStyle)(this.borders.get(type), borderStyle); } else { this.borders.set(type, (0, g_renders_1.renderLine)(this, borderStyle)); } }); } /** * 绘制 hover 悬停,刷选的外框 */ drawInteractiveBorderShape() { const style = Object.assign(Object.assign({}, this.getBBoxByType(interface_1.CellClipBox.PADDING_BOX)), { visibility: 'hidden', pointerEvents: 'none' }); const interactiveBorderShape = this.stateShapes.get('interactiveBorderShape'); if (interactiveBorderShape) { (0, g_utils_1.batchSetStyle)(interactiveBorderShape, style); } else { this.stateShapes.set('interactiveBorderShape', (0, g_renders_1.renderRect)(this, style)); } } /** * 交互使用的背景色 */ drawInteractiveBgShape() { const style = Object.assign(Object.assign({}, this.getBBoxByType()), { visibility: 'hidden', pointerEvents: 'none' }); const reuseInteractiveBgShape = this.stateShapes.get('interactiveBgShape'); if (reuseInteractiveBgShape) { (0, g_utils_1.batchSetStyle)(reuseInteractiveBgShape, style); } else { this.stateShapes.set('interactiveBgShape', (0, g_renders_1.renderRect)(this, style)); } } drawBackgroundShape() { const { backgroundColor, backgroundColorOpacity } = this.getBackgroundColor(); const style = Object.assign(Object.assign({}, this.getBBoxByType()), { fill: backgroundColor, fillOpacity: backgroundColorOpacity }); if (this.backgroundShape) { (0, g_utils_1.batchSetStyle)(this.backgroundShape, style); } else { this.backgroundShape = (0, g_renders_1.renderRect)(this, style); } } renderTextShape(style, options) { const text = (0, text_1.getDisplayText)(style.text, this.getEmptyPlaceholder()); const shallowRender = (options === null || options === void 0 ? void 0 : options.shallowRender) || this.isShallowRender(); if (this.textShape && !shallowRender) { (0, g_utils_1.batchSetStyle)(this.textShape, Object.assign(Object.assign({}, style), { // 文本必须为字符串 text: `${text}` })); } else { this.textShape = (0, g_renders_1.renderText)({ group: this, textShape: shallowRender ? undefined : this.textShape, style: Object.assign(Object.assign({}, style), { // 文本必须为字符串 text: `${text}` }), }); } this.addTextShape(this.textShape); if (shallowRender) { return this.textShape; } this.originalText = text; return this.textShape; } updateTextPosition(position) { var _a, _b, _c, _d; const defaultPosition = this.getTextPosition(); (_a = this.textShape) === null || _a === void 0 ? void 0 : _a.attr('x', (_b = position === null || position === void 0 ? void 0 : position.x) !== null && _b !== void 0 ? _b : defaultPosition === null || defaultPosition === void 0 ? void 0 : defaultPosition.x); (_c = this.textShape) === null || _c === void 0 ? void 0 : _c.attr('y', (_d = position === null || position === void 0 ? void 0 : position.y) !== null && _d !== void 0 ? _d : defaultPosition === null || defaultPosition === void 0 ? void 0 : defaultPosition.y); } drawTextOrCustomRenderer() { const renderer = this.getRenderer(); if (renderer) { renderer_1.SingletonRenderer.render(renderer, this).then(() => { this.afterDrawText(); }); return; } this.drawTextShape(); this.afterDrawText(); } drawTextShape() { // 额外添加一像素余量,防止出现省略号 (文本和省略后的宽度一致): https://github.com/antvis/S2/issues/2726 const EXTRA_PIXEL = 1; // G 遵循浏览器的规范, 空间不足以展示省略号时, 会裁剪文字, 而不是展示省略号: https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow#ellipsis const maxTextWidth = Math.max(this.getMaxTextWidth(), 0) + EXTRA_PIXEL; const textStyle = this.getTextStyle(); const maxLines = this.getResizedTextMaxLines() || (textStyle === null || textStyle === void 0 ? void 0 : textStyle.maxLines); const text = this.getFieldValue(); // 在坐标计算 (getTextPosition) 之前, 预渲染一次, 提前生成 textShape, 获得文字宽度, 用于计算 icon 绘制坐标 this.renderTextShape(Object.assign(Object.assign({}, textStyle), { x: 0, y: 0, text, wordWrapWidth: maxTextWidth, maxLines })); if (this.isShallowRender()) { return; } this.updateTextPosition(); this.drawLinkField(this.meta); } drawLinkFieldShape(showLinkFieldShape, linkFillColor) { if (!showLinkFieldShape) { return; } const { device } = this.spreadsheet.options; // 配置了链接跳转 if (!(0, is_mobile_1.isMobile)(device)) { const textStyle = this.getTextStyle(); const position = this.getTextPosition(); const actualTextWidth = this.getActualTextWidth(); // 默认居左,其他 align 方式需要调整 let startX = position.x; if (textStyle.textAlign === 'center') { startX -= actualTextWidth / 2; } else if (textStyle.textAlign === 'right') { startX -= actualTextWidth; } const { bottom: maxY } = this.textShape.getBBox(); const options = { x1: startX, y1: maxY + 1, // 不用 bbox 的 maxX,因为 g-base 文字宽度预估偏差较大 x2: startX + actualTextWidth, y2: maxY + 1, stroke: linkFillColor, lineWidth: 1, }; if (this.linkFieldShape) { (0, g_utils_1.batchSetStyle)(this.linkFieldShape, options); } else { this.linkFieldShape = (0, g_renders_1.renderLine)(this, options); } } this.textShape.style.fill = linkFillColor; this.textShape.style.cursor = 'pointer'; this.textShape.appendInfo = { // 标记为字段标记文本,方便做链接跳转直接识别 isLinkFieldText: true, meta: this.meta, }; } // 要被子类覆写,返回颜色字符串 getLinkFieldStyle() { return this.getTextStyle().linkTextFill; } drawLinkField(meta) { const { linkFields = [] } = this.spreadsheet.options.interaction; const linkTextFill = this.getLinkFieldStyle(); const isLinkField = (0, link_field_1.isLinkFieldNode)(linkFields, meta); this.drawLinkFieldShape(isLinkField, linkTextFill); } // 根据当前 state 来更新 cell 的样式 updateByState(stateName, cell) { this.spreadsheet.interaction.setInteractedCells(cell); const stateStyles = (0, lodash_1.get)(this.theme, `${this.cellType}.cell.interactionState.${stateName}`); (0, lodash_1.each)(stateStyles, (style, styleKey) => { const targetShapeNames = (0, lodash_1.keys)((0, lodash_1.pickBy)(constant_1.SHAPE_ATTRS_MAP, (attrs) => (0, lodash_1.includes)(attrs, styleKey))); targetShapeNames.forEach((shapeName) => { const isStateShape = this.stateShapes.has(shapeName); const shapeGroup = isStateShape ? this.stateShapes.get(shapeName) : // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this[shapeName]; // 兼容多列文本 (MultiData) const shapes = (!(0, lodash_1.isArray)(shapeGroup) ? [shapeGroup] : shapeGroup); // stateShape 默认 visible 为 false if (isStateShape) { shapes.forEach((shape) => { shape.setAttribute('visibility', 'visible'); }); } // 根据 borderWidth 更新 borderShape 大小 https://github.com/antvis/S2/pull/705 if (shapeName === 'interactiveBorderShape' && styleKey === 'borderWidth') { if ((0, lodash_1.isNumber)(style)) { const marginStyle = this.getInteractiveBorderShapeStyle(style); (0, lodash_1.each)(marginStyle, (currentStyle, currentStyleKey) => { (0, g_renders_1.updateShapeAttr)(shapes, currentStyleKey, currentStyle); }); } } // @ts-ignore (0, g_renders_1.updateShapeAttr)(shapes, constant_1.SHAPE_STYLE_MAP[styleKey], style); }); }); } getInteractiveBorderShapeStyle(borderSize) { const { x, y, height, width } = this.getBBoxByType(interface_1.CellClipBox.PADDING_BOX); const halfSize = borderSize / 2; return { x: x + halfSize, y: y + halfSize, width: width - borderSize, height: height - borderSize, }; } hideInteractionShape() { this.stateShapes.forEach((shape) => { (0, g_renders_1.updateShapeAttr)(shape, constant_1.SHAPE_STYLE_MAP.backgroundOpacity, 0); (0, g_renders_1.updateShapeAttr)(shape, constant_1.SHAPE_STYLE_MAP.backgroundColor, 'transparent'); (0, g_renders_1.updateShapeAttr)(shape, constant_1.SHAPE_STYLE_MAP.borderOpacity, 0); (0, g_renders_1.updateShapeAttr)(shape, constant_1.SHAPE_STYLE_MAP.borderWidth, 1); (0, g_renders_1.updateShapeAttr)(shape, constant_1.SHAPE_STYLE_MAP.borderColor, 'transparent'); }); } clearUnselectedState() { (0, g_renders_1.updateShapeAttr)(this.backgroundShape, constant_1.SHAPE_STYLE_MAP.backgroundOpacity, 1); (0, g_renders_1.updateShapeAttr)(this.textShapes, constant_1.SHAPE_STYLE_MAP.textOpacity, 1); (0, g_renders_1.updateShapeAttr)(this.linkFieldShape, constant_1.SHAPE_STYLE_MAP.opacity, 1); } getTextShape() { return this.textShape; } getTextShapes() { return this.textShapes || [this.textShape]; } addTextShape(textShape) { if (!textShape) { return; } this.textShapes.push(textShape); } getConditionIconShape() { return this.conditionIconShape; } getConditionIconShapes() { return this.conditionIconShapes || [this.conditionIconShape]; } addConditionIconShape(iconShape) { if (!iconShape) { return; } this.conditionIconShapes.push(iconShape); } resetTextAndConditionIconShapes() { this.textShapes = []; this.conditionIconShapes = []; } get cellConditions() { return this.conditions; } drawConditionIconShapes() { const attrs = this.getIconConditionResult(); if (attrs) { const position = this.getIconPosition(); const { size } = this.getStyle().icon; const iconCfg = Object.assign(Object.assign({}, position), { name: attrs === null || attrs === void 0 ? void 0 : attrs.name, width: size, height: size, fill: attrs === null || attrs === void 0 ? void 0 : attrs.fill }); if (this.conditionIconShape) { this.conditionIconShape.reRender(iconCfg); } else { this.conditionIconShape = (0, g_renders_1.renderIcon)(this, iconCfg); } this.addConditionIconShape(this.conditionIconShape); } } getTextConditionMappingResult() { var _a; const textCondition = this.findFieldCondition((_a = this.conditions) === null || _a === void 0 ? void 0 : _a.text); if (textCondition === null || textCondition === void 0 ? void 0 : textCondition.mapping) { return this.mappingValue(textCondition); } return null; } getContainConditionMappingResultTextStyle(style) { // 优先级:默认字体颜色(已经根据背景反色后的) < 主题配置文字样式 < 条件格式文字样式 const defaultTextFill = this.getDefaultTextFill(style.fill); const conditionStyle = this.getTextConditionMappingResult(); return Object.assign(Object.assign(Object.assign({}, style), conditionStyle), { fill: (conditionStyle === null || conditionStyle === void 0 ? void 0 : conditionStyle.fill) || defaultTextFill }); } /** * 获取默认字体颜色:根据字段标记背景颜色,设置字体颜色 * @param textStyle * @private */ getDefaultTextFill(textFill) { const { backgroundColor, intelligentReverseTextColor } = this.getBackgroundColor(); // text 默认为黑色,当背景颜色亮度过低时,修改 text 为白色 if ((0, color_1.shouldReverseFontColor)(backgroundColor) && (textFill === constant_1.DEFAULT_FONT_COLOR || !(0, color_1.isReadableText)(backgroundColor, textFill)) && intelligentReverseTextColor) { textFill = constant_1.REVERSE_FONT_COLOR; } return textFill || ''; } getBackgroundConditionFill() { var _a; // get background condition fill color const bgCondition = this.findFieldCondition((_a = this.conditions) === null || _a === void 0 ? void 0 : _a.background); if (bgCondition === null || bgCondition === void 0 ? void 0 : bgCondition.mapping) { const attrs = this.mappingValue(bgCondition); if (attrs) { return { backgroundColor: attrs.fill, backgroundColorOpacity: 1, intelligentReverseTextColor: attrs.intelligentReverseTextColor || false, }; } } return { intelligentReverseTextColor: false, }; } getIconConditionResult() { var _a; const iconCondition = this.findFieldCondition((_a = this.conditions) === null || _a === void 0 ? void 0 : _a.icon); if (iconCondition === null || iconCondition === void 0 ? void 0 : iconCondition.mapping) { const attrs = this.mappingValue(iconCondition); if (attrs && attrs.icon) { return { name: attrs.icon, position: (0, condition_1.getIconPosition)(iconCondition), fill: attrs.fill, isConditionIcon: true, }; } } } getActionAndConditionIconWidth(position) { const { left, right } = this.groupedIcons; const iconStyle = this.getStyle().icon; if (!position) { return ((0, header_cell_1.getIconTotalWidth)(left, iconStyle) + (0, header_cell_1.getIconTotalWidth)(right, iconStyle)); } return (0, header_cell_1.getIconTotalWidth)(this.groupedIcons[position], iconStyle); } getCrossBackgroundColor(rowIndex) { const { crossBackgroundColor, backgroundColorOpacity } = this.getStyle().cell; if (crossBackgroundColor && rowIndex % 2 === 0) { // 隔行颜色的配置 // 偶数行展示灰色背景,因为index是从0开始的 return { backgroundColorOpacity, backgroundColor: crossBackgroundColor }; } return { backgroundColorOpacity, backgroundColor: this.getStyle().cell.backgroundColor, }; } getMaxLinesByCustomHeight(options) { var _a; const { targetCell = this, displayHeight = this.meta.height, isCustomHeight = false, } = options; const cell = targetCell || this; const cellStyle = (_a = this.spreadsheet.options.style) === null || _a === void 0 ? void 0 : _a[cell.cellType]; const isEnableHeightAdaptive = (cellStyle === null || cellStyle === void 0 ? void 0 : cellStyle.maxLines) > 1 && (cellStyle === null || cellStyle === void 0 ? void 0 : cellStyle.wordWrap); if (!isEnableHeightAdaptive || !isCustomHeight) { return; } const { cell: cellTheme } = cell === null || cell === void 0 ? void 0 : cell.getStyle(); const padding = cellTheme.padding.top + cellTheme.padding.bottom; const lineHeight = cell === null || cell === void 0 ? void 0 : cell.getTextLineHeight(); const maxLines = Math.max(1, Math.round((displayHeight - padding) / lineHeight)); return maxLines; } getRenderer() { var _a, _b; return (_b = (_a = this.spreadsheet.dataCfg.meta) === null || _a === void 0 ? void 0 : _a.find((m) => m.field === this.getMetaField())) === null || _b === void 0 ? void 0 : _b.renderer; } getConditionIntervalShape() { return this.conditionIntervalShape; } } exports.BaseCell = BaseCell; //# sourceMappingURL=base-cell.js.map