HotXLS 保留所导入 Excel 图表的原始 ChartML,并在类型化模型没有任何改动时把它逐字节重放回去,所以打开再保存一份工作簿不会悄悄剥离对象模型无法表示的那部分图表。编辑一个标题、一个系列或一个坐标轴时,HotXLS 会把新的类型化节点结构性地合并进原始树,而不是从头重新生成图表
这一区分——未触碰时重放、被编辑时合并——正是让 Delphi 应用程序可以安全地指向分析师在 Excel 里构建的图表的原因。从类型化模型重新生成图表总会丢掉模型不知道的东西,而 Excel 图表里满是库不知道的东西:自定义样式、扩展列表、备选内容分支、来自加载项的带命名空间限定格式
为什么重新保存工作簿会把图表压平
因为通常的实现读它懂的东西、写它懂的东西。夹在中间的一切——来自更新版本 Excel 构建的 extLst、一个 mc:AlternateContent 分支、一个模型里没有对应属性的形状效果——存在于文件里、在对象模型里没有家、在保存时消失。用户看到的是一张丢失了渐变、自定义数据标签或 3D 效果的图表,而工作簿解释不了为什么
在工作簿上启用 PreserveUnsupportedParts 之后,HotXLS 在导入时把原始 UTF-8 图表字节连同长度和一个模型化内容的 64 位指纹一起存下来。如果到保存时类型化模型没有变化,它就把原始字节原样写回。没有 XML 重排、没有空白漂移、也没有一份没人编辑过的图表在内存里常驻的 UTF-16 副本
var
Workbook: TXLSXWorkbook;
Chart: TXLSXChart;
begin
Workbook := TXLSXWorkbook.Create(nil);
try
Workbook.PreserveUnsupportedParts := True; // set before Open
Workbook.Open('quarterly-review.xlsx');
Chart := Workbook.Sheets[1].Charts[0]; // Sheets[] is 1-based
if Chart.HasPreservedXml then
Log('original ChartML retained; untouched charts replay exactly');
Chart.Title := 'Revenue by region, Q3 2026'; // now the merge path runs
Workbook.SaveAs('quarterly-review-out.xlsx');
finally
Workbook.Free;
end;
end;
合并保留什么,模型拥有什么
规则是所有权。公开的模型节点——标题、系列、缓存、绘图组(plot group)、坐标轴——由新生成的树决定。未建模的子树从原始树原样带过来。这种不对称是刻意的,也需要一点时间来消化:删除一个系列不能让旧系列从保留的 XML 里复活,所以模型拥有的任何东西都从模型取得,哪怕模型说的是"没了"
无主内容被重新插回它原本所挨着的已匹配节点旁边,而 extLst 在其父节点之内保持最后,因为 schema 要求它就在那儿。清掉一个已知属性——去掉一个加粗标志、丢掉一种颜色——被视为一次清除,而不是从原始树填补的空隙。同一邻近区域的未知属性则保持各自的位置
标记兼容(markup-compatibility)内容得到专门处理。解析时 HotXLS 跳过 mc:Choice、读取 mc:Fallback;如果那条回退分支包住了一个模型认识的绘图组,合并就把新的类型化内容写回那条回退子树。没有这一步,保存会一边发出新的绘图组,一边把旧的留在回退里,于是图表里就有两份
把单张图表排除在外
ClearPreservedXml 丢掉某张图表的原始 XML 和指纹,于是下一次保存纯粹按类型化模型重建该图表。这是每张图表各自的决策;工作簿级别的 PreserveUnsupportedParts 仍然决定导入时是否构建保留状态
当你想要一张干净图表而不是一张忠实图表时再用它——比如模板图表带着旧的企业主题格式,而这次操作的目的就是丢掉它们。不要把它当成调试反射来用:一张编辑后渲染不对的图表,更常见的是一个建模问题而不是保留问题,而清掉保留的 XML 等于销毁证据
组合图表与第二根坐标轴
AddPlotGroup 构建有顺序的组合图表,其中每个绘图组保留各自的图表族(family)和各自的主或副坐标轴分配。这正是那种无处不在的业务图表所需要的:营收作为柱形画在左侧坐标轴,利润率作为折线画在右侧
图表暴露主副分类轴和数值轴,外加一个系列轴,类型化的分类、数值、日期和系列轴设置覆盖位置、标识符、交叉、日期单位、标签间隔与可见性。旧的扁平 API——ChartType、AddSeries、扁平的 Series 集合——仍然指向主绘图组,所以既有代码能继续工作,而其下的模型持有每一个有序的组
var
Chart: TXLSXChart;
Line: TXLSXChartPlotGroup;
begin
// Column chart anchored over rows 1..15, columns 5..12
Chart := Sheet.Charts.Add(xlsxChartColumn, 'Revenue and margin',
1, 5, 15, 12);
Chart.AddSeries('Revenue', 'Data!$A$2:$A$13', 'Data!$B$2:$B$13');
// Second plot group: a line, read against the secondary value axis
Line := Chart.AddPlotGroup(xlsxChartLine, xlsxAxisSecondary);
Line.AddSeries('Margin %', 'Data!$A$2:$A$13', 'Data!$C$2:$C$13');
end;
在这个模型存在之前,打开并重新保存一张组合图表会把它压平成单一图表族,并丢失副坐标轴、日期轴和系列轴。图表公式引用和缓存的系列值如今也能扛过工作簿复制和开/保存循环,而图表锚点通过关系标识符匹配到对应的图表分部——所以一个重新排列关系条目的应用,不再会把图表内容在不同工作表位置之间调换
手工布局的陷阱
如果你手工定位一块绘图区、标题、坐标轴标题或图例,请把四个坐标都写全。只要其中任何一个出现,Office 就要求 x、y、w 和 h 作为一个完整组出现,否则整个 manualLayout 都被忽略。HotXLS 正因如此总是发出完整的矩形,而 SetPositionAndSize 一次调用就把它设置好并启用
还有两个细节跟随 Excel 而不是规范的全部自由度。位置写出时 xMode 和 yMode 取 edge,而尺寸用 factor 作为 wMode 和 hMode,因为那是 Excel 写出的、也是 Excel 把其他组合转换成的形式。而 layoutTarget 只为绘图区发出,绝不为标题或图例发出,在那些地方微软的实现笔记是禁止它的
把这一点纳入工作簿往返
图表保留是一项通用原则的一个实例:一个编辑他处所作文档的库,应当只改它被要求改的、其余一概不动。同样的推理驱动着 HotXLS 对主题、计算链和工作表扩展的处理,描述在 theme、extLst 与 calcChain 的无损往返的笔记里,以及 VBA 工程和外部链接在 保留 VBA 与外部链接里。至于图表创作面本身——从 Delphi 创建图表、放置图像和绘图——参见 HotXLS 中的图表、图像与绘图的详解
HotXLS 用原生 Delphi 和 C++Builder 代码读取、编辑和写入 XLSX 图表,机器上无需安装 Excel,这正是让保真度成为一个库问题而不是自动化问题的原因——HotXLS 电子表格组件页有图表功能清单和试用下载