HotXLS upisuje BIFF8 grupe sekundarne ose grafikona tako što emituje drugi AxisParent blok, a ne tako što dodaje drugu chart grupu posle osa. U Classic XLS chart podstream-u svaka chart grupa — ChartFormat, zapis tipa grafikona i CrtLink — živi unutar sopstvenog bloka grupe ose, a svaka serija se na jednu od njih vezuje preko SerToCrt. Ako ovo ugnježđivanje okrenete naopako, nema druge chart grupe za koju bi se serija vezala, bez obzira na broj emitovanih zapisa osa
Zašto se druga chart grupa posle osa ni za šta ne vezuje?
Gramatika je ceo odgovor i staje u jednu liniju ABNF-a. Pravilo CHARTFOMATS u [MS-XLS] 2.1.7.20.1 kaže AxesUsed 1*2AXISPARENT, a zatim razlaže AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End. Pročitajte te dve produkcije zajedno i oblik postaje jasan: chart grupe su deca grupe ose, a ne njena braća. Chart podstream sa dva para osa i jednom završnom chart grupom nije dual-axis grafikon sa neobičnim layout-om; to je grafikon sa jednom chart grupom i skupom siročadi zapisa osa. To je važno zato što SerToCrt ($1045), koji živi unutar SERIESFORMAT bloka, nosi indeks chart grupe počevši od nule, a ne indeks ose. Ako upišete crt = 1 kada postoji samo jedan CRT blok, seriju usmeravate na chart grupu koja nikada nije emitovana. Intuicija koja ljude spotiče proizlazi iz imena zapisa: AXESUSED zvuči kao da broji ose, pa je prirodan sledeći korak emitovati još osa. On broji grupe osa, a svaka grupa osa sa sobom nosi kompletnu plot oblast i chart grupu
Označavanje serije za sekundarnu grupu ose
Na HotXLS strani sve se svodi na jedan Boolean. TXLSChartSeriesInfo nosi polje SecondaryAxis, a njegovo postavljanje na bilo kojoj seriji u nizu koji predate TXLSWorksheets.AddChartSheet prebacuje ceo builder u režim sa dve grupe. Ne postoji zaseban poziv „uključi sekundarnu osu“ niti parametar broja osa, jer broj može da se izvede: ako bilo koja serija želi sekundarnu grupu, grafikonu trebaju dve
var
Wb: TXLSWorkbook;
Series: array [0..1] of TXLSChartSeriesInfo;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Sheets.Add.Name := 'Data';
// ... popuni A1:C12 kategorijama, prihodom i maržom ...
Series[0] := Default(TXLSChartSeriesInfo); // nikada ne koristi FillChar za ovaj zapis
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 postaje 2
Wb.Sheets.AddChartSheet('Dual Axis', xlsChartTypeLine,
'Revenue vs Margin', '', '', Series);
Wb.SaveAs('dual-axis.xls', xlExcel97);
finally
Wb.Free;
end;
end;
Linija Default(TXLSChartSeriesInfo) nije ukras. TXLSChartSeriesInfo meša managed polja (imena WideString, dinamičke trendline i error-bar nizove) sa običnim Boolean članovima, a Delphi garantuje da će za vas očistiti samo managed polja. Ostavite SecondaryAxis neinicijalizovan i on će biti ono što je bilo na steku, što u praksi znači da isti binar proizvodi chart sa jednom osom kada se pokrene iz console host-a, a chart sa dve ose pod test runner-om. Opsezi kategorija i vrednosti, u međuvremenu, razrešavaju se kroz EXTERNSHEET tabelu radne sveske pre nego što ih builder uopšte vidi — ista index mehanika obrađena u tekstu o klasifikaciji BIFF SupBook i XTI eksternih linkova u HotXLS-u — pa se opseg koji imenuje nepoznat sheet degradira u prazan placeholder BRAI umesto da izgradnja padne
Šta HotXLS emituje kada je serija sekundarna
Emitter menja oblik, a ne samo vrednost. Bez sekundarne serije, AXESUSED nosi 1 (ili 0 za pie i 3D pie, koji uopšte nemaju grupe osa), a zatim sledi jedan AxisParent blok. Sa jednom sekundarnom serijom, AXESUSED nosi 2 i builder dva puta prolazi kroz blok, sa iax — prvom rečju 18-bajtnog payload-a AxisParent ($1041) — postavljenim prvo na 0, a zatim na 1. Svaki prolaz emituje Pos, category osu i value osu (Axis, $101D), marker PlotArea ($1035), podrazumevani Frame, zatim ChartFormat ($1014), zapis tipa grafikona, CrtLink ($1022) i dva End markera za zatvaranje chart grupe i grupe ose. Sekundarna serija se zatim vezuje sa SerToCrt crt = 1, a primarna zadržava crt = 0. Jedan zapis namerno se ne duplira: legenda se emituje samo u prvoj grupi, jer Excel grafikonu daje jednu legendu bez obzira na broj grupa osa. Još dve osobine vredi reći jasno. Restrukturiranje emitter-a tako da chart grupa bude unutar axis-parent bloka nije promenilo izlaz običnih grafikona — bez sekundarne serije podstream je bajtovski identičan prethodnoj verziji, jer je parametrizovanje AddAxisParent sa iax = 0 upravo stara putanja koda. Builder i dalje emituje pun par osa po grupi, pa sekundarna grupa uvek dobija sopstvenu category osu čak i kada vas zanima samo njena value skala
Kako inspekcija grafikona ponovo pronalazi vezu sa grupom ose?
Čitanje se radi u dva prolaza kroz spisak zapisa i mora tako, jer AXESUSED stiže pre blokova koje opisuje. Prvi prolaz traži samo $1046 i čita njegovu prvu reč kao broj grupa osa. Ta vrednost počinje od 1 i samo raste: HotXLS uzima maksimum trenutnog broja i deklarisanog broja, pa neispravan ili dupliran AXESUSED ne može da unazadi grafikon koji je već viđen kao grafikon sa dve grupe. Drugi prolaz prati trenutnu grupu ose, ažurira je na svakom AxisParent i taj indeks utiskuje na svaki Axis zapis koji sretne dok se ne pojavi sledeći AxisParent
var
Model: TXLSChartModel;
i: Integer;
begin
// Sheets[1] je worksheet sa podacima, Sheets[2] chart sheet
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;
Vredi imenovati dva ograničenja. TXLSChartModel.AxisGroupCount prijavljuje ono što fajl deklariše, a ne broj stvarno pronađenih AxisParent blokova; fajl koji kaže 2, a isporuči jedan blok, prijaviće 2, a AxisCount je mesto gde to primećujete. Na strani serije, dekodiranje SerToCrt uključuje se samo unutar Series bloka, jer se isti ID zapisa pojavljuje u kontekstima koji nisu binding serije, a TXLSChartAxis.AxisGroup je poziciona oznaka koju zapis dobija iz bloka, dok dekoder bez te zaštite rado bi prepisao pogrešnu seriju
Provera sekundarnih osa bez stvarnog Excel fajla
Za ovu proveru nije bio potreban Excel fajl sa sekundarnom osom i to je korisni deo priče. Strukturalno dekodiranje jeste svojstvo niza zapisa, pa sintetizovani niz dokazuje isto koliko i uhvaćeni niz. Regresija gradi AXESUSED sa payload-om 2, zatim dva AxisParent bloka, pri čemu svaki obuhvata category osu i value osu, i proverava da model vraća AxisGroupCount = 2, četiri ose označene sa 0, 0, 1, 1 i očekivane tipove osa u drugom paru
// Strukturalna provera bez ikakvog Excel fajla
Chart := TXLSCustomChart.Create(nil, $0600);
try
AddWordRecord($1046, [2]); // AXESUSED: dve grupe osa
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;
Dve praktične napomene ako sami sintetizujete zapise. TXLSCustomChart.AddData(RecID, Len, nil) dereferencira payload kada je Len različit od nule, pa Begin ($1033) i End ($1034) markeri moraju da se dodaju sa dužinom nula, a ne sa nil blob-om i zastarelom dužinom. Sintetički niz dokazuje decoder, ali nikada Excel-ovo prihvatanje vašeg izlaza — write strana strukturisana je iz ABNF-a, a zatim proverena round-trip-om kroz GetChartModel, uz asercije po osi 0/0/1/1 i po seriji za chart grupe 0/1, dok je bajtovski identična putanja bez sekundarne grupe safety net. To je isti konzervativni stav na kojem počiva ostatak HotXLS podrške za grafikone, slike i crteže u Delphi-ju: dekodirajte ono što zapisi kažu i odbijte nagađanje binarnog layout-a koji niste pročitali u specifikaciji. Ceo Delphi suite posle ugradnje write strane prošao je 1650 od 1650 testova na Win32 i Win64
Parametri 3D scene i fAuto zamka
Dva susedna detalja zadaju probleme ljudima koji odu dalje od podrazumevanog grafikona. Prvi je Chart3d ($103A, [MS-XLS] 2.4.46), ravan payload od 14 bajtova koji se emituje unutar grupe grafikona za 3D varijante: anRot (rotacija, 0 do 360), anElev (elevacija, sa znakom, -90 do 90), pcDist (perspektivna udaljenost, 0 do 100, ignoriše se ako fPerspective nije postavljen), pcHeight i pcDepth (procenat širine grafikona, 5 do 500), pcGap (0 do 500), kao i grbit čiji su bitovi fPerspective $0001, fCluster $0002, fAutoscale $0004, f3DScaling $0010 i f2DWalls $0020. Specifikacija dodaje ograničenja koja layout zapisa neće nametnuti umesto vas: na transponovanom bar chart-u anRot i anElev ne smeju biti veći od 44, a na pie chart-u anElev ne sme biti negativan
Drugi detalj je bit fAuto i on proizvodi prijavu „moje boje su ignorisane“. LineFormat ($1007), AreaFormat ($100A) i MarkerFormat ($1009) svi nose fAuto u bitu 0 svog grbit, a kada je taj bit postavljen, Excel primenjuje automatski stil i eksplicitne RGB vrednosti, stil linije, oblik markera i veličinu markera pored njega tretira kao ukras. Svaki emitter koji upisuje prilagođeni stil serije mora da obriše bit 0; podrazumevani emitter-i ga drže postavljenim upravo da bi Excel birao paletu. Ako uređujete postojeću radnu svesku, a ne gradite novu, pravila očuvanja ponovo se razlikuju i obrađena su u tekstu o uređivanju Excel grafikona bez gubitka sačuvanog ChartML-a
Grupe sekundarnih osa, SerToCrt binding i tipizovani model grafikona prikazan ovde isporučuju se u HotXLS Delphi spreadsheet komponenti za Delphi i C++Builder, koja čita i upisuje BIFF8 grafikone bez instaliranog Excel-a; stranica proizvoda nosi punu referencu zapisa grafikona i spisak overload-a AddChartSheet