Teknisk artikel

Bädda in diagramobjekt på kalkylblad med HotXLS

HotXLS kan placera ett diagram direkt på ett kalkylblad, förankrat i ett cellområde, i stället för att lägga det på ett eget diagramblad. I BIFF8-termer betyder det att skriva en ritningsform med en OBJ-post av typ 5 och parkera diagramunderströmmen i slutet av arkets postström, vilket är exakt den layout Excel producerar och exakt där läsaren förväntar sig hitta den

Skillnaden spelar roll för alla som genererar driftsrapporter. Ett diagramblad är ett fint hem för en enskild huvudvisualisering. En månatlig regional uppdelning vill ha diagrammet bredvid talen det sammanfattar, på samma ark, storleksanpassat till blocket av celler det hör till, så att en läsare scrollar en gång i stället för att byta flikar och tappa kontext

Läsning fanns redan, skrivning gjorde det inte

Asymmetrin är värd att namnge eftersom den formar arbetet. HotXLS kunde redan läsa inbäddade diagram: när kalkylbladets postström innehåller en BOF märkt som en diagramunderström växlar parsaren kontext, samlar in diagramposterna, och vid den avslutande EOF lämnar den tillbaka dem till ritningsformen som OBJ-posten introducerade. Den vägen hade övats av varenda Excel-skriven arbetsbok biblioteket någonsin öppnat

Det som saknades var författarsidan, och den användbara konsekvensen är att den nya skrivaren hade en precis specifikation att träffa: producera den byte-layout den befintliga läsaren redan återfäster. Det finns inget bättre acceptanskriterium för en binärformatfunktion än en oberoende skriven läsare du inte fick ändra

Vad ett inbäddat diagram är gjort av

Tre delar måste hålla med. Ritningslagret bidrar med en värdkontrollform, objektlagret bidrar med en OBJ-post vars gemensamma objektdata deklarerar objekttyp 5, och postströmmen bidrar med diagramunderströmmen själv. Alternativflaggorna på OBJ-posten är de Excel skriver för en diagramram: positionerad, låst, automatisk linje och automatisk fyllning, vilket är det som får det inbäddade diagrammet att bete sig som ett inbyggt när en användare klickar på det

HotXLS förankrar en BIFF8-diagramunderström vid ett Delphi-kalkylblad genom tre överenskomna delar: ritningslagrets värdkontrollform, OBJ-posten vars gemensamma objektdata deklarerar objekttyp 5, och diagrampostkedjan parkerad i slutet av arkets postström, där en diagram-BOF växlar parsarkontext och den avslutande EOF återfäster posterna
Tre lager bär ett inbäddat diagram: ritningsformen förankrar det, OBJ-posten typar det som en diagramvärd, och diagramunderströmmen i slutet av arkströmmen levererar posterna läsaren återfäster

Förankringen förtjänar en notis eftersom den är en vanlig källa till off-by-one-buggar. HotXLS-API:et tar enbaserade rad- och kolumnnummer, i linje med resten av biblioteket, och klientförankringen som skrivs in i filen är nollbaserad. Konverteringen sker inne i AddChartObject, så anropare stannar i det koordinatsystem de använder överallt annars, men vem som helst som jämför en hexdump mot sitt eget anrop behöver komma ihåg vilken sida av den gränsen hen 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;

    // Förankrat i E2:M20 på detta ark, enbaserat
    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å seriearrayen är inte dekoration. TXLSChartSeriesInfo bär på flera valfria underposter, dataetiketter, stil per serie, trendlinjer och felfält, var och en gated av en boolesk, och en partiellt initierad post på stacken lämnar emittern flaggor ingen satte. Nollställ arrayen, sätt sedan de fält du menar

Vilka seriereferenser accepterar den inbäddade vägen?

Vanliga område i A1-stil inuti samma arbetsbok, och den begränsningen är medveten snarare än ett förbiseende. Varje referens löses mot arbetsbokens arklista och förvandlas till det externa referensindex diagramposterna behöver. Ett namngivet område eller en extern arbetsboksreferens faller tillbaka till en platshållare med ett nollängdat parsat uttryck, så diagrammet skrivs rent men den serien saknar datakälla tills du pekar den mot ett område

Seriereferensacceptans i HotXLS på den inbäddade BIFF8-diagramvägen: A1-stil-område som Data!$B$2:$B$13 inuti samma arbetsbok löses mot arklistan till det externa referensindex diagramposterna behöver, medan namngivna områden och externa arbetsboksreferenser faller tillbaka till en platshållare med ett nollängdat parsat uttryck, med båda täckta av AddChartSheet
Endast vanliga område i A1-stil inuti samma arbetsbok kompileras till diagramseriereferenser; allt annat skrivs rent som en platshållare tills det pekas om, och den fullständiga vägen bor på AddChartSheet

Anledningen är en ren ingenjörsavvägning. Den fullständiga referenskompileringsvägen finns på diagrambladsrutten, insvept i kalkylbladssamlingslagret, och att lyfta ut den rent skulle betyda att duplicera hundra rader av upplösningslogik för ett fall som är ovanligt i praktiken. Ett inbäddat diagram plottar nästan alltid celler på sitt eget ark eller ett syskon-dataark. Namngivna och externa referenser täcks på diagrambladsvägen via AddChartSheet, så ingenting är otillgängligt, bara nått från en annan ingångspunkt

Allt annat i seriemodellen fungerar identiskt på båda rutterna. Sekundäraxelbindning, linje, fyllning och markörstil per serie, trendlinjer, felfält och dataetiketter är alla del av TXLSChartSeriesInfo och alla emitteras på samma sätt, så en diagramdefinition kan flytta mellan ett inbäddat objekt och ett diagramblad med endast anropet ändrat. Axelgruppsmekaniken bakom flaggan för sekundäraxel tas upp i sekundära axelgrupper vid BIFF-skrivning

Varför lästes diagramtiteln som två tecken?

Därför att ett teckenantal skickades där ett byteantal förväntades, och BIFF Unicode-strängar gör det misstaget lätt att skriva och svårt att se. En kort BIFF Unicode-sträng börjar med ett teckenantal och en flaggbyte, och flaggbyten bär hög-byte-bitten som säger huruvida nyttolasten är en byte per tecken eller två. Läs en 16-bit-nyttolast med teckenantalet som om det vore en bytelängd och du får exakt halva strängen: en serie namngiven Sales kommer tillbaka som Sa, och en diagramtitel trunkeras på samma sätt eftersom titlar och seriemärkningar delar avkodningsvägen

Det som gör defekten anmärkningsvärd är att den återkom tre gånger i samma familj av poster, en gång i trendlinjenamn, en gång i pivotdiagramnamn och en gång i diagramtitlar. Varje förekomst såg ut som en färsk bugg i en ny funktion. Alla tre var samma saknade multiplikation. Regeln som till slut stängde den är mekanisk och bör tillämpas utan omdöme: närhelst du läser en av dessa strängar, konsultera hög-byte-flaggan först och multiplicera teckenantalet med nyttolastbredden innan du rör bufferten. Postnivådetaljerna finns i avkodning av XLUnicodeString-teckenantal och hög-byte-flaggan

// Det inbäddade diagrammet delar ritningslagret med bilder och former,
// så en befintlig ritning på arket bevaras. AddChartObject
// returnerar index för det skapade objektet
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;

Var inbäddade diagram passar in jämfört med alternativen

Tre rutter finns och de svarar på olika frågor. Ett inbäddat diagramobjekt hör hemma bredvid sin data på ett kalkylblad och är vad de flesta rapporter vill ha. Ett diagramblad passar en enskild presentationsvisualisering och ger dig den fullständiga referenskompileringsvägen. Att bevara ett befintligt diagram från en inläst fil, orört, är det rätta svaret när arbetsboken kom från Excel med formatering ingen vill att ett bibliotek ska tolka om; det pass-through-beteendet beskrivs i bevarade ChartML- och kombinationsdiagram

Eftersom det inbäddade diagrammet rider ritningslagret samexisterar det med bilder och former på samma ark i stället för att ersätta dem, och den allmänna modellen för det lagret tas upp i diagram, bilder och ritningar i HotXLS. Alla tre rutterna medföljer HotXLS Delphi spreadsheet component, så valet handlar om hur rapporten ska se ut snarare än om vad biblioteket kan uttrycka

Den metodologiska punkten är den värd att behålla. När en binärformatfunktion har en befintlig läsare, bygg skrivaren mot läsaren i stället för mot din läsning av specifikationen. Läsaren kodar år av kontakt med filer riktiga applikationer faktiskt producerade, inklusive de delar specifikationen uttrycker löst, och en skrivare som tillfredsställer den är betydligt mer benägen att tillfredsställa Excel också