Teknik Makale

Delphi'de HotXLS ile Excel Waterfall ve Treemap Grafikleri

HotXLS, Excel waterfall ve treemap grafiklerini bağımsız cx:chartSpace parçaları olarak yazar — xl/charts/chartExN.xml, application/vnd.ms-office.chartex+xml içerik türünü taşır — ve onları bir mc:AlternateContent sarmalayıcıyla çapalayarak Excel 2016 ve sonrasının grafiği render etmesini, eski okuyucuların ise bir fallback şekli almasını sağlar. xlsxChartWaterfall ya da xlsxChartTreemap seç ve bütün parça hattı arkadan gelir

Bu sorun genellikle kendini bir destek biletiyle duyurur. Bir müşteri, finans ekibinin Excel 2019'da kurduğu bir çalışma kitabı gönderir; raporlama aracın onu açar, kaydeder ve waterfall grafiği kaybolmuştur. İstisna yok, uyarı yok, logda hiçbir şey yok — grafik yalnızca çıktıda yoktur. Klasik grafik kod yolunda aramaya gidersen uzun süre ararsın, çünkü grafik en başından beri hiç orada olmadı

HotXLS'in bir chartEx grafiği için yazdığı mc:AlternateContent çapası: mc:Choice dalı cx1 gerektirir ve a:graphicData'sı chartex ad alanına referans veren, çıplak bir cx:chart ilişkisi taşıyan bir xdr:graphicFrame tutar; mc:Fallback ise eski okuyucuların onun yerine çizdiği bir bilgi dikdörtgeni taşır
mc:AlternateContent çapası bir chartEx grafiğini Excel 2016 ile eski okuyucular arasında yönlendirir: Choice dalı cx1 gerektirir ve chartex çerçevesini taşır, Fallback ise eski okuyucuların çizmek zorunda olduğu bilgi dikdörtgenini tutar

Waterfall ve treemap grafikleri neden c:chartSpace içinde değil?

Çünkü Microsoft onları oraya koyamazdı. ECMA-376 Bölüm 1 §21.2'deki DrawingML grafik şeması, grafik elemanlarını — c:barChart, c:lineChart, c:pieChart ve gerisi — kapalı bir seçim olarak numaralandırır; o yüzden Excel 2016'da waterfall, treemap, sunburst ve funnel eklemek, mevcut her okuyucunun dayandığı bir şemayı kırmak anlamına gelirdi. Yeni tipler bunun yerine paralel bir belgeye gitti: http://schemas.microsoft.com/office/drawing/2014/chartex adresindeki 2014 chartex ad alanında bir cx:chartSpace parçası, klasik grafiklerin yanında saklanır ama yapısal olarak onlarla alakasız. Çizim tarafı iki dünyayı mc:AlternateContent ile diker — ECMA-376 Bölüm 3'teki uyumluluk mekanizması: mc:Choice dalı Requires="cx1" bildirir ve a:graphicData/@uri değeri chartex ad alanı olan, yükü çıplak bir <cx:chart r:id="rIdN"/> referansından ibaret bir xdr:graphicFrame tutar; mc:Fallback ise Excel'in oraya kendisinin yazdığı bilgiyi içeren bir dikdörtgen tutar. cx1 anlamayan bir okuyucu, Bölüm 3 sözleşmesi gereği fallback'i almak zorundadır

İlişki türü de diğer ipucu. Klasik bir grafik, standart OOXML grafik ilişkisiyle çizim parçasına asılır; bir chartEx parçası bunun yerine [MS-ODRAWXML]'de belgelenen http://schemas.microsoft.com/office/2014/relationships/chartEx ilişkisini kullanır. HotXLS iki dünyayı da sabitler olarak açığa çıkarır — o ilişki için XlsxRtChartEx, içerik türü için XlsxCtChartEx, çapanın ihtiyaç duyduğu iki ad alanı için XlsxNsMsChartEx ve XlsxNsMsChartEx2015 — ve XlsxChartIsChartEx yüklemi, kaydederken bir grafiği genişletilmiş yola yönlendiren şeydir. Kombinasyon grafikleri ve korunmuş klasik grafik XML'i, korunmuş grafik XML'i ve kombinasyon grafikleri makalesinde anlatılan diğer yoldan gider

Excel'in dosyayı açıp açmamasına karar veren dört şema kuralı

Chartex şeması dört belirli noktada klasik olandan daha katıdır ve yanlış yaptığında dördü de aynı faydasız onarım istemini üretir. HotXLS'teki BuildChartExKnownXml ve BuildChartExRelsXml dördünü de içine gömer, ama XML'i elle incelersen bilmeye değerler

  • Sıra. Belgede cx:chartData, cx:chart'tan önce gelmelidir. Bu, klasik grafiklerden edindiğin sezginin tersidir; orada cache'ler plot alanının içindeki serinin içinde yaşar; bir chartEx parçasında veri önde kendi bloğunda durur ve grafik gövdesi ona yalnızca id ile referans verir
  • Kardinalite. cx:plotArea tam olarak bir cx:plotAreaRegion içerebilir. Sıfır değil, iki değil. Burada kombinasyon grafiği hikâyesi yok — bir bölge ve içindeki seriler
  • Tür yerleşimi. Grafik türü hiçbir eleman adı değildir. Microsoft chartex şemasındaki ST_SeriesLayout numaralandırmasından bir token alan, cx:series üzerindeki layoutId özniteliğidir. XlsxChartExLayoutId, HotXLS enum'unu o token'a eşler ve 'waterfall' ya da 'treemap' döndürür; XlsxChartExLayoutIdToType okumada eşlemeyi ters yönde çalıştırır
  • Klasik grafiklerin hiç ihtiyaç duymadığı ilişki. cx:chartData bir <cx:externalData r:id="rId1"/> elemanı tutar ve o ilişkinin, verinin sahibi çalışma sayfasını işaret eden xl/charts/_rels/chartExN.xml.rels içinde var olması gerekir, çünkü Excel cx:f aralık formüllerini çözmek için onu kullanır. Rels parçasını atla ve grafik, nedenini açıklayan hiçbir şey olmadan boş açılır

Delphi'de bir waterfall ya da treemap grafiği nasıl eklersin?

HotXLS'te başka bir grafik eklediğin yolla aynı — genişletilmiş tipler TXLSXChartType üyesidir, o yüzden TXLSXWorksheet.AddChart onları doğrudan alır ve aşağı akıştaki her şey enum değerine göre anahtarlanır. Öğrenilecek ayrı bir chartEx API'si yok

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Chart: TXLSXChart;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('cashflow.xlsx');
    Sheet := Book.Sheets[0];

    // Satır/sütun koordinatlarında çapa dikdörtgeni, klasik bir grafikle aynı
    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;
Genişletilmiş Excel grafikleri için HotXLS kaydetme hattı: xlsxChartWaterfall ya da xlsxChartTreemap ile AddChart, XlsxChartIsChartEx'e ulaşır; o da klasik grafikleri paylaşılan bir sayaca, chartEx grafiklerini ise chartEx1.xml'i, çalışma sayfasına geri işaret eden rels dosyasını, chartex içerik türü geçersiz kılmasını ve AlternateContent çapa yükünü yazan bağımsız bir sayaca yönlendirir
Tek bir grafik modeli ve tek bir yönlendirme yüklemi kaydederken iki sayacı besler: klasik grafikler chart1.xml ve chart2.xml paylaşır, waterfall ise chartEx sayacı klasik olandan bağımsız olduğu için chartEx1.xml olarak düşer

Kaydederken HotXLS, XlsxChartIsChartEx değerinin True döndüğünü görür, ayrı bir chartEx sayacından bir parça adı ayırır ve xl/charts/chartEx1.xml, rels dosyası, içerik türü geçersiz kılması ve XlsxChartExAnchorPayloadXml tarafından üretilen mc:AlternateContent çapasını yazar. Bağımsız sayaç önemlidir: iki klasik grafikli ve bir waterfall'lı bir çalışma kitabı, chartEx3.xml değil, chart1.xml, chart2.xml ve chartEx1.xml alır. Treemap'ler sonra waterfall'lardan iki ayrıntıda ayrılır ve ikisi de aynı kurucu tarafından halledilir. Sayısal boyut tür değiştirir — waterfall için cx:numDim type="val", treemap için type="size" olur, çünkü bir treemap dikdörtgeni büyüklüğü çizilen bir değer olarak değil alan olarak kodlar — ve treemap'ler genelde bir kategori hiyerarşisi ister; model bunu TXLSXChartSeries.CategoriesMultiLevel ile ifade eder. Bu bayrağı kur ve S.ResolveCategoryLevels yazma zamanında aralığı çalışma sayfasından okur, S.CategoryLevelCount ve S.CategoryLevels[] arkasında her düzey için bir TXLSXChartDataCache'e böler ve BuildChartExLevelXml, tek bir cx:strDim içinde her düzey için bir cx:lvl yayar — yaprak düzey önce — ECMA-376 Bölüm 1 §21.2'nin c:multiLvlStrCache için kullandığı fiziksel sıralamanın aynısı; o yüzden işaretlemelerin hiçbiri taşınmasa da bu kural taşınır

Chart := Sheet.AddChart(xlsxChartTreemap, 'Revenue by Region',
  2, 8, 24, 16);
S := Chart.AddSeries('Revenue',
  'Sheet1!$A$2:$B$40',   // iki sütun: yaprak etiketi, sonra üst grup
  'Sheet1!$C$2:$C$40');
S.CategoriesMultiLevel := True;

Klasik grafiklerdeki her varsayımı kıran cache şekli

Klasik bir grafik cache okuyucusu yazdıysan, üç alışkanlık bir chartEx parçasında sana ihanet eder ve üçü de tek bir tasarım kararından gelir: chartex, DrawingML'in alt elemanlarda sakladığını özniteliklerde ve eleman içeriğinde saklar

  • Cache'lenmiş bir nokta <cx:pt idx="0">42</cx:pt> biçimindedir. Değer eleman içeriğidir. İnecek bir c:v alt elemanı yoktur; o yüzden v düğümleri arayarak yürüyen bir parser hiçbir şey bulamaz ve cache'i boş raporlar
  • ptCount ve formatCode, cx:lvl üzerindeki özniteliklerdir. Klasik bir c:numCache içinde ikisi de alt elemandır
  • Birden çok hiyerarşi düzeyi, tek bir cx:strDim içinde birden çok cx:lvl elemanıdır. c:multiLvlStrCache'in c:strCache'ten ayrı olduğu gibi ayrı bir çok düzeyli cache elemanı yoktur

BuildChartExLevelXml bu şekli yazar ve ParseChartExXml onu geri okur; her değeri dışarı çıkarken XlsxEscapeText üzerinden geçirir, o yüzden ampersand ya da açılı ayraç içeren bir kategori etiketi round trip'tan sağ çıkar. Kurucunun sayısal bir düzey biçim kodu taşımadığında ne yaptığını not etmeye değer: özniteliği düşürmek yerine formatCode="General" yazar, çünkü burada öznitelik, klasik karşılığındaki gibi isteğe bağlı değildir

HotXLS chartEx parçalarında yan yana cache şekilleri: klasik bir c:numCache, ptCount ve formatCode değerlerini c:v içinde olacak şekilde alt elemanlar olarak saklar; cx:numDim ise onları değer eleman içeriği olacak şekilde cx:lvl üzerinde öznitelik olarak saklar ve CategoriesMultiLevel, tek bir cx:strDim içinde yaprak önce olacak şekilde her kategori düzeyi için bir cx:lvl yayar
Chartex, DrawingML'in alt elemanlarda sakladığını özniteliklerde ve eleman içeriğinde saklar: değer doğrudan cx:pt içinde durur, ptCount ve formatCode cx:lvl üzerinde öznitelik olur ve bir kategori hiyerarşisi tek cx:strDim içinde birkaç cx:lvl elemanıdır

Tür geri okumasını bozan ad alanı öneki

HotXLS chartEx parser'ının ilk sürümü kusursuz dosyalar yazıyor ve her birini sütun grafiği olarak geri okuyordu. Kök neden tek bir satırdı ve ders, yazacağın her OOXML parser'ına genelleniyor: TXMLReader.Name, adı belgede yazıldığı gibi döndürür, önek dahil. Klasik bir grafik parçasında çoğu eleman öneksiz gelir, o yüzden SameText(Reader.Name, 'series') çalışır ve artık onu düşünmezsin. Bir chartEx parçasında her eleman önekli gelir — okuyucu sana 'cx:series' uzatır, karşılaştırma hepsinde sessizce başarısız olur ve ne istisna ne bozuk XML alırsın; yalnızca boş bir seri listesi ve varsayılana düşen bir grafik türü. Düzeltme, yerel ada göre karşılaştırmaktır; her çağrı noktasında değil, tek bir yerde

// Reader.Name, ad alanı önekini taşır: 'series' değil 'cx: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 her eleman testi için bu yardımcıyı ve öznitelikler için eşdeğer bir AttrVal yardımcısını kullanır, çünkü cx:autoUpdate ve dostları da öneklidir. Seri elemanları gerçekten görülmeye başlandığında tür, karşılaşılan ilk layoutId'den geri gelir ve her cx:series, cx:dataId/@val değeri cx:data/@id ile eşleştirilerek kendi cx:data bloğuna bağlanır. Açılıp da değiştirilmeyen grafikler bunun yerine eski yolu alır: orijinal baytlar FRawChartXml'den yeniden oynatılır, o yüzden dokunulmamış bir müşteri dosyası girdiği gibi geri çıkar — sparkline uzantısı makalesinde sayfa düzeyi uzantıları yöneten muhafazakâr yazar disiplininin aynısı

HotXLS'in bu grafiklerle bilinçli olarak yapmadığı şey

İki sınır açıkça söylenmeye değer, çünkü ikisi de doldurulmayı bekleyen boşluklar değil, seçimler. Yerleşik PDF ve SVG render edicileri waterfall ya da treemap serilerini çizmez: tipler hiçbir çizim dalıyla eşleşmez, o yüzden seri render'ı atlanır ve sayfanın geri kalanı normal biçimde dışa aktarılır — dışa aktarılan bir PDF'de makul görünen bir treemap sahtesi yapmak hiç çizmemekten daha kötü olur, çünkü aynı dosya için Excel'in gösterdiğiyle sessizce çelişir. Ve xlsxChartWaterfall ile xlsxChartTreemap, HotXLS'in modelden kurduğu yalnızca iki genişletilmiş düzendir; ST_SeriesLayout ayrıca sunburst, funnel, box-and-whisker ve histogram tanımlar ve bunlardan herhangi birini içeren bir çalışma kitabı yine de ham XML yeniden oynatma yolundan round trip olur, yalnızca bugün Delphi kodundan birini yazamazsın. Klasik grafik tiplerinin render'ı değişmedi ve grafikler, görseller ve çizimler makalesinde ele alınıyor; chartEx hattı da onun yanında, Delphi ve C++Builder için HotXLS Delphi Excel Component içinde geliyor