技術文章

Delphi 中的 Excel 走勢圖:HotXLS 與 x14 擴充

HotXLS 僅需一次呼叫即可從 Delphi 與 C++Builder 建立 Excel 走勢圖(Excel 在單一儲存格內繪製的微型圖表):TXLSXWorksheet.AddSparklineGroup 在資料範圍上建置一個走勢圖群組,且儲存活頁簿時,會將其寫入為工作表 extLst 內部的 x14 擴充區塊,Excel 2010 及更新版本會轉譯此區塊,而舊版讀取器會無阻礙地忽略它;沒有圖表物件、沒有繪圖部分、沒有錨點

讓走勢圖顯得很有價值的場景總是同一個:儀表板活頁簿載有數百列(每列代表一個產品、地區或帳戶,各有 12 個月數據),且有人希望在每列的末端顯示一個趨勢圖形;如果使用真實的圖表來建立此模型,您將會建立數百個 DrawingML 圖表部分,每個都帶有自己的 XML 部分、關係與錨定幾何結構,更不用說這會對檔案大小和開啟時間造成什麼影響了;走勢圖群組是一個 XML 區塊,指明「將一條微型線從 B2:M2 繪製到 N2,並從 B3:M3 繪製到 N3」,這可以針對您擁有的任何列進行低成本的重複套用

為什麼走勢圖是 x14 擴充而不是圖表?

走勢圖隨 Excel 2010 引入,這是在 ECMA-376 基礎結構描述被凍結的三年後,因此它們無法直接存在於工作表標記中,以免損毀每一個現有的讀取器;微軟的解決方案是擴充清單機制:工作表結尾可以是一個包含 <ext> 區塊的 <extLst> 元素,每個區塊都以識別該功能的 URI 進行標記 ([MS-XLSX] §2.3.6);走勢圖使用 URI {05C60535-1F16-4fd2-B633-F4F36F0B64E0} 以及 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、用於微型長條圖的 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 決定空白的來源儲存格會對線條造成什麼影響:留下間記、視為零,或是藉由連結鄰近點來跨越該空白

儲存時 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 的結構描述預設值是 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 元件 出貨