HotXLS kan placere et diagram direkte på et worksheet, forankret til et celleområde, i stedet for at lægge det på et dedikeret chart sheet. I BIFF8-termer betyder det at skrive en drawing shape med en OBJ-record af type 5 og parkere chart substream i enden af sheet record stream, hvilket er præcis det layout, Excel producerer, og præcis dér, hvor readeren forventer at finde det
Skelnen betyder noget for alle, der genererer driftsrapporter. Et chart sheet er et fint hjem til ét enkelt hovedvisual. En månedlig regional opdeling vil have diagrammet ved siden af tallene, den opsummerer, på samme ark, størrelsesbestemt til den blok af celler, den hører til, så en læser scroller én gang i stedet for at skifte faner og miste konteksten
Læsning var der allerede, skrivning var ikke
Asymmetrien er værd at nævne, for den former arbejdet. HotXLS kunne allerede læse indlejrede diagrammer: når worksheet record stream indeholder en BOF markeret som chart substream, skifter parseren kontekst, samler chart-recordene og giver ved den afsluttende EOF dem tilbage til den drawing shape, som OBJ-recorden introducerede. Den vej var blevet brugt af hver eneste Excel-fabrikeret projektmappe, biblioteket nogensinde havde åbnet
Det, der manglede, var authorsiden, og den nyttige konsekvens er, at den nye writer havde en præcis specifikation at ramme: producere det byte-layout, den eksisterende reader allerede gensammenføjer. Der findes intet bedre acceptkriterium for en binær format-feature end en uafhængigt skrevet reader, du ikke fik lov at ændre
Hvad et indlejret diagram er lavet af
Tre brikker skal være enige. Drawing-laget bidrager med en host-control shape, objektlaget bidrager med en OBJ-record, hvis common object data deklarerer object type 5, og record stream bidrager med selve chart substream. Option-flaggene på OBJ-recorden er dem, Excel skriver for en chart frame: positioned, locked, automatic line og automatic fill, hvilket er det, der gør, at det indlejrede diagram opfører sig som et native et, når en bruger klikker på det
Forankringen fortjener en note, for den er en almindelig kilde til off-by-one bugs. HotXLS API tager én-baserede række- og kolonnenumre, matchende resten af biblioteket, og den client anchor, der skrives ind i filen, er nul-baseret. Konverteringen sker inde i AddChartObject, så kaldere forbliver i det koordinatsystem, de bruger alle andre steder, men enhver, der sammenligner en hex dump med sit eget kald, må huske, hvilken side af den grænse vedkommende læser
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;
// Forankret til E2:M20 på dette ark, én-baseret
Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
'Month', 'Amount', Series, 2, 5, 20, 13);
Book.SaveToFile('regional-sales-charted.xls');
finally
Book.Free;
end;
end;
FillChar på series-arrayet er ikke pynt. TXLSChartSeriesInfo bærer flere valgfrie sub-records, data labels, per-series style, trendlines og error bars, hver gated af en boolean, og en delvist initialiseret record på stakken vil give emitteren flags, ingen har sat. Nulstil arrayet, og sæt så felterne, du mener
Hvilke series-referencer accepterer den indlejrede vej?
Rene A1-style ranges inde i samme projektmappe, og den begrænsning er bevidst snarere end en forglemmelse. Enhver reference resolves mod projektmappens sheet-liste og omdannes til den external reference index, chart-recordene behøver. En named range eller en reference til en ekstern projektmappe falder tilbage til en placeholder med et parsed expression af længde nul, så diagrammet skriver rent, men netop den serie har ingen datakilde, før du peger den på et område
Grunden er en lige ingeniørafgørelse. Den fulde referencekompileringsvej findes på chart sheet-ruten, indpakket i worksheet-collection-laget, og at løfte den rent ud ville betyde at duplikere hundrede linjer resolutionslogik for et tilfælde, der er ualmindeligt i praksis. Et indlejret diagram plotter næsten altid celler på sit eget ark eller et søster-datark. Named- og eksterne referencer er dækket på chart sheet-vejen via AddChartSheet, så intet er utilgængeligt, det nås bare fra et andet indgangspunkt
Alt andet i series-modellen virker identisk på begge ruter. Secondary axis-binding, per-series line, fill- og marker-styles, trendlines, error bars og data labels er alle del af TXLSChartSeriesInfo og alle emitteret på samme måde, så en diagramdefinition kan flytte mellem et indlejret objekt og et chart sheet med kun kaldet ændret. Axis-group-mekanikken bag secondary axis-flagget er dækket i secondary axis groups ved BIFF-skrivning
Hvorfor blev diagramtitlen læst som to tegn?
Fordi et tegnantal blev sendt der, hvor et byteantal var forventet, og BIFF Unicode strings gør den fejl let at skrive og svær at se. En kort BIFF Unicode-streng begynder med et tegnantal og en flags-byte, og flags-byten bærer high-byte-bitten, der siger, om payloaden er én byte per tegn eller to. Læser man en 16-bit payload med tegnantallet, som om det var en byttelængde, får man præcis halvdelen af strengen: en serie ved navn Sales kommer tilbage som Sa, og en diagramtitel afkortes på samme måde, for titler og series-labels deler dekodevejen
Det, der gør denne defekt bemærkelsesværdig, er, at den vendte tilbage tre gange i samme recordfamilie, én gang i trendline-navne, én gang i pivot chart-navne og én gang i diagramtitler. Hver forekomst lignede en frisk bug i en ny feature. Alle tre var den samme manglende multiplikation. Reglen, der til sidst lukkede den, er mekanisk og bør anvendes uden skøn: hver gang du læser en af disse strenge, så konsulter high-byte-flagget først og multiplicér tegnantallet med payload-breden, før du rører bufferen. Record-level-detaljerne er i dekodning af XLUnicodeString-tegnantal og high-byte-flagget
// Det indlejrede diagram deler drawing-laget med billeder og shapes,
// så en eksisterende tegning på arket bevares. AddChartObject
// returnerer indekset for det oprettede objekt
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;
Hvor indlejrede diagrammer passer i forhold til alternativerne
Der findes tre ruter, og de besvarer forskellige spørgsmål. Et indlejret chart-objekt hører hjemme ved siden af sine data på et worksheet og er, hvad de fleste rapporter vil have. Et chart sheet passer til ét enkelt præsentationsvisual og giver dig den fulde referencekompileringsvej. At bevare et eksisterende diagram fra en indlæst fil urørt er det rigtige svar, når projektmappen kom fra Excel med formatering, ingen ønsker, at et bibliotek skal reinterpretere; den pass-through-adfærd er beskrevet i bevaret ChartML og kombinationsdiagrammer
Fordi det indlejrede diagram rider på drawing-laget, eksisterer det side om side med billeder og shapes på samme ark snarere end at erstatte dem, og den generelle model for det lag er dækket i diagrammer, billeder og tegninger i HotXLS. Alle tre ruter følger med i HotXLS Delphi spreadsheet-komponenten, så valget handler om, hvordan rapporten skal se ud, snarere end om, hvad biblioteket kan udtrykke
Det metodiske punkt er det, der er værd at beholde. Når en binær format-feature har en eksisterende reader, så byg writeren mod readeren snarere end mod din læsning af specifikationen. Readeren indkoder år af kontakt med filer, rigtige applikationer faktisk producerede, inklusive de dele, specifikationen siger løst om, og en writer, der tilfredsstiller den, er langt mere tilbøjelig til også at tilfredsstille Excel