Teknisk artikkel

BIFF8-chart med sekundæraksegrupper i Delphi

HotXLS skriver BIFF8-chart med sekundæraksegrupper ved å emittere en ny AxisParent-blokk, ikke ved å legge til en ny chart group etter aksene. I Classic XLS-chart-understrømmen ligger hver chart group — ChartFormat, chart type-record-en og CrtLink — inne i sin egen axis-group-blokk, og hver serie binder seg til én gjennom SerToCrt. Får du denne nestingen bakvendt, finnes det ingen ekstra chart group som en serie kan binde seg til, uansett hvor mange axis-recorder du emitterer

Hvorfor binder en ekstra chart group etter aksene ingenting?

Grammatikken er hele svaret, og den er én linje ABNF. CHARTFOMATS-regelen i [MS-XLS] 2.1.7.20.1 sier AxesUsed 1*2AXISPARENT, og spesifiserer deretter AXISPARENT = AxisParent Begin Pos [AXES] 1*4CRT End. Les de to produksjonene sammen, så faller formen ut: Chart-gruppene er barn av en axis group, ikke søsken av den. En chart-understrøm med to aksepar og én etterfølgende chart group er ikke et diagram med to akser og en layoutdetalj; det er et diagram med én chart group og et sett foreldreløse axis-recorder. Det betyr noe fordi SerToCrt ($1045), som ligger inne i SERIESFORMAT-blokken, bærer en nullbasert chart group-indeks, ikke en axis-indeks. Skriver du crt = 1 når bare én CRT-blokk finnes, peker du serien på en chart group som aldri ble emittert. Intuisjonen som fanger folk, ligger i record-navnet: AXESUSED ($1046) høres ut som det teller akser, så det naturlige neste steget er å emittere flere akser. Det teller axis groups, og hver axis group drar med seg et komplett plot area og en chart group

Merk en serie for den sekundære axis group

På HotXLS-siden koker dette ned til én Boolean. TXLSChartSeriesInfo har feltet SecondaryAxis, og når det settes på en serie i matrisen du sender til TXLSWorksheets.AddChartSheet, bytter hele builderen til modus med to grupper. Det finnes ikke noe separat kall for "aktiver sekundærakse" og ingen parameter for antall akser, fordi antallet kan utledes: Hvis en serie ønsker den sekundære gruppen, trenger diagrammet to

var
  Wb: TXLSWorkbook;
  Series: array [0..1] of TXLSChartSeriesInfo;
begin
  Wb := TXLSWorkbook.Create;
  try
    Wb.Sheets.Add.Name := 'Data';
    // ... fyll A1:C12 med kategorier, inntekt og margin ...

    Series[0] := Default(TXLSChartSeriesInfo);   // bruk aldri FillChar på denne record-en
    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 blir 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 (WideString-navnene, den dynamiske trendline- og error-bar-matrisen) med vanlige Boolean-medlemmer, og Delphi garanterer bare at de managed-feltene tømmes for deg. La SecondaryAxis være uinitialisert, så er den det som lå på stacken, noe som i praksis betyr at den samme binærfilen produserer et diagram med én akse fra en konsollvert og et diagram med to akser under testkjøreren. Kategori- og verdiområdene løses samtidig gjennom arbeidsbokens EXTERNSHEET-tabell før builderen i det hele tatt ser dem — den samme indeksmekanismen som dekkes i hvordan HotXLS klassifiserer eksterne BIFF SupBook- og XTI-lenker — så et område som navngir et ukjent ark, degraderes til en tom BRAI-plassholder i stedet for å feile bygget

Hva emitterer HotXLS når en serie er sekundær?

Emitteren endrer form, ikke bare en verdi. Uten en sekundær serie inneholder AXESUSED 1 (eller 0 for pie og 3D pie, som ikke har axis groups i det hele tatt), og én AxisParent-blokk følger. Med én sekundær serie inneholder AXESUSED 2, og builderen kjører blokken to ganger, med iax — det første ordet i payloaden på 18 byte for AxisParent ($1041) — satt først til 0 og så til 1. Hvert pass emitterer Pos, kategoriaksen og verdiaksen (Axis, $101D), PlotArea-markøren ($1035), en standard Frame, deretter ChartFormat ($1014), chart type-record-en, CrtLink ($1022) og to End-markører for å lukke chart group og axis group. Den sekundære serien binder seg deretter med SerToCrt crt = 1, mens den primære beholder crt = 0. Én record blir med vilje ikke duplisert: Legend-emitteres bare i den første gruppen, fordi Excel gir et chart én legend uansett hvor mange axis groups det har. To andre egenskaper bør sies tydelig. Å legge chart group inn i axis-parent-blokken endret ikke output-en for vanlige diagrammer — uten sekundær serie er understrømmen byte-identisk med den forrige versjonen, siden parametrisering av AddAxisParent med iax = 0 er nøyaktig den gamle kodebanen. Og builderen emitterer fortsatt et komplett aksepar per gruppe, så en sekundær gruppe kommer alltid med sin egen kategoriakse, selv når du bare bryr deg om verdiskalaen

Hvordan finner chart-inspeksjon bindingen til axis group?

Lesingen er to pass over record-listen, og den må være det fordi AXESUSED kommer før blokkene den beskriver. Det første passet ser bare etter $1046 og leser det første ordet som antall axis groups. Denne verdien starter på 1 og heves, aldri senkes: HotXLS tar maksimumet av gjeldende antall og det deklarerte, slik at en feilformet eller duplisert AXESUSED ikke kan redusere et chart som allerede er sett deklarere to grupper. Det andre passet følger gjeldende axis group, oppdaterer den ved hver AxisParent og stempler denne indeksen på hver Axis-record den møter frem til neste AxisParent dukker opp

var
  Model: TXLSChartModel;
  i: Integer;
begin
  // Sheets[1] er dataarket, Sheets[2] er chart-arket
  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 grenser er verdt å nevne. TXLSChartModel.AxisGroupCount rapporterer det filen deklarerer, ikke hvor mange AxisParent-blokker som faktisk ble funnet; en fil som sier 2 og leverer én blokk, rapporterer 2, og AxisCount er stedet du oppdager det. Og TXLSChartAxis.AxisGroup er et posisjonelt stempel: Det registrerer hvilken blokk en akse ble lest inne i, noe som er det eneste formatet forteller deg. På seriesiden er SerToCrt-dekoding avhengig av at du befinner deg inne i en Series-blokk, fordi samme record-id dukker opp i kontekster der den ikke er en seriebinding, og en dekoder uten slik avgrensning gladelig ville overskrevet feil serie

Verifiser sekundærakser uten en ekte Excel-fil

Verifikasjonen her trengte ingen Excel-fil med sekundærakse, og det er den nyttige delen av historien. Strukturell dekoding er en egenskap ved record-sekvensen, så en syntetisert sekvens beviser den like godt som en innlest sekvens. Regresjonen bygger AXESUSED med payload 2, deretter to AxisParent-blokker som hver pakker inn en kategoriakse og en verdiakse, og hevder at modellen kommer tilbake med AxisGroupCount = 2, fire akser stemplet 0, 0, 1, 1 og de forventede aksestypene på det andre paret

// Strukturell verifikasjon uten at en Excel-fil er involvert i det hele tatt
Chart := TXLSCustomChart.Create(nil, $0600);
try
  AddWordRecord($1046, [2]);   // AXESUSED: to axis groups
  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 notater hvis du syntetiserer recorder selv. TXLSCustomChart.AddData(RecID, Len, nil) derefererer payloaden når Len ikke er null, så Begin ($1033)- og End ($1034)-markører må legges til med lengde null, ikke med en nil-blob og en foreldet lengde. Og en syntetisk sekvens beviser dekoderen, aldri Excels aksept av output-en din — skrivesiden ble strukturert fra ABNF og deretter kontrollert ved tur-retur gjennom GetChartModel, med asserts på stemplene per akse 0/0/1/1 og chart-gruppene per serie 0/1, med den byte-identiske stien uten sekundærakse som sikkerhetsnett. Det er den samme konservative holdningen som resten av HotXLS-støtten for chart, bilder og tegninger i Delphi bygger på: Dekod det recorder sier, og nekt å gjette et binærlayout du ikke har lest i spesifikasjonen. Hele Delphi-testpakken kjørte 1650 av 1650 på Win32 og Win64 etter at skrivesiden var på plass

Chart3d-sceneparametere og fAuto-fellen

To detaljer ved siden av hverandre biter på folk som går forbi standarddiagrammet. Den første er Chart3d ($103A, [MS-XLS] 2.4.46), en flat payload på 14 byte som emitteres inne i chart group for 3D-varianter: anRot (rotasjon, 0 til 360), anElev (elevasjon, med fortegn, -90 til 90), pcDist (perspektivavstand, 0 til 100, ignoreres med mindre fPerspective er satt), pcHeight og pcDepth (prosent av chart-bredden, 5 til 500), pcGap (0 til 500) og en grbit der bitene er fPerspective $0001, fCluster $0002, fAutoscale $0004, f3DScaling $0010 og f2DWalls $0020. Spesifikasjonen legger til begrensninger record-layouten ikke håndhever for deg: På et transponert stolpediagram må anRot og anElev ikke overstige 44, og på et kakediagram må anElev ikke være negativ

Den andre er fAuto-biten, og det er den som produserer feilrapporten "fargene mine ble ignorert". LineFormat ($1007), AreaFormat ($100A) og MarkerFormat ($1009) har alle fAuto i bit 0 av grbit, og når den biten er satt, bruker Excel den automatiske stilen og behandler de eksplisitte RGB-verdiene, linjestilen, markørformen og markørstørrelsen ved siden av som pynt. Enhver emitter som skriver en tilpasset stil for en serie, må nullstille bit 0; standardemitterne lar den være satt nettopp slik at Excel velger paletten. Hvis du redigerer en eksisterende arbeidsbok i stedet for å bygge en, er bevaringsreglene annerledes igjen, og de er dekket i redigering av Excel-chart uten å miste bevart ChartML

Sekundæraksegrupper, SerToCrt-binding og den typede chart-modellen som vises her, leveres i HotXLS Delphi spreadsheet-komponenten for Delphi og C++Builder, som leser og skriver BIFF8-chart uten Excel installert; produktsiden har den fullstendige chart-record-referansen og overload-listen for AddChartSheet