diff --git a/packages/s2-core/scripts/test-live.js b/packages/s2-core/scripts/test-live.js index f73db73e50..473b2a09d5 100644 --- a/packages/s2-core/scripts/test-live.js +++ b/packages/s2-core/scripts/test-live.js @@ -17,6 +17,7 @@ async function main() { type: 'rawlist', message: '📢 请选择测试文件 (输入序号可快速选择)', name: 'path', + loop: false, choices: paths, default: () => defaultSelectedIndex, }, diff --git a/packages/s2-core/src/cell/base-cell.ts b/packages/s2-core/src/cell/base-cell.ts index 857ebb38c2..ef7311985c 100644 --- a/packages/s2-core/src/cell/base-cell.ts +++ b/packages/s2-core/src/cell/base-cell.ts @@ -4,16 +4,14 @@ import { SHAPE_ATTRS_MAP, SHAPE_STYLE_MAP, } from '@/common/constant'; -import { InteractionStateTheme, SpreadSheetTheme } from '@/common/interface'; +import { SpreadSheetTheme } from '@/common/interface'; import { Group, IShape } from '@antv/g-canvas'; import { each, findKey, get, includes } from 'lodash'; import { SpreadSheet } from '../sheet-type'; import { updateShapeAttr } from '../utils/g-renders'; +import { StateShapeLayer } from './../common/interface/interaction'; export abstract class BaseCell extends Group { - // used to determine the cell type - public cellType: CellTypes; - // cell's data meta info protected meta: T; @@ -23,19 +21,14 @@ export abstract class BaseCell extends Group { // spreadsheet's theme protected theme: SpreadSheetTheme; - // background control by icon condition + // background control shape protected backgroundShape: IShape; + // text control shape protected textShape: IShape; - // render interactive background, - protected interactiveBgShape: IShape; - - // 需要根据state改变样式的shape集合 - // 需要这个属性的原因是在state clear时知道具体哪些shape要hide。不然只能手动改,比较麻烦 - protected stateShapes: IShape[] = []; - - // protected actionIcons: GuiIcon[]; + // interactive control shapes, unify read and manipulate operations + protected stateShapes = new Map(); public constructor( meta: T, @@ -50,11 +43,6 @@ export abstract class BaseCell extends Group { this.initCell(); } - /** - * Update cell's selected state - */ - public abstract update(): void; - public getMeta(): T { return this.meta; } @@ -67,31 +55,49 @@ export abstract class BaseCell extends Group { * in case there are more params to be handled * @param options any type's rest params */ - protected handleRestOptions(...options: any) { + protected handleRestOptions(...options: unknown[]) { // default do nothing } + /** + * Return the type of the cell + */ + public abstract get cellType(): CellTypes; + /** * Determine how to render this cell area */ protected abstract initCell(): void; /** - * Return the type of the cell + * Update cell's selected state */ - protected abstract getCellType(): CellTypes; + public abstract update(): void; + + /* -------------------------------------------------------------------------- */ + /* common functions that will be used in subtype */ + /* -------------------------------------------------------------------------- */ // 根据当前state来更新cell的样式 public updateByState(stateName: InteractionStateName) { - const stateStyles = get(this.theme, `${this.cellType}.cell.${stateName}`); + const stateStyles = get( + this.theme, + `${this.cellType}.cell.interactionState.${stateName}`, + ); each(stateStyles, (style, styleKey) => { - if (styleKey) { - const currentShape = findKey(SHAPE_ATTRS_MAP, (attrs) => - includes(attrs, styleKey), - ); - if (!currentShape) return; - updateShapeAttr(this[currentShape], SHAPE_STYLE_MAP[styleKey], style); + const currentShape = findKey(SHAPE_ATTRS_MAP, (attrs) => + includes(attrs, styleKey), + ) as StateShapeLayer | undefined; + + if (!currentShape || !this.stateShapes.has(currentShape)) { + return; } + + updateShapeAttr( + this.stateShapes.get(currentShape), + SHAPE_STYLE_MAP[styleKey], + style, + ); }); } diff --git a/packages/s2-core/src/cell/col-cell.ts b/packages/s2-core/src/cell/col-cell.ts index c024f7e9e1..e97c5b3344 100644 --- a/packages/s2-core/src/cell/col-cell.ts +++ b/packages/s2-core/src/cell/col-cell.ts @@ -1,51 +1,32 @@ -import { BaseCell } from '@/cell/base-cell'; import { CellTypes, COLOR_DEFAULT_RESIZER, KEY_GROUP_COL_RESIZER, - InteractionStateName, } from '@/common/constant'; import { GuiIcon } from '@/common/icons'; import { TextAlign } from '@/common/interface'; import { HIT_AREA } from '@/facet/header/base'; import { ColHeaderConfig } from '@/facet/header/col'; import { ResizeInfo } from '@/facet/header/interface'; -import { Node } from '@/index'; -import { renderRect } from '@/utils/g-renders'; +import { renderLine, renderRect, renderText } from '@/utils/g-renders'; import { getEllipsisText, getTextPosition, measureTextWidth, } from '@/utils/text'; import { IGroup } from '@antv/g-canvas'; -import _ from 'lodash'; +import { get, isEqual } from 'lodash'; +import { HeaderCell } from './header-cell'; -export class ColCell extends BaseCell { +export class ColCell extends HeaderCell { protected headerConfig: ColHeaderConfig; - public update() { - const stateName = this.spreadsheet.interaction.getCurrentStateName(); - const cells = this.spreadsheet.interaction.getActiveCells(); - const currentCell = _.first(cells); - if ( - !currentCell || - (stateName !== InteractionStateName.HOVER && - stateName !== InteractionStateName.HOVER_FOCUS) - ) { - return; - } - if (currentCell?.cellType === CellTypes.DATA_CELL || cells.includes(this)) { - this.updateByState(InteractionStateName.HOVER); - } - } - - protected handleRestOptions(...options: ColHeaderConfig[]) { - this.headerConfig = options[0]; + public get cellType() { + return CellTypes.COL_CELL; } protected initCell() { - this.cellType = this.getCellType(); - // draw rect background + // 1、draw rect background this.drawRectBackground(); // interactive background shape this.drawInteractiveBgShape(); @@ -57,14 +38,9 @@ export class ColCell extends BaseCell { this.drawRightBorder(); // draw hot-spot rect this.drawHotSpot(); - // update the interaction state this.update(); } - protected getCellType() { - return CellTypes.COL_CELL; - } - protected getColHotSpotKey() { return this.meta.key; } @@ -82,12 +58,8 @@ export class ColCell extends BaseCell { } protected drawCellText() { - const { - offset, - width, - scrollContainsRowHeader, - cornerWidth, - } = this.headerConfig; + const { offset, width, scrollContainsRowHeader, cornerWidth } = + this.headerConfig; const { label, x, @@ -114,7 +86,7 @@ export class ColCell extends BaseCell { ? iconCfg.size + iconCfg.margin.right : 0; const textStyle = isLeaf && !isTotals ? textCfg : bolderTextCfg; - const padding = _.get(this, 'theme.colCell.cell.padding'); + const padding = get(this, 'theme.colCell.cell.padding'); const text = getEllipsisText( content, cellWidth - sortIconPadding - padding?.left - padding?.right, @@ -126,8 +98,8 @@ export class ColCell extends BaseCell { let textAlign: TextAlign; if (isLeaf) { // 最后一个层级的维值,与 dataCell 对齐方式保持一致 - textAlign = _.get(this, 'theme.dataCell.text.textAlign'); - const textBaseline = _.get(this, 'theme.dataCell.text.textBaseline'); + textAlign = this.theme.dataCell.text.textAlign; + const textBaseline = this.theme.dataCell.text.textBaseline; textStyle.textBaseline = textBaseline; const cellBoxCfg = { x, @@ -183,7 +155,7 @@ export class ColCell extends BaseCell { } // const derivedValue = this.spreadsheet.getDerivedValue(value); // if ( - // !_.isEqual( + // !isEqual( // derivedValue.derivedValueField, // derivedValue.displayDerivedValueField, // ) && @@ -197,44 +169,48 @@ export class ColCell extends BaseCell { // if ( // key === EXTRA_FIELD && // this.spreadsheet.isDerivedValue(value) && - // _.last(derivedValue.displayDerivedValueField) === value + // last(derivedValue.displayDerivedValueField) === value // ) { // // 度量列,找到 value值 // text += '...'; // } // } - this.addShape('text', { - attrs: { - x: textX, - y: textY, - text, + this.textShape = renderText( + this, + [this.textShape], + textX, + textY, + text, + { textAlign, ...textStyle, - cursor: 'pointer', }, - }); + { cursor: 'pointer' }, + ); } // 交互使用的背景色 protected drawInteractiveBgShape() { const { x, y, height, width } = this.meta; - this.interactiveBgShape = renderRect(this, { - x, - y, - width, - height, - fill: 'transparent', - stroke: 'transparent', - }); - this.stateShapes.push(this.interactiveBgShape); + this.stateShapes.set( + 'interactiveBgShape', + renderRect(this, { + x, + y, + width, + height, + fill: 'transparent', + stroke: 'transparent', + }), + ); } private showSortIcon() { const { sortParam } = this.headerConfig; const query = this.meta.query; return ( - _.isEqual(_.get(sortParam, 'query'), query) && - _.get(sortParam, 'type') !== 'none' + isEqual(get(sortParam, 'query'), query) && + get(sortParam, 'type') !== 'none' ); } @@ -245,7 +221,7 @@ export class ColCell extends BaseCell { const { sortParam } = this.headerConfig; const { x, y, width: cellWidth, height: cellHeight } = this.meta; const iconShape = new GuiIcon({ - type: _.get(sortParam, 'type', 'none'), + type: get(sortParam, 'type', 'none'), x: x + cellWidth - icon.size - icon.margin.right, y: y + (cellHeight - icon.size) / 2, width: icon.size, @@ -334,16 +310,20 @@ export class ColCell extends BaseCell { if (!this.meta.isLeaf) { const { height, viewportHeight } = this.headerConfig; const { x, y, width: cellWidth, height: cellHeight } = this.meta; - this.addShape('line', { - attrs: { + + renderLine( + this, + { x1: x + cellWidth, y1: y + cellHeight, x2: x + cellWidth, - y2: y + height + viewportHeight, // 高度有多,通过 clip 裁剪掉 + y2: y + height + viewportHeight, + }, + { stroke: this.theme.colCell.cell.horizontalBorderColor, lineWidth: 1, }, - }); + ); } } } diff --git a/packages/s2-core/src/cell/corner-cell.ts b/packages/s2-core/src/cell/corner-cell.ts index 74d27f48c8..e04214e27a 100644 --- a/packages/s2-core/src/cell/corner-cell.ts +++ b/packages/s2-core/src/cell/corner-cell.ts @@ -1,59 +1,49 @@ -import _ from 'lodash'; -import { getEllipsisText, getTextPosition } from '../utils/text'; -import { isIPhoneX } from '../utils/is-mobile'; -import { IShape } from '@antv/g-canvas'; -import { renderText } from '../utils/g-renders'; +import { renderRect } from '@/utils/g-renders'; +import { IGroup, IShape, ShapeAttrs } from '@antv/g-canvas'; +import { get, isEmpty, isEqual } from 'lodash'; +import { GuiIcon } from '..'; import { + CellTypes, + COLOR_DEFAULT_RESIZER, EXTRA_FIELD, KEY_GROUP_CORNER_RESIZER, - COLOR_DEFAULT_RESIZER, KEY_TREE_ROWS_COLLAPSE_ALL, - CellTypes, } from '../common/constant'; import { HIT_AREA } from '../facet/header/base'; import { CornerHeaderConfig } from '../facet/header/corner'; import { ResizeInfo } from '../facet/header/interface'; -import { GuiIcon, Node } from '..'; -import { BaseCell } from './base-cell'; -import { IGroup } from '@antv/g-canvas'; -export class CornerCell extends BaseCell { +import { renderText } from '../utils/g-renders'; +import { isIPhoneX } from '../utils/is-mobile'; +import { getEllipsisText, getTextPosition } from '../utils/text'; +import { HeaderCell } from './header-cell'; +export class CornerCell extends HeaderCell { protected headerConfig: CornerHeaderConfig; - protected textShapes: IShape[]; + protected textShapes: IShape[] = []; - protected handleRestOptions(...options) { - if (options.length === 0) { - throw new Error( - 'CornerCell render need headerConfig&hotConfig in CornerHeader!!!', - ); - } - this.headerConfig = options[0]; + public get cellType() { + return CellTypes.CORNER_CELL; } public update() {} protected initCell() { - this.cellType = this.getCellType(); this.textShapes = []; this.drawCellRect(); this.drawCellText(); this.drawHotpot(); } - protected getCellType() { - return CellTypes.CORNER_CELL; - } - protected drawCellText() { const { position } = this.headerConfig; const { label, x, y, width: cellWidth, height: cellHeight } = this.meta; - if (_.isEqual(label, EXTRA_FIELD)) { + if (isEqual(label, EXTRA_FIELD)) { // don't render extra node return; } - const cornerTheme = _.get(this.theme, 'cornerCell'); + const cornerTheme = get(this.theme, 'cornerCell'); const textStyle = { ...cornerTheme?.bolderText }; const iconStyle = cornerTheme?.icon; const cellPadding = cornerTheme?.cell?.padding; @@ -66,7 +56,7 @@ export class CornerCell extends BaseCell { } // 当为树状结构下需要计算文本前收起展开的icon占的位置 - const extraPadding = this.ifNeedIcon() + const extraPadding = this.shouldShowIcon() ? iconStyle?.size + iconStyle?.margin?.left + iconStyle?.margin?.right : 0; @@ -113,39 +103,37 @@ export class CornerCell extends BaseCell { }); const textY = - position.y + - y + - (_.isEmpty(secondLine) ? cellHeight / 2 : cellHeight / 4); + position.y + y + (isEmpty(secondLine) ? cellHeight / 2 : cellHeight / 4); // first line this.textShapes.push( renderText( + this, [this.textShapes[0]], textX, textY, firstLine, textStyle, - this, extraInfo, ), ); // second line - if (!_.isEmpty(secondLine)) { + if (!isEmpty(secondLine)) { this.textShapes.push( renderText( + this, [this.textShapes[1]], textX, position.y + y + cellHeight * 0.65, secondLine, textStyle, - this, extraInfo, ), ); } // 如果为树状模式,角头第一个单元格前需要绘制收起展开的icon - if (this.ifNeedIcon()) { + if (this.shouldShowIcon()) { this.drawIcon(); } } @@ -157,8 +145,8 @@ export class CornerCell extends BaseCell { // 只有交叉表才有icon const { hierarchyCollapse, height, spreadsheet, position } = this.headerConfig; - const iconStyle = _.get(this.theme, 'cornerCell.icon'); - const textStyle = _.get(this.theme, 'cornerCell.text'); + const iconStyle = get(this.theme, 'cornerCell.icon'); + const textStyle = get(this.theme, 'cornerCell.text'); const colHeight = spreadsheet.options.style.colCfg.height; const icon = new GuiIcon({ type: hierarchyCollapse ? 'plus' : 'MinusSquare', @@ -184,7 +172,7 @@ export class CornerCell extends BaseCell { private drawCellRect() { const { position } = this.headerConfig; const { x, y, width: cellWidth, height: cellHeight } = this.meta; - const attrs: any = { + const attrs: ShapeAttrs = { x: position.x + x, y: position.y + y, width: cellWidth, @@ -196,9 +184,7 @@ export class CornerCell extends BaseCell { attrs.stroke = this.theme.cornerCell.cell.horizontalBorderColor; } - this.addShape('rect', { - attrs, - }); + this.backgroundShape = renderRect(this, attrs); } private drawHotpot() { @@ -234,7 +220,7 @@ export class CornerCell extends BaseCell { }); } - private ifNeedIcon() { + private shouldShowIcon() { // 批量折叠或者展开的icon,只存在树状结构的第一个cell前 return ( this.headerConfig.spreadsheet.isHierarchyTreeType() && diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index cbd58d67a7..f13bffbd66 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -11,6 +11,7 @@ import { IconCondition, S2CellType, ViewMeta, + ViewMetaIndex, } from '@/common/interface'; import { DataItem } from '@/common/interface/s2DataConfig'; import { getIconLayoutPosition } from '@/utils/condition'; @@ -23,8 +24,7 @@ import { import { renderLine, renderRect, renderText } from '@/utils/g-renders'; import { getEllipsisText } from '@/utils/text'; import { IShape, SimpleBBox } from '@antv/g-canvas'; -import { find, first, get, includes, isEmpty, map, isEqual } from 'lodash'; -import type { SpreadSheet } from 'src/sheet-type'; +import { find, first, get, includes, isEmpty, isEqual, map } from 'lodash'; /** * DataCell for panelGroup area @@ -39,7 +39,10 @@ import type { SpreadSheet } from 'src/sheet-type'; * 3、left rect area is interval(in left) and text(in right) */ export class DataCell extends BaseCell { - // 3、condition shapes + // cell configs conditions(Determine how to render this cell) + protected conditions: Conditions; + + // condition shapes // background color by bg condition protected conditionBgShape: IShape; @@ -49,17 +52,8 @@ export class DataCell extends BaseCell { // interval condition shape protected intervalShape: IShape; - // 4、render main text - protected textShape: IShape; - - // 5、brush-select prepareSelect border - protected interactiveBorderShape: IShape; - - // cell configs conditions(Determine how to render this cell) - protected conditions: Conditions; - - constructor(meta: ViewMeta, spreadsheet: SpreadSheet) { - super(meta, spreadsheet); + public get cellType() { + return CellTypes.DATA_CELL; } protected handlePrepareSelect(cells: S2CellType[]) { @@ -165,10 +159,6 @@ export class DataCell extends BaseCell { }; } - public getInteractiveBgShape() { - return this.interactiveBgShape; - } - /** * @description get cell text style * @protected @@ -245,7 +235,6 @@ export class DataCell extends BaseCell { } protected initCell() { - this.cellType = this.getCellType(); this.conditions = this.spreadsheet.options?.conditions; this.drawBackgroundShape(); this.drawStateShapes(); @@ -256,10 +245,6 @@ export class DataCell extends BaseCell { this.update(); } - protected getCellType() { - return CellTypes.DATA_CELL; - } - // 根据state要改变样式的shape protected drawStateShapes() { this.drawInteractiveBgShape(); @@ -294,7 +279,7 @@ export class DataCell extends BaseCell { * @param condition */ protected mappingValue(condition: Condition): CellMapping { - const value = (this.meta.fieldValue as unknown) as number; + const value = this.meta.fieldValue as unknown as number; return condition?.mapping(value, get(this.meta.data, [0])); } @@ -328,12 +313,12 @@ export class DataCell extends BaseCell { const position = this.getTextPosition(); this.textShape = renderText( + this, [this.textShape], position.x, position.y, ellipsisText, { ...textStyle, fill: textFill }, - this, ); } @@ -413,15 +398,17 @@ export class DataCell extends BaseCell { // 往内缩一个像素,避免和外边框重叠 const margin = 1; const { x, y, height, width } = this.meta; - this.interactiveBorderShape = renderRect(this, { - x: x + margin, - y: y + margin, - width: width - margin * 2, - height: height - margin * 2, - fill: 'transparent', - stroke: 'transparent', - }); - this.stateShapes.push(this.interactiveBorderShape); + this.stateShapes.set( + 'interactiveBorderShape', + renderRect(this, { + x: x + margin, + y: y + margin, + width: width - margin * 2, + height: height - margin * 2, + fill: 'transparent', + stroke: 'transparent', + }), + ); } /** @@ -429,15 +416,17 @@ export class DataCell extends BaseCell { */ protected drawInteractiveBgShape() { const { x, y, height, width } = this.meta; - this.interactiveBgShape = renderRect(this, { - x, - y, - width, - height, - fill: 'transparent', - stroke: 'transparent', - }); - this.stateShapes.push(this.interactiveBgShape); + this.stateShapes.set( + 'interactiveBgShape', + renderRect(this, { + x, + y, + width, + height, + fill: 'transparent', + stroke: 'transparent', + }), + ); } /** @@ -527,11 +516,8 @@ export class DataCell extends BaseCell { } } - /** - * @description change the state style for column selection or row selection - * @param index colIndex / rowIndex - */ - private changeRowColSelectState(index: 'colIndex' | 'rowIndex') { + // dataCell根据state 改变当前样式, + private changeRowColSelectState(index: ViewMetaIndex) { const currentIndex = get(this.meta, index); const nodes = this.spreadsheet.interaction.getState()?.nodes; const selectedIndexes = map(nodes, (node) => get(node, index)); @@ -554,26 +540,34 @@ export class DataCell extends BaseCell { // horizontal border renderLine( - x, - y, - x + width, - y, - cell.horizontalBorderColor, - cell.horizontalBorderWidth, this, - cell.horizontalBorderColorOpacity, + { + x1: x, + y1: y, + x2: x + width, + y2: y, + }, + { + stroke: cell.horizontalBorderColor, + lineWidth: cell.horizontalBorderWidth, + opacity: cell.horizontalBorderColorOpacity, + }, ); // vertical border renderLine( - x + width, - y, - x + width, - y + height, - cell.verticalBorderColor, - cell.verticalBorderWidth, this, - cell.horizontalBorderColorOpacity, + { + x1: x + width, + y1: y, + x2: x + width, + y2: y + height, + }, + { + stroke: cell.verticalBorderColor, + lineWidth: cell.verticalBorderWidth, + opacity: cell.horizontalBorderColorOpacity, + }, ); } } diff --git a/packages/s2-core/src/cell/detail-col-cell.ts b/packages/s2-core/src/cell/detail-col-cell.ts index b0c55fa06d..ae27df0145 100644 --- a/packages/s2-core/src/cell/detail-col-cell.ts +++ b/packages/s2-core/src/cell/detail-col-cell.ts @@ -1,10 +1,9 @@ import { ColCell } from '@/cell/col-cell'; +import { renderText } from '@/utils/g-renders'; import { get } from 'lodash'; -import { Node } from '@/index'; -import { SpreadSheet } from '../sheet-type'; -import { getEllipsisText, getTextPosition } from '../utils/text'; import { EXTRA_FIELD } from '../common/constant'; import { renderDetailTypeSortIcon } from '../facet/layout/util/add-detail-type-sort-icon'; +import { getEllipsisText, getTextPosition } from '../utils/text'; export class DetailColCell extends ColCell { protected drawCellText() { @@ -51,16 +50,18 @@ export class DetailColCell extends ColCell { textStyle, ); - this.addShape('text', { - attrs: { - x: textX, - y: textY, - text, + this.textShape = renderText( + this, + [this.textShape], + textX, + textY, + text, + { textAlign, ...textStyle, - cursor: 'pointer', }, - }); + { cursor: 'pointer' }, + ); renderDetailTypeSortIcon( this, diff --git a/packages/s2-core/src/cell/detail-row-cell.ts b/packages/s2-core/src/cell/detail-data-cell.ts similarity index 88% rename from packages/s2-core/src/cell/detail-row-cell.ts rename to packages/s2-core/src/cell/detail-data-cell.ts index 503ad000d3..28b28cd2b4 100644 --- a/packages/s2-core/src/cell/detail-row-cell.ts +++ b/packages/s2-core/src/cell/detail-data-cell.ts @@ -1,9 +1,7 @@ -import { get } from 'lodash'; import { DataCell } from 'src/cell/data-cell'; import { renderText } from '../utils/g-renders'; -import { isMobile } from '../utils/is-mobile'; import { getEllipsisText, getTextPosition } from '../utils/text'; -export class DetailRowCell extends DataCell { +export class DetailDataCell extends DataCell { protected drawTextShape() { const { x, y, height, width } = this.getContentAreaBBox(); const { formattedValue: text } = this.getData(); @@ -26,12 +24,12 @@ export class DetailRowCell extends DataCell { }; const position = getTextPosition(cellBoxCfg); this.textShape = renderText( + this, [this.textShape], position.x, position.y, ellipsisText, textStyle, - this, ); } } diff --git a/packages/s2-core/src/cell/header-cell.ts b/packages/s2-core/src/cell/header-cell.ts new file mode 100644 index 0000000000..e8725d223c --- /dev/null +++ b/packages/s2-core/src/cell/header-cell.ts @@ -0,0 +1,36 @@ +import { BaseHeaderConfig } from '@/facet/header/base'; +import { first } from 'lodash'; +import { BaseCell } from 'src/cell'; +import { CellTypes, InteractionStateName, Node, SpreadSheet } from '../index'; + +export abstract class HeaderCell extends BaseCell { + protected headerConfig: BaseHeaderConfig; + + constructor( + meta: Node, + spreadsheet: SpreadSheet, + headerConfig: BaseHeaderConfig, + ) { + super(meta, spreadsheet, headerConfig); + } + + protected handleRestOptions(...[headerConfig]: [BaseHeaderConfig]) { + this.headerConfig = headerConfig; + } + + public update() { + const stateName = this.spreadsheet.interaction.getCurrentStateName(); + const cells = this.spreadsheet.interaction.getActiveCells(); + const currentCell = first(cells); + if ( + !currentCell || + (stateName !== InteractionStateName.HOVER && + stateName !== InteractionStateName.HOVER_FOCUS) + ) { + return; + } + if (currentCell?.cellType === CellTypes.DATA_CELL || cells.includes(this)) { + this.updateByState(InteractionStateName.HOVER); + } + } +} diff --git a/packages/s2-core/src/cell/index.ts b/packages/s2-core/src/cell/index.ts index 07d55957fa..bf994e5f7e 100644 --- a/packages/s2-core/src/cell/index.ts +++ b/packages/s2-core/src/cell/index.ts @@ -4,13 +4,13 @@ import { CornerCell } from './corner-cell'; import { DataCell } from './data-cell'; import { MergedCells } from './merged-cells'; import { RowCell } from './row-cell'; -import { DetailRowCell } from './detail-row-cell'; +import { DetailDataCell } from './detail-data-cell'; import { DetailColCell } from './detail-col-cell'; import { TableDataCell } from './table-data-cell'; export { DetailColCell, - DetailRowCell, + DetailDataCell, RowCell, ColCell, DataCell, diff --git a/packages/s2-core/src/cell/merged-cells.ts b/packages/s2-core/src/cell/merged-cells.ts index e66c73530a..e234b141a8 100644 --- a/packages/s2-core/src/cell/merged-cells.ts +++ b/packages/s2-core/src/cell/merged-cells.ts @@ -1,5 +1,6 @@ +import { SpreadSheet } from '@/sheet-type'; import { getPolygonPoints } from '@/utils/interaction/merge-cells'; -import { IShape, SimpleBBox } from '@antv/g-canvas'; +import { SimpleBBox } from '@antv/g-canvas'; import { isEmpty, isObject } from 'lodash'; import { S2CellType } from 'src/common/interface/interaction'; import { renderPolygon } from 'src/utils/g-renders'; @@ -15,7 +16,21 @@ import { BaseCell } from './base-cell'; export class MergedCells extends BaseCell { public cells: S2CellType[]; - protected textShape: IShape; + public constructor( + meta: ViewMeta, + spreadsheet: SpreadSheet, + cells: S2CellType[], + ) { + super(meta, spreadsheet, cells); + } + + handleRestOptions(...[cells]: [S2CellType[]]) { + this.cells = cells; + } + + public get cellType() { + return CellTypes.MERGED_CELLS; + } public update() {} @@ -39,12 +54,7 @@ export class MergedCells extends BaseCell { }; } - handleRestOptions(...options: S2CellType[][]) { - this.cells = options[0]; - } - protected initCell() { - this.cellType = this.getCellType(); // TODO:1、条件格式支持; 2、交互态扩展; 3、合并后的单元格文字布局及文字内容(目前参考Excel合并后只保留第一个单元格子的数据) this.drawBackgroundShape(); // this.drawStateShapes(); @@ -52,15 +62,11 @@ export class MergedCells extends BaseCell { // this.update(); } - protected getCellType() { - return CellTypes.MERGED_CELLS; - } - /** * Get left rest area size by icon condition * @protected */ - protected getLeftAreaBBox(): SimpleBBox { + protected getContentAreaBBox(): SimpleBBox { const { x, y, height, width } = this.meta; return { x, diff --git a/packages/s2-core/src/cell/row-cell.ts b/packages/s2-core/src/cell/row-cell.ts index c68982f6cd..e8159f7edb 100644 --- a/packages/s2-core/src/cell/row-cell.ts +++ b/packages/s2-core/src/cell/row-cell.ts @@ -1,26 +1,26 @@ +import { + CellTypes, + COLOR_DEFAULT_RESIZER, + ID_SEPARATOR, + KEY_COLLAPSE_TREE_ROWS, + KEY_GROUP_ROW_RESIZER, +} from '@/common/constant'; import { GuiIcon } from '@/common/icons'; import { HIT_AREA } from '@/facet/header/base'; import { ResizeInfo } from '@/facet/header/interface'; import { RowHeaderConfig } from '@/facet/header/row'; -import { renderRect, updateShapeAttr } from '@/utils/g-renders'; +import { renderLine, renderRect, renderText } from '@/utils/g-renders'; import { getAllChildrenNodeHeight } from '@/utils/get-all-children-node-height'; import { isMobile } from '@/utils/is-mobile'; import { getAdjustPosition } from '@/utils/text-absorption'; import { IGroup } from '@antv/g-canvas'; import { GM } from '@antv/g-gesture'; -import { each, first, get, isEmpty } from 'lodash'; -import { - CellTypes, - COLOR_DEFAULT_RESIZER, - ID_SEPARATOR, - KEY_COLLAPSE_TREE_ROWS, - KEY_GROUP_ROW_RESIZER, - InteractionStateName, -} from '@/common/constant'; -import { Node } from '../index'; +import { get } from 'lodash'; import { getEllipsisText, measureTextWidth } from '../utils/text'; -import { BaseCell } from './base-cell'; -export class RowCell extends BaseCell { +import { HeaderCell } from './header-cell'; + +console.log(HeaderCell); +export class RowCell extends HeaderCell { protected headerConfig: RowHeaderConfig; // 绘制完其他后,需要额外绘制的起始x坐标 @@ -32,31 +32,8 @@ export class RowCell extends BaseCell { // mobile event private gm: GM; - public update() { - const stateName = this.spreadsheet.interaction.getCurrentStateName(); - const cells = this.spreadsheet.interaction.getActiveCells(); - const currentCell = first(cells); - - if ( - !currentCell || - (stateName !== InteractionStateName.HOVER && - stateName !== InteractionStateName.HOVER_FOCUS) - ) { - return; - } - if (currentCell?.cellType === CellTypes.DATA_CELL || cells.includes(this)) { - this.updateByState(InteractionStateName.HOVER); - } - } - - public setActive() { - updateShapeAttr(this.interactiveBgShape, 'fillOpacity', 1); - each(this.actionIcons, (icon) => icon.set('visible', true)); - } - - public setInactive() { - updateShapeAttr(this.interactiveBgShape, 'fillOpacity', 0); - // each(this.actionIcons, (icon) => icon.set('visible', false)); + public get cellType() { + return CellTypes.ROW_CELL; } public destroy(): void { @@ -64,14 +41,8 @@ export class RowCell extends BaseCell { this.gm?.destroy(); } - // TODO: options能不能不要固定顺序?headerConfig必须是 0下的吗? - protected handleRestOptions(...options: RowHeaderConfig[]) { - this.headerConfig = options[0]; - } - protected initCell() { - this.cellType = this.getCellType(); - // draw rect background + // 1、draw rect background this.drawBackgroundColor(); this.drawInteractiveBgShape(); // draw text @@ -88,16 +59,12 @@ export class RowCell extends BaseCell { this.update(); } - protected getCellType() { - return CellTypes.ROW_CELL; - } - protected drawBackgroundColor() { const { rowCell: rowHeaderStyle } = this.spreadsheet.theme; const bgColor = rowHeaderStyle.cell.backgroundColor; const { x, y, height, width } = this.meta; - renderRect(this, { + this.backgroundShape = renderRect(this, { x, y, width, @@ -111,15 +78,17 @@ export class RowCell extends BaseCell { // 交互使用的背景色 protected drawInteractiveBgShape() { const { x, y, height, width } = this.meta; - this.interactiveBgShape = renderRect(this, { - x, - y, - width, - height, - fill: 'transparent', - stroke: 'transparent', - }); - this.stateShapes.push(this.interactiveBgShape); + this.stateShapes.set( + 'interactiveBgShape', + renderRect(this, { + x, + y, + width, + height, + fill: 'transparent', + stroke: 'transparent', + }), + ); } protected drawIconInTree() { @@ -200,17 +169,20 @@ export class RowCell extends BaseCell { const { x, y } = this.meta; // 1、bottom border const textIndent = this.getTextIndent(); - this.addShape('line', { - attrs: { + renderLine( + this, + { x1: x + textIndent, y1: y, x2: position.x + width + viewportWidth + scrollX, y2: y, + }, + { stroke: this.theme.rowCell.cell.horizontalBorderColor, - opacity: this.theme.rowCell.cell.horizontalBorderOpacity, lineWidth: this.theme.rowCell.cell.horizontalBorderWidth, + opacity: this.theme.rowCell.cell.horizontalBorderOpacity, }, - }); + ); } protected isTreeType() { @@ -291,40 +263,41 @@ export class RowCell extends BaseCell { textTheme.fontSize, ); - const textShape = this.addShape('text', { - attrs: { - x: textX, - y: textY, - text, - ...textStyle, - cursor: 'pointer', - }, - }); + this.textShape = renderText( + this, + [this.textShape], + textX, + textY, + text, + textStyle, + { cursor: 'pointer' }, + ); + // handle link nodes if (linkFieldIds.includes(this.meta.key)) { const device = get(this.headerConfig, 'spreadsheet.options.style.device'); // 配置了链接跳转 if (!isMobile(device)) { - const textBBox = textShape.getBBox(); - this.addShape('line', { - attrs: { + const textBBox = this.textShape.getBBox(); + renderLine( + this, + { x1: textBBox.bl.x, y1: textBBox.bl.y + 1, x2: textBBox.br.x, y2: textBBox.br.y + 1, - stroke: textStyle.fill, - lineWidth: 1, }, - }); - textShape.attr({ + { stroke: textStyle.fill, lineWidth: 1 }, + ); + this.textShape.attr({ appendInfo: { isRowHeaderText: true, // 标记为行头文本,方便做链接跳转直接识别 cellData: this.meta, }, }); } else { - textShape.attr({ - fill: '#0000ee', + this.textShape.attr({ + fill: textTheme.linkTextFill, appendInfo: { isRowHeaderText: true, // 标记为行头文本,方便做链接跳转直接识别 cellData: this.meta, @@ -381,12 +354,8 @@ export class RowCell extends BaseCell { protected drawActionIcons() { const rowActionIcons = this.spreadsheet.options.rowActionIcons; if (!rowActionIcons) return; - const { - iconTypes, - display, - action, - customDisplayByRowName, - } = rowActionIcons; + const { iconTypes, display, action, customDisplayByRowName } = + rowActionIcons; if (customDisplayByRowName) { const { rowNames, mode } = customDisplayByRowName; const rowIds = rowNames.map((rowName) => `root${ID_SEPARATOR}${rowName}`); diff --git a/packages/s2-core/src/common/constant/interaction.ts b/packages/s2-core/src/common/constant/interaction.ts index 5f2cb9516c..ef8f9b349a 100644 --- a/packages/s2-core/src/common/constant/interaction.ts +++ b/packages/s2-core/src/common/constant/interaction.ts @@ -23,10 +23,12 @@ export enum InteractionStateName { HOVER = 'hover', HOVER_FOCUS = 'hoverFocus', PREPARE_SELECT = 'prepareSelect', + OUT_OF_SPOTLIGHT = 'outOfTheSpotlight', } export enum CellTypes { DATA_CELL = 'dataCell', + HEADER_CELL = 'headerCell', ROW_CELL = 'rowCell', COL_CELL = 'colCell', CORNER_CELL = 'cornerCell', diff --git a/packages/s2-core/src/common/interface/basic.ts b/packages/s2-core/src/common/interface/basic.ts index 76d0964273..dc8c615ceb 100644 --- a/packages/s2-core/src/common/interface/basic.ts +++ b/packages/s2-core/src/common/interface/basic.ts @@ -13,8 +13,8 @@ import { BaseDataSet } from 'src/data-set'; import { Frame } from 'src/facet/header'; import { Padding } from '../interface/theme'; import { BaseTooltip } from '../tooltip'; -import { DataItem, S2DataConfig } from './s2DataConfig'; import { S2CellType } from './interaction'; +import { DataItem, S2DataConfig } from './s2DataConfig'; import { IconTheme } from './theme'; import { Event } from '@antv/g-canvas'; @@ -375,6 +375,9 @@ export interface ViewMeta { colId?: string; [key: string]: any; } + +export type ViewMetaIndex = keyof Pick; + export type GetCellMeta = (rowIndex: number, colIndex: number) => ViewMeta; export interface LayoutResult { diff --git a/packages/s2-core/src/common/interface/interaction.ts b/packages/s2-core/src/common/interface/interaction.ts index 3854b56a20..6e78cbd3fe 100644 --- a/packages/s2-core/src/common/interface/interaction.ts +++ b/packages/s2-core/src/common/interface/interaction.ts @@ -1,12 +1,14 @@ import { BaseCell, ColCell, CornerCell, DataCell, RowCell } from '@/cell'; +import { HeaderCell } from '@/cell/header-cell'; +import { Node } from '@/index'; import { BaseInteraction } from '@/interaction/base'; import { SpreadSheet } from '@/sheet-type'; import { InteractionStateName } from '../constant'; import { ViewMeta } from './basic'; -import { Node } from '@/index'; export type S2CellType = ViewMeta> = | DataCell + | HeaderCell | ColCell | CornerCell | RowCell @@ -43,3 +45,5 @@ export interface BrushRange { width: number; height: number; } + +export type StateShapeLayer = 'interactiveBgShape' | 'interactiveBorderShape'; diff --git a/packages/s2-core/src/common/interface/theme.ts b/packages/s2-core/src/common/interface/theme.ts index 58e22d6c68..73fcab3e07 100644 --- a/packages/s2-core/src/common/interface/theme.ts +++ b/packages/s2-core/src/common/interface/theme.ts @@ -1,8 +1,11 @@ +import { InteractionStateName } from '../constant'; + // 文本内容的水平对齐方式, 默认 left export type TextAlign = 'left' | 'center' | 'right'; // 绘制文本时的基线, 对应垂直方向对齐方式 默认 bottom export type TextBaseline = 'top' | 'middle' | 'bottom'; + export interface Palette { /* brand colors */ brandColors: string[]; @@ -12,6 +15,7 @@ export interface Palette { semanticColors: { red?: string; green?: string; + blue?: string; /* 额外颜色字段 */ [key: string]: string; }; @@ -31,6 +35,10 @@ export interface InteractionStateTheme { opacity?: string | number; } +export type InteractionState = { + [K in InteractionStateName]?: InteractionStateTheme; +}; + export type Margin = Padding; export interface TextTheme { @@ -38,6 +46,7 @@ export interface TextTheme { fontSize?: number; fontWeight?: string; fill?: string; + linkTextFill?: string; opacity?: number; textAlign?: TextAlign; textBaseline?: TextBaseline; @@ -64,18 +73,9 @@ export interface CellTheme { verticalBorderWidth?: number; /* 单元格内边距 */ padding: Padding; - /* hover 单元格状态 */ - hover?: InteractionStateTheme; - /* hover 焦点单元格 */ - hoverFocus?: InteractionStateTheme; - /* 选中态 */ - selected?: InteractionStateTheme; - /* 预选中态 */ - prepareSelect?: InteractionStateTheme; + interactionState?: InteractionState; /* 单元格内条件格式-迷你条形图高度 */ miniBarChartHeight?: number; - /* 聚光灯之外的单元格 */ - outOfTheSpotlight?: InteractionStateTheme; /* 额外属性字段 */ [key: string]: any; } diff --git a/packages/s2-core/src/facet/base-facet.ts b/packages/s2-core/src/facet/base-facet.ts index e9211738ab..93beaceb68 100644 --- a/packages/s2-core/src/facet/base-facet.ts +++ b/packages/s2-core/src/facet/base-facet.ts @@ -162,9 +162,9 @@ export abstract class BaseFacet { }; onContainerWheelForPc = () => { - (this.spreadsheet.container.get( - 'el', - ) as HTMLCanvasElement).addEventListener('wheel', this.onWheel); + ( + this.spreadsheet.container.get('el') as HTMLCanvasElement + ).addEventListener('wheel', this.onWheel); }; onContainerWheelForMobile = () => { @@ -175,13 +175,13 @@ export abstract class BaseFacet { const originEvent = ev.event; const { deltaX, deltaY, x, y } = ev; // The coordinates of mobile and pc are three times different - this.onWheel(({ + this.onWheel({ ...originEvent, deltaX, deltaY, layerX: x / 3, layerY: y / 3, - } as unknown) as S2WheelEvent); + } as unknown as S2WheelEvent); }); }; @@ -1000,7 +1000,8 @@ export abstract class BaseFacet { viewportHeight: height, position: { x, y: 0 }, data: this.layoutResult.colNodes, - scrollContainsRowHeader: this.cfg.spreadsheet.isScrollContainsRowHeader(), + scrollContainsRowHeader: + this.cfg.spreadsheet.isScrollContainsRowHeader(), offset: 0, formatter: (field: string): Formatter => this.cfg.dataSet.getFieldFormatter(field), @@ -1054,7 +1055,8 @@ export abstract class BaseFacet { // When both a row header and a panel scroll bar exist, show viewport shadow showViewPortRightShadow: !isNil(this.hRowScrollBar) && !isNil(this.hScrollBar), - scrollContainsRowHeader: this.cfg.spreadsheet.isScrollContainsRowHeader(), + scrollContainsRowHeader: + this.cfg.spreadsheet.isScrollContainsRowHeader(), isPivotMode: this.cfg.spreadsheet.isPivotMode(), spreadsheet: this.cfg.spreadsheet, }; diff --git a/packages/s2-core/src/facet/header/corner.ts b/packages/s2-core/src/facet/header/corner.ts index a24f5a538e..1bd8e2f3ba 100644 --- a/packages/s2-core/src/facet/header/corner.ts +++ b/packages/s2-core/src/facet/header/corner.ts @@ -1,21 +1,21 @@ -import { SimpleBBox, Group, Point } from '@antv/g-canvas'; -import { get, last, includes, isEmpty } from 'lodash'; import { i18n } from '@/common/i18n'; +import { BaseDataSet } from '@/data-set'; +import { Group, Point, SimpleBBox } from '@antv/g-canvas'; +import { get, includes, isEmpty, last } from 'lodash'; import { - KEY_SERIES_NUMBER_NODE, - KEY_GROUP_CORNER_RESIZER, COLOR_DEFAULT_RESIZER, + KEY_GROUP_CORNER_RESIZER, + KEY_SERIES_NUMBER_NODE, } from '../../common/constant'; -import { BaseDataSet } from '@/data-set'; -import { SpreadSheet, Hierarchy, Node, CornerCell } from '../../index'; import { LayoutResult, S2Options, SpreadSheetFacetCfg, } from '../../common/interface'; +import { CornerCell, Hierarchy, Node, SpreadSheet } from '../../index'; +import { translateGroup } from '../utils'; import { BaseHeader, BaseHeaderConfig, HIT_AREA } from './base'; import { CornerData, ResizeInfo } from './interface'; -import { translateGroup } from '../utils'; export interface CornerHeaderConfig extends BaseHeaderConfig { // header's hierarchy type diff --git a/packages/s2-core/src/interaction/brush-selection.ts b/packages/s2-core/src/interaction/brush-selection.ts index 16c07dff87..f31fde83a0 100644 --- a/packages/s2-core/src/interaction/brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection.ts @@ -71,7 +71,7 @@ export class BrushSelection extends BaseInteraction { this.brushStage = InteractionBrushStage.CLICK; this.initPrepareSelectMaskShape(); - const originalEvent = (ev.originalEvent as unknown) as OriginalEvent; + const originalEvent = ev.originalEvent as unknown as OriginalEvent; const point: Point = { x: originalEvent.layerX, y: originalEvent.layerY }; this.dataCells = this.interaction.getPanelGroupAllDataCells(); @@ -89,7 +89,7 @@ export class BrushSelection extends BaseInteraction { event.preventDefault(); this.interaction.interceptEvent.add(DefaultInterceptEventType.HOVER); - const originalEvent = (event.originalEvent as unknown) as OriginalEvent; + const originalEvent = event.originalEvent as unknown as OriginalEvent; const currentPoint: Point = { x: originalEvent.layerX, y: originalEvent.layerY, diff --git a/packages/s2-core/src/interaction/events/click-events/data-cell-click.ts b/packages/s2-core/src/interaction/events/click-events/data-cell-click.ts index 9fd6cb0f0f..3885f17de6 100644 --- a/packages/s2-core/src/interaction/events/click-events/data-cell-click.ts +++ b/packages/s2-core/src/interaction/events/click-events/data-cell-click.ts @@ -51,8 +51,8 @@ export class DataCellClick extends BaseEvent { } private getTooltipOperator(meta: ViewMeta): TooltipOperatorOptions { - const cellOperator: TooltipOperatorOptions = this.spreadsheet.options - ?.cellOperator; + const cellOperator: TooltipOperatorOptions = + this.spreadsheet.options?.cellOperator; if (cellOperator) { return cellOperator; diff --git a/packages/s2-core/src/interaction/row-col-resize.ts b/packages/s2-core/src/interaction/row-col-resize.ts index b3ddd52577..83089e83da 100644 --- a/packages/s2-core/src/interaction/row-col-resize.ts +++ b/packages/s2-core/src/interaction/row-col-resize.ts @@ -124,9 +124,8 @@ export class RowColResize extends BaseInteraction { const children = this.resizeGroup.getChildren(); if (children) { const info = this.getResizeInfo(); - const startPoint: ['M', number, number] = children[0]?.attr( - 'path', - )[0]; + const startPoint: ['M', number, number] = + children[0]?.attr('path')[0]; const endPoint: ['M', number, number] = children[1]?.attr('path')[0]; let resizeEventType: ResizeEventType; diff --git a/packages/s2-core/src/sheet-type/index.ts b/packages/s2-core/src/sheet-type/index.ts index 7003fbb357..8bbb6b9c7b 100644 --- a/packages/s2-core/src/sheet-type/index.ts +++ b/packages/s2-core/src/sheet-type/index.ts @@ -1,4 +1,4 @@ -import { BaseCell, DataCell, DetailRowCell, TableDataCell } from '@/cell'; +import { BaseCell, DataCell, DetailDataCell, TableDataCell } from '@/cell'; import { KEY_AFTER_COLLAPSE_ROWS, KEY_COLLAPSE_ROWS, @@ -16,11 +16,11 @@ import { Pagination, S2CellType, S2DataConfig, + S2MountContainer, S2Options, safetyDataConfig, safetyOptions, SpreadSheetFacetCfg, - S2MountContainer, ThemeCfg, TooltipData, TooltipOptions, @@ -143,8 +143,9 @@ export class SpreadSheet extends EE { this.tooltip = this.renderTooltip(); if (!(this.tooltip instanceof BaseTooltip)) { console.warn( - `[Custom Tooltip]: ${(this - .tooltip as unknown)?.constructor?.toString()} should be extends from BaseTooltip`, + `[Custom Tooltip]: ${( + this.tooltip as unknown + )?.constructor?.toString()} should be extends from BaseTooltip`, ); } } @@ -509,7 +510,7 @@ export class SpreadSheet extends EE { const defaultCell = (facet: ViewMeta) => { if (this.isTableMode()) { if (this.options.showSeriesNumber && facet.colIndex === 0) { - return new DetailRowCell(facet, this); + return new DetailDataCell(facet, this); } return new TableDataCell(facet, this); } diff --git a/packages/s2-core/src/theme/index.ts b/packages/s2-core/src/theme/index.ts index df15c61d5f..b30d0145d6 100644 --- a/packages/s2-core/src/theme/index.ts +++ b/packages/s2-core/src/theme/index.ts @@ -7,7 +7,9 @@ import { getPalette } from '../utils/theme'; * @describe generate the theme according to the type * @param name */ -export const getTheme = (themeCfg: ThemeCfg): SpreadSheetTheme => { +export const getTheme = ( + themeCfg: Omit, +): SpreadSheetTheme => { const themePalette: Palette = themeCfg?.palette || getPalette(themeCfg?.name, themeCfg?.hueInvert); const { brandColors, grayColors, semanticColors } = themePalette; @@ -76,6 +78,7 @@ export const getTheme = (themeCfg: ThemeCfg): SpreadSheetTheme => { fontSize: 12, fontWeight: 'normal', fill: grayColors[6], + linkTextFill: brandColors[7], opacity: 1, textAlign: 'right', }, @@ -98,10 +101,13 @@ export const getTheme = (themeCfg: ThemeCfg): SpreadSheetTheme => { bottom: 12, left: 8, }, - // -------------- hover ------------------- - hover: { - backgroundColor: brandColors[2], - backgroundOpacity: 1, + /* ---------- interaction state ----------- */ + interactionState: { + // -------------- hover ------------------- + hover: { + backgroundColor: brandColors[2], + backgroundOpacity: 1, + }, }, }, icon: { @@ -151,10 +157,13 @@ export const getTheme = (themeCfg: ThemeCfg): SpreadSheetTheme => { bottom: 12, left: 8, }, - // -------------- hover ------------------- - hover: { - backgroundColor: brandColors[5], - backgroundOpacity: 1, + /* ---------- interaction state ----------- */ + interactionState: { + // -------------- hover ------------------- + hover: { + backgroundColor: brandColors[5], + backgroundOpacity: 1, + }, }, }, icon: { @@ -207,38 +216,42 @@ export const getTheme = (themeCfg: ThemeCfg): SpreadSheetTheme => { bottom: 12, left: 8, }, - // -------------- hover ------------------- - hover: { - backgroundColor: brandColors[2], - backgroundOpacity: 1, - }, - // -------------- keep hover ------------------- - hoverFocus: { - backgroundColor: brandColors[2], - backgroundOpacity: 1, - borderColor: grayColors[6], - borderOpacity: 1, - }, - // -------------- selected ------------------- - selected: { - backgroundColor: brandColors[2], - backgroundOpacity: 1, - }, - // -------------- unselected ------------------- - // TODO: 条件格式的icon和mini chart也需要置灰 - unselected: { - backgroundOpacity: 0.3, - textOpacity: 0.3, - }, - // -------------- prepare select -------------- - prepareSelect: { - borderColor: grayColors[6], - borderOpacity: 1, - }, - // -------------- out of spotlight -------------- - outOfTheSpotlight: { - opacity: 0.3, + /* ---------- interaction state ----------- */ + interactionState: { + // -------------- hover ------------------- + hover: { + backgroundColor: brandColors[2], + backgroundOpacity: 1, + }, + // -------------- keep hover ------------------- + hoverFocus: { + backgroundColor: brandColors[2], + backgroundOpacity: 1, + borderColor: grayColors[6], + borderOpacity: 1, + }, + // -------------- selected ------------------- + selected: { + backgroundColor: brandColors[2], + backgroundOpacity: 1, + }, + // -------------- unselected ------------------- + // TODO: 条件格式的icon和mini chart也需要置灰 + unselected: { + backgroundOpacity: 0.3, + textOpacity: 0.3, + }, + // -------------- prepare select -------------- + prepareSelect: { + borderColor: grayColors[6], + borderOpacity: 1, + }, + // -------------- out of spotlight -------------- + outOfTheSpotlight: { + opacity: 0.3, + }, }, + // ------------- mini chart --------------- miniBarChartHeight: MINI_BAR_CHART_HEIGHT, }, diff --git a/packages/s2-core/src/theme/palette/colorful-blue.ts b/packages/s2-core/src/theme/palette/colorful-blue.ts index f4ff325de5..c9fd2de770 100644 --- a/packages/s2-core/src/theme/palette/colorful-blue.ts +++ b/packages/s2-core/src/theme/palette/colorful-blue.ts @@ -8,6 +8,7 @@ export const paletteColorfulBlue = { '#3471F9', '#2C60D3', '#2C60D3', + '#0000ee', ], // ----------neutral colors ---------- diff --git a/packages/s2-core/src/theme/palette/default.ts b/packages/s2-core/src/theme/palette/default.ts index 19d18facef..6df5249f42 100644 --- a/packages/s2-core/src/theme/palette/default.ts +++ b/packages/s2-core/src/theme/palette/default.ts @@ -8,6 +8,7 @@ export const paletteDefault = { '#ffffff', '#D9EAFF', '#2C60D3', + '#0000ee', ], // ----------neutral colors ---------- diff --git a/packages/s2-core/src/theme/palette/simple-blue.ts b/packages/s2-core/src/theme/palette/simple-blue.ts index 2a0f27fce0..f06ffe7abd 100644 --- a/packages/s2-core/src/theme/palette/simple-blue.ts +++ b/packages/s2-core/src/theme/palette/simple-blue.ts @@ -8,6 +8,7 @@ export const paletteSimpleBlue = { '#E0E9FE', '#CCDBFD', '#2C60D3', + '#0000ee', ], // ----------neutral colors ---------- diff --git a/packages/s2-core/src/utils/g-renders.ts b/packages/s2-core/src/utils/g-renders.ts index 2601f58f11..01d788cb1b 100644 --- a/packages/s2-core/src/utils/g-renders.ts +++ b/packages/s2-core/src/utils/g-renders.ts @@ -4,7 +4,7 @@ */ import { TextTheme } from '@/common/interface/theme'; import { Group, IShape, ShapeAttrs } from '@antv/g-canvas'; -import _ from 'lodash'; +import { forEach, isEmpty, set } from 'lodash'; export function renderRect(group: Group, attrs: ShapeAttrs): IShape { return group?.addShape?.('rect', { @@ -20,16 +20,16 @@ export function renderPolygon(group: Group, attrs: ShapeAttrs): IShape { } export function renderText( + group: Group, shapes: IShape[], x: number, y: number, text: string, textStyle: TextTheme, - group: Group, - extraStyle?: any, + extraStyle?: ShapeAttrs, ): IShape { - if (!_.isEmpty(shapes) && group) { - _.forEach(shapes, (shape: IShape) => { + if (!isEmpty(shapes) && group) { + forEach(shapes, (shape: IShape) => { if (group.contain(shape)) group.removeChild(shape, true); }); } @@ -45,34 +45,33 @@ export function renderText( } export function renderLine( - x1: number, - y1: number, - x2: number, - y2: number, - stroke: string, - lineWidth: number, group: Group, - opacity?: number, + coordinate: { x1: number; y1: number; x2: number; y2: number }, + lineStyle: Pick, ): IShape { return group?.addShape?.('line', { + zIndex: 100, attrs: { - x1, - y1, - x2, - y2, - stroke, - opacity, - lineWidth, + ...coordinate, + ...lineStyle, }, }); } -export function updateShapeAttr( +export function updateShapeAttr( shape: IShape, - attribute: keyof ShapeAttrs, - value: ShapeAttrs[keyof ShapeAttrs], + attribute: K, + value: ShapeAttrs[K], ) { if (shape) { - _.set(shape, `attrs.${attribute}`, value); + set(shape, `attrs.${attribute}`, value); } } + +export function updateFillOpacity(shape: IShape, opacity: number) { + updateShapeAttr(shape, 'fillOpacity', opacity); +} + +export function updateStrokeOpacity(shape: IShape, opacity: number) { + updateShapeAttr(shape, 'strokeOpacity', opacity); +} diff --git a/packages/s2-core/src/utils/interaction/merge-cells.ts b/packages/s2-core/src/utils/interaction/merge-cells.ts index 1df421db02..2e5babd788 100644 --- a/packages/s2-core/src/utils/interaction/merge-cells.ts +++ b/packages/s2-core/src/utils/interaction/merge-cells.ts @@ -1,15 +1,15 @@ import { + filter, find, - isEqual, forEach, + isArray, isEmpty, - filter, + isEqual, merge, - isArray, } from 'lodash'; -import { S2CellType } from 'src/common/interface/interaction'; -import { MergedCellInfo } from 'src/common/interface/index'; import { MergedCells } from 'src/cell/merged-cells'; +import { MergedCellInfo } from 'src/common/interface/index'; +import { S2CellType } from 'src/common/interface/interaction'; import { SpreadSheet } from 'src/sheet-type'; /** @@ -146,10 +146,10 @@ export const mergeCells = ( cellsInfo: MergedCellInfo[], hideData?: boolean, ) => { - const allCells = (filter( + const allCells = filter( sheet.panelGroup.getChildren(), (child) => !(child instanceof MergedCells), - ) as unknown) as S2CellType[]; + ) as unknown as S2CellType[]; const { cells, viewMeta } = getCellsByInfo(cellsInfo, allCells); if (!isEmpty(cells)) { @@ -192,10 +192,10 @@ export const updateMergedCells = (sheet: SpreadSheet) => { const mergedCellsInfo = sheet.options?.mergedCellsInfo; if (isEmpty(mergedCellsInfo)) return; - const allCells = (filter( + const allCells = filter( sheet.panelGroup.getChildren(), (child) => !(child instanceof MergedCells), - ) as unknown) as S2CellType[]; + ) as unknown as S2CellType[]; if (isEmpty(allCells)) return; const allMergedCells = []; @@ -203,10 +203,10 @@ export const updateMergedCells = (sheet: SpreadSheet) => { allMergedCells.push(getCellsByInfo(cellsInfo, allCells)); }); - const oldMergedCells = (filter( + const oldMergedCells = filter( sheet.panelGroup.getChildren(), (child) => child instanceof MergedCells, - ) as unknown) as MergedCells; + ) as unknown as MergedCells; allMergedCells.forEach((mergedCell) => { const { cells, viewMeta } = mergedCell; diff --git a/packages/s2-core/src/utils/text.ts b/packages/s2-core/src/utils/text.ts index 3631029b19..fa10900272 100644 --- a/packages/s2-core/src/utils/text.ts +++ b/packages/s2-core/src/utils/text.ts @@ -23,13 +23,8 @@ const ctx = canvas.getContext('2d'); */ export const measureTextWidth = memoize( (text: number | string = '', font: unknown): number => { - const { - fontSize, - fontFamily, - fontWeight, - fontStyle, - fontVariant, - } = font as CSSStyleDeclaration; + const { fontSize, fontFamily, fontWeight, fontStyle, fontVariant } = + font as CSSStyleDeclaration; ctx.font = [ fontStyle, fontVariant, @@ -302,7 +297,7 @@ const getStyle = ( * @param cell */ export const drawObjectText = (cell) => { - const { x, y, height, width } = cell.getLeftAreaBBox(); + const { x, y, height, width } = cell.getContentAreaBBox(); const { formattedValue: text } = cell.getData(); const labelStyle = cell.theme?.view?.bolderText; const textStyle = cell.theme?.view?.text; @@ -313,6 +308,7 @@ export const drawObjectText = (cell) => { const realWidth = width / (text?.values[0].length + 1); const realHeight = height / (text?.values.length + 1); renderText( + cell, cell.textShape, calX(x, padding), y + realHeight / 2, @@ -323,7 +319,6 @@ export const drawObjectText = (cell) => { ), labelStyle, textFill, - cell, ); const { values: textValues } = text; @@ -349,13 +344,13 @@ export const drawObjectText = (cell) => { curX = calX(x, padding, totalWidth); totalWidth += curWidth; curTextShape = renderText( + cell, cell.textShape, curX, curY, getEllipsisText(`${curText}`, curWidth, curStyle), curStyle, curStyle?.fill, - cell, ); } } @@ -367,7 +362,7 @@ export const drawObjectText = (cell) => { * @returns 文本左上角起点坐标 */ export const drawStringText = (cell) => { - const { x, y, height, width } = cell.getLeftAreaBBox(); + const { x, y, height, width } = cell.getContentAreaBBox(); const { formattedValue: text } = cell.getData(); const { isTotals } = cell.meta; const textStyle = isTotals @@ -377,6 +372,7 @@ export const drawStringText = (cell) => { const padding = cell.theme.dataCell.cell.padding; cell.textShape = renderText( + cell, cell.textShape, x + width - padding.right, y + height / 2, @@ -387,7 +383,6 @@ export const drawStringText = (cell) => { ), textStyle, textFill, - cell, ); }; diff --git a/packages/s2-core/src/utils/tooltip.ts b/packages/s2-core/src/utils/tooltip.ts index e5cdc4431f..16947ed61a 100644 --- a/packages/s2-core/src/utils/tooltip.ts +++ b/packages/s2-core/src/utils/tooltip.ts @@ -186,12 +186,9 @@ export const getFieldList = ( concat([], fields), (field) => field !== EXTRA_FIELD && hoverData[field], ); - const fieldList = map( - currFields, - (field: string): ListItem => { - return getListItem(spreadsheet, hoverData, field); - }, - ); + const fieldList = map(currFields, (field: string): ListItem => { + return getListItem(spreadsheet, hoverData, field); + }); return fieldList; }; diff --git a/packages/s2-core/tests/spreadsheet/table-sheet-spec.tsx b/packages/s2-core/tests/spreadsheet/table-sheet-spec.tsx index e1dd6e5a75..9d80a4a4aa 100644 --- a/packages/s2-core/tests/spreadsheet/table-sheet-spec.tsx +++ b/packages/s2-core/tests/spreadsheet/table-sheet-spec.tsx @@ -15,15 +15,13 @@ import { useEffect } from 'react'; const data = getMockData('../data/tableau-supermarket.csv'); -const getSpreadSheet = (ref) => ( - dom: string | HTMLElement, - dataCfg: S2DataConfig, - options: S2Options, -) => { - const s2 = new SpreadSheet(dom, dataCfg, options); - ref.current = s2; - return s2; -}; +const getSpreadSheet = + (ref) => + (dom: string | HTMLElement, dataCfg: S2DataConfig, options: S2Options) => { + const s2 = new SpreadSheet(dom, dataCfg, options); + ref.current = s2; + return s2; + }; const getDataCfg = () => { return {