HotXLS umí umístit graf přímo na list, ukotvený do rozsahu buněk, místo aby ho odložil na samostatný grafový list. V pojmech BIFF8 to znamená zapsat kreslicí tvar s OBJ záznamem typu 5 a zaparkovat grafový substream na konci proudu záznamů listu, což je přesně to rozložení, které produkuje Excel, a přesně místo, kde ho parser očekává
Ten rozdíl zajímá každého, kdo generuje provozní reporty. Grafový list je slušné bydliště pro jediný vizuál do záhlaví. Měsíční přehled po regionech chce mít graf vedle čísel, která shrnuje, na stejném listu a ve velikosti bloku buněk, do nějž patří, takže si čtenář projede scroll jednou místo přepínání záložek a ztráty kontextu
Čtení už tam bylo, zápis ne
Ta asymetrie stojí za pojmenování, protože určuje podobu práce. HotXLS už embedded grafy číst uměl: když proud záznamů listu obsahuje BOF označený jako grafový substream, parser přepne kontext, posbírá grafové záznamy a při zavírajícím EOF je vrátí kreslicímu tvaru, kterého OBJ záznam představil. Tou cestou prošel každý sešit od Excelu, který knihovna kdy otevřela
Chyběla strana zápisu a užitečný důsledek je, že nový writer měl přesnou specifikaci, kterou má trefit: vyprodukovat bajtové rozložení, které stávající reader už umí znovu připojit. Lepší akceptační kritérium pro featuru binárního formátu, než je nezávisle napsaný reader, který jste nemohli měnit, neexistuje
Z čeho se embedded graf skládá
Tři kusy musí souhlasit. Vrstva kreslení přispěje tvarem host controlu, objektová vrstva OBJ záznamem, jehož common object data deklaruje objektový typ 5, a proud záznamů přispěje samotným grafovým substreamem. Option flags na OBJ záznamu jsou ty, které Excel píše pro rámec grafu: positioned, locked, automatic line a automatic fill, a právě to dělá, že se embedded graf po kliknutí chová jako nativní
Anchor si zaslouží poznámku, protože je běžným zdrojem off-by-one bugů. API HotXLS bere čísla řádků a sloupců od jedničky, v souladu se zbytkem knihovny, a client anchor zapsaný do souboru je od nuly. Konverze proběhne uvnitř AddChartObject, takže volající zůstávají v souřadném systému, který používají všude jinde, ale kdo srovnává hex dump se svým voláním, musí si pamatovat, kterou stranu té hranice zrovna čte
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Series: array[0..1] of TXLSChartSeriesInfo;
begin
Book := TXLSWorkbook.Create(nil);
try
Book.LoadFromFile('regional-sales.xls');
Sheet := Book.Sheets[0];
FillChar(Series, SizeOf(Series), 0);
Series[0].Name := 'Actual';
Series[0].Categories := 'Data!$A$2:$A$13';
Series[0].Values := 'Data!$B$2:$B$13';
Series[0].DataLabels.ShowValue := True;
Series[0].HasDataLabels := True;
Series[1].Name := 'Target';
Series[1].Categories := 'Data!$A$2:$A$13';
Series[1].Values := 'Data!$C$2:$C$13';
Series[1].SecondaryAxis := True;
// Ukotveno do E2:M20 na tomto listu, číslování od jedničky
Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
'Month', 'Amount', Series, 2, 5, 20, 13);
Book.SaveToFile('regional-sales-charted.xls');
finally
Book.Free;
end;
end;
FillChar nad polem series není dekorace. TXLSChartSeriesInfo nese několik volitelných sub-záznamů, data labels, per-series styl, trendlines a error bars, každý střežený booleanem, a částečně inicializovaný záznam na stacku podá emitteru flagy, které nikdo nenastavil. Pole nejprve vynulujte a pak nastavte jen ty členy, které skutečně chcete
Které reference na série embedded cesta přijímá?
Prosté rozsahy ve stylu A1 uvnitř téhož sešitu, a toto omezení je úmysl, ne přehlédnutí. Každá reference se vyřeší proti seznamu listů sešitu a převede na external reference index, který grafové záznamy potřebují. Pojmenovaný rozsah nebo reference do externího sešitu spadne na placeholder s parsed expression nulové délky, takže se graf zapíše čistě, ale ta konkrétní série nemá datový zdroj, dokud ji nasměrujete na rozsah
Důvodem je prostý inženýrský trade-off. Plná cesta kompilace referencí existuje na route grafového listu, zabalená ve vrstvě kolekce worksheetů, a vyndat ji čistě ven by znamenalo duplikovat sto řádků resolution logiky pro případ, který je v praxi neobvyklý. Embedded graf skoro vždy plotuje buňky na vlastním listu nebo na sousedním datovém listu. Pojmenované a externí reference pokrývá cesta grafového listu přes AddChartSheet, takže nic není nedostupné, jen se to dosáhne z jiného vstupního bodu
Všechno ostatní v modelu series funguje na obou routách identicky. Vázání na sekundární osu, per-series styl čáry, výplně a markerů, trendlines, error bars a data labels jsou všichni součástí TXLSChartSeriesInfo a emitují se stejným způsobem, takže definice grafu může přejít mezi embedded objektem a grafovým listem se změnou jen volání. Mechaniku axis groups za flagem sekundární osy pokrývá článek sekundární axis groups při BIFF zápisu
Proč se titulek grafu načetl jako dva znaky?
Protože se počet znaků četl tam, kde se čekal počet bajtů, a BIFF Unicode strings tenhle omyl umožňují napsat snadno a vidět těžko. Krátký BIFF Unicode string začíná počtem znaků a flags bytem a flags byte nese high-byte bit říkající, jestli payload je jeden bajt na znak, nebo dva. Přečtete-li 16bitový payload s počtem znaků, jako by to byla délka v bajtech, dostanete přesně polovinu stringu: series jménem Sales se vrátí jako Sa a titulek grafu se usekne stejně, protože titulky a labely serií sdílí dekódovací cestu
Tenhle defekt je pozoruhodný tím, že se třikrát opakoval ve stejné rodině záznamů, jednou ve jménech trendline, jednou ve jménech pivot chart a jednou v titulcích grafů. Každý výskyt vypadal jako čerstvý bug v nové featuře. Všechny tři byly tentýž chybějící násobek. Pravidlo, které to nakonec uzavřelo, je mechanické a má se aplikovat bez úsudku: kdykoli čtete jeden z těchhle stringů, nejdřív se podívejte na high-byte flag a vynásobte počet znaků šířkou payloadu, než sáhnete na buffer. Detaily na úrovni záznamů pokrývá článek dekódování počtů znaků XLUnicodeString a high-byte flagu
// Embedded graf sdílí vrstvu kreslení s obrázky a tvary, takže
// existující kresba na listu zůstane zachována. AddChartObject
// vrací index vytvořeného objektu
var
ObjIndex: Integer;
begin
ObjIndex := Sheet.AddChartObject(xlsChartTypeLine, 'Trend',
'Week', 'Units', Series, 2, 8, 18, 16);
if ObjIndex < 0 then
raise Exception.Create('chart object was not created');
end;
Jak se embedded grafy staví proti alternativám
Routy jsou tři a odpovídají na různé otázky. Embedded chart object patří vedle svých dat na worksheet a to je, co většina reportů chce. Grafový list sedí na jediný vizuál pro prezentaci a dává vám plnou cestu kompilace referencí. Zachovat existující graf z načteného souboru nedotčený je správná odpověď, když sešit přišel z Excelu s formátováním, které nikdo nechce, aby knihovna přeinterpretovala; tohle pass-through chování popisuje článek zachované ChartML a combination charts
Protože embedded graf jede po vrstvě kreslení, koexistuje s obrázky a tvary na témže listu místo aby je nahrazoval, a obecný model té vrstvy pokrývá článek grafy, obrázky a kresby v HotXLS. Všechny tři routy se dodávají v HotXLS Delphi spreadsheet komponentě, takže volba je o tom, jak má report vypadat, ne o tom, co knihovna dokáže vyjádřit
Metodický point je ten, který stojí za zapamatování. Když má featura binárního formátu existující reader, stavějte writer proti readeru, ne proti svému čtení specifikace. Reader kóduje roky kontaktu se soubory, které reálné aplikace skutečně produkovaly, včetně částí, které specifikace popisuje volně, a writer, který reader uspokojí, má mnohem větší šanci uspokojit i Excel