Articol tehnic

Încorporarea obiectelor de grafic pe foi de lucru cu HotXLS

HotXLS poate plasa un grafic direct pe o foaie de lucru, ancorat la un interval de celule, în loc să îl pună pe o foaie de grafic dedicată. În termeni BIFF8, asta înseamnă a scrie o formă de desenare cu o înregistrare OBJ de tip 5 și a parca substream-ul de grafic la capătul stream-ului de înregistrări al foii, exact aranjamentul pe care îl produce Excel și exact locul în care cititorul așteaptă să îl găsească

Distincția contează oricui generează rapoarte operaționale. O foaie de grafic e o casă bună pentru o singură vizualizare-cheie. Un defalcare regională lunară vrea graficul lângă cifrele pe care le rezumă, pe aceeași foaie, dimensionat pe blocul de celule căruia îi aparține, astfel încât cititorul să defileze o dată, nu să comute între tab-uri și să piardă contextul

Citirea exista deja, scrierea nu

Asimetria merită numită pentru că modelează munca. HotXLS putea deja citi grafice încorporate: când stream-ul de înregistrări al foii de lucru conține un BOF marcat ca substream de grafic, parser-ul schimbă contextul, adună înregistrările de grafic și, la EOF-ul de închidere, le preda înapoi formei de desenare pe care înregistrarea OBJ a introdus-o. Calea aceea fusese exersată de fiecare registru de lucru scris de Excel pe care biblioteca l-a deschis vreodată

Ce lipsea era partea de autor, iar consecința utilă e că noul scriitor a avut o specificație precisă de atins: să producă aranjamentul de octeți pe care cititorul existent îl reatașează deja. Nu există criteriu de acceptanță mai bun pentru o trăsătură de format binar decât un cititor scris independent, pe care nu ați avut voie să îl schimbați

Din ce e făcut un grafic încorporat

Trei piese trebuie să fie de acord. Stratul de desenare contribuie cu o formă de control gazdă, stratul de obiect contribuie cu o înregistrare OBJ al cărei set comun de date de obiect declară tipul de obiect 5, iar stream-ul de înregistrări contribuie cu substream-ul de grafic în sine. Fanioanele de opțiune de pe înregistrarea OBJ sunt cele pe care Excel le scrie pentru un cadru de grafic: poziționat, blocat, linie automată și umplere automată, iar asta face ca graficul încorporat să se comporte ca unul nativ când un utilizator dă clic pe el

HotXLS ancorează un substream de grafic BIFF8 pe o foaie de lucru Delphi prin trei piese concordante: forma de control gazdă din stratul de desenare, înregistrarea OBJ al cărei set comun de date de obiect declară tipul de obiect 5 și lanțul de înregistrări de grafic parcat la capătul stream-ului de înregistrări al foii, unde un BOF de grafic schimbă contextul parser-ului, iar EOF-ul de închidere reatașează înregistrările
Trei straturi poartă un singur grafic încorporat: forma de desenare îl ancorează, înregistrarea OBJ îl tiparizează drept gazdă de grafic, iar substream-ul de grafic de la capătul stream-ului foii furnizează înregistrările pe care cititorul le reatașează

Ancora merită o notă, pentru că e o sursă comună de bug-uri de off-by-one. API-ul HotXLS primește numere de rând și coloană cu bază unu, potrivite cu restul bibliotecii, iar ancora de client scrisă în fișier e cu bază zero. Conversia se întâmplă în interiorul lui AddChartObject, deci apelanții rămân în sistemul de coordonate pe care îl folosesc oriunde altundeva, dar oricine compară un dump hexagonal cu propriul apel are de reținut pe care parte a acelei granițe citește

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;

    // Ancorat la E2:M20 pe această foaie, numere cu bază 1
    Sheet.AddChartObject(xlsChartTypeColumn, 'Regional sales',
      'Month', 'Amount', Series, 2, 5, 20, 13);

    Book.SaveToFile('regional-sales-charted.xls');
  finally
    Book.Free;
  end;
end;

FillChar-ul pe vectorul de serii nu e decorațiune. TXLSChartSeriesInfo poartă mai multe sub-înregistrări opționale — etichete de date, stil per serie, linii de tendință și bare de eroare — fiecare gardată de un boolean, iar o înregistrare parțial inițializată, pe stivă, îi va preda emitter-ului fanioane pe care nu i-a stabilit nimeni. Zero-izați vectorul, apoi stabiliți câmpurile pe care le vreți

Ce referințe de serie acceptă calea încorporată?

Intervale simple în stil A1 din interiorul aceluiași registru de lucru, iar restricția aceasta e deliberată, nu o neglijență. Fiecare referință e rezolvată contra listei de foi a registrului de lucru și transformată în indexul de referință extern de care au nevoie înregistrările de grafic. Un interval numit sau o referință către un registru extern cade pe un substituent cu o expresie parsată de lungime zero, deci graficul se scrie curat, dar seria respectivă nu are sursă de date până nu o îndreptați spre un interval

Acceptarea referințelor de serie HotXLS pe calea de grafic BIFF8 încorporat: intervalele în stil A1 precum Data!$B$2:$B$13 din interiorul aceluiași registru de lucru se rezolvă contra listei de foi în indexul de referință extern de care au nevoie înregistrările de grafic, în timp ce intervalele numite și referințele către registre externe cad pe un substituent cu expresie parsată de lungime zero, ambele fiind acoperite de AddChartSheet
Doar intervalele simple în stil A1 din interiorul aceluiași registru de lucru se compilează în referințe de serie de grafic; tot restul se scrie curat ca substituent până la reîndreptare, iar calea completă stă pe AddChartSheet

Motivul e un compromis de inginerie simplu. Calea completă de compilare a referințelor există pe ruta foii de grafic, învelită în stratul de colecție de foi de lucru, iar scoaterea ei curată ar însemna duplicarea a o sută de linii de logică de rezolvare pentru un caz mai puțin comun în practică. Un grafic încorporat reprezintă grafic aproape întotdeauna celule de pe propria foaie sau de pe o foaie de date soră. Referințele numite și externe sunt acoperite pe calea foii de grafic prin AddChartSheet, deci nimic nu e indisponibil, doar ajunge dintr-un alt punct de intrare

Tot restul modelului de serie funcționează identic pe ambele rute. Legarea la axa secundară, stilurile de linie, umplere și marker per serie, liniile de tendință, barele de eroare și etichetele de date fac toate parte din TXLSChartSeriesInfo și se emit toate la fel, deci o definiție de grafic se poate muta între un obiect încorporat și o foaie de grafic schimbând doar apelul. Mecanica grupurilor de axe de dincolo de fanionul de axă secundară e acoperită în grupurile de axă secundară la scrierea BIFF

De ce se citea titlul graficului ca două caractere?

Pentru că s-a pasat un număr de caractere acolo unde se aștepta un număr de octeți, iar șirurile BIFF Unicode fac greșeala ușor de scris și greu de văzut. Un șir BIFF Unicode scurt începe cu un număr de caractere și un octet de fanioane, iar octetul de fanioane poartă bitul de octet înalt, care spune dacă încărcătura e de un octet per caracter sau de doi. Citiți o încărcătură de 16 biți cu numărul de caractere ca și cum ar fi o lungime în octeți și obțineți exact jumătate din șir: o serie numită Sales revine ca Sa, iar un titlu de grafic se trunchiază la fel, pentru că titlurile și etichetele de serie partajează calea de decodare

Ce face defectul notabil e că a recurent de trei ori în aceeași familie de înregistrări: o dată în numele liniilor de tendință, o dată în numele graficelor pivot și o dată în titlurile de grafic. Fiecare apariție părea un bug proaspăt într-o trăsătură nouă. Toate trei erau aceeași înmulțire lipsă. Regula care a închis-o definitiv e mecanică și ar trebui aplicată fără judecată: ori de câte ori citiți unul dintre aceste șiruri, consultați mai întâi fanionul de octet înalt și înmulțiți numărul de caractere cu lățimea încărcăturii înainte să atingeți buffer-ul. Detaliile la nivel de înregistrare sunt în decodarea numărătorilor de caractere XLUnicodeString și a fanionului high-byte

// Graficul încorporat partajează stratul de desenare cu imaginile și
// formele, deci un desen existent pe foaie se păstrează. AddChartObject
// întoarce indexul obiectului creat
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;

Unde se încadrează graficele încorporate pe lângă alternative

Trei rute există și ele răspund la întrebări diferite. Un obiect de grafic încorporat aparține lângă datele lui, pe o foaie de lucru, și e ceea ce vor majoritatea rapoartelor. O foaie de grafic se potrivește unei singure vizualizări de prezentare și vă dă calea completă de compilare a referințelor. Păstrarea unui grafic existent dintr-un fișier încărcat, neatinse, e răspunsul corect când registrul de lucru a venit din Excel cu o formatare pe care nimeni nu vrea ca o bibliotecă să o reinterpreteze; acel comportament de pas-through e descris în ChartML păstrat și graficele combinate

Pentru că graficul încorporat călărește stratul de desenare, coexistă cu imaginile și formele de pe aceeași foaie, în loc să le înlocuiască, iar modelul general al stratului aceluia e acoperit în grafice, imagini și desene în HotXLS. Toate cele trei rute se livrează în HotXLS Delphi spreadsheet component, deci alegerea ține de cum ar trebui să arate raportul, nu de ce poate exprima biblioteca

Punctul metodologic e cel care merită reținut. Când o trăsătură de format binar are un cititor existent, construiți scriitorul contra cititorului, nu contra citirii dumneavoastră a specificației. Cititorul encodează ani de contact cu fișiere pe care aplicații reale le-au produs efectiv, inclusiv părțile pe care specificația le enunță vag, iar un scriitor care îl satisface are cu mult mai multe șanse să satisfacă și Excel