HotXLS kan een grafiek rechtstreeks op een worksheet zetten, verankerd aan een celbereik, in plaats van hem op een speciale chart sheet te plaatsen. In BIFF8-termen betekent dat het schrijven van een drawing shape met een OBJ-record van type 5 en het parkeren van de chart substream aan het einde van de sheet record stream, precies de layout die Excel produceert en precies waar de reader hem verwacht
Het onderscheid telt voor iedereen die operationele rapporten genereert. Een chart sheet is een prima thuis voor één opvallende visual. Een maandelijkse regionale uitsplitsing wil de grafiek naast de cijfers die hij samenvat, op hetzelfde blad, op maat van het cellenblok waartoe hij behoort, zodat een lezer één keer scrollt in plaats van van tab te wisselen en context te verliezen
Lezen kon al, schrijven nog niet
De asymmetrie is het benoemen waard omdat zij het werk vormgeeft. HotXLS kon al embedded charts lezen: bevat de worksheet record stream een BOF die als chart substream is gemarkeerd, dan wisselt de parser van context, verzamelt hij de chart records en geeft hij ze bij de afsluitende EOF terug aan de drawing shape die het OBJ-record introduceerde. Dat pad was belopen door elke workbook die de library ooit opende en die door Excel was gemaakt
Wat ontbrak was de auteurskant, en het nuttige gevolg is dat de nieuwe writer een precieze specificatie had om op te mikken: produceer de bytelayout die de bestaande reader al weer aan elkaar plakt. Er is geen beter acceptatiecriterium voor een binaire formaatfeature dan een onafhankelijk geschreven reader die u niet mocht aanpassen
Waaruit een embedded chart bestaat
Drie stukken moeten met elkaar kloppen. De drawing layer levert een host-control shape, de objectlaag levert een OBJ-record waarvan de common object data object type 5 declareert, en de record stream levert de chart substream zelf. De optieflags op het OBJ-record zijn die welke Excel voor een chart frame schrijft: positioned, locked, automatic line en automatic fill, en dat is wat de embedded chart zich als een native laat gedragen zodra een gebruiker erop klikt
De anchor verdient een kanttekening want hij is een gebruikelijke bron van off-by-one-bugs. De HotXLS API neemt rij- en kolomnummers vanaf één, aansluitend bij de rest van de library, en de client anchor die in de file wordt weggeschreven is nulgebaseerd. De conversie gebeurt binnen AddChartObject, dus aanroepers blijven in het coördinatensysteem dat ze overal anders gebruiken, maar wie een hex dump naast zijn eigen aanroep legt, moet onthouden aan welke kant van die grens hij leest
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;
// Verankerd aan E2:M20 op dit blad, ééngebaseerd
Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
'Month', 'Amount', Series, 2, 5, 20, 13);
Book.SaveToFile('regional-sales-charted.xls');
finally
Book.Free;
end;
end;
De FillChar op de series-array is geen versiering. TXLSChartSeriesInfo draagt verschillende optionele sub-records, data labels, per-series stijl, trendlines en error bars, elk achter een booleaanse poort, en een half geïnitialiseerd record op de stack geeft de emitter flags die niemand heeft gezet. Zet de array op nul en zet daarna de velden die u bedoelt
Welke series-verwijzingen accepteert het embedded pad?
Gewone A1-stijl bereiken binnen dezelfde workbook, en die beperking is bewust in plaats van een vergissing. Elke verwijzing wordt geresolved tegen de sheetlijst van de workbook en omgezet in de external reference index die de chart records nodig hebben. Een named range of een verwijzing naar een externe workbook valt terug op een placeholder met een nul-lange geparsde expressie, dus de grafiek schrijft netjes weg maar die ene series heeft geen datasource totdat u hem naar een bereik wijst
De reden is een zuivere engineeringafweging. Het volledige referentiecompilatiepad bestaat op de chart-sheet-route, verpakt in de worksheet-collection-laag, en het er netjes uittillen zou betekenen dat honderd regels resolutielogica worden gedupliceerd voor een geval dat in de praktijk zelden voorkomt. Een embedded chart plot bijna altijd cellen op zijn eigen blad of een zusterblad met data. Named en external references zijn op de chart-sheet-route gedekt via AddChartSheet, dus niets is onbereikbaar, het wordt alleen via een andere entry point bereikt
Alles andere in het series-model werkt identiek op beide routes. Secondary axis-koppeling, per-series lijn-, vul- en markerstijlen, trendlines, error bars en data labels maken allemaal deel uit van TXLSChartSeriesInfo en worden allemaal op dezelfde manier geëmitteerd, dus een grafiekdefinitie kan wisselen tussen een embedded object en een chart sheet met alleen een andere aanroep. De axis-group-mechaniek achter de secondary axis-flag staat in secondary axis groups bij BIFF-schrijven
Waarom las de grafiektitel als twee tekens?
Omdat een tekenaantal werd doorgegeven waar een byteaantal werd verwacht, en BIFF Unicode strings maken die fout makkelijk te schrijven en moeilijk te zien. Een korte BIFF Unicode string begint met een tekenaantal en een flags-byte, en de flags-byte draagt de high-byte-bit die zegt of de payload één byte per teken is of twee. Lees een 16-bit payload met het tekenaantal alsof het een bytelengte is, en u krijgt precies de helft van de string: een series genaamd Sales komt terug als Sa, en een grafiektitel wordt op dezelfde manier afgekapt omdat titels en series-labels het decodepad delen
Wat dit defect bijzonder maakt, is dat het drie keer terugkwam in dezelfde recordfamilie: een keer in trendline-namen, een keer in pivot chart-namen, en een keer in grafiektitels. Elke keer leek het op een nieuwe bug in een nieuwe feature. Alle drie dezelfde vergeten vermenigvuldiging. De regel die hem uiteindelijk sloot, is mechanisch en moet zonder oordeel worden toegepast: raadpleeg bij het lezen van een van deze strings eerst de high-byte-flag en vermenigvuldig het tekenaantal met de payloadbreedte voordat u de buffer aanraakt. De details op recordniveau staan in XLUnicodeString-tekenaantallen en de high-byte-flag decoderen
// De embedded chart deelt de drawing layer met afbeeldingen en shapes,
// dus een bestaande tekening op het blad blijft bewaard. AddChartObject
// geeft de index van het aangemaakte object terug
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;
Waar embedded charts passen naast de alternatieven
Er zijn drie routes en ze beantwoorden verschillende vragen. Een embedded chart-object hoort naast zijn data op een worksheet en is wat de meeste rapporten willen. Een chart sheet past bij één presentatievisual en geeft het volledige referentiecompilatiepad. Een bestaande grafiek uit een geladen file behouden, onaangeroerd, is het juiste antwoord als de workbook uit Excel komt met opmaak die niemand de library opnieuw wil laten interpreteren; dat pass-through-gedrag staat beschreven in bewaarde ChartML en combination charts
Omdat de embedded chart op de drawing layer rijdt, bestaat hij naast afbeeldingen en shapes op hetzelfde blad in plaats van ze te vervangen, en het algemene model voor die laag staat in charts, afbeeldingen en tekeningen in HotXLS. Alle drie de routes worden geleverd in de HotXLS Delphi spreadsheet component, dus de keuze gaat over hoe het rapport eruitziet en niet over wat de library kan uitdrukken
Het methodologische punt is degene die u moet onthouden. Als een binaire formaatfeature al een bestaande reader heeft, bouw de writer dan tegen de reader aan in plaats van tegen uw lezing van de specificatie. De reader codeert jaren contact met files die echte applicaties werkelijk produceerden, inclusief de delen die de specificatie vaag formuleert, en een writer die daaraan voldoet, voldoet een stuk vaker ook aan Excel