UNPKG

@antv/s2

Version:

effective spreadsheet render core lib

478 lines (477 loc) 23.8 kB
"use strict"; /** * 获取tooltip中需要显示的数据项 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyTheElementInTooltip = exports.getTooltipVisibleOperator = exports.getTooltipOptions = exports.getTooltipOptionsByCellType = exports.getCellsTooltipData = exports.mergeCellInfo = exports.getTooltipData = exports.getSummaries = exports.getCustomFieldsSummaries = exports.getSelectedCellsData = exports.getSelectedCellIndexes = exports.getSummaryName = exports.getTooltipDetailList = exports.getHeadInfo = exports.getFieldList = exports.getListItem = exports.setTooltipContainerStyle = exports.getMergedQuery = exports.getTooltipDefaultOptions = exports.getAutoAdjustPosition = void 0; const lodash_1 = require("lodash"); const constant_1 = require("../common/constant"); const tooltip_1 = require("../common/constant/tooltip"); const i18n_1 = require("../common/i18n"); const cell_data_1 = require("../data-set/cell-data"); const utils_1 = require("../facet/utils"); const number_calculate_1 = require("../utils/number-calculate"); const merge_1 = require("./merge"); const text_1 = require("./text"); /** * calculate tooltip show position */ const getAutoAdjustPosition = ({ spreadsheet, position, tooltipContainer, autoAdjustBoundary, }) => { const canvas = spreadsheet.getCanvasElement(); let x = position.x + tooltip_1.TOOLTIP_POSITION_OFFSET.x; let y = position.y + tooltip_1.TOOLTIP_POSITION_OFFSET.y; if (!autoAdjustBoundary || !canvas) { return { x, y, }; } const isAdjustBodyBoundary = autoAdjustBoundary === 'body'; const { maxX, maxY } = spreadsheet.facet.panelBBox; const { width, height } = spreadsheet.options; const { top: canvasOffsetTop, left: canvasOffsetLeft } = canvas.getBoundingClientRect(); const { width: tooltipWidth, height: tooltipHeight } = tooltipContainer.getBoundingClientRect(); const { width: viewportWidth, height: viewportHeight } = document.body.getBoundingClientRect(); const maxWidth = isAdjustBodyBoundary ? viewportWidth : Math.min(width, maxX) + canvasOffsetLeft; const maxHeight = isAdjustBodyBoundary ? viewportHeight : Math.min(height, maxY) + canvasOffsetTop; if (x + tooltipWidth >= maxWidth) { x = maxWidth - tooltipWidth; } if (y + tooltipHeight >= maxHeight) { y = maxHeight - tooltipHeight; } return { x, y, }; }; exports.getAutoAdjustPosition = getAutoAdjustPosition; const getTooltipDefaultOptions = (options) => { return Object.assign({ operator: { menu: { onClick: lodash_1.noop, items: [], selectedKeys: [], }, }, enableFormat: true }, options); }; exports.getTooltipDefaultOptions = getTooltipDefaultOptions; const getMergedQuery = (meta) => { return Object.assign(Object.assign({}, meta === null || meta === void 0 ? void 0 : meta.colQuery), meta === null || meta === void 0 ? void 0 : meta.rowQuery); }; exports.getMergedQuery = getMergedQuery; const setTooltipContainerStyle = (container, options) => { if (!container) { return; } const { style, className = [], visible, dark = false } = options; if (style) { Object.assign(container.style, style); } if (className.length) { const classList = className.filter(Boolean); container.classList.add(...classList); } container.classList.toggle(tooltip_1.TOOLTIP_CONTAINER_SHOW_CLS, visible); container.classList.toggle(tooltip_1.TOOLTIP_CONTAINER_HIDE_CLS, !visible); container.classList.toggle(constant_1.DARK_THEME_CLS, dark); }; exports.setTooltipContainerStyle = setTooltipContainerStyle; const getListItem = (spreadsheet, { data, field, valueField, useCompleteDataForFormatter = true, targetCell, }) => { var _a, _b; const defaultFieldName = (_a = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _a === void 0 ? void 0 : _a.getFieldName(field); const name = spreadsheet.isCustomRowFields() ? (spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet.getCustomRowFieldName(targetCell)) || defaultFieldName : defaultFieldName; const formatter = (_b = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _b === void 0 ? void 0 : _b.getFieldFormatter(field); // 非数值类型的 data 不展示 (趋势分析表/迷你图/G2 图表),上层通过自定义 tooltip 的方式去自行定制 const dataValue = cell_data_1.CellData.getFieldValue(data, field); const displayDataValue = (0, lodash_1.isObject)(dataValue) ? null : dataValue; const value = formatter(valueField || displayDataValue, useCompleteDataForFormatter ? data : undefined); return { name, value, }; }; exports.getListItem = getListItem; const getFieldList = (spreadsheet, fields, activeData) => { const currentFields = (0, lodash_1.filter)((0, lodash_1.concat)([], fields), (field) => field !== constant_1.EXTRA_FIELD && cell_data_1.CellData.getFieldValue(activeData, field)); return (0, lodash_1.map)(currentFields, (field) => (0, exports.getListItem)(spreadsheet, { data: activeData, field, useCompleteDataForFormatter: false, })); }; exports.getFieldList = getFieldList; /** * 获取选中格行/列头信息 * @param spreadsheet * @param activeData */ const getHeadInfo = (spreadsheet, activeData, options) => { var _a, _b, _c, _d; const { isTotals } = options || {}; let colList = []; let rowList = []; if (activeData) { const colFields = (_b = (_a = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.columns; const rowFields = (_d = (_c = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _c === void 0 ? void 0 : _c.fields) === null || _d === void 0 ? void 0 : _d.rows; colList = (0, exports.getFieldList)(spreadsheet, (0, utils_1.getLeafColumnsWithKey)(colFields || []), activeData); rowList = (0, exports.getFieldList)(spreadsheet, rowFields, activeData); } // 此时是总计-总计 if ((0, lodash_1.isEmpty)(colList) && (0, lodash_1.isEmpty)(rowList) && isTotals) { colList = [{ value: (0, i18n_1.i18n)('总计') }]; } return { cols: colList, rows: rowList }; }; exports.getHeadInfo = getHeadInfo; /** * 获取数据明细 * @param spreadsheet * @param activeData * @param options */ const getTooltipDetailList = (spreadsheet, activeData, options, targetCell) => { if (!activeData) { return []; } const { isTotals } = options; const field = activeData[constant_1.EXTRA_FIELD]; const detailList = []; if (isTotals) { // total/subtotal detailList.push((0, exports.getListItem)(spreadsheet, { data: activeData, field, targetCell, valueField: activeData[constant_1.VALUE_FIELD], })); } else { detailList.push((0, exports.getListItem)(spreadsheet, { data: activeData, field, targetCell })); } return detailList; }; exports.getTooltipDetailList = getTooltipDetailList; const getSummaryName = (spreadsheet, currentField, isTotals) => { var _a; if (isTotals) { return (0, i18n_1.i18n)('总计'); } const name = (_a = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _a === void 0 ? void 0 : _a.getFieldName(currentField); return name && name !== 'undefined' ? name : ''; }; exports.getSummaryName = getSummaryName; const getRowOrColSelectedIndexes = (nodes, leafNodes, isRow = true) => { const selectedIndexes = []; (0, lodash_1.forEach)(leafNodes, (_, index) => { (0, lodash_1.forEach)(nodes, (item) => { if (!isRow && item.colIndex !== -1) { selectedIndexes.push([index, item.colIndex]); } else if (isRow && item.rowIndex !== -1) { selectedIndexes.push([item.rowIndex, index]); } }); }); return selectedIndexes; }; const getSelectedCellIndexes = (spreadsheet) => { var _a; const rowLeafNodes = spreadsheet.facet.getRowLeafNodes(); const colLeafNodes = spreadsheet.facet.getColLeafNodes(); const { nodes = [], cells = [] } = spreadsheet.interaction.getState(); const cellType = (_a = cells === null || cells === void 0 ? void 0 : cells[0]) === null || _a === void 0 ? void 0 : _a.type; // 高亮所有的子节点, 但是只有叶子节点需要参与数据计算 https://github.com/antvis/S2/pull/1443 const needCalcNodes = spreadsheet.isHierarchyTreeType() ? nodes : nodes.filter((node) => node === null || node === void 0 ? void 0 : node.isLeaf); if (cellType === constant_1.CellType.COL_CELL) { return getRowOrColSelectedIndexes(needCalcNodes, rowLeafNodes, false); } if (cellType === constant_1.CellType.ROW_CELL) { return getRowOrColSelectedIndexes(needCalcNodes, colLeafNodes); } return []; }; exports.getSelectedCellIndexes = getSelectedCellIndexes; const getSelectedCellsData = (spreadsheet, targetCell, onlyShowCellText) => { const { facet } = spreadsheet; /** * 当开启小计/总计后 * 1. [点击列头单元格时], 选中列所对应的数值单元格的数据如果是小计/总计, 则不应该参与计算: * - 1.1 [小计/总计 位于行头]: 点击的都是 (普通列头), 需要去除 (数值单元格) 对应 (行头为小计) 的单元格的数据 * - 1.2 [小计/总计 位于列头]: 点击的是 (普通列头/小计/总计列头), 由于行头没有, 所有数值单元格参与计算即可 * - 1.3 [小计/总计 同时位于行头/列头]: 和 1.1 处理一致 * 2. [点击行头单元格时]: * - 2.1 如果本身就是小计/总计单元格, 且列头无小计/总计, 则当前行所有 (数值单元格) 参与计算 * - 2.2 如果本身不是小计/总计单元格, 去除当前行/列 (含子节点) 所对应小计/总计数据 * * 3. [刷选/多选], 暂不考虑这种场景 * - 3.1 如果全部是小计或全部是总计单元格, 则正常计算 * - 3.2 如果部分是, 如何处理? 小计/总计不应该被选中, 还是数据不参与计算? * - 3.3 如果选中的含有小计, 并且有总计, 数据参与计算也没有意义, 如何处理? */ const isBelongTotalCell = (cellMeta) => { if (!cellMeta) { return false; } const targetCellMeta = targetCell === null || targetCell === void 0 ? void 0 : targetCell.getMeta(); // target: 当前点击的单元格类型 const isTargetTotalCell = targetCellMeta === null || targetCellMeta === void 0 ? void 0 : targetCellMeta.isTotals; const isTargetColCell = (targetCell === null || targetCell === void 0 ? void 0 : targetCell.cellType) === constant_1.CellType.COL_CELL; const isTargetRowCell = (targetCell === null || targetCell === void 0 ? void 0 : targetCell.cellType) === constant_1.CellType.ROW_CELL; if (!isTargetColCell && !isTargetRowCell) { return false; } const currentColCellNode = facet.getColNodeByIndex(cellMeta.colIndex); const currentRowCellNode = facet.getRowNodeByIndex(cellMeta.rowIndex); // 行头点击, 去除列头对应的小计/总计, 列头相反 const isTotalCell = isTargetColCell ? currentRowCellNode === null || currentRowCellNode === void 0 ? void 0 : currentRowCellNode.isTotals : currentColCellNode === null || currentColCellNode === void 0 ? void 0 : currentColCellNode.isTotals; return ((!isTargetTotalCell && (cellMeta === null || cellMeta === void 0 ? void 0 : cellMeta.isTotals)) || (isTargetTotalCell && isTotalCell)); }; // 列头选择和行头选择没有存所有selected的cell,因此要遍历index对比,而selected则不需要 if (onlyShowCellText) { // 行头列头单选多选 const selectedCellIndexes = (0, exports.getSelectedCellIndexes)(spreadsheet); return (0, lodash_1.compact)((0, lodash_1.map)(selectedCellIndexes, ([rowIndex, colIndex]) => { const currentCellMeta = facet.getCellMeta(rowIndex, colIndex); if (isBelongTotalCell(currentCellMeta)) { return; } return (currentCellMeta === null || currentCellMeta === void 0 ? void 0 : currentCellMeta.data) || (0, exports.getMergedQuery)(currentCellMeta); })); } // 其他(刷选,data cell多选) const cells = spreadsheet.interaction.getCells(); return cells .filter((cellMeta) => { const meta = facet.getCellMeta(cellMeta.rowIndex, cellMeta.colIndex); return !isBelongTotalCell(meta); }) .map((cellMeta) => { const meta = facet.getCellMeta(cellMeta.rowIndex, cellMeta.colIndex); return (meta === null || meta === void 0 ? void 0 : meta.data) || (0, exports.getMergedQuery)(meta); }); }; exports.getSelectedCellsData = getSelectedCellsData; const getCustomFieldsSummaries = (summaries) => { const customFieldGroup = (0, lodash_1.groupBy)(summaries, 'name'); return Object.keys(customFieldGroup || {}).map((name) => { const cellsData = customFieldGroup[name]; const selectedData = (0, lodash_1.flatMap)(cellsData, (cellData) => cellData.selectedData); const validCellsData = cellsData.filter((item) => (0, lodash_1.isNumber)(item.value)); const value = (0, lodash_1.isEmpty)(validCellsData) ? null : (0, lodash_1.sumBy)(validCellsData, 'value'); return { name, selectedData, value, }; }); }; exports.getCustomFieldsSummaries = getCustomFieldsSummaries; const getSummaries = (params) => { const { spreadsheet, targetCell, options = {} } = params; const summaries = []; const summary = {}; const isTableMode = spreadsheet.isTableMode(); if (isTableMode && (options === null || options === void 0 ? void 0 : options.onlyShowCellText)) { const meta = targetCell === null || targetCell === void 0 ? void 0 : targetCell.getMeta(); // 如果是列头, 获取当前列所有数据, 其他则获取当前整行 (1条数据) const selectedCellsData = ((meta === null || meta === void 0 ? void 0 : meta.field) ? spreadsheet.dataSet.getCellMultiData({ query: { field: meta.field } }) : [spreadsheet.dataSet.getRowData(meta)]); return [{ selectedData: selectedCellsData, name: '', value: '' }]; } // 拿到选择的所有 dataCell的数据 const selectedCellsData = (0, exports.getSelectedCellsData)(spreadsheet, targetCell, options.onlyShowCellText); (0, lodash_1.forEach)(selectedCellsData, (item) => { var _a; const key = item === null || item === void 0 ? void 0 : item[constant_1.EXTRA_FIELD]; if (summary[key]) { (_a = summary[key]) === null || _a === void 0 ? void 0 : _a.push(item); } else { summary[key] = [item]; } }); (0, lodash_1.mapKeys)(summary, (selected, field) => { var _a, _b; const name = spreadsheet.isCustomHeaderFields() ? spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet.getCustomRowFieldName(targetCell) : (0, exports.getSummaryName)(spreadsheet, field, options === null || options === void 0 ? void 0 : options.isTotals); let value = ''; let originVal = ''; if ((0, lodash_1.every)(selected, (item) => (0, number_calculate_1.isNotNumber)((0, lodash_1.get)(item, constant_1.VALUE_FIELD)))) { const { placeholder } = spreadsheet.options; const emptyPlaceholder = (0, text_1.getEmptyPlaceholder)(summary, placeholder); // 如果选中的单元格都无数据,则显示"-" 或 options 里配置的占位符 value = emptyPlaceholder; originVal = emptyPlaceholder; } else { const formatter = (_a = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _a === void 0 ? void 0 : _a.getFieldFormatter(field); const dataSum = (0, number_calculate_1.getDataSumByField)(selected, constant_1.VALUE_FIELD); originVal = dataSum; value = (_b = formatter === null || formatter === void 0 ? void 0 : formatter(dataSum, selected)) !== null && _b !== void 0 ? _b : parseFloat(dataSum.toPrecision(constant_1.PRECISION)); } summaries.push({ selectedData: selected, name: name || '', value, originValue: originVal, }); }); if (spreadsheet.isCustomHeaderFields()) { return (0, exports.getCustomFieldsSummaries)(summaries); } return summaries; }; exports.getSummaries = getSummaries; const getTooltipData = (params) => { var _a; const { spreadsheet, cellInfos = [], options = {}, targetCell } = params; let name = null; let summaries = []; let headInfo = null; let details = null; const description = spreadsheet.dataSet.getCustomFieldDescription(targetCell); const firstCellInfo = (cellInfos[0] || {}); if (!(options === null || options === void 0 ? void 0 : options.hideSummary)) { // 计算多项的 sum(默认为 sum,可自定义) summaries = (0, exports.getSummaries)({ spreadsheet, options, targetCell, }); } else if (options.onlyShowCellText) { // 行列头 hover & 明细表所有 hover const value = cell_data_1.CellData.getFieldValue(firstCellInfo, 'value'); const valueField = cell_data_1.CellData.getFieldValue(firstCellInfo, 'valueField'); const formatter = (_a = spreadsheet === null || spreadsheet === void 0 ? void 0 : spreadsheet.dataSet) === null || _a === void 0 ? void 0 : _a.getFieldFormatter(valueField); const formattedValue = formatter === null || formatter === void 0 ? void 0 : formatter(value); const cellText = options.enableFormat ? spreadsheet.dataSet.getFieldName(value) || formattedValue : spreadsheet.dataSet.getFieldName(valueField); // https://github.com/antvis/S2/issues/2843 name = (0, lodash_1.isString)(cellText) ? cellText : ''; } else { headInfo = (0, exports.getHeadInfo)(spreadsheet, firstCellInfo, options); details = (0, exports.getTooltipDetailList)(spreadsheet, firstCellInfo, options, targetCell); } const { interpretation, infos, tips } = (firstCellInfo || {}); return { name, summaries, interpretation, infos, tips, headInfo, details, description, }; }; exports.getTooltipData = getTooltipData; const mergeCellInfo = (cells) => (0, lodash_1.map)(cells, (stateCell) => { const stateCellMeta = stateCell.getMeta(); return (0, lodash_1.assign)({}, stateCellMeta.query || {}, (0, lodash_1.pick)(stateCellMeta, ['colIndex', 'rowIndex'])); }); exports.mergeCellInfo = mergeCellInfo; const getCellsTooltipData = (spreadsheet) => { if (!spreadsheet.interaction.isSelectedState()) { return []; } const { valueInCols } = spreadsheet.dataCfg.fields; // 包括不在可视区域内的格子, 如: 滚动刷选 return spreadsheet.interaction .getCells() .reduce((tooltipData, cellMeta) => { const meta = spreadsheet.facet.getCellMeta(cellMeta === null || cellMeta === void 0 ? void 0 : cellMeta.rowIndex, cellMeta === null || cellMeta === void 0 ? void 0 : cellMeta.colIndex); const query = (0, exports.getMergedQuery)(meta); if ((0, lodash_1.isEmpty)(meta) || (0, lodash_1.isEmpty)(query)) { return []; } const currentCellInfo = Object.assign(Object.assign({}, query), { colIndex: valueInCols ? meta === null || meta === void 0 ? void 0 : meta.colIndex : null, rowIndex: !valueInCols ? meta === null || meta === void 0 ? void 0 : meta.rowIndex : null }); const isEqualCellInfo = tooltipData.find((cellInfo) => (0, lodash_1.isEqual)(currentCellInfo, cellInfo)); if (!isEqualCellInfo) { tooltipData.push(currentCellInfo); } return tooltipData; }, []); }; exports.getCellsTooltipData = getCellsTooltipData; const getTooltipOptionsByCellType = (cellTooltipConfig = {}, cellType) => { const getOptionsByCell = (cellConfig) => (0, merge_1.customMerge)(cellTooltipConfig, cellConfig); const { colCell, rowCell, dataCell, cornerCell } = cellTooltipConfig; if (cellType === constant_1.CellType.COL_CELL) { return getOptionsByCell(colCell); } if (cellType === constant_1.CellType.ROW_CELL) { return getOptionsByCell(rowCell); } if (cellType === constant_1.CellType.DATA_CELL) { return getOptionsByCell(dataCell); } if (cellType === constant_1.CellType.CORNER_CELL) { return getOptionsByCell(cornerCell); } return Object.assign({}, cellTooltipConfig); }; exports.getTooltipOptionsByCellType = getTooltipOptionsByCellType; const getTooltipOptions = (spreadsheet, event) => { var _a; if (!event || !spreadsheet) { return null; } const { options, interaction } = spreadsheet; const cellType = (_a = spreadsheet.getCellType) === null || _a === void 0 ? void 0 : _a.call(spreadsheet, event === null || event === void 0 ? void 0 : event.target); // 如果没有 cellType, 说明是刷选丢失 event target 的场景, 此时从产生过交互状态的单元格里取, 避免刷选读取不到争取 tooltip 配置的问题 const sampleCell = (0, lodash_1.last)(interaction.getInteractedCells()); return (0, exports.getTooltipOptionsByCellType)(options.tooltip, cellType || (sampleCell === null || sampleCell === void 0 ? void 0 : sampleCell.cellType)); }; exports.getTooltipOptions = getTooltipOptions; const getTooltipVisibleOperator = (operation, options) => { var _a; const { defaultMenus = [], cell } = options; const getDisplayMenus = (menus = []) => menus .filter((menu) => { var _a; return (0, lodash_1.isFunction)(menu.visible) ? menu.visible(cell) : (_a = menu.visible) !== null && _a !== void 0 ? _a : true; }) .map((menu) => { if (menu.children) { menu.children = getDisplayMenus(menu.children); } return menu; }); const displayMenus = getDisplayMenus((_a = operation === null || operation === void 0 ? void 0 : operation.menu) === null || _a === void 0 ? void 0 : _a.items); return { menu: Object.assign(Object.assign({}, operation === null || operation === void 0 ? void 0 : operation.menu), { items: (0, lodash_1.compact)([...defaultMenus, ...displayMenus]) }), }; }; exports.getTooltipVisibleOperator = getTooltipVisibleOperator; const verifyTheElementInTooltip = (parent, child) => { let result = false; let currentNode = child; while (currentNode && currentNode !== document.body) { if (parent === currentNode) { result = true; break; } currentNode = currentNode.parentElement; } return result; }; exports.verifyTheElementInTooltip = verifyTheElementInTooltip; //# sourceMappingURL=tooltip.js.map