HotXLS skriver sekundære BIFF8-diagramaksegrupper ved at udsende en ekstra AxisParent-blok og ikke ved at tilføje en ekstra chart-gruppe efter akserne. I den klassiske XLS-chart-substream ligger hver chart-gruppe — ChartFormat, chart-type-recorden og CrtLink — inde i sin egen aksegruppeblok, og hver serie bindes til én gennem SerToCrt. Få denne nesting bagvendt, og der er ingen anden chart-gruppe, en serie kan bindes til, uanset hvor mange axis-records du udsender
Hvorfor binder en anden chart-gruppe efter akserne ingenting?
Grammatikken er hele svaret, og den står på én linje ABNF. CHARTFOMATS-reglen i [MS-XLS] 2.1.7.20.1 siger AxesUsed 1*2AXISPARENT og skriver derefter AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End ud. Læs de to produktioner sammen, og formen falder på plads: Chart-grupperne er children af en aksegruppe og ikke søskende til den. En chart-substream med to aksepar og én efterfølgende chart-gruppe er ikke et dual-axis-chart med en layoutfinurlighed; det er et chart med én chart-gruppe og et sæt forældreløse axis-records. Det betyder noget, fordi SerToCrt ($1045), som ligger inde i SERIESFORMAT-blokken, bærer et nulbaseret chart group-indeks og ikke et akseindeks. Hvis du skriver crt = 1, når der kun findes én CRT-blok, peger du en serie på en chart-gruppe, der aldrig blev udsendt. Intuitionen, der narrer folk, ligger i recordnavnet: AXESUSED ($1046) lyder, som om det tæller akser, så det naturlige næste skridt er at udsende flere akser. Det tæller aksegrupper, og hver aksegruppe trækker et komplet plotområde og en chart-gruppe med sig
Markering af en serie til den sekundære aksegruppe
På HotXLS-siden bliver dette til én Boolean. TXLSChartSeriesInfo har et felt SecondaryAxis, og når du sætter det på en serie i det array, du giver til TXLSWorksheets.AddChartSheet, skifter hele builderen til togruppetilstand. Der er ikke noget separat kald til "enable secondary axis" og ingen parameter for antal akser, fordi antallet kan udledes: Hvis en serie ønsker den sekundære gruppe, har chartet brug for to
var
Wb: TXLSWorkbook;
Series: array [0..1] of TXLSChartSeriesInfo;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Sheets.Add.Name := 'Data';
// ... udfyld A1:C12 med kategorier, revenue og margin ...
Series[0] := Default(TXLSChartSeriesInfo); // brug aldrig FillChar på denne record
Series[0].Name := 'Revenue';
Series[0].Categories := 'Data!$A$1:$A$12';
Series[0].Values := 'Data!$B$1:$B$12';
Series[1] := Default(TXLSChartSeriesInfo);
Series[1].Name := 'Margin';
Series[1].Categories := 'Data!$A$1:$A$12';
Series[1].Values := 'Data!$C$1:$C$12';
Series[1].SecondaryAxis := True; // AXESUSED bliver 2
Wb.Sheets.AddChartSheet('Dual Axis', xlsChartTypeLine,
'Revenue vs Margin', '', '', Series);
Wb.SaveAs('dual-axis.xls', xlExcel97);
finally
Wb.Free;
end;
end;
Linjen Default(TXLSChartSeriesInfo) er ikke pynt. TXLSChartSeriesInfo blander managed felter (navnene af typen WideString, de dynamiske trendline- og error-bar-arrays) med almindelige Boolean-medlemmer, og Delphi garanterer kun, at de managed felter bliver ryddet for dig. Lader du SecondaryAxis være uinitialiseret, er den, hvad der lå på stacken, hvilket i praksis betyder, at det samme binærprogram producerer et chart med én akse fra en console-host og et chart med to akser under test runneren. Kategori- og værdiområderne opløses imens gennem workbookens EXTERNSHEET-tabel, før builderen nogensinde ser dem — den samme indeksmekanik, som dækkes i hvordan HotXLS klassificerer eksterne BIFF SupBook- og XTI-links — så et område, der navngiver et ukendt sheet, degraderer til en tom placeholder BRAI i stedet for at fejle buildet
Hvad udsender HotXLS, når en serie er sekundær?
Emitteren ændrer form og ikke kun en værdi. Uden en sekundær serie indeholder AXESUSED 1 (eller 0 for pie og 3D pie, som slet ikke har aksegrupper), og én AxisParent-blok følger. Med én sekundær serie indeholder AXESUSED 2, og builderen kører blokken to gange med iax — det første word i den 18 bytes store AxisParent ($1041)-payload — sat til først 0 og derefter 1. Hvert pass udsender Pos, kategoriaksen og værdiaksen (Axis, $101D), PlotArea-markøren ($1035), en default Frame og derefter ChartFormat ($1014), chart-type-recorden, CrtLink ($1022) og to End-markører for at lukke chart-gruppen og aksegruppen. Den sekundære serie bindes derefter med SerToCrt crt = 1, mens den primære beholder crt = 0. Én record bliver med vilje ikke duplikeret: Legenden udsendes kun i den første gruppe, fordi Excel giver et chart én legende, uanset hvor mange aksegrupper det har. To andre egenskaber er værd at sige klart. At omstrukturere emitteren, så chart-gruppen ligger inde i axis-parent-blokken, ændrede ikke outputtet for almindelige charts — uden en sekundær serie er substreamen byte-identisk med den tidligere version, eftersom parameterisering af AddAxisParent med iax = 0 præcis er den gamle kodevej. Og builderen udsender stadig et helt aksepar pr. gruppe, så en sekundær gruppe kommer altid med sin egen kategoriakse, selv når du kun interesserer dig for dens værdiakse
Hvordan gendanner chart-inspektion bindingen til aksegruppen?
Læsning er to pass over recordlisten, og sådan må det være, fordi AXESUSED kommer før de blokke, den beskriver. Det første pass leder kun efter $1046 og læser det første word som antallet af aksegrupper. Værdien starter ved 1 og hæves, men sænkes aldrig: HotXLS tager maksimum af den aktuelle count og den deklarerede, så en fejlformet eller duplikeret AXESUSED ikke kan få et chart, som allerede er set deklarere to grupper, til at gå tilbage. Det andet pass holder styr på den aktuelle aksegruppe, opdaterer den ved hver AxisParent og stempler dette indeks på hver Axis-record, den møder, indtil den næste AxisParent dukker op
var
Model: TXLSChartModel;
i: Integer;
begin
// Sheets[1] er data-worksheeten, Sheets[2] er chart-sheetet
Model := Wb.Sheets[2]._Chart.GetChartModel;
try
if Model.AxisGroupCount = 2 then
Writeln('AXESUSED declares a secondary axis group');
for i := 0 to Model.AxisCount - 1 do
Writeln('axis ', i, ' group ', Model.GetAxis(i).AxisGroup);
for i := 0 to Model.SeriesCount - 1 do
Writeln('series ', i, ' chart group ', Model.GetSeries(i).ChartGroup);
finally
Model.Free;
end;
end;
To begrænsninger er værd at nævne. TXLSChartModel.AxisGroupCount rapporterer, hvad filen deklarerer, ikke hvor mange AxisParent-blokke der faktisk blev fundet; en fil, der siger 2 og leverer én blok, rapporterer 2, og AxisCount er dér, du opdager det. Og TXLSChartAxis.AxisGroup er et positionelt stempel: Det registrerer, hvilken blok en akse blev læst inde i, hvilket er det eneste, formatet fortæller dig. På seriesiden er SerToCrt-afkodning betinget af, at du befinder dig inde i en Series-blok, fordi det samme record-id optræder i kontekster, hvor det ikke er en seriebinding, og en afkoder uden denne betingelse med glæde ville overskrive den forkerte serie
Verificering af sekundære akser uden en rigtig Excel-fil
Verificeringen her krævede ingen Excel-fil med en sekundær akse, og det er den nyttige del af historien. Strukturel afkodning er en egenskab ved recordsekvensen, så en syntetiseret sekvens beviser den lige så præcist som en optaget. Regressionen bygger AXESUSED med en payload på 2, derefter to AxisParent-blokke, der hver omslutter en kategoriakse og en værdiakse, og hævder, at modellen kommer tilbage med AxisGroupCount = 2, fire akser stemplet 0, 0, 1, 1 og de forventede aksetyper på det andet par
// Strukturel verificering uden nogen Excel-fil overhovedet
Chart := TXLSCustomChart.Create(nil, $0600);
try
AddWordRecord($1046, [2]); // AXESUSED: to aksegrupper
AddAxisParentGroup(0); // AxisParent iax=0 + Begin + 2 Axis + End
AddAxisParentGroup(1); // AxisParent iax=1 + Begin + 2 Axis + End
Model := Chart.GetChartModel;
Assert.AreEqual(2, Model.AxisGroupCount);
Assert.AreEqual(4, Model.AxisCount);
Assert.AreEqual(0, Model.GetAxis(1).AxisGroup);
Assert.AreEqual(1, Model.GetAxis(2).AxisGroup);
finally
Model.Free;
Chart.Free;
end;
To praktiske noter, hvis du selv syntetiserer records. TXLSCustomChart.AddData(RecID, Len, nil) derefererer payloaden, når Len er non-zero, så Begin ($1033)- og End ($1034)-markører skal tilføjes med en længde på nul og ikke med en nil-blob og en forældet længde. Og en syntetisk sekvens beviser afkoderen, aldrig Excels accept af dit output — skrivevejen blev struktureret ud fra ABNF og derefter kontrolleret ved round-trip gennem GetChartModel med assertions for per-akse-stempler 0/0/1/1 og per-serie-chart-grupper 0/1, med den byte-identiske vej uden sekundær gruppe som sikkerhedsnet. Det er den samme konservative holdning, som resten af HotXLS-understøttelsen af charts, images og drawings i Delphi er bygget på: Afkod det, records siger, og nægt at gætte på et binærlayout, du ikke har læst i specifikationen. Den fulde Delphi-suite kørte 1650 af 1650 på Win32 og Win64, efter at skrivesiden var landet
Chart3d-sceneparametre og fAuto-fælden
To detaljer ved siden af hinanden rammer folk, der går ud over standardchartet. Den første er Chart3d ($103A, [MS-XLS] 2.4.46), en flad payload på 14 bytes, der udsendes inde i chart-gruppen for 3D-varianter: anRot (rotation, 0 til 360), anElev (elevation, signed, -90 til 90), pcDist (perspektivafstand, 0 til 100, ignoreres, medmindre fPerspective er sat), pcHeight og pcDepth (procent af chartbredde, 5 til 500), pcGap (0 til 500) og en grbit, hvis bits er fPerspective $0001, fCluster $0002, fAutoscale $0004, f3DScaling $0010 og f2DWalls $0020. Specifikationen tilføjer begrænsninger, som recordlayoutet ikke håndhæver for dig: På et transponeret bar-chart må anRot og anElev ikke overstige 44, og på et pie-chart må anElev ikke være negativ
Den anden er fAuto-bitten, og det er den, der producerer fejlrapporten "my colors were ignored". LineFormat ($1007), AreaFormat ($100A) og MarkerFormat ($1009) har alle fAuto i bit 0 af deres grbit, og når bitten er sat, anvender Excel sin automatiske stil og behandler de eksplicitte RGB-værdier, linjestil, markørform og markørstørrelse ved siden af som pynt. Enhver emitter, der skriver en brugerdefineret seriestil, skal rydde bit 0; default-emitterne lader den stå netop for at få Excel til at vælge paletten. Hvis du redigerer en eksisterende workbook i stedet for at bygge en, er bevaringsreglerne igen anderledes, og de er dækket i redigering af Excel-charts uden at miste bevaret ChartML
Sekundære aksegrupper, SerToCrt-binding og den typede chartmodel, der vises her, leveres i HotXLS Delphi-regnearkskomponenten til Delphi og C++Builder, som læser og skriver BIFF8-charts uden Excel installeret; produktsiden indeholder den fulde reference til chart-records og overload-listen for AddChartSheet