HotXLSはExcelのウォーターフォールとツリーマップチャートを、スタンドアロンのcx:chartSpaceパート、つまりコンテンツタイプapplication/vnd.ms-office.chartex+xmlを運ぶxl/charts/chartExN.xmlとして書き、mc:AlternateContentラッパーを通じてアンカーします。Excel 2016以降はチャートを描画し、古いリーダはフォールバック形状を受け取ります。xlsxChartWaterfallかxlsxChartTreemapを選べば、パートパイプライン全体がそれに従います
この問題が通常姿を現すのはサポートチケットです。顧客が財務チームがExcel 2019で作ったワークブックを送ってきて、あなたのレポートツールがそれを開いて保存すると、ウォーターフォールチャートが消えています。例外も警告もログもなく、チャートは単純に出力に存在しないのです。従来のチャートコード経路を探しに行くと、長いこと見つからないでしょう。チャートはそもそもそこに無かったのですから
ウォーターフォールとツリーマップチャートがなぜc:chartSpaceにないのか
マイクロソフトがそこに置けなかったからです。ECMA-376 Part 1 §21.2のDrawingMLチャートスキーマは、c:barChart、c:lineChart、c:pieChartなどのチャート要素を閉じた選択肢として列挙しています。Excel 2016でウォーターフォール、ツリーマップ、サンバースト、ファネルを追加することは、既存のすべてのリーダが依存するスキーマを壊すことを意味したでしょう。新しい型は代わりに並行するドキュメントへ入りました。2014年chartex名前空間http://schemas.microsoft.com/office/drawing/2014/chartexのcx:chartSpaceパートで、従来のチャートの隣に保存されますが、構造的には無関係です。描画側はmc:AlternateContent、つまりECMA-376 Part 3の互換性メカニズムで2つの世界をつなぎ合わせます。mc:Choice分岐はRequires="cx1"を宣言し、a:graphicData/@uriがchartex名前空間であるxdr:graphicFrameを保持し、そのペイロードは裸の<cx:chart r:id="rIdN"/>参照です。一方mc:FallbackはExcel自身がそこに書く通知を載せた矩形を保持します。cx1を理解しないリーダは、Part 3の契約によりフォールバックを取ることが要求されます
リレーションシップタイプがもう1つの手がかりです。従来のチャートは標準のOOXMLチャートリレーションシップで描画パートからぶら下がります。chartExパートは代わりにhttp://schemas.microsoft.com/office/2014/relationships/chartExを使います。[MS-ODRAWXML]に文書化されています。HotXLSは両方の世界を定数として公開します。XlsxRtChartExがそのリレーションシップ、XlsxCtChartExがコンテンツタイプ、XlsxNsMsChartExとXlsxNsMsChartEx2015がアンカーが必要とする2つの名前空間です。そしてXlsxChartIsChartExという述語が、保存時にチャートを拡張パスへ振り分けます。組み合わせチャートと保存された従来のチャートXMLはもう片方の経路を下ります。保存済みチャートXMLと組み合わせチャートの記事で説明されています
Excelがファイルを開けるかどうかを決める4つのスキーマ規則
chartexスキーマは4つの特定の場所で従来のものより厳格で、間違えるとそのどれもが同じ役に立たない修復プロンプトを生みます。HotXLSのBuildChartExKnownXmlとBuildChartExRelsXmlが4つとも組み込んでいますが、XMLを手で検査するなら知っておく価値があります
- 順序。ドキュメント内では
cx:chartDataがcx:chartより先に来なければなりません。これは従来のチャートから作る直感の逆です。従来はキャッシュがプロットエリア内のシリーズの中に住んでいました。chartExパートではデータが先頭の独立ブロックに置かれ、チャート本体はidで参照するだけです - カーディナリティ。
cx:plotAreaが含めるのはcx:plotAreaRegionのちょうど1つだけです。0でも2つでもありません。ここに組み合わせチャートの話はありません。1つのリージョンと、その中のシリーズだけです - 型の配置。チャート型は要素名ではありません。
cx:series上のlayoutId属性で、マイクロソフトのchartexスキーマにあるST_SeriesLayout列挙からトークンを取ります。XlsxChartExLayoutIdはHotXLSの列挙をそのトークンへマップし、'waterfall'か'treemap'を返します。XlsxChartExLayoutIdToTypeは読み取り時にそのマッピングを逆方向に実行します - 従来のチャートが決して必要としないリレーションシップ。
cx:chartDataは<cx:externalData r:id="rId1"/>要素を保持し、そのリレーションシップは、データを所有するワークシートを指し戻すxl/charts/_rels/chartExN.xml.relsに存在しなければなりません。Excelがcx:f範囲数式を解決するのにそれを使うからです。relsパートを省くとチャートは空で開き、理由を説明するものは何もありません
Delphiでウォーターフォールやツリーマップチャートを追加するには
HotXLSで他のチャートを追加するのと同じ方法です。拡張型はTXLSXChartTypeのメンバーなので、TXLSXWorksheet.AddChartはそれらを直接受け取り、下流のすべては列挙値を鍵に動きます。学ぶべき別のchartEx APIはありません
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Chart: TXLSXChart;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('cashflow.xlsx');
Sheet := Book.Sheets[0];
// 行/列座標のアンカー矩形。従来のチャートと同じ
Chart := Sheet.AddChart(xlsxChartWaterfall, 'Q3 Cash Flow',
2, 5, 20, 12);
Chart.AddSeries('Movement', 'Sheet1!$A$2:$A$9', 'Sheet1!$B$2:$B$9');
Book.SaveAs('cashflow-waterfall.xlsx');
finally
Book.Free;
end;
end;
保存時に、HotXLSはXlsxChartIsChartExがTrueを返すのを見て、別のchartExカウンタからパート名を割り当て、xl/charts/chartEx1.xml、そのrelsファイル、コンテンツタイプのオーバーライド、そしてXlsxChartExAnchorPayloadXmlが生成したmc:AlternateContentアンカーを書きます。独立カウンタは重要です。従来のチャート2つとウォーターフォール1つを持つワークブックは、chartEx3.xmlではなくchart1.xml、chart2.xml、chartEx1.xmlを得ます。ツリーマップはその後、2つの詳細でウォーターフォールと異なります。両方とも同じビルダーが処理します。数値次元の型が変わります。ウォーターフォールのcx:numDim type="val"はツリーマップではtype="size"になります。ツリーマップの矩形は大きさをプロット値としてではなく面積としてエンコードするからです。そしてツリーマップは通常、カテゴリ階層を望みます。モデルはそれをTXLSXChartSeries.CategoriesMultiLevelで表現します。そのフラグを立てると、S.ResolveCategoryLevelsは書き込み時にワークシートから範囲を読み出し、S.CategoryLevelCountとS.CategoryLevels[]の背後でレベルごとに1つのTXLSXChartDataCacheへ分割し、BuildChartExLevelXmlは単一のcx:strDim内にレベルごとに1つのcx:lvlを、リーフレベルを先にして出力します。これはECMA-376 Part 1 §21.2がc:multiLvlStrCacheに使うのと同じ物理順序で、マークアップのどれも共有されなくても、その規約は持ち越されます
Chart := Sheet.AddChart(xlsxChartTreemap, 'Revenue by Region',
2, 8, 24, 16);
S := Chart.AddSeries('Revenue',
'Sheet1!$A$2:$B$40', // 2列:リーフラベル、次に親グループ
'Sheet1!$C$2:$C$40');
S.CategoriesMultiLevel := True;
従来のチャートの前提をすべて壊すキャッシュ形状
従来のチャートキャッシュリーダを書いたことがあるなら、3つの習慣がchartExパートであなたを裏切ります。その3つはすべて1つの設計判断から来ています。chartexは、DrawingMLが子要素に格納するものを、属性と要素コンテンツに格納するのです
- キャッシュされたポイントは
<cx:pt idx="0">42</cx:pt>です。値は要素コンテンツです。降りていくc:v子要素はなく、vノードを探して歩くパーサは何も見つけず、空のキャッシュを報告します ptCountとformatCodeはcx:lvl上の属性です。従来のc:numCacheでは両方とも子要素です- 複数の階層レベルは、1つの
cx:strDim内の複数のcx:lvl要素です。c:multiLvlStrCacheがc:strCacheから分離しているような、独立したマルチレベルキャッシュ要素はありません
BuildChartExLevelXmlがその形状を書き、ParseChartExXmlが読み戻します。外への途中ですべての値をXlsxEscapeTextに通すため、アンパサンドや山括弧を含むカテゴリラベルはラウンドトリップを生き延びます。数値レベルがフォーマットコードを持たないときにビルダーが何をするかは注目に値します。属性を落とすのではなくformatCode="General"を書きます。ここでは属性が、従来版のそれのようにオプションではないからです
型の読み戻しを失敗させた名前空間プレフィックス
HotXLS chartExパーサの最初の版は、まったく正しいファイルを書き、それらをすべて縦棒チャートとして読み戻しました。根本原因は1行で、教訓はあなたがこれから書くどんなOOXMLパーサにも一般化します。TXMLReader.Nameは、ドキュメントに書かれたままの名前を返します。プレフィックスを含めてです。従来のチャートパートではほとんどの要素がプレフィックスなしで届くため、SameText(Reader.Name, 'series')が機能し、それについて考えなくなります。chartExパートではすべての要素がプレフィックス付きです。リーダは'cx:series'を渡し、比較はそのすべてで静かに失敗し、例外も不正なXMLもなく、空のシリーズリストとデフォルトにフォールバックするチャート型だけが残ります。修正は、呼び出しサイトごとではなく1か所でローカル名を比較することです
// Reader.Nameは名前空間プレフィックスを運ぶ:'cx:series'であって'series'ではない
function IsEl(const LocalName: string): Boolean;
var
NodeName: string;
begin
NodeName := String(Reader.Name);
Result := SameText(NodeName, LocalName) or
EndsText(':' + LocalName, NodeName);
end;
ParseChartExXmlはすべての要素テストでそのヘルパーを使い、属性には同等のAttrValヘルパーを使います。cx:autoUpdateやその仲間もプレフィックス付きだからです。シリーズ要素が実際に見えるようになると、型は最初に遭遇したlayoutIdから戻り、各cx:seriesはcx:dataId/@valをcx:data/@idに突き合わせて自分のcx:dataブロックに配線されます。開いたが一度も変更されないチャートは、代わりに古い経路を取ります。元のバイトがFRawChartXmlからリプレイされるため、手を付けていない顧客ファイルは入ったままの形で出てきます。スパークライン拡張の記事でシートレベル拡張を支配するのと同じ、保守的な書き手の規律です
HotXLSがこれらのチャートで意図的にやらないこと
2つの境界は率直に言う価値があります。どちらも埋めるのを待っている穴ではなく選択だからです。内蔵のPDFとSVGレンダラはウォーターフォールやツリーマップのシリーズを描きません。型がどの描画分岐にも一致しないため、シリーズレンダリングはスキップされ、シートの残りは普通にエクスポートされます。エクスポートしたPDFにもっともらしいツリーマップを偽装することは、何も描かないより悪いでしょう。同じファイルに対してExcelが示すものと静かに食い違うからです。そしてxlsxChartWaterfallとxlsxChartTreemapは、HotXLSがモデルから構築する唯一の2つの拡張レイアウトです。ST_SeriesLayoutはサンバースト、ファネル、箱ひげ図、ヒストグラムも定義しており、それらのどれかを含むワークブックも生XMLリプレイ経路をラウンドトリップします。今日のDelphiコードから作成できないだけです。従来のチャート型のレンダリングは変わらず、チャート、画像、図形描画の記事で扱われており、chartExパイプラインは、DelphiとC++Builder向けのHotXLS Delphi Excel Componentと並んで同梱されます