Tehnički članak

BIFF8 skupine sekundarne osi grafikona u Delphiju s HotXLS

HotXLS zapisuje skupine sekundarne osi grafikona BIFF8 emitiranjem drugog bloka AxisParent, a ne dodavanjem druge grupe grafikona nakon osi. U podtoku grafikona Classic XLS svaka grupa grafikona — ChartFormat, zapis vrste grafikona, CrtLink — živi unutar vlastitog bloka skupine osi, a svaka se serija na jednu veže kroz SerToCrt. Okrenete li to ugniježđivanje, nema druge grupe grafikona na koju bi se serija vezala, bez obzira na broj zapisa osi koje emitirate

Zašto druga grupa grafikona nakon osi ne veže ništa?

Gramatika je cijeli odgovor i stane u jedan redak ABNF-a. Pravilo CHARTFOMATS u [MS-XLS] 2.1.7.20.1 kaže AxesUsed 1*2AXISPARENT, a zatim navodi AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End. Pročitajte te dvije produkcije zajedno i oblik se sam pokaže: grupe grafikona djeca su skupine osi, a ne njihovi susjedi. Podtok grafikona s dva para osi i jednom završnom grupom grafikona nije grafikon s dvjema osima i neobičnim rasporedom; to je grafikon s jednom grupom grafikona i skupom siročadi zapisa osi. To je važno jer SerToCrt ($1045), koji sjedi unutar bloka SERIESFORMAT, nosi indeks grupe grafikona s početkom od nule, a ne indeks osi. Zapisivanje crt = 1 kada postoji samo jedan blok CRT usmjerava seriju na grupu grafikona koja nikad nije emitirana. Intuicija koja ljude spotiče krije se u imenu zapisa: AXESUSED ($1046) zvuči kao da broji osi, pa je prirodan sljedeći korak emitirati više osi. On broji skupine osi, a svaka skupina osi sa sobom povlači cijelo područje iscrtavanja i grupu grafikona

Označavanje serije za sekundarnu skupinu osi

Na strani HotXLS-a sve se svodi na jednu Boolean vrijednost. TXLSChartSeriesInfo nosi polje SecondaryAxis, a njegovo postavljanje na bilo kojoj seriji u polju koje predate funkciji TXLSWorksheets.AddChartSheet prebacuje cijeli builder u način rada s dvjema skupinama. Ne postoji zaseban poziv za uključivanje sekundarne osi ni parametar broja osi jer se broj može izvesti: ako bilo koja serija želi sekundarnu skupinu, grafikon treba dvije

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);   // nikad ne koristi FillChar nad ovim zapisom
    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;

Redak Default(TXLSChartSeriesInfo) nije ukras. TXLSChartSeriesInfo miješa upravljana polja (imena WideString, dinamički niz trendlinea i nizove traka pogreške) s običnim članovima Boolean, a Delphi vam jamči čišćenje samo upravljanih polja. Ostavite SecondaryAxis neinicijaliziran i on će sadržavati ono što je bilo na stogu, što u praksi znači da isti binarij proizvodi grafikon s jednom osi kada se pokrene iz konzolnog domaćina, a grafikon s dvjema osima pod testnim pokretačem. Rasponi kategorija i vrijednosti u međuvremenu se razrješavaju kroz tablicu EXTERNSHEET radne knjige prije nego što ih builder uopće vidi — ista mehanika indeksa obrađena u članku o klasifikaciji vanjskih veza BIFF SupBook i XTI u HotXLS-u — pa se raspon koji imenuje nepoznati list degradira u prazan rezervirani BRAI umjesto da izgradnja ne uspije

Što HotXLS emitira kada je serija sekundarna?

Emiter mijenja oblik, a ne samo jednu vrijednost. Bez sekundarnih serija AXESUSED nosi 1 (ili 0 za tortni i 3D tortni grafikon, koji uopće nemaju skupine osi), a slijedi jedan blok AxisParent. S jednom sekundarnom serijom AXESUSED nosi 2 i builder dvaput pokreće blok, pri čemu je iax — prva riječ 18-bajtnog tereta AxisParent ($1041) — najprije 0, a zatim 1. Svaki prolaz emitira Pos, os kategorija i os vrijednosti (Axis, $101D), oznaku PlotArea ($1035), zadani Frame, zatim ChartFormat ($1014), zapis vrste grafikona, CrtLink ($1022) i dvije oznake End koje zatvaraju grupu grafikona i skupinu osi. Sekundarna se serija zatim veže s SerToCrt crt = 1, a primarna zadržava crt = 0. Jedan se zapis namjerno ne duplicira: legenda se emitira samo u prvoj grupi jer Excel grafikonu daje jednu legendu bez obzira na broj skupina osi. Još dvije značajke vrijedi jasno navesti. Restrukturiranje emitera tako da grupu grafikona stavi unutar bloka AxisParent nije promijenilo izlaz običnih grafikona — bez sekundarne serije podtok je bajt po bajt jednak prethodnoj verziji jer je parametriziranje AddAxisParent s iax = 0 upravo stari put koda. Builder i dalje emitira puni par osi po skupini, pa sekundarna skupina uvijek dolazi s vlastitom osi kategorija čak i kada vas zanima samo njezino mjerilo vrijednosti

Kako pregled grafikona ponovno pronalazi vezu sa skupinom osi?

Čitanje je u dva prolaza kroz popis zapisa i tako mora biti jer AXESUSED dolazi prije blokova koje opisuje. Prvi prolaz traži samo $1046 i njegovu prvu riječ čita kao broj skupina osi. Ta vrijednost počinje od 1 i samo raste, nikad se ne smanjuje: HotXLS uzima maksimum trenutačnog broja i deklariranog broja, pa pogrešno oblikovan ili dupliciran AXESUSED ne može unazad promijeniti grafikon za koji je već viđeno da deklarira dvije skupine. Drugi prolaz prati trenutačnu skupinu osi, ažurira je na svakom AxisParentu i taj indeks utiskuje u svaki Axis zapis na koji naiđe dok se ne pojavi sljedeći AxisParent

var
  Model: TXLSChartModel;
  i: Integer;
begin
  // Sheets[1] je radni list s podacima, Sheets[2] list grafikona
  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;

Vrijedi imenovati dva ograničenja. TXLSChartModel.AxisGroupCount prijavljuje ono što datoteka deklarira, a ne koliko je blokova AxisParent stvarno pronađeno; datoteka koja kaže 2 i isporuči jedan blok prijavit će 2, a AxisCount je mjesto na kojem to primjećujete. A TXLSChartAxis.AxisGroup pozicijska je oznaka: bilježi unutar kojeg je bloka os pročitana, što je jedino što vam format govori. Na strani serija dekodiranje SerToCrt dopušta se samo unutar bloka Series jer se isti ID zapisa pojavljuje u kontekstima gdje nije veza serije, a dekoder bez te zaštite rado bi prepisao pogrešnu seriju

Provjera sekundarnih osi bez stvarne Excel datoteke

Ovdje provjera nije trebala Excel datoteku sa sekundarnom osi i upravo je to korisni dio priče. Strukturno dekodiranje svojstvo je slijeda zapisa, pa sintetizirani slijed dokazuje isto toliko točno koliko i snimljeni. Regresija gradi AXESUSED s korisnim teretom 2, zatim dva bloka AxisParent, od kojih svaki obavija os kategorija i os vrijednosti, te potvrđuje da se model vraća s AxisGroupCount = 2, četirima osima označenima 0, 0, 1, 1 i očekivanim vrstama osi u drugom paru

// Strukturna provjera bez ikakve Excel datoteke
Chart := TXLSCustomChart.Create(nil, $0600);
try
  AddWordRecord($1046, [2]);   // AXESUSED: dvije skupine osi
  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;

Dvije praktične napomene ako sami sintetizirate zapise. TXLSCustomChart.AddData(RecID, Len, nil) dereferencira korisni teret kada je Len različit od nule, pa se oznake Begin ($1033) i End ($1034) moraju dodati s duljinom nula, a ne s nil blobom i zastarjelom duljinom. Sintetički slijed usto dokazuje dekoder, nikad Excelovo prihvaćanje vašeg izlaza — strana za pisanje strukturirana je iz ABNF-a, a zatim provjerena povratnim prolazom kroz GetChartModel, uz provjere oznaka po osi 0/0/1/1 i grupa grafikona po seriji 0/1, s bajt po bajt jednakim putem bez sekundarnih osi kao sigurnosnom mrežom. To je isti konzervativni stav na kojem počiva ostatak HotXLS podrške za grafikone, slike i crteže u Delphiju: dekodiraj ono što zapisi govore i odbij pogađati binarni raspored koji nisi pročitao u specifikaciji. Cijeli Delphi paket prošao je 1650 od 1650 testova na Win32 i Win64 nakon što je strana za pisanje dodana

Parametri scene Chart3d i zamka fAuto

Dva susjedna detalja grizu one koji odu dalje od zadanog grafikona. Prvi je Chart3d ($103A, [MS-XLS] 2.4.46), plosnati teret od 14 bajtova emitiran unutar grupe grafikona za 3D varijante: anRot (rotacija, 0 do 360), anElev (elevacija, predznačna, -90 do 90), pcDist (udaljenost perspektive, 0 do 100, zanemarena osim ako je postavljen fPerspective), pcHeight i pcDepth (postotak širine grafikona, 5 do 500), pcGap (0 do 500) i grbit čiji su bitovi fPerspective $0001, fCluster $0002, fAutoscale $0004, f3DScaling $0010 i f2DWalls $0020. Specifikacija dodaje ograničenja koja raspored zapisa neće provesti umjesto vas: na transponiranom stupčastom grafikonu anRot i anElev ne smiju prelaziti 44, a na tortnom grafikonu anElev ne smije biti negativan

Drugi je bit fAuto i on proizvodi prijavu greške moja boja je zanemarena. LineFormat ($1007), AreaFormat ($100A) i MarkerFormat ($1009) svi nose fAuto u bitu 0 svojeg grbita, a kada je taj bit postavljen, Excel primjenjuje automatski stil i izričite RGB vrijednosti, stil crte, oblik oznake i veličinu oznake koji sjede odmah pokraj njega tretira kao ukras. Svaki emiter koji zapisuje prilagođeni stil serije mora očistiti bit 0; zadani emiteri drže ga postavljenim upravo da Excel odabere paletu. Ako uređujete postojeću radnu knjigu umjesto da je gradite, pravila očuvanja ponovno se razlikuju i obrađena su u članku o uređivanju Excelovih grafikona bez gubitka sačuvanog ChartML-a

Skupine sekundarne osi, vezivanje SerToCrt i tipizirani model grafikona prikazan ovdje isporučuju se u HotXLS Delphi spreadsheet component za Delphi i C++Builder, koja čita i zapisuje BIFF8 grafikone bez instaliranog Excela; stranica proizvoda sadrži potpunu referencu zapisa grafikona i popis overloadova AddChartSheet