公式引用追踪

SpreadJS 提供公式引用追踪功能,帮助用户快速理解复杂公式的计算来源。本示例展示了如何获取公式的引用单元格,并通过可视化树形结构直观展示多层级引用关系,便于公式审核和错误排查。

概述 本 Demo 展示了如何使用 getPrecedents 方法追踪公式的引用单元格,并通过可视化树形结构展示引用关系。当用户点击包含公式的单元格时,Demo 会在下方的工作表中绘制公式树,显示该单元格的所有引用单元格及其层级关系。 实现思路 初始化两个 Workbook 实例:一个用于显示源数据表格,另一个用于显示公式树 监听源表格的 SelectionChanging 事件,获取用户选中的单元格 调用 getPrecedents 方法获取选中单元格的引用单元格信息 递归构建引用关系树节点,包含单元格的值、位置和批注等描述信息 使用 Shape 形状和连接线可视化绘制引用关系树 代码解析 构建引用关系树节点 这段代码构建了引用关系树的节点结构。每个节点包含三个属性:value 存储单元格的值,position 使用 rangeToFormula 方法将单元格范围转换为公式字符串格式(例如 "Sheet1!A1"),description 存储单元格的批注文本。如果节点有引用单元格,还会添加 childNodes 属性。 获取引用单元格并递归构建树 这段代码使用 getPrecedents 方法获取引用单元格信息数组。每个引用单元格信息包含 row、col、rowCount、colCount 和 sheetName 属性。如果引用的是一个范围(rowCount 或 colCount 大于 1),则遍历范围内的所有单元格;如果是单个单元格,直接构建节点。通过递归调用 creatNodeTree,可以构建多层级引用关系树。 可视化绘制引用关系树 这段代码使用 Shape 形状绘制树形结构。每个节点用矩形表示,显示单元格的值、位置和描述信息。节点之间使用连接线形状 connectorShape 连接,形成可视化的引用关系树。startConnector 和 endConnector 方法分别设置连接线的起点和终点,index 参数指定连接点的位置(1 表示左侧,3 表示右侧)。 运行效果 在上方的工作表中点击包含公式的单元格(例如 D7 或 F7) 下方的工作表会显示公式树,展示该单元格的所有引用单元格 每个矩形节点显示单元格的值、位置和批注描述(如果有) 节点之间用连接线表示引用关系,从左到右展开树形结构 可以查看多层级的引用关系,了解复杂公式的计算来源 API 参考 getPrecedents 方法 获取指定单元格的前置引用单元格范围信息数组。 row: 单元格的行索引 col: 单元格的列索引 返回值: 返回 ICellsInfo[] 数组,每个元素包含: row: 单元格范围的行索引 col: 单元格范围的列索引 rowCount: 单元格范围的行数 colCount: 单元格范围的列数 sheetName: 工作表名称 rangeToFormula 方法 将指定的单元格范围转换为公式字符串。 range: 要转换的单元格范围 baseRow: 可选,相对引用的基行索引 baseCol: 可选,相对引用的基列索引 rangeReferenceRelative: 可选,指定引用的相对性 useR1C1: 可选,是否使用 R1C1 样式
window.onload = function () { initFunction(); } function initFunction() { var spread = new GC.Spread.Sheets.Workbook(document.getElementById('ss')); var spreadForShow = new GC.Spread.Sheets.Workbook(document.getElementById('show')); initShowSpread(spreadForShow); buildNodeTreeAndPaint(spread, spreadForShow); }; function initShowSpread(spreadForShow) { var sheetForShow = spreadForShow.getActiveSheet(); spreadForShow.suspendPaint(); var spreadOptions = spreadForShow.options, sheetOptions = sheetForShow.options; spreadOptions.allowContextMenu = false; spreadOptions.scrollbarMaxAlign = true; spreadOptions.tabStripVisible = false; spreadOptions.allowUserResize = false; spreadOptions.allowUserDragDrop = false; spreadOptions.allowUserDragFill = false; spreadOptions.allowUserZoom = false; spreadOptions.grayAreaBackColor = '#ccddff'; sheetOptions.colHeaderVisible = false; sheetOptions.rowHeaderVisible = false; sheetOptions.selectionBackColor = "transparent"; sheetOptions.selectionBorderColor = "transparent"; sheetOptions.gridline = { showVerticalGridline: false, showHorizontalGridline: false }; sheetForShow.getCell(1, 0) .foreColor("#71134E") .text("公式树") .font("bold italic 16pt Calibri") .vAlign(GC.Spread.Sheets.VerticalAlign.center) .textIndent(2); sheetForShow.getRange(0, 0, 100, 100).backColor("#ccddff"); sheetOptions.isProtected = true; spreadForShow.resumePaint(); } function buildNodeTreeAndPaint(spread, spreadForShow) { var sd = data; if (sd.length > 0) { spread.fromJSON(sd[0]); var sheet = spread.getActiveSheet(); var sheetForShow = spreadForShow.getActiveSheet(); sheet.bind(GC.Spread.Sheets.Events.SelectionChanging, function (e, info) { sheetForShow.shapes.clear(); var row = info.newSelections[0].row; var col = info.newSelections[0].col; var nodeTree = creatNodeTree(row, col, sheet); paintDataTree(sheetForShow, nodeTree); }) } } function creatNodeTree(row, col, sheet) { var _comment = sheet.getCell(row, col).comment(); var node = { value: sheet.getValue(row, col), position: sheet.name() + '!' + GC.Spread.Sheets.CalcEngine.rangeToFormula(sheet.getRange(row, col, 1, 1)), description: _comment && _comment.text(), }; var childNodeArray = addChildNode(row, col, sheet); if (childNodeArray.length > 0) { node.childNodes = childNodeArray; } return node; } function addChildNode(row, col, sheet) { var childNodeArray = []; var childNodes = sheet.getPrecedents(row, col); if (childNodes.length >= 1) { childNodes.forEach(function (node) { var row = node.row, col = node.col, rowCount = node.rowCount, colCount = node.colCount, _sheet = sheet.parent.getSheetFromName(node.sheetName); if (rowCount > 1 || colCount > 1) { for (var r = row; r < row + rowCount; r++) { for (var c = col; c < col + colCount; c++) { childNodeArray.push(creatNodeTree(r, c, _sheet)); } } } else { childNodeArray.push(creatNodeTree(row, col, _sheet)) } }) } return childNodeArray; } function getRectShape(sheetForShow, name, x, y, width, height) { var rectShape = sheetForShow.shapes.add(name, GC.Spread.Sheets.Shapes.AutoShapeType.rectangle, x, y, width, height); var oldStyle = rectShape.style(); oldStyle.textEffect.color = "white"; oldStyle.fill.color = "#0065ff"; oldStyle.textEffect.font = "bold 15px Calibri"; oldStyle.textFrame.vAlign = GC.Spread.Sheets.VerticalAlign.top; oldStyle.textFrame.hAlign = GC.Spread.Sheets.HorizontalAlign.left; oldStyle.line.beginArrowheadWidth = 2; oldStyle.line.endArrowheadWidth = 2; rectShape.style(oldStyle); return rectShape; } function getConnectorShape(sheetForShow) { var connectorShape = sheetForShow.shapes.addConnector('', GC.Spread.Sheets.Shapes.ConnectorType.elbow); var LineStyle = connectorShape.style(); var line = LineStyle.line; line.beginArrowheadWidth = GC.Spread.Sheets.Shapes.ArrowheadWidth.wide; line.endArrowheadWidth = GC.Spread.Sheets.Shapes.ArrowheadWidth.wide; line.color = "#FF6600"; connectorShape.style(LineStyle); return connectorShape; } function paintDataTree(sheetForShow, nodeTree, index, childLength, fatherShape) { var rectWidth = 260, rectHeight = 60; var spacingWidth = 300; var convertArray = [-0.75, 0.75, -2.25, 2.25, -2.25, 2.25, -4, 4, -5, 5]; var spacingHeightMapping = [145, 135, 125, 50, 50]; var name = Math.random().toString(); var rectShape; if (fatherShape) { var x = fatherShape.x(), y = fatherShape.y(); rectShape = getRectShape(sheetForShow, name, x + spacingWidth, y + convertArray[index] * spacingHeightMapping[childLength], rectWidth, rectHeight); var connectorShape = getConnectorShape(sheetForShow); connectorShape.startConnector({ name: fatherShape.name(), index: 3 }); connectorShape.endConnector({ name: rectShape.name(), index: 1 }); } else { rectShape = getRectShape(sheetForShow, name, 200, 250, rectWidth, rectHeight); } var _description = 'Value: ' + nodeTree.value + '\nCell: ' + nodeTree.position + ((nodeTree.description !== null) ? ('\nDescription: ' + nodeTree.description) : ''); rectShape.text(_description); var childNodes = nodeTree.childNodes; if (childNodes) { childNodes.forEach(function (node, index) { if (node.description) { paintDataTree(sheetForShow, node, index, childNodes.length, rectShape) } }); } }
<!DOCTYPE html> <html lang="en" style="height: 100%;font-size: 14px;"> <head> <meta charset="utf-8"> <meta name="spreadjs culture" content="zh-cn" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-shapes/dist/gc.spread.sheets.shapes.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/data/precedent.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <div class="sample-tutorial"> <div id="ss"></div> <div id="show"></div> </div> </body> </html>
#ss { width: 100%; height: 60%; border: 1px solid black; } #show { width: 100%; height: 40%; border: 1px solid black; } .sample-tutorial { height: 100%; width: 100%; overflow: hidden; } body{ height: 100%; }