技术文章

Delphi 中的 Excel 迷你图(Sparklines)与 x14 扩展

HotXLS 支持在 Delphi 和 C++Builder 中通过一次调用创建 Excel 迷你图(Sparklines —— 即 Excel 在单个单元格内绘制的微型图表): TXLSXWorksheet.AddSparklineGroup 在数据范围内构建迷你图组,保存工作簿时将其写入工作表 extLst 内部的 x14 扩展块中,Excel 2010 及更高版本可以对其进行渲染,而旧版的读取器则默默地跳过;没有图表对象,没有绘图部分,没有锚点

让迷你图具有价值的场景总是相同的;一个仪表板工作簿承载着几百行数据,每行对应一个产品、区域或账户,每个都有 12 个月的月度数据,并且有人希望在每行的末尾放置一个趋势字形;如果用真正的图表来建模,您将创建数百个 DrawingML 图表部分,每个都有自己的 XML 部分、关系和锚点几何形状,更不用说这会对文件大小和打开时间造成什么影响;迷你图组则是单个 XML 块,它表明“将 B2:M2 的微线画到 N2,将 B3:M3 画到 N3”,以极低的成本为您拥有的任意行数进行重复

为什么迷你图是 x14 扩展而不是图表?

迷你图随 Excel 2010 一起问世,比 ECMA-376 基础 Schema 冻结晚了三年,因此它们不能直接存活在工作表标记中,否则会破坏每个现有的读取器;微软给出的答案是扩展列表(Extension list)机制:工作表可以以一个 <extLst> 元素结束,该元素持有 <ext> 块,每个块都用标识该功能的 URI 进行标记([MS-XLSX] §2.3.6);迷你图使用的是 {05C60535-1F16-4fd2-B633-F4F36F0B64E0} URI 和 x14 命名空间;能够识别该 GUID 的读取器会解析该块,不能识别的则被要求跳过;这就是前向兼容性故事的全部,也是 Excel 2007 能够清晰打开迷你图工作簿的原因 —— 它仅仅显示源数字,而没有显示字形

该机制的一个细节值得指出来,因为它与普遍的假设相反:在工作表根节点上不需要 mc:Ignorable 声明;仅 ext GUID 关卡本身就足够了,Excel 自己的写入器在 <ext> 元素上本地声明了 x14 命名空间,而不是去触及根节点;HotXLS 遵循相同的模式,这保持了工作表 XML 的其余部分完好无损;这也是它与真实图表的结构差异:图表是带有自己渲染流水线的独立 DrawingML 部分(在关于图表、图像和绘图的配套文章中有所涵盖),而迷你图则是电子表格网格本身进行绘制的工作表元数据

如何以编程方式为 XLSX 文件添加迷你图?

TXLSXWorksheet.AddSparklineGroup(AType, ASourceRange, ALocation) 是单次调用路径:它创建给定类型的组,并用 Excel 的默认系列颜色播种,然后添加将带有工作表限定的源范围映射到宿主单元格上的第一个迷你图;返回的 TXLSXSparklineGroup 接着通过其 Add 方法接受更多的迷你图(每行一个 F/Sqref 对),它们都共享该组的类型和格式;对于仪表板的情况,这正是您想要的形态:一个组、一套视觉选项、数百个成员

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Grp: TXLSXSparklineGroup;
  Row: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('dashboard.xlsx');
    Sheet := Book.Sheets[0];

    // One group: every product row shares type and formatting
    Grp := Sheet.AddSparklineGroup(xlsxSparklineLine,
      'Sheet1!B2:M2', 'N2');
    for Row := 3 to 201 do
      Grp.Add(Format('Sheet1!B%d:M%d', [Row, Row]),
        Format('N%d', [Row]));

    Book.SaveAs('dashboard-trends.xlsx');
  finally
    Book.Free;
  end;
end;

TXLSXSparklineType 涵盖了 Excel 提供的三种类型:用于趋势线的 xlsxSparklineLine、用于微型条形图 of xlsxSparklineColumn 以及用于每个值渲染为向上或向下块的盈亏图的 xlsxSparklineStacked;每个成员迷你图仅是两个字符串 —— F(带有工作表限定的源范围)以及 Sqref(绘制字形的单元格) —— 意即每行没有其他要配置的内容;当您想要从空白组而不是播种的默认组开始时,也可以通过 Worksheet.SparklineGroups 集合完全手动构建组

类型化模型上的标记、颜色插槽和轴缩放

TXLSXSparklineGroup 将 Excel 迷你图工具功能区的完整选项集公开为普通属性;六个布尔切换可以强调数据点:用于折线迷你图上每个点的 Markers,以及 HighPointLowPointNegativePointsFirstPointLastPoint;八个 ARGB 颜色插槽与它们配对 —— ColorSeriesColorNegativeColorAxisColorMarkersColorFirstColorLastColorHighColorLow —— 约定保留为 0 的插槽将从文件中省略,且 Excel 应用其自身的默认值;整个库中出现了相同的完全不透明 ARGB 形式,因此这里的 $FFD00000 与它在条件格式样式中所代表的红色相同:

Grp := Sheet.AddSparklineGroup(xlsxSparklineStacked,
  'Sheet1!B2:M2', 'N2');
Grp.NegativePoints := True;          // losing months stand out
Grp.ColorNegative := $FFD00000;
Grp.HighPoint := True;
Grp.ColorHigh := $FF00B050;
Grp.MinAxisType := xlsxSparklineAxisGroup;   // one scale for all rows
Grp.MaxAxisType := xlsxSparklineAxisGroup;
Grp.DisplayEmptyCellsAs := xlsxSparklineEmptyGap;

在您接受默认值之前,轴属性值得稍微考虑一下; MinAxisTypeMaxAxisType 各自接受独立、组或自定义缩放:独立缩放将每个迷你图拉伸到其自己的最小值和最大值,这使得在 99 和 101 之间波动的行看起来像翻了三倍的行一样戏剧化;组缩放则将所有成员置于单个共享的垂直比例上,因此字形高度在每行中代表相同的数量 —— 这通常是对比式仪表板所想要的;DisplayEmptyCellsAs 决定了空白源单元格对折线有什么影响:留出空白(Gap)、算作零值(Zero)或通过连接邻居跨越孔洞

HotXLS 在保存时向工作表 extLst 写入了什么

保存时,HotXLS 将每个组序列化为携带类型和切换属性的 x14:sparklineGroup 元素、作为子元素的颜色插槽,以及一个其成员将 xm:f 范围公式与 xm:sqref 宿主单元格配对的 x14:sparklines 列表;整个结构位于工作表部分末尾的 <extLst><ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"> 内部,且在该块上直接声明了 x14 命名空间,这与 Excel 写入的完全一样;没有迷你图组的工作表根本不会发出 extLst,因此从未使用该功能的文件输出时与早期 HotXLS 版本产生的文件在字节上完全相同 —— 这正是在关于无损往返基础设施的文章中描述的相同的保守写入器纪律

如果您曾对比 Excel 的输出或手工检查 XML,有一个规范细节是重要的: lineWeight 的 Schema 默认值是 0.75 磅,而不是一些次要来源引用的 1.25 —— [MS-XLSX] 定义了 CT_SparklineGroup@lineWeight 默认值为 0.75,与 Excel 用户界面实际应用的粗细相匹配;HotXLS 通过省略来遵守该默认值:将 LineWeight 设置为 0.75 之外的任何值都会写入该属性,保留其为 0.75 则不会;在任何方向搞错这一点,都会导致文件渲染出的粗细比从 Excel 保存的同一工作簿略粗或略细

往返运行、工作表复制以及 HotXLS 未建模的扩展

打开是保存的镜像;当 HotXLS 打开承载着迷你图的工作簿时 —— 无论是 HotXLS 还是 Excel 写入的 —— 解析器都会根据迷你图 ext GUID 重新在类型化模型中构建每个组、成员、颜色和轴选项,因此打开→编辑→保存周期完整地保留了它们;将工作表复制到另一个工作簿中会克隆其迷你图组以及工作表的其余部分,这使模板驱动的报告生成变得简单:保留样式化的模板表,按报告进行复制并重新指向范围

extLst 还可以包含 HotXLS 没有类型化模型的扩展块 —— Excel 会将切片器和扩展条件格式等功能的 x14 块写入同一个列表中;自 v2.131.0 起,这些外部块在往返处理中会被逐字保留,而不是被丢弃:解析器捕获它们,通过 Worksheet.RawWorksheetExts 将它们公开为只读,并在保存时将它们写回,它们在复制到另一个工作簿时随工作表一起移动;保留的 XML 是从解析器事件重建的,因此它在语义上与原始的等效,而不是字节完全相同,但 Excel 接受它,这些功能也得以存活;实际的结果是在生产中很重要的:打开客户的工作簿并重新保存不再会剥离您未让 HotXLS 理解的扩展

诚实的边界线:迷你图在 Excel 2010 及更高版本中进行渲染,并且可在实现 x14 扩展的任何其他读取器中进行渲染;Excel 2007 显示没有字形的数据,这是该格式设计时做出的降级表现;这三种迷你图类型、八个颜色插槽、数据点切换和三种轴模式涵盖了 Excel 自身对话框所提供的全部选项表面;迷你图支持与此处展示的 XLSX 引擎其余部分一起,随适用于 Delphi 和 C++Builder 的 HotXLS Delphi Excel Component 一起提供