|
| 1 | +## Excel 图表(Chart)动态数据绑定开发指引 |
| 2 | + |
| 3 | +本文介绍如何在 Excel 模板中通过图表的“可选文字”(Alt Text,对应模型字段 description/descr)配置图表的动态数据来源与行为,重点说明: |
| 4 | + |
| 5 | +- 在可选文字中以简洁的 key=value 语法配置动态绑定(dynamicBindings) |
| 6 | +- 通过 seriesTestExpr 决定最终生成哪些系列(series),并要求在模板中预先准备“最多可能需要”的系列数 |
| 7 | +- 在表达式中使用 seriesModel.index 获取当前系列的 0 基索引 |
| 8 | +- 利用 seriesDataCellRefExpr 等表达式动态计算 Excel 单元格引用;表达式内可直接调用 IXptRuntime.buildCellRef 助手函数 |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | + |
| 14 | +## 在 Excel 中写“descr”(可选文字) |
| 15 | + |
| 16 | +右键图表 →“编辑可选文字”,将描述与配置写在同一文本框中: |
| 17 | + |
| 18 | +- “----” 之前的内容为人类可读的描述,将保留到模型的 description 字段 |
| 19 | +- “----” 之后按多行 key=value 写配置,解析为 ChartModel.dynamicBindings |
| 20 | + |
| 21 | +语法规则(由 MultiLineConfigParser 解析): |
| 22 | + |
| 23 | +- 每行一个配置项,形如 key = value |
| 24 | +- 字符串可直接写;如包含换行可用 """ 多行字符串 """ 包裹;或用 `反引号字符串` |
| 25 | + |
| 26 | +最小示例: |
| 27 | + |
| 28 | +``` |
| 29 | +我的销售图表 |
| 30 | +---- |
| 31 | +chartTitleCellRefExpr = `Sheet1!$A$1` |
| 32 | +seriesTestExpr = `series.index < 3` |
| 33 | +seriesDataCellRefExpr= `xptRt.buildCellRef("Sheet1!$B$2:$B$101", 0, series.index, 0, 0)` |
| 34 | +seriesCatCellRefExpr = `Sheet1!$A$2:$A$101` |
| 35 | +``` |
| 36 | + |
| 37 | +上述配置效果: |
| 38 | + |
| 39 | +- 图表标题引用 Sheet1!$A$1 |
| 40 | +- 仅生成 index 为 0、1、2 的三个系列 |
| 41 | +- X 轴分类统一引用 A2:A101 |
| 42 | + |
| 43 | + |
| 44 | +## dynamicBindings 可配置项与签名 |
| 45 | + |
| 46 | +可在“----”之后配置以下常用表达式(更多项见 schema:/nop/schema/excel/chart.xdef 的 <dynamicBindings>): |
| 47 | + |
| 48 | +- chartTestExpr: (chartModel) => boolean |
| 49 | + - 决定是否生成整个图表 |
| 50 | +- chartTitleCellRefExpr: (chartModel) => string |
| 51 | +- chartTitleExpr: (chartModel) => string |
| 52 | + - 标题可用单元格引用或直接文本(二选一)。若 cellRefExpr 返回非空,则优先按单元格;否则尝试 titleExpr 文本 |
| 53 | +- seriesTestExpr: (seriesModel, chartModel) => boolean |
| 54 | + - 决定某个系列是否保留(返回 true 保留) |
| 55 | +- seriesNameCellRefExpr | seriesNameExpr: (seriesModel, chartModel) => string |
| 56 | +- seriesDataCellRefExpr | seriesDataExpr: (seriesModel, chartModel) => string/any |
| 57 | +- seriesCatCellRefExpr | seriesCatExpr: (seriesModel, chartModel) => string/any |
| 58 | +- axisDataCellRefExpr: (axisModel, chartModel) => string |
| 59 | +- axisTitleCellRefExpr | axisTitleExpr: (axisModel, chartModel) => string |
| 60 | + |
| 61 | +引擎行为(参见 ExpandedSheetChartGenerator): |
| 62 | + |
| 63 | +- 若 *CellRefExpr 返回非空*,则覆盖模板中相应的单元格引用 |
| 64 | +- 若返回空,则保留模板原值;对于标题/名称,若 cellRef 为空则再尝试 *Expr* 返回直接文本 |
| 65 | + |
| 66 | + |
| 67 | +## seriesTestExpr 与“模板需预置最多的 series 数” |
| 68 | + |
| 69 | +- 运行时不会“新增”系列,只会基于模板中已有系列做“保留/删除”。 |
| 70 | +- 因此必须在模板中预先设计“最多可能需要”的系列数量(例如最多 5 条线),运行期用 seriesTestExpr 过滤超出的系列。 |
| 71 | +- 例:若数据集只有 3 条线,且模板预置 5 个系列,则可以配置 |
| 72 | + |
| 73 | + |
| 74 | + |
| 75 | +## seriesModel.index(从 0 开始) |
| 76 | + |
| 77 | +- 引擎在加载模板时会为系列自动编号:0、1、2、…(详见 ExcelChartModel.init 与 ChartPlotAreaParser) |
| 78 | +- 在所有系列相关表达式中可通过 series.index 取得该序号,用于偏移列/行、拼接名称等 |
| 79 | + |
| 80 | + |
| 81 | +## 动态生成单元格引用:xptRt.buildCellRef |
| 82 | + |
| 83 | +表达式中可直接返回: |
| 84 | + |
| 85 | +- A1 或 A1:B10 形式的字符串(可包含 Sheet! 前缀) |
| 86 | +- 或返回 IXptRuntime.buildCellRef 的结果对象,系统会自动转为字符串 |
| 87 | + |
| 88 | +buildCellRef 签名: |
| 89 | + |
| 90 | +- buildCellRef(cellRefTpl, rowOffset, colOffset, rowSize, colSize) ⇒ ExcelCellRef |
| 91 | + |
| 92 | +典型用法: |
| 93 | + |
| 94 | +1) 按列横向扩展系列(多系列共用一段模板区域,每个系列向右偏移一列) |
| 95 | + |
| 96 | +``` |
| 97 | +seriesDataCellRefExpr = `xptRt.buildCellRef("Sheet1!$B$2:$B$101", 0, series.index, 0, 0)` |
| 98 | +
|
| 99 | +``` |
| 100 | + |
| 101 | +2) 按行向下滚动(每个系列向下偏移固定行数) |
| 102 | + |
| 103 | +``` |
| 104 | +seriesDataCellRefExpr = `xptRt.buildCellRef("Sheet1!$B$2:$B$21", series.index*20, 0, 0, 0)` |
| 105 | +``` |
| 106 | + |
| 107 | + |
| 108 | +## 标题与坐标轴的动态绑定 |
| 109 | + |
| 110 | +``` |
| 111 | +chartTitleCellRefExpr = `Sheet1!$D$1` |
| 112 | +chartTitleExpr = `销售额走势` |
| 113 | +
|
| 114 | +``` |
| 115 | + |
| 116 | +注意:当 cellRefExpr 返回非空时,titleExpr 将被忽略。 |
| 117 | + |
| 118 | + |
| 119 | +## 支持的图表示例 |
| 120 | + |
| 121 | +以下为部分效果图(模板+绑定后均可用): |
| 122 | + |
| 123 | +- 柱/条形:   |
| 124 | +- 折线:  |
| 125 | +- 面积:   |
| 126 | +- 饼/环:  |
| 127 | +- 雷达/散点:    |
| 128 | + |
| 129 | + |
| 130 | +## 参考与实现位置 |
| 131 | + |
| 132 | +- XDef(schema):`/nop/schema/excel/chart.xdef` 的 `<dynamicBindings>` 段定义了所有可用表达式与签名 |
| 133 | +- 模型与生成:`io.nop.report.core.engine.ExpandedSheetChartGenerator`(动态绑定的执行逻辑) |
| 134 | +- “可选文字”解析:`io.nop.report.core.build.ExcelToXptModelTransformer#parseChartModel`(以 "----" 分隔描述与配置;key=value 逐项解析) |
| 135 | +- 运行期上下文:`io.nop.report.core.engine.IXptRuntime`(表达式作用域变量名为 `xptRt`;提供 `buildCellRef`、`ds` 等方法) |
| 136 | +- 图表解析/构建(OOXML):`nop-format/nop-ooxml/nop-ooxml-xlsx` 模块下 `io.nop.ooxml.xlsx.chart.*`(Parser/Builder) |
| 137 | + |
| 138 | + |
| 139 | +## 小贴士(常见问题) |
| 140 | + |
| 141 | +- 必须在模板中预置最多的系列(series),运行时不会创建超过模板数量的新系列 |
| 142 | +- 返回 null 或空字符串会保留模板中的默认引用/文本 |
| 143 | +- 返回 ExcelCellRef 或 A1/A1:A10 字符串均可;若需要包含工作表名,建议写 Sheet!$A$1:$A$10 |
| 144 | +- seriesModel.index 从 0 开始 |
| 145 | + |
| 146 | +完成以上配置后,导出时引擎会根据可选文字中的 dynamicBindings 自动计算标题、分类、数据等引用,并按需过滤系列,达到“模板一次设计、运行多场景复用”的效果。 |
| 147 | + |
0 commit comments